Skip to content

Commit 76111ba

Browse files
committed
step ca
1 parent 327bedd commit 76111ba

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
---
2+
layout: post
3+
title: "Host own CA for K3S"
4+
date: 2025-10-21 22:51:05 +0800
5+
categories: K3S
6+
---
7+
My K3S is not exposed to the Internet. Therefore, I never felt the necesity to use HTTPS for all WebUIs. Also because it is hard to use Let's Encrypt in this case.
8+
9+
However, when I setup Nextcloud to backup my desktop, the client required the transmission to be TLS encrypted. This led to this article, hosting an own CA.
10+
11+
I do not know if there are other options, I use **Step CA** from *smallstep.com*.
12+
13+
1. Install **step-ca**/**step**.
14+
15+
1. Generate step-ca helm values.
16+
17+
`step ca init --helm`
18+
19+
Be careful with those questions. Some are tied to other configurations that are not here or changeable.
20+
21+
```text
22+
✔ Deployment Type: Standalone
23+
24+
What would you like to name your new PKI?
25+
✔ (e.g. Smallstep): Smallstep
26+
27+
What DNS names or IP addresses would you like to add to your new CA?
28+
# This is the only available value in K8S setup, except the namespace part.
29+
✔ (e.g. ca.smallstep.com[,1.1.1.1,etc.]): step-certificates.default.svc.cluster.local
30+
31+
What IP and port will your new CA bind to (it should match service.targetPort)?
32+
# The service is on 9000 by default. Change the value here does not affect the service.
33+
✔ (e.g. :443 or 127.0.0.1:443): :9000
34+
35+
What would you like to name the CA's first provisioner?
36+
# Unlike said in doc, a pre-configured "admin" provisioner, this is the only one in helm installation.
37+
✔ (e.g. you@smallstep.com): me@example.org
38+
39+
Choose a password for your CA keys and first provisioner.
40+
# If chose "generate", the password is shown until press Enter. Copy it and base64 it for used later.
41+
✔ [leave empty and we'll generate one]:
42+
```
43+
44+
2. Install step-ca
45+
46+
`helm upgrade -i step-certificates smallstep/step-certificates -f step-ca-values.yaml --set inject.secrets.ca_password="${BASE64_PASSWORD_FROM_ABOVE}" --set inject.secrets.provisioner_password="${BASE64_PASSWORD_FROM_ABOVE}"/`
47+
48+
2. Install **step-issuer**.
49+
50+
1. `helm install step-issuer smallstep/step-issuer`
51+
52+
2. Setup the issuer.
53+
54+
```Shell
55+
#!/usr/bin/env bash
56+
set -eu -o pipefail
57+
58+
# Same as the one in answer.
59+
CA_URL=https://step-certificates.default.svc.cluster.local
60+
CA_ROOT_B64=$(kubectl get -o jsonpath="{.data['root_ca\.crt']}" configmaps/step-certificates-certs | step base64)
61+
# Same as the one in answer.
62+
CA_PROVISIONER_NAME=magicloud@magicloud.lan
63+
CA_PROVISIONER_KID=$(kubectl get -o jsonpath="{.data['ca\.json']}" configmaps/step-certificates-config | jq -r .authority.provisioners[0].key.kid)
64+
65+
kubectl apply -f - << EOF
66+
---
67+
apiVersion: certmanager.step.sm/v1beta1
68+
kind: StepClusterIssuer
69+
metadata:
70+
name: step-issuer
71+
spec:
72+
# The CA URL:
73+
url: $CA_URL
74+
# The base64 encoded version of the CA root certificate in PEM format:
75+
caBundle: $CA_ROOT_B64
76+
# The provisioner name, kid, and a reference to the provisioner password secret:
77+
provisioner:
78+
name: $CA_PROVISIONER_NAME
79+
kid: $CA_PROVISIONER_KID
80+
passwordRef:
81+
name: step-certificates-provisioner-password
82+
namespace: default
83+
key: password
84+
---
85+
EOF
86+
```
87+
88+
3. Install **cert-manager**.
89+
90+
`kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.1/cert-manager.yaml`
91+
92+
4. Use Step CA for Ingress.
93+
94+
The above steps create a `StepClusterIssuer`. Note that it is not a `ClusterIssuer`, since step-ca is an out of tree issuer. Its usage differs slightly from that of the in tree ones, such as ACME.
95+
96+
There are three annotations to be used in Ingress for this case. According to the documentation, I think two of them are enough.
97+
98+
```YAML
99+
annotations:
100+
# `step-issuer` is the name created above.
101+
cert-manager.io/issuer: step-issuer
102+
# Either of the following should work.
103+
cert-manager.io/issuer-group: certmanager.step.sm
104+
cert-manager.io/issuer-kind: StepClusterIssuer
105+
```
106+
107+
5. Expose the Step CA interface outside the cluster.
108+
109+
The interface itself is protected by a cert signed by the current CA. Its SAN is apparently the service address. However, Traefik does not know about the CA, and Traefik requires the target must be in the SAN while the target is the pod (not the service) with its IP address. Therefore, Traefik refuses to perform ingressing for Step CA.
110+
111+
The solution is using `IngressRouteTCP` with `spec.tls.passthrough`.
112+
113+
```YAML
114+
apiVersion: traefik.io/v1alpha1
115+
kind: IngressRouteTCP
116+
metadata:
117+
name: step-ca
118+
spec:
119+
routes:
120+
- match: HostSNI(`step-ca.magicloud.lan`)
121+
services:
122+
- name: step-certificates
123+
port: 443
124+
tls:
125+
passthrough: true
126+
```
127+
128+
This leads to another issue, ExternalDNS does not know how to monitor this object. Therefore, we need its CRD.
129+
130+
First, apply [the dnsendpoint CRD](https://raw.githubusercontent.com/kubernetes-sigs/external-dns/master/config/crd/standard/dnsendpoints.externaldns.k8s.io.yaml). Then in the ExternalDNS helm values file, add the following and upgrade. `service` and `ingress` are the default values, we need `crd`.
131+
132+
```YAML
133+
sources:
134+
- service
135+
- ingress
136+
- crd
137+
```
138+
139+
Now create the DNS record.
140+
141+
```YAML
142+
apiVersion: externaldns.k8s.io/v1alpha1
143+
kind: DNSEndpoint
144+
metadata:
145+
name: step-ca
146+
spec:
147+
endpoints:
148+
- dnsName: step-ca.magicloud.lan
149+
recordType: A
150+
targets:
151+
- 192.168.0.102
152+
```
153+
154+
6. Install the CA.
155+
156+
In theory, the CA should be installed everywhere. For other pods in the cluster, the tool is`autocert`. However, existing pods are problematic. Luckily I do not need that. All I need to do is install the CA to my desktop so that it can communicate with the secured services in the cluster.
157+
158+
After installing the `step` tool on the desktop, run the command `step ca bootstrap --ca-url step-ca.magicloud.lan --fingerprint $FINGER_PRINT` to initialize. `$FINGER_PRINT` could be found in the first few lines of the step-certificates pod log. If you missed viewing the log, you can also use the command `step certificate fingerprint $(step path)/certs/root_ca.crt` within the step-certificates pod to find it. Then run the command `step certificate install $CRT_PATH_SHOWED_IN_LAST_STEP`.

0 commit comments

Comments
 (0)