diff --git a/step-ca/.gitignore b/step-ca/.gitignore new file mode 100644 index 0000000..2531279 --- /dev/null +++ b/step-ca/.gitignore @@ -0,0 +1,3 @@ +*.key +password +ssh_*_key diff --git a/step-ca/README.md b/step-ca/README.md new file mode 100644 index 0000000..a8f2100 --- /dev/null +++ b/step-ca/README.md @@ -0,0 +1,100 @@ +# Step CA + +[Step CA] is an open-source online X.509 and SSH certificate authority servier. +It provides an HTTP API for remote control via the `step` command, which is +used by clients for certificate issuance and administrators for configuration +and control. It also supports other certificate issuance protocols, including +[ACME]. Clients can authenticate using a variety of protocols, such as JWK, +OpenID Connect, mTLS, and more. + + +## Offline Root CA + +The *dch Root CA R2* private key is managed externally from Step CA. It is +stored offline (on a flash drive in a fireproof save). Only the CA certificate +is used by the online CA service, where it is provided to clients to include in +as a trust anchor in their respective certificate stores. + +*dch Root CA R2* replaces *dch Root CA R1*, which has not been used for some +time. + + +## Online Intermediate CA + +Step CA manages the *dch CA R2* intermediate certificate authority. The +private key for this CA is stored in the `intermediate_ca.key` file, encrypted +with the password in `password`. This key pair is needed by the online CA to +sign end-entity certificates. + + +## SSH CA + +In addition to X.509 (TLS) certificates, Step CA can also manage SSH +certificates. These can be used in place of "plain" SSH keys that must be +managed out-of-band (i.e. `~/.ssh/known_hosts` and `~/.ssh/authorized_keys`). + +Instead of maintaining a database of hosts' public keys, clients can trust the +CA certificate that signs the hosts' certificates. To do so, simply add a +single line to `~/.ssh/known_hosts`: + +``` +@cert-authority *.pyrocufflink.blue ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ8KfNjDh0R/jCmPcJrVafvmuw5JZw+cKoy9RCYNwHsRwPoRHfyzjV1VUZolJfEz+Qm3u+mgYJ/oSquCelY84xE= +``` + +Any host with a hostname that matches `*.pyrocufflink.blue` and presents a +certificate signed by the listed certificate will be automatically trusted; the +`ssh` client will not prompt for manual key verification on the first +connection. + +Similarly, hosts can trust client keys that are signed by the CA by *either* +adding the certificate to per-user `~/.ssh/authorized_keys` files *or* by +setting the global `TrustedUserCAKeys` parameter in the SSH server +configuration. + +`~/.ssh/authorized_keys`: +``` +cert-authority ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBImIoTTmhynCVy/vJ/Q2bWydzqVsvwhGvDgBbklw0eDt8UEbbP9HHPhxiMDtiAhbvRTg5BhYVAlR1MgdooT5dwQ= +``` + +`/etc/ssh/sshd_config`: +``` +TrustedUserCAKeys /etc/ssh/ca.pub +``` + +`/etc/ssh/ca.pub`: +``` +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBImIoTTmhynCVy/vJ/Q2bWydzqVsvwhGvDgBbklw0eDt8UEbbP9HHPhxiMDtiAhbvRTg5BhYVAlR1MgdooT5dwQ= +``` + +SSH host certificates are typically valid for 30 days, so hosts need to have +some automation in place to automatically renew their certificates. Using the +"SSHPOP" provisioner, hosts can use the `step ssh renew` command to renew their +certificates; existing signed SSH certificates are usable as authentication +credentials. + +SSH user certificates are typically valid for 24 hours. Clients will need to +request a new certificate every day using the `step ssh login` command: + +```sh +step ssh login --provisioner=authelia dustin +``` + +Note the `--provisioner=authelia` argument seems to be required, even if a +default provisioner is specified in `~/.step/config/defaults.json`. + +The final positional argument is the name of the *remote* SSH user, *not* the +user logging in to the OIDC IdP. + + +## NodePort Service (No Ingress) + +Step CA supports authenticating clients using mTLS, such as to renew a user +certificate. For this to work, the client must communicate directly with the +server; proxies and load balancers must not intercept the communication or +provide TLS termination, as then the server would not have access to the client +certificate. As such, the service is exposed as a NodePort and not via an +Ingress. + + +[Step CA]: https://smallstep.com/docs/step-ca/ +[ACME]: https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment diff --git a/step-ca/ca.json b/step-ca/ca.json new file mode 100644 index 0000000..5bf2cb8 --- /dev/null +++ b/step-ca/ca.json @@ -0,0 +1,36 @@ +{ + "root": "certs/root_ca.crt", + "federatedRoots": null, + "crt": "certs/intermediate_ca.crt", + "key": "secrets/intermediate_ca.key", + "address": ":32599", + "insecureAddress": "", + "dnsNames": [ + "ca.pyrocufflink.blue" + ], + "ssh": { + "hostKey": "secrets/ssh_host_ca_key", + "userKey": "secrets/ssh_user_ca_key" + }, + "logger": { + "format": "text" + }, + "db": { + "type": "badgerv2", + "dataSource": "db", + "badgerFileLoadingMode": "" + }, + "authority": { + "enableAdmin": true, + "provisioners": [] + }, + "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 + } +} diff --git a/step-ca/intermediate_ca.crt b/step-ca/intermediate_ca.crt new file mode 100644 index 0000000..b7b7db0 --- /dev/null +++ b/step-ca/intermediate_ca.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICCTCCAa+gAwIBAgIUOrt38QEPFGRaBSZgyuDNQPCLUZowCgYIKoZIzj0EAwIw +QDELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD0R1c3RpbiBDLiBIYXRjaDEXMBUGA1UE +AwwORENIIFJvb3QgQ0EgUjIwHhcNMjMwOTI4MDI0NzMwWhcNMjMxMDI4MDI0NzMw +WjAUMRIwEAYDVQQDEwlkY2gtY2EgUjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AAQ1rK98igj6Y5lbeP8HS1zqQCtkcmz8uk1jp4VgznWT3Q8BanjA55UHQi/xx4xz +BYu4QIkJhtqcR5a7YXSr7fQvo4GyMIGvMB0GA1UdDgQWBBQGy1GZZxrCjGDiIGdR +YhTMZZqhkTAfBgNVHSMEGDAWgBTM+d8kb1koGmKRtJs4gN9zYa+6oTASBgNVHRMB +Af8ECDAGAQH/AgEAMAsGA1UdDwQEAwIBhjBMBggrBgEFBQcBAQRAMD4wPAYIKwYB +BQUHMAKGMGh0dHBzOi8vZHVzdGluLmhhdGNoLm5hbWUvZGNoLWNhL2RjaC1yb290 +LWNhLmNydDAKBggqhkjOPQQDAgNIADBFAiADzZFTGwWNkwZF1U7uZEon7D6sLmCS +WGftl3/IgOrpwwIhAM8bE5UlY3gXt8AObj8VQgGk5Bh38jmOXnDaK0iRz1qm +-----END CERTIFICATE----- diff --git a/step-ca/kustomization.yaml b/step-ca/kustomization.yaml new file mode 100644 index 0000000..95e27c5 --- /dev/null +++ b/step-ca/kustomization.yaml @@ -0,0 +1,28 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: step-ca + +resources: +- namespace.yaml +- step-ca.yaml + +configMapGenerator: +- name: step-ca-config + files: + - ca.json + +- name: step-ca-certs + files: + - root_ca.crt + - intermediate_ca.crt + - ssh_host_ca_key.pub + - ssh_user_ca_key.pub + +secretGenerator: +- name: step-ca + files: + - intermediate_ca.key + - password + - ssh_host_ca_key + - ssh_user_ca_key diff --git a/step-ca/namespace.yaml b/step-ca/namespace.yaml new file mode 100644 index 0000000..e3ee049 --- /dev/null +++ b/step-ca/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: step-ca diff --git a/step-ca/root_ca.crt b/step-ca/root_ca.crt new file mode 100644 index 0000000..6705c7a --- /dev/null +++ b/step-ca/root_ca.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBxDCCAWqgAwIBAgIUbHz2tssa09zsHk+EdGD3QKprMKQwCgYIKoZIzj0EAwQw +QDELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD0R1c3RpbiBDLiBIYXRjaDEXMBUGA1UE +AwwORENIIFJvb3QgQ0EgUjIwHhcNMjMwOTI0MjA1MzA5WhcNNDMwOTE5MjA1MzA5 +WjBAMQswCQYDVQQGEwJVUzEYMBYGA1UECgwPRHVzdGluIEMuIEhhdGNoMRcwFQYD +VQQDDA5EQ0ggUm9vdCBDQSBSMjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABE2D +NJHRcjuA19ZoprBKaxIfUxAbz6LigM7dgtO6+isaMlxRAVJmsITADIE/22RrUDgD +Ofkt2iZTUjMrz3AxXhWjQjBAMB0GA1UdDgQWBBTM+d8kb1koGmKRtJs4gN9zYa+6 +oTASBgNVHRMBAf8ECDAGAQH/AgEBMAsGA1UdDwQEAwIBBjAKBggqhkjOPQQDBANI +ADBFAiEA2Ka8mMiAFLmrFWt0dAml247re2+i4UPhyHcOBfNK+goCIHv+vEw7CHZQ +irIa697nfe4KiXIMwHlAMS1+1QZohFDC +-----END CERTIFICATE----- diff --git a/step-ca/ssh_host_ca_key.pub b/step-ca/ssh_host_ca_key.pub new file mode 100644 index 0000000..5841ab9 --- /dev/null +++ b/step-ca/ssh_host_ca_key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ8KfNjDh0R/jCmPcJrVafvmuw5JZw+cKoy9RCYNwHsRwPoRHfyzjV1VUZolJfEz+Qm3u+mgYJ/oSquCelY84xE= diff --git a/step-ca/ssh_user_ca_key.pub b/step-ca/ssh_user_ca_key.pub new file mode 100644 index 0000000..ab0f1b8 --- /dev/null +++ b/step-ca/ssh_user_ca_key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBImIoTTmhynCVy/vJ/Q2bWydzqVsvwhGvDgBbklw0eDt8UEbbP9HHPhxiMDtiAhbvRTg5BhYVAlR1MgdooT5dwQ= diff --git a/step-ca/step-ca.yaml b/step-ca/step-ca.yaml new file mode 100644 index 0000000..202ed19 --- /dev/null +++ b/step-ca/step-ca.yaml @@ -0,0 +1,128 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: step-ca + namespace: step-ca + labels: + app.kubernetes.io/name: step-ca + app.kubernetes.io/component: step-ca + app.kubernetes.io/instance: step-ca + app.kubernetes.io/part-of: step-ca +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + +--- +apiVersion: v1 +kind: Service +metadata: + name: step-ca + namespace: step-ca + labels: + app.kubernetes.io/name: step-ca + app.kubernetes.io/component: step-ca + app.kubernetes.io/instance: step-ca + app.kubernetes.io/part-of: step-ca +spec: + ports: + - port: 32599 + nodePort: 32599 + name: step-ca + selector: + app.kubernetes.io/name: step-ca + app.kubernetes.io/component: step-ca + app.kubernetes.io/instance: step-ca + type: NodePort + +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: step-ca + namespace: step-ca + labels: + app.kubernetes.io/name: step-ca + app.kubernetes.io/component: step-ca + app.kubernetes.io/instance: step-ca + app.kubernetes.io/part-of: step-ca +spec: + serviceName: step-ca + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: step-ca + app.kubernetes.io/component: step-ca + app.kubernetes.io/instance: step-ca + template: + metadata: + labels: + app.kubernetes.io/name: step-ca + app.kubernetes.io/component: step-ca + app.kubernetes.io/instance: step-ca + spec: + enableServiceLinks: false + containers: + - name: step-ca + image: docker.io/smallstep/step-ca:0.25.0 + workingDir: /step + env: + - name: CONFIGPATH + value: /step/config/ca.json + - name: PWDPATH + value: /step/secrets/password + - name: STEPPATH + value: /step + ports: + - containerPort: 32599 + name: step-ca + readinessProbe: &probe + httpGet: + port: 32599 + path: /health + scheme: HTTPS + failureThreshold: 3 + periodSeconds: 60 + successThreshold: 1 + timeoutSeconds: 1 + startupProbe: + <<: *probe + failureThreshold: 30 + periodSeconds: 3 + successThreshold: 1 + timeoutSeconds: 1 + volumeMounts: + - mountPath: /step/certs + name: certs + readOnly: true + - mountPath: /step/config + name: config + readOnly: true + - mountPath: /step/db + name: data + subPath: db + - mountPath: /step/secrets + name: secrets + readOnly: true + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + volumes: + - name: config + configMap: + name: step-ca-config + - name: certs + configMap: + name: step-ca-certs + - name: secrets + secret: + secretName: step-ca + - name: data + persistentVolumeClaim: + claimName: step-ca + +