....
This is just a wip command by command play by play for now, i will add context and infos later(TM).
random comments:
- root$ denotes stuff done as root
- $ denotes stuff done as normal user
- you totally can use a different easy-rsa instance to generate your sub-ca, it does not need to be done in the root-ca "pki" - i will try to mark the points where you need to divert at a later time, essentially you need two easy-rsa instances(go figure, one with a ca build and one without - and then move the CSR/Finished cert between them)
Relevant Domains:
- kubecluster.example.org - subdomain your cluster should have
- step-certificates.step-ca.svc.cluster.local - service and namespace under which your intermediate is reachable in the cluster
- kubemaster.example.org - node fqdn
- kubenode01.example.org - node fqdn
- kubenode02.example.org - node fqdn
- (add/remove more node fqdns)
Derived domains: - ca.kubecluster.example.org - fqdn for your ca - .kubecluster.example.org - subdomain for your cluster including "children"
You need a root ca managed by easy-rsa. Our first step is to create a template for our intermediate. This will need be run on the CA side.
$ cd $CA_PATH
$ cat > x509-types/ca.kubecluster.example.org <<EOF
# CA_PATH_LEN for CA path length limits. You could also do this here
# manually as in the following example in place of the existing line:
#
# basicConstraints = CA:TRUE, pathlen:0
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
keyUsage = cRLSign, keyCertSign
# this magic bit creates a subca which is not allowed to sign other CAs - nice fun :)
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
nameConstraints = critical,@nameconsts
subjectAltName = critical,@sans
[sans]
# configure all the namespaces & aliases here - atleast what you used for the commonname of the subca(to be created) and the CA alias in the cluster
DNS.0=ca.kubecluster.example.org
# helm-release-name.namespace.svc.cluster-local
DNS.1=step-certificates.step-ca.svc.cluster.local
[nameconsts]
# apparently ordered numbers are not needed :P
permitted;DNS.0=.kubecluster.example.org
permitted;DNS.2=kubecluster.example.org
# repeat the DNS.1 SAN here in the nameconstraint
permitted;DNS.3=step-certificates.step-ca.svc.cluster.local
# allow it to build certs for all nodenames in the cluster
permitted;DNS.4=kubemaster.example.org
permitted;DNS.5=kubenode01.example.org
permitted;DNS.6=kubenode02.example.org
EOF
The next commands need to run on the node preparing the step-ca config:
$ make-cadir cluster-step-ca
$ cd cluster-step-ca
$ ./easyrsa init-pki
$ pwgen -s 50 1 > ./intermediate-pw
$ EASYRSA_CN=ca.kubecluster.example.org EASYRSA_ALGO=ec EASYRSA_PASSOUT=file:./intermediate-pw ./easyrsa gen-req ca.kubecluster.example.org batch
* Notice:
Using Easy-RSA configuration from: /home/k3s-rootless-experiments/dev/tmp/talos1-home-easy-rsa/vars
* WARNING:
Move your vars file to your PKI folder, where it is safe!
* Notice:
Using SSL: openssl OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)
-----
* Notice:
Keypair and certificate request completed. Your files are:
req: /home/k3s-rootless-experiments/dev/tmp/talos1-home-easy-rsa/pki/reqs/ca.kubecluster.example.org.req
key: /home/k3s-rootless-experiments/dev/tmp/talos1-home-easy-rsa/pki/private/ca.kubecluster.example.org.key
We now have a request we copy over to the CA, with which we can then generate the signed intermediate cert.
$ cd $CA_DIR
$ # copy the generated req to ca.kubecluster.example.org.req to this dir
$ ./easyrsa import-req ./ca.kubecluster.example.org.csr.pem ca.kubecluster.example.org
$ ./easyrsa sign-req ca.kubecluster.example.org ca.kubecluster.example.org
* No Easy-RSA 'vars' configuration file exists!
* Using SSL: openssl OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)
You are about to sign the following certificate.
Please check over the details shown below for accuracy. Note that this request
has not been cryptographically verified. Please be sure it came from a trusted
source or that you have verified the request checksum with the sender.
Request subject, to be signed as a ca.kubecluster.home.stejau.vpn certificate for 825 days:
subject=
commonName = ca.kubecluster.example.org
Type the word 'yes' to continue, or any other input to abort.
Confirm request details: yes
Using configuration from /root/vpnint-easy-rsa/easyrsa3/pki/98b8aa22/temp.5.x
Enter pass phrase for /root/vpnint-easy-rsa/easyrsa3/pki/private/ca.key:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :ASN.1 12:'ca.kubecluster.example.org'
Certificate is to be certified until Dec 4 22:53:17 2026 GMT (825 days)
Write out database with 1 new entries
Database updated
Notice
------
Certificate created at:
* /root/vpnint-easy-rsa/easyrsa3/pki/issued/ca.kubecluster.example.org.crt
$ openssl x509 -in /home/kind01/talos1-step-ca-autocert/pki/issued/ca.kubecluster.example.org.crt -noout -text
...
The generated cert should contain all the SANs/name constraints from the template.
We continue on the step-ca host:
$ # Copy the signed cert into pki/issued/ca.kubecluster.example.org.crt.
$ # Copy the root ca crt from pk/ca.crt into pki/ca.crt
$ bash finaggle-step-configs.sh ca.kubecluster.example.org step-certificates.step-ca.svc.cluster.local step-ca-issuer
Follow the helm repo part from https://github.com/smallstep/step-issuer and install the repo and the step-issuer via helm.
The files: - test2.yaml will contain your helm values to be fed to "helm upgrade --install --create-namespace --namespace step-ca --values test2.yaml step-certificates smallstep/step-certificates" - test-cluster.issuer-filled.yaml will contain the manifest for the cluster issuer, it can simply be applied.
If this works the cmd "kubectl get stepclusterissuers.certmanager.step.sm step-cluster-issuer -o yaml" should output "StepClusterIssuer verified and ready to sign certificates" or "Ready"/"true".
With this you can annotate your resources properly and will get certificates.
finaggle-step-configs.sh:
#!/bin/bash
EXTERNAL_DNS_NAME=$1
CLUSTER_DNS_NAME=$2
ISSUER_NAME=$3
# atleast the provisioner pw causes issues when it has a newline, this avoids that issue
echo -n "$(pwgen -s 50 1)" > provisioner-pw
echo -n "$(pwgen -s 50 1)" > useless-pw
rm test1.yaml
# we use the useless-pw here as if we use the actual files, step ca init will overwrite them
step ca init --password-file=useless-pw --provisioner-password-file=provisioner-pw --name=$EXTERNAL_DNS_NAME --deployment-type=standalone --dns $EXTERNAL_DNS_NAME --dns $CLUSTER_DNS_NAME --address=:9000 --provisioner=$ISSUER_NAME --helm > test1.yaml
rm useless-pw
# overwrite various fields with our own values, according to our needs
yq < test1.yaml | jq --arg fingerprint "$(openssl x509 -in pki/ca.crt -noout -fingerprint -sha256 | sed 's/.*=//; s/://g')" --rawfile root_ca_crt pki/ca.crt --rawfile intermediate_ca_pw ./intermediate-pw --rawfile intermediate_ca_key pki/private/${EXTERNAL_DNS_NAME}.key --rawfile intermediate_ca_crt pki/issued/${EXTERNAL_DNS_NAME}.crt --rawfile provisioner_pw provisioner-pw '.inject.certificates.root_ca = $root_ca_crt | .inject.secrets.x509.root_ca_key = "" | .inject.certificates.intermediate_ca = $intermediate_ca_crt | .inject.secrets.x509.intermediate_ca_key = $intermediate_ca_key | .inject.secrets.ca_password = ($intermediate_ca_pw | @base64) | .inject.secrets.provisioner_password = ($provisioner_pw | @base64) | .inject.config.files["defaults.json"].fingerprint = $fingerprint' > test2.json
yq < test-cluster.issuer.yaml | jq --slurpfile ca_helm test2.json '.spec.provisioner.kid = $ca_helm[0].inject.config.files["ca.json"].authority.provisioners[0].key.kid | .spec.provisioner.name = $ca_helm[0].inject.config.files["ca.json"].authority.provisioners[0].name | .spec.caBundle = ($ca_helm[0].inject.certificates.root_ca | @base64) | .spec.url = "https://\( $ca_helm[0].inject.config.files["ca.json"].dnsNames[1])/"' | yq -y | tee test-cluster.issuer-filled.yaml
test1.yaml:
# Helm template
inject:
enabled: true
# Config contains the configuration files ca.json and defaults.json
config:
files:
ca.json:
root: /home/step/certs/root_ca.crt
federateRoots: []
crt: /home/step/certs/intermediate_ca.crt
key: /home/step/secrets/intermediate_ca_key
address: :9000
dnsNames:
- ca.s02-k3s-vault.XXXX
- step-certificates.step-ca.svc.cluster.local
logger:
format: json
db:
type: badgerv2
dataSource: /home/step/db
authority:
enableAdmin: false
provisioners:
- {"type":"JWK","name":"step-ca-issuer","key":{"use":"sig","kty":"EC","kid":"xx","crv":"P-256","alg":"ES256","x":"xxx","y":"sxx"},"encryptedKey":"xxx","options":{"x509":{},"ssh":{}}}
tls:
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
minVersion: 1.2
maxVersion: 1.3
renegotiation: false
defaults.json:
ca-url: https://ca.s02-k3s-vault.xxx
ca-config: /home/step/config/ca.json
fingerprint: xx
root: /home/step/certs/root_ca.crt
# Certificates contains the root and intermediate certificate and
# optionally the SSH host and user public keys
certificates:
# intermediate_ca contains the text of the intermediate CA Certificate
intermediate_ca: |
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
# root_ca contains the text of the root CA Certificate
root_ca: |
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
# Secrets contains the root and intermediate keys and optionally the SSH
# private keys
secrets:
# ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key
# This value must be base64 encoded.
ca_password:
provisioner_password:
x509:
# intermediate_ca_key contains the contents of your encrypted intermediate CA key
intermediate_ca_key: |
-----BEGIN EC PRIVATE KEY-----
-----END EC PRIVATE KEY-----
# root_ca_key contains the contents of your encrypted root CA key
# Note that this value can be omitted without impacting the functionality of step-certificates
# If supplied, this should be encrypted using a unique password that is not used for encrypting
# the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key.
root_ca_key: |
-----BEGIN EC PRIVATE KEY-----
-----END EC PRIVATE KEY-----
test-cluster.issuer.yaml:
apiVersion: certmanager.step.sm/v1beta1
kind: StepClusterIssuer
metadata:
name: step-cluster-issuer
spec:
caBundle: XXXXX
provisioner:
kid: XXXX
name: XXXX
passwordRef:
key: password
name: step-certificates-provisioner-password
namespace: step-ca
url: https://step-certificates.step-ca.svc.cluster.local/
TODOs:
* put finaggle-step-configs.sh and its deps somewhere
* document stuff around cert-manager / ingress-shim docs - https://cert-manager.io/docs/usage/ingress/#supported-annotations
* multiple provisioners - how to do?
* ???
* PROFIT
Caveat: * it might be that x509-types in a modern easy-rsa install is under pki/ instead of in the same dir as the easy-rsa script