{"id":19039891,"url":"https://github.com/cert-manager/sample-external-issuer","last_synced_at":"2025-06-14T18:35:00.992Z","repository":{"id":39584200,"uuid":"314600152","full_name":"cert-manager/sample-external-issuer","owner":"cert-manager","description":"A sample external Issuer for cert-manager","archived":false,"fork":false,"pushed_at":"2025-05-06T11:19:47.000Z","size":358,"stargazers_count":32,"open_issues_count":3,"forks_count":22,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-06-04T22:45:39.838Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cert-manager.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-11-20T15:53:51.000Z","updated_at":"2025-05-06T11:19:51.000Z","dependencies_parsed_at":"2023-12-26T12:09:01.948Z","dependency_job_id":"32a2041c-50a1-47bd-b229-236459327c97","html_url":"https://github.com/cert-manager/sample-external-issuer","commit_stats":{"total_commits":113,"total_committers":8,"mean_commits":14.125,"dds":"0.17699115044247793","last_synced_commit":"a15c1629608125734e4dace494140983d661b983"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cert-manager%2Fsample-external-issuer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cert-manager%2Fsample-external-issuer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cert-manager%2Fsample-external-issuer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cert-manager%2Fsample-external-issuer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cert-manager","download_url":"https://codeload.github.com/cert-manager/sample-external-issuer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cert-manager%2Fsample-external-issuer/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259161046,"owners_count":22814654,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T22:19:25.572Z","updated_at":"2025-06-14T18:35:00.977Z","avatar_url":"https://github.com/cert-manager.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/cert-manager/cert-manager/d53c0b9270f8cd90d908460d69502694e1838f5f/logo/logo-small.png\" height=\"256\" width=\"256\" alt=\"cert-manager project logo\" /\u003e\n\u003c/p\u003e\n\n# sample-external-issuer\n\nExternal issuers extend [cert-manager](https://cert-manager.io/) to issue certificates using APIs and services\nwhich aren't built into the cert-manager core.\n\nThis repository provides an example of an [External Issuer][] built using the [issuer-lib][] library.\n\n## Install\n\n```console\nkubectl apply -f https://github.com/cert-manager/sample-external-issuer/releases/download/v0.1.0/install.yaml\n```\n\n## Demo\n\nYou can run the sample-external-issuer on a local cluster with this command:\n\n```console\nmake kind-cluster deploy-cert-manager docker-build kind-load deploy e2e\n```\n\n## How to write your own external issuer\n\nIf you are writing an external issuer you may find it helpful to review the sample code in this repository\nand to follow the steps below, replacing references to `sample-external-issuer` with the name of your project.\n\n### Prerequisites\n\nYou will need the following command line tools installed on your PATH:\n\n* [Git](https://git-scm.com/)\n* [Golang v1.20+](https://golang.org/)\n* [Docker v17.03+](https://docs.docker.com/install/)\n* [Kind v0.18.0+](https://kind.sigs.k8s.io/docs/user/quick-start/)\n* [Kubectl v1.26.3+](https://kubernetes.io/docs/tasks/tools/install-kubectl/)\n* [Kubebuilder v3.9.1+](https://book.kubebuilder.io/quick-start.html#installation)\n* [Kustomize v3.8.1+](https://kustomize.io/)\n\nYou may also want to read: the [Kubebuilder Book][] and the [cert-manager Concepts Documentation][] for further background\ninformation.\n\n### Create a test cluster\n\nWe will need a Kubernetes cluster on which to test our issuer and we can quickly create one using `kind`.\n\n```console\nkind create cluster\n```\n\nThis will update your KUBECONFIG file with the URL and credentials for the test cluster.\nYou can explore it using `kubectl`\n\n```console\nkubectl get nodes\n```\n\nThis should show you details of a single node.\n\n### Copy the sample-external-issuer code\n\nWe need a Git repository to track changes to the issuer code.\nYou can start by creating a repository on GitHub or you can create it locally.\n\n```console\nmkdir my-external-issuer\ncd my-external-issuer\ngit clone https://github.com/cert-manager/sample-external-issuer.git .\ngit remote rm origin\ngit remote add origin https://github.com/\u003cusername\u003e/my-external-issuer.git\n```\n\n### Run the controller-manager\n\nWith all these tools in place and with the project initialised you should now be able to run the issuer for the first time.\n\n```console\nmake run\n```\n\nThis will compile and run the issuer locally and it will connect to the test cluster and log some startup messages.\nWe will add more to it in the next steps.\n\n### Creating MyIssuer and MyClusterIssuer custom resources\n\nAn [External Issuer][] must implement two custom resources for compatibility with cert-manager: `MyIssuer` and `MyClusterIssuer`\n\nNOTE: It is important to understand the [Concept of Issuers] before proceeding.\n\nThe `MyIssuer` and `MyClusterIssuer` custom resources can be defined in the `api/v1alpha1` directory.\nUse the `SampleIssuer` and `SampleClusterIssuer` definitions as a starting point.\n\nAdditionally, the group, version and kind of the custom resources must be customised to be unique to your issuer:\n\n* `group` is the name given to a collection of custom resource APIs\n* `kind` is the name of an individual resource in that group\n* `version` allows you to create multiple versions of your APIs as they evolve, whilst providing backwards compatibility for clients using older API versions\n\nAfter modifying the API source files you should always regenerate all generated code and configuration,\nas follows:\n\n```console\nmake generate manifests\n```\n\nYou should see a number of new and modified files, reflecting the changes you made to the API source files.\n\n#### Issuer health checks\n\nAn issuer that connects to a certificate authority API may want to perform periodic health checks and sanity checks,\nto ensure that the API server is responding and if not,\nto set update the `Ready` condition of the `Issuer` to false, and log a meaningful error message with the condition.\nThis will give early warning of problems with the configuration or with the API,\nrather than waiting a for `CertificateRequest` to fail before being alerted to the problem.\nAdditionally, this implements the \"Circuit Breaker\" pattern, it makes all the `CertificateRequest` wait until the `Issuer`\nis healthy again.\n\nThe health check is implemented in the `Check` function in the `./internal/controllers/signer.go` file.\n\nTODO: issuer-lib does not yet support performing the health checks periodically.\nThere should be some return value for the `Check` function so we can make controller-runtime retry reconciling regularly, even when the current reconcile succeeds.\n\nSee [the issuer-lib README](https://github.com/cert-manager/issuer-lib?tab=readme-ov-file#how-it-works) for more information.\n\n### Sign the cert-manager CertificateRequest resources and kubernetes CertificateSigningRequest resources\n\nThe `Sign` function in the `./internal/controllers/signer.go` file is used by the CertificateRequest and CertificateSigningRequest reconcilers\nto create signed x509 certificates for the provided x509 certificate signing requests.\n\nIf `Sign` succeeds it returns the bytes of a signed certificate which we then use as the value for `CertificateRequest.Status.Certificate`.\nIf it returns a normal error, the Sign function will be retried as long as we have not spent more than the configured MaxRetryDuration after the certificate request was created.\n\nSee [the issuer-lib README](https://github.com/cert-manager/issuer-lib?tab=readme-ov-file#how-it-works) for more information.\n\n#### Get the Issuer or ClusterIssuer credentials from a Secret\n\nThe API for your CA may require some configuration and credentials and the obvious place to store these is in a Kubernetes `Secret`.\nWe extend the `IssuerSpec` to include a `URL` field and a `AuthSecretName`, which is the name of a `Secret`.\nAs usual run `make generate manifests` after modifying the API source files:\n\n```console\nmake generate manifests\n```\n\nNOTE: The namespace of that Secret is deliberately not specified here,\nbecause that would breach a security boundary and potentially allow someone who has permission to create `Issuer` resources,\nto make the controller access secrets in another namespace which that person would not normally have access to.\n\nFor this reason, the Secret for an Issuer MUST be in the same namespace as the Issuer.\nThe Secret for a ClusterIssuer MUST be in a namespace defined by cluster administrator,\nbut that is a little more complicated and for now we will concentrate on Issuer Secrets.\n\nBoth the `IssuerReconciler` and the `CertificateRequestReconciler` are updated to `GET` the `Secret` referred to by the `Issuer`.\n\nAdd a new [Kubebuilder RBAC Marker](https://book.kubebuilder.io/reference/markers/rbac.html) to both controllers,\npermitting them read-only access to `Secret` resources.\n\n```go\n// +kubebuilder:rbac:groups=\"\",resources=secrets,verbs=get;list;watch\n```\n\nThen run `make manifests` to regenerate the RBAC configuration in `config/`.\n\nAdd the `corev1` types to the `Scheme` in the unit-tests.\n\nNOTE: It has already been added to the `main.go` `Scheme` as part of the `clientgoscheme`.\n\nWrite a test to check that if the `GET` `Secret` operation fails,\nthe error is returned and triggers a retry-with-backoff.\nThis important because the `Secret` may not exist at the time the `Issuer` or `CertificateRequest` is created.\n\nNOTE: Ideally, we would `WATCH` for the particular `Secret` and trigger the reconciliation when it becomes available.\nAnd that may be a future enhancement to this project.\n\nIn the case of the `CertificateRequestReconciler` we need to deal with both `Issuer` and `ClusterIssuer` types,\nso we modify the `issuerutil` function to allow us to extract an `IssuerSpec` from either of those types.\n\n### Logging and Events\n\nWe want to make it easy to debug problems with the issuer,\nso in addition to setting Conditions on the Issuer, ClusterIssuer and CertificateRequest,\nwe can provide more feedback to the user by logging Kubernetes Events.\nYou may want to read more about [Application Introspection and Debugging][] before continuing.\n\n[Application Introspection and Debugging]: https://kubernetes.io/docs/tasks/debug-application-cluster/debug-application-introspection/\n\nKubernetes Events are saved to the API server on a best-effort basis,\nthey are (usually) associated with some other Kubernetes resource,\nand they are temporary; old Events are periodically purged from the API server.\nThis allows tools such as `kubectl describe \u003cresource-kind\u003e \u003cresource-name\u003e` to show not only the resource details,\nbut also a table of the recent events associated with that resource.\n\nThe aim is to produce helpful debug output that looks like this:\n\n```\n$ kubectl describe clusterissuers.sample-issuer.example.com clusterissuer-sample\n...\n    Type:                  Ready\nEvents:\n  Type     Reason            Age                From                    Message\n  ----     ------            ----               ----                    -------\n  Normal   IssuerReconciler  13s                sample-external-issuer  First seen\n  Warning  IssuerReconciler  13s (x3 over 13s)  sample-external-issuer  Temporary error. Retrying: failed to get Secret containing Issuer credentials, secret name: sample-external-issuer-system/clusterissuer-sample-credentials, reason: Secret \"clusterissuer-sample-credentials\" not found\n  Normal   IssuerReconciler  13s (x3 over 13s)  sample-external-issuer  Success\n```\nAnd this:\n\n```\n$ kubectl describe certificaterequests.cert-manager.io issuer-sample\n...\nEvents:\n  Type     Reason                        Age   From                    Message\n  ----     ------                        ----  ----                    -------\n  Normal   CertificateRequestReconciler  23m   sample-external-issuer  Initialising Ready condition\n  Warning  CertificateRequestReconciler  23m   sample-external-issuer  Temporary error. Retrying: error getting issuer: Issuer.sample-issuer.example.com \"issuer-sample\" not found\n  Normal   CertificateRequestReconciler  23m   sample-external-issuer  Signed\n\n```\n\nFirst add [record.EventRecorder][] attributes to the `IssuerReconciler` and to the `CertificateRequestReconciler`.\nAnd then in the Reconciler code, you can then generate an event by executing `r.recorder.Eventf(...)` whenever a significant change is made to the resource.\n\n[record.EventRecorder]: https://pkg.go.dev/k8s.io/client-go/tools/record#EventRecorder\n\nYou can also write unit tests to verify the Reconciler events by using a [record.FakeRecorder][].\n\n[record.FakeRecorder]: https://pkg.go.dev/k8s.io/client-go/tools/record#FakeRecorder\n\nSee [PR 10: Generate Kubernetes Events](https://github.com/cert-manager/sample-external-issuer/pull/10) for an example of how you might generate events in your issuer.\n\n### End-to-end tests\n\nNow our issuer is almost feature complete and it should be possible to write an end-to-end test that\ndeploys a cert-manager `Certificate`\nreferring to an external `Issuer` and check that a signed `Certificate` is saved to the expected secret.\n\nWe can make such a test easier by tidying up the `Makefile` and adding some new targets\nwhich will help create a test cluster and to help install cert-manager.\n\nWe can write a simple end-to-end test which deploys a `Certificate` manifest and waits for it to be ready.\n\n```console\nkubectl apply --filename config/samples\nkubectl wait --for=condition=Ready --timeout=5s sampleissuers.sample-issuer.example.com sampleissuer-sample\nkubectl wait --for=condition=Ready --timeout=5s  certificates.cert-manager.io certificate-by-sampleissuer\n```\n\nYou can of course write more complete tests than this,\nbut this is a good start and demonstrates that the issuer is doing what we hoped it would do.\n\nRun the tests as follows:\n\n```bash\n# Create a Kind cluster along with cert-manager.\nmake kind-cluster deploy-cert-manager\n\n# Wait for cert-manager to start...\n\n# Build and install sample-external-issuer and run the E2E tests.\n# This step can be run iteratively when ever you make changes to the code or to the installation manifests.\nmake docker-build kind-load deploy e2e\n```\n\n#### Continuous Integration\n\nYou should configure a CI system to automatically run the unit-tests when the code changes.\nSee the `.github/workflows/`  directory for some examples of using GitHub Actions\nwhich are triggered by changes to pull request branches and by any changes to the master branch.\n\nThe E2E tests can be executed with GitHub Actions too.\nThe GitHub Actions Ubuntu runner has Docker installed and is capable of running a Kind cluster for the E2E tests.\nThe Kind cluster logs can be saved in the event of an E2E test failure,\nand uploaded as a GitHub Actions artifact,\nto make it easier to diagnose E2E test failures.\n\n## Security considerations\n\nWe use a [Distroless Docker Image][] as our Docker base image,\nand we configure our `manager` process to run as `USER: nonroot:nonroot`.\nThis limits the privileges of the `manager` process in the cluster.\n\nAdditionally we [Configure a Security Context][] for the manager Pod.\nWe set `runAsNonRoot`, which ensure that the Kubelet will validate the image at runtime\nto ensure that it does not run as UID 0 (root) and fail to start the container if it does.\n\n## Notes for cert-manager Maintainers\n\n### Release Process\n\nVisit the [GitHub New Release Page][] and fill in the form.\nHere are some example values:\n\n * Tag Version: `v0.1.0-alpha.0`, `v0.1.0` for example.\n * Target: `main`\n * Release Title: `Release v0.1.0-alpha.2`\n * Description: (optional) a short summary of the changes since the last release.\n\nClick the `Publish release` button to trigger the automated release process:\n\n* A Docker image will be generated and published to `ghcr.io/cert-manager/sample-external-issuer/controller` with the chosen tag.\n* An `install.yaml` file will be generated and attached to the release.\n\n## Links\n\n* [External Issuer]\n* [issuer-lib]\n* [cert-manager Concepts Documentation]\n* [Kubebuilder Book]\n* [Kubebuilder Markers]\n* [Distroless Docker Image]\n* [Configure a Security Context]\n* [GitHub New Release Page]\n\n[External Issuer]: https://cert-manager.io/docs/contributing/external-issuers\n[issuer-lib]: https://github.com/cert-manager/issuer-lib\n[cert-manager Concepts Documentation]: https://cert-manager.io/docs/concepts\n[Kubebuilder Book]: https://book.kubebuilder.io\n[Kubebuilder Markers]: https://book.kubebuilder.io/reference/markers.html\n[Distroless Docker Image]: https://github.com/GoogleContainerTools/distroless\n[Configure a Security Context]: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/\n[GitHub New Release Page]: https://github.com/cert-manager/sample-external-issuer/releases/new\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcert-manager%2Fsample-external-issuer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcert-manager%2Fsample-external-issuer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcert-manager%2Fsample-external-issuer/lists"}