Copy and tranform the contents of your Kubernetes Secrets that contain TLS key material. When a Secret is changed, secret-transform automatically re-copies or re-transforms the Secret.
- Installation & Quick Start
- Debugging
- Renaming the key of a Secret
- Combined PEM bundle
- Cut a New Release
A Helm chart is available as well as container images. To install secret-transform, run:
helm upgrade --install secret-transform -n secret-transform --create-namespace \
oci://ghcr.io/maelvls/charts/secret-transform
Then, annotate a Secret:
kubectl annotate secret cert-1 cert-manager.io/secret-copy-tls.crt=tlsCert
You will see that the value for the key tls.crt
has been copied to the
tlsCert
key.
If you want to know why one of the Secrets you have annotated hasn't been processed by secret-transform, you can run the following command:
kubectl events -n default --for secret/cert-1
If everything went well, you should see:
LAST SEEN TYPE REASON OBJECT MESSAGE
0s Normal CopiedKey Secret/cert-1 Copied the contents of "tls.crt" into key "cert"
If you would like to check whether both values are the same, you can run:
diff -u \
<(kubectl get secret cert-1 -ojson | jq '.data."tls.crt"' -r | base64 -d | openssl x509 -text -noout) \
<(kubectl get secret cert-1 -ojson | jq '.data."cert"' -r | base64 -d | openssl x509 -text -noout)
If the output is empty, then secret-transform is working well.
cert-manager doesn't support customizing the name of the keys used in the
Secrets. The keys are fixed to tls.crt
, tls.key
, and ca.crt
.
You can use the three annotations below to "rename" (or rather copy) the keys of
a Secret. Let's imagine you want the Secret to have the private key stored in
the key keyFile
, the certificate in the key certFile
, and the CA certificate
in the key caFile
. You can annotate your Secret with the following
annotations:
kind: Secret
metadata:
annotations:
cert-manager.io/secret-copy-ca.crt: caFile # ✨ "ca.crt" to be renamed to "caFile"
cert-manager.io/secret-copy-tls.crt: certFile # ✨ "tls.crt" to be renamed to "certFile"
cert-manager.io/secret-copy-tls.key: keyFile # ✨ "tls.key" to be renamed to "keyFile"
stringData:
tls.crt: <the PEM-encoded contents of the certificate>
tls.key: <the PEM-encoded contents of the private key>
ca.crt: <the PEM-encoded contents of the CA certificate>
After adding the annotations, you will see the new keys appear in the Secret:
kind: Secret
metadata:
annotations:
cert-manager.io/secret-copy-ca.crt: caFile
cert-manager.io/secret-copy-tls.crt: certFile
cert-manager.io/secret-copy-tls.key: keyFile
data:
tls.crt: <the PEM-encoded contents of the certificate>
tls.key: <the PEM-encoded contents of the private key>
ca.crt: <the PEM-encoded contents of the CA certificate>
+ certFile: <copied from tls.crt>
+ keyFile: <copied from tls.key>
+ caFile: <copied from ca.crt>
If you are using Redis Enterprise for Kubernetes, the page Manage Redis Enterprise cluster (REC) certificates will ask you to create a Secret with the following keys:
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
stringData:
name: proxy # <proxy | api | cm | syncer | metrics_exporter>
key: <the PEM-encoded contents of the private key>
certificate: <the PEM-encoded contents of the certificate>
You can use secret-transform in combination with cert-manager to obtain this Secret.
The Secret needs to be created beforehand so that name: proxy
shows correctly.
When a Secret already exists, cert-manager doesn't create a new one: it simply
updates tls.crt
, tls.key
, and ca.crt
.
The pre-created Secret I suggest is:
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
name: redis-cert1
annotations:
cert-manager.io/secret-copy-tls.crt: certificate
cert-manager.io/secret-copy-tls.key: key
data:
name: proxy
After cert-manager has filled in tls.crt
and tls.key
, secret-manager will
copy these two fields into certificate
and key
. The resulting Secret will
look like this:
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
name: redis-cert1
annotations:
cert-manager.io/secret-copy-tls.crt: certificate
cert-manager.io/secret-copy-tls.key: key
data:
tls.crt: LS0tLCR...UdJ0tC7g==
tls.key: CRUdJTo...Ci0tLS0t==
ca.crt: ...
certificate: LS0tLCR...UdJ0tC7g==
key: CRUdJTo...Ci0tLS0t==
name: proxy
FluxCD expects the keys caFile
, certFile
, and keyFile
. The
secret-transform
controller can be used to create a copy of the standard keys
so that you can use them from FluxCD.
For example, if you annotate your Secret with the following annotation:
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
annotations:
cert-manager.io/secret-copy-ca.crt: caFile
cert-manager.io/secret-copy-tls.crt: certFile
cert-manager.io/secret-copy-tls.key: keyFile
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FU...CBDRVJUSUZJQ0FURS0tLS0tCg==
tls.key: LS0tLS1CRUdJToCi0tLS0tRU5EIF...SBQUklWQVRFIEtFWS0tLS0tCg==
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FU...CBDRVJUSUZJQ0FURS0tLS0tCg==
The Secret will be transformed to:
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
annotations:
cert-manager.io/secret-copy-ca.crt: caFile
cert-manager.io/secret-copy-tls.crt: certFile
cert-manager.io/secret-copy-tls.key: keyFile
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FU...CBDRVJUSUZJQ0FURS0tLS0tCg==
tls.key: LS0tLS1CRUdJToCi0tLS0tRU5EIF...SBQUklWQVRFIEtFWS0tLS0tCg==
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FU...CBDRVJUSUZJQ0FURS0tLS0tCg==
certFile: LS0tLS1CRUdJTiBDRVJUSUZJQ0FU...CBDRVJUSUZJQ0FURS0tLS0tCg== # ✨
keyFile: LS0tLS1CRUdJToCi0tLS0tRU5EIF...SBQUklWQVRFIEtFWS0tLS0tCg== # ✨
caFile: LS0tLS1CRUdJTiBDRVJUSUZJQ0FU...CBDRVJUSUZJQ0FURS0tLS0tCg== # ✨
Important
The combined PEM feature provided by this addon has been added to
cert-manager 1.7 with the field additionalOutputFormats: CombinedPEM
.
Since the feature is still in alpha (as of Sept 2023), you will need to use the feature
flag --feature-gates=AdditionalCertificateOutputFormats=true
. You can read more in the cert-manager documentation page
Additional Certificate Output Formats.
Another common request reported in the cert-manager issue #843 is to create a PEM bundle containing both the key and certificate for easier use with software that require a unified PEM bundle, such as
- HAProxy,
- Hitch,
- OpenDistro for Elasticsearch.
You can run the secret-transform
controller (right now, it has to be run
out-of-cluster since I did not write any manifest) and if you annotate your
Secret with the following annotation:
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
annotations:
cert-manager.io/secret-transform: tls.pem
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FU...CBDRVJUSUZJQ0FURS0tLS0tCg==
tls.key: LS0tLS1CRUdJToCi0tLS0tRU5EIF...SBQUklWQVRFIEtFWS0tLS0tCg==
then a new data key will be created with the name tls.pem
and the value
contains the key and certificate concatenated:
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
annotations:
cert-manager.io/secret-transform: tls.pem
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FU...CBDRVJUSUZJQ0FURS0tLS0tCg==
tls.key: LS0tLS1CRUdJToCi0tLS0tRU5EIF...SBQUklWQVRFIEtFWS0tLS0tCg==
tls.pem: LS0tLS1CRUdJTiBSUXc0ZHk3NTNl...kQgQ0VSVElGSUNBVEUtLS0tLQo= # ✨
The updated Secret looks like this:
$ kubectl get secret example -ojsonpath='{.data.tls\.pem}' | base64 -d
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzmuXe0BSZqjh7V94wfTifk/5hKS/V1RjyBa4RVdFBBHNGsUb
u+8UhhRgadS+R5ZrcErpt1YIchNuliqaZbXEW0BpWtRc3NmqDRzh
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIFXTCCBEWgAwIBAgISBP8i8Bm2p/jl6yxMoLrrJlQkMA0GCSqGSIb3DQEBCwUA
tBpwpdCVsgQqdy69SIU4AYKejVC4nJK9mwAsJi41/W+M
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
-----END CERTIFICATE-----
In order to configure mTLS, the mongod
and mongos
require a combined PEM file using the key certificateKeyFile
. The PEM file must contain the PKCS#8 PEM-encoded private key followed by the chain of PEM-encoded X.509 certificates. The configuration looks like this:
net:
tls:
mode: requireTLS
certificateKeyFile: /etc/ssl/mongodb.pem
✔️ secret-tranform should be able to get around this.
The crt
parameter requires a PEM bundle containing the PKCS#8 private key followed by the X.509 certificate chain. An example of configuration looks like this:
frontend www
bind :443 ssl crt /etc/certs/ssl.pem
✔️ secret-tranform should be able to get around this.
Hitch, a reverse-proxy that aims at terminating TLS connections, requires the use of a combined PEM bundle using the configuration key pem-file
. The bundle must be comprised of a PKCS#8-encode private key followed by the X.509 certificate leaf followed by intermediate certificates. An example of configuration looks like this:
pem-file = "/etc/tls/combined.pem"
or
pem-file = {
cert = "/etc/tls/combined.pem"
}
✔️ secret-tranform should be able to get around this.
If you are stuck with a version of the Postgres JDBC driver older than 42.2.9 (released before Dec 2019), sslkey
refers to a file containing the PKCS#8-formated DER-encoded private key.
props.setProperty("sslkey","/etc/ssl/protgres/postgresql.key");
❌ secret-transform is not able to work around this issue yet.
Related issue in the cert-manager repository: Add ca.crt to TLS secret generated by ACME issuers.
Ejabbed, an open-source Erlang-based XMPP server, requires all file paths given with certfiles
to be "valid" (i.e., not empty). The pain point is that Ejabbed fails when the ca.crt
file is empty on disk. This makes it difficult to use Ejabberd with cert-manager, for example with the following Ejabbed configuration:
certfiles:
- /etc/ssl/ejabbed/tls.crt
- /etc/ssl/ejabbed/tls.key
- /etc/ssl/ejabbed/ca.crt # May be empty with the ACME Issuer.
❌ secret-transform is not able to work around this issue yet.
Related to the issue on the cloud-on-k8s project: fleet and elastic agent doesn't work without a ca.crt.
Elasticsearch cannot start when the ca.crt
file is empty on disk, which may happen for ACME issued certificates. A "possible" workaround for these empty ca.crt
could be to set pemtrustedcas_filepath
to the existing system CA bundle. For example, on REHL, that could be /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
or /etc/ssl/cert.pem
on Alpine Linux. But Elasticsearch expects this file to exist within its config path (i.e., /usr/share/elasticsearch/config
).
❌ secret-transform is not able to work around this issue yet.
Source: cert-manager/cert-manager#843 (comment)
Dovecot is an IMAP and POP3 server. It requires separate PEM files for the certificate and private key. One person is asking for "PEM format" but I don't quite understand why. See: https://doc.dovecot.org/configuration_manual/dovecot_ssl_configuration/
❌ secret-transform is not able to work around this issue yet.
We use goreleaser
. To cut a new release:
git tag v0.1.0
git push origin v0.1.0
The GitHub Action will push the new Helm chart and Docker images, and a draft GitHub release will be created.
Then, edit the draft GitHub release by rewriting the commit messages into user-focused messages.
Finally, click "Publish" to announce the release to everyone who is watching the repository!
Note: It is also possible to run
goreleaser
locally. First, install Goreleaser and Helm 3.12 (or above) since we need the annotationorg.opencontainers.image.source
. Then, run:# This is a dry-run just to see if the Helm chart and the images can be build. goreleaser --snapshot --clean
I often don't have the time to wait for GitHub Actions to run goreleaser, so I often run it myself:
# This is the real deal. export GITHUB_TOKEN=... goreleaserBut it is preferable to let the GitHub Action do it.