Computing/Running my own CA: Difference between revisions

From Cricalix.Net
mNo edit summary
 
(11 intermediate revisions by the same user not shown)
Line 11: Line 11:
# Export out the root CRT file, will need it for trust everywhere
# Export out the root CRT file, will need it for trust everywhere
## <code>certs/root_ca.crt</code>
## <code>certs/root_ca.crt</code>
An existing acme configuration can have the default duration extended by setting a parameter in ca.json.
  "claims": {                                                                     
    "defaultTLSCertDuration": "730h0m0s",                                   
    ...                               
  },


== Deploy ==
== Deploy ==
Line 25: Line 31:
  homeassistant.home.arpa {
  homeassistant.home.arpa {
  reverse_proxy homeassistant:8123
  reverse_proxy homeassistant:8123
  tls ca@home.arpa {
  tls acme@home.arpa {
  ca <nowiki>https://vault.home.arpa:9000/acme/acme/directory</nowiki>
  ca <nowiki>https://vault.home.arpa:9000/acme/acme/directory</nowiki>
  ca_root /ssl/ca.pem
  ca_root /config/ssl/ca.pem
  }
  }
  }
  }
Line 42: Line 48:
# Copy the cert to /var/db/ca-certificates, named .crt, 0644
# Copy the cert to /var/db/ca-certificates, named .crt, 0644
# Run update-ca-certificates.sh
# Run update-ca-certificates.sh
# Grab acme.sh from Github - <code>wget <nowiki>https://github.com/acmesh-official/acme.sh/archive/master.tar.gz</nowiki></code>
# Grab acme.sh from Github
## <code>wget <nowiki>https://github.com/acmesh-official/acme.sh/archive/master.tar.gz</nowiki></code>
# Install to /usr/local/share/acme.sh/
# Install to /usr/local/share/acme.sh/
# Piggyback on the DSM Let's Encrypt setup in nginx, and run <code>/usr/local/share/acme.sh/acme.sh --issue --home /usr/local/share/acme.sh -d vault.home.arpa  --server <nowiki>https://vault.home.arpa:9000/acme/acme/directory</nowiki> --webroot /var/lib/letsencrypt --days 90</code>
## <code>./acme.sh --install --nocron --home /usr/local/share/acme.sh --accountemail "acme@home.arpa"</code>
# Set SYNO_Username, SYNO_Password
# Piggyback on the DSM Let's Encrypt setup in nginx, and run
# <code>/usr/local/share/acme.sh/acme.sh --deploy --home /usr/local/share/acme.sh -d vault.home.arpa --deploy-hook synology_dsm</code>
## <code>/usr/local/share/acme.sh/acme.sh --issue --home /usr/local/share/acme.sh -d vault.home.arpa -d jellyfin.home.arpa -d lidarr.home.arpa -d sonarr.home.arpa -d radarr.home.arpa -d erddap.home.arpa --server <nowiki>https://vault.home.arpa:9000/acme/acme/directory</nowiki> --webroot /var/lib/letsencrypt</code>
# Set SYNO_Username, SYNO_Password in the shell environment
# Deploy the main name
## <code>/usr/local/share/acme.sh/acme.sh --deploy --home /usr/local/share/acme.sh -d vault.home.arpa --deploy-hook synology_dsm</code>
# Add a Task Scheduler entry to run <code>/usr/local/share/acme.sh/acme.sh --cron --home /usr/local/share/acme.sh/</code> frequently. step-ca defaults to 24 hours, so every 4 hours should ensure the certificate stays updated. With a 1 month renewal, this could be weekly.
# Add a Task Scheduler entry to run <code>/usr/local/share/acme.sh/acme.sh --cron --home /usr/local/share/acme.sh/</code> frequently. step-ca defaults to 24 hours, so every 4 hours should ensure the certificate stays updated. With a 1 month renewal, this could be weekly.
# Add a Task Scheduler entry to run
## <code>/usr/local/share/acme.sh/acme.sh --deploy --home /usr/local/share/acme.sh -d vault.home.arpa --deploy-hook synology_dsm</code>
## Without it, the cron schedule will happily regenerate the certificate, but it won't get installed when it expires.


==== Dockers on Synology ====
==== Dockers on Synology ====
Line 59: Line 72:
# Save
# Save
# Try the new name in a browser or whatever.
# Try the new name in a browser or whatever.
 
-----
=== Desktop ===
=== Desktop ===


# For firefox, find the CA cert, add it to the local store
# For firefox, find the CA cert, add it to the local store
-----
=== Router ===
# Add router.home.arpa to the static DNS setup
# Install the luci-app-acme UI; it pulls in acme.sh
# Delete existing configs
# Add new config pointing to IP address of vault as custom source, including the port
# Hack the code to add --insecure to the curl call
# Request name router.home.arpa
# Should all work
<code>/etc/config/acme</code>
config acme
        option state_dir '/etc/acme'
        option debug '0'
        option account_email 'acme@home.arpa'
 
config cert 'router'
        option enabled '1'
        option use_staging '0'
        option keylength '2048'
        list domains 'router.home.arpa'
        option update_uhttpd '1'
        option validation_method 'webroot'
        option webroot '/www'
        option use_acme_server '1'
        option acme_server 'https://192.168.0.218:9000/acme/acme/directory'


==== Errors ====
* Curl error 35 - probably forgot the port number
* Curl error 60 - probably forgot --insecure
-----
=== iOS ===
=== iOS ===


Line 81: Line 128:
# Use the letsencrypt add-on for HA, and specify the CA crt in the configuration. Have to apply https://github.com/home-assistant/addons/issues/2713<nowiki/>'s fix or things fail with error code 6.
# Use the letsencrypt add-on for HA, and specify the CA crt in the configuration. Have to apply https://github.com/home-assistant/addons/issues/2713<nowiki/>'s fix or things fail with error code 6.
# Use the step-client add-on for HA. This got the root CA crt copied over, but didn't help with anything else really. Easier to just copy the certificate by hand.
# Use the step-client add-on for HA. This got the root CA crt copied over, but didn't help with anything else really. Easier to just copy the certificate by hand.
== Troubleshooting ==
At some point, my ACME provisioner vanished. This caused HomeAssistant's Caddy2 docker no end of grief, because it couldn't renew any more. The error message on the client side is very perplexing - <nowiki>urn:ietf:params:acme:error:accountDoesNotExist</nowiki> / "account does not exist". It's not saying that the email identifier of the '''client''' doesn't exist, but that the '''provisioner''' doesn't exist.
Root-caused - hadn't provided an outside-of-docker store for secrets etcetera, so an upgrade of the image lost all the generated configuration.
Other root-caused - acme.sh has cached credentials for the certificate, and now that account doesn't exist either. Happens when the filesystem resets post upgrade, and after restoring the acme provider.
It's easily fixed by re-adding the provisioner to the Step CA setup, but then a whole new set of CA certs may need distributing - I had to do this at least, and the dates on the certs had changed. Without doing this, Firefox will show SEC_ERROR_BAD_SIGNATURE.

Latest revision as of 15:24, 10 December 2023

I use the RFC-approved home.arpa domain at home. You cannot get certificates for devices from Let's Encrypt when you do this, because no-one (those of us who want certificates at least) has control over the DNS for home.arpa. This also means the CA has to be on the LAN, because a net-based service a) won't know how to look up home.arpa entries on the local router, b) and even if it could, it can't reach the internal services without firewall hole punching.

Thus, run step-ca locally in a Docker container, and get everything configured to talk to it.

CA Setup

  1. Use step-ca in docker on Synology NAS, mapping through port 9000:9000
  2. Add the acme provider per the docs
    1. SSH to the host
    2. docker -it smallstep-step-ca-1 /bin/bash
    3. step ca provisioner add acme --type ACME --x509-default-dur 730h # 1 monthish
  3. Export out the root CRT file, will need it for trust everywhere
    1. certs/root_ca.crt

An existing acme configuration can have the default duration extended by setting a parameter in ca.json.

 "claims": {                                                                      
   "defaultTLSCertDuration": "730h0m0s",                                    
   ...                                 
 },

Deploy

HomeAssistant

  1. Update the http config to allow trusted reverse proxies
  2. Install the Caddy2 add-on from https://github.com/einschmidt/hassio-addons
  3. Add a Caddyfile that specifies the local CA
  4. Ensure the Caddyfile points to the exported CA certificate in somewhere like /config
  5. Start Caddy2 and it should successfully retrieve a certificate
Working Caddyfile
homeassistant.home.arpa {
	reverse_proxy homeassistant:8123
	tls acme@home.arpa {
		ca https://vault.home.arpa:9000/acme/acme/directory
		ca_root /config/ssl/ca.pem
	}
}
Working configuration.yaml
http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 192.168.0.194  # whatever the homeassistant box resolves to

Synology

To add TLS/SSL to the NAS itself,

  1. Copy the cert to /var/db/ca-certificates, named .crt, 0644
  2. Run update-ca-certificates.sh
  3. Grab acme.sh from Github
    1. wget https://github.com/acmesh-official/acme.sh/archive/master.tar.gz
  4. Install to /usr/local/share/acme.sh/
    1. ./acme.sh --install --nocron --home /usr/local/share/acme.sh --accountemail "acme@home.arpa"
  5. Piggyback on the DSM Let's Encrypt setup in nginx, and run
    1. /usr/local/share/acme.sh/acme.sh --issue --home /usr/local/share/acme.sh -d vault.home.arpa -d jellyfin.home.arpa -d lidarr.home.arpa -d sonarr.home.arpa -d radarr.home.arpa -d erddap.home.arpa --server https://vault.home.arpa:9000/acme/acme/directory --webroot /var/lib/letsencrypt
  6. Set SYNO_Username, SYNO_Password in the shell environment
  7. Deploy the main name
    1. /usr/local/share/acme.sh/acme.sh --deploy --home /usr/local/share/acme.sh -d vault.home.arpa --deploy-hook synology_dsm
  8. Add a Task Scheduler entry to run /usr/local/share/acme.sh/acme.sh --cron --home /usr/local/share/acme.sh/ frequently. step-ca defaults to 24 hours, so every 4 hours should ensure the certificate stays updated. With a 1 month renewal, this could be weekly.
  9. Add a Task Scheduler entry to run
    1. /usr/local/share/acme.sh/acme.sh --deploy --home /usr/local/share/acme.sh -d vault.home.arpa --deploy-hook synology_dsm
    2. Without it, the cron schedule will happily regenerate the certificate, but it won't get installed when it expires.

Dockers on Synology

The NAS runs multiple contains for things like Jellyfin. A small dance is needed, but it's not a bad one.

  1. Add DNS name on router DNS zone, A record pointing at the vault.home.arpa name
  2. Re-run the --issue command, adding another -d for the name to add to the certificate. This turns into a SAN certificate.
  3. Deploy the certificate
  4. Settings > Login Portal > Advanced > Reverse Proxy
  5. Proxy the new name on https to localhost on the relevant port.
  6. Save
  7. Try the new name in a browser or whatever.

Desktop

  1. For firefox, find the CA cert, add it to the local store

Router

  1. Add router.home.arpa to the static DNS setup
  2. Install the luci-app-acme UI; it pulls in acme.sh
  3. Delete existing configs
  4. Add new config pointing to IP address of vault as custom source, including the port
  5. Hack the code to add --insecure to the curl call
  6. Request name router.home.arpa
  7. Should all work

/etc/config/acme

config acme
        option state_dir '/etc/acme'
        option debug '0'
        option account_email 'acme@home.arpa'
 
config cert 'router'
        option enabled '1'
        option use_staging '0'
        option keylength '2048'
        list domains 'router.home.arpa'
        option update_uhttpd '1'
        option validation_method 'webroot'
        option webroot '/www'
        option use_acme_server '1'
        option acme_server 'https://192.168.0.218:9000/acme/acme/directory'


Errors

  • Curl error 35 - probably forgot the port number
  • Curl error 60 - probably forgot --insecure

iOS

  1. Email the root certificate to myself
  2. Tap on it in Mail
  3. Save to iCloud or phone
  4. Open Files app
  5. Tap certificate
  6. Open Settings, should go to profile imports
  7. Import the certificate
  8. Settings root, search for trust
  9. Enable trust for the certificate


Paths taken, but reversed

  1. Use the letsencrypt add-on for HA, and specify the CA crt in the configuration. Have to apply https://github.com/home-assistant/addons/issues/2713's fix or things fail with error code 6.
  2. Use the step-client add-on for HA. This got the root CA crt copied over, but didn't help with anything else really. Easier to just copy the certificate by hand.

Troubleshooting

At some point, my ACME provisioner vanished. This caused HomeAssistant's Caddy2 docker no end of grief, because it couldn't renew any more. The error message on the client side is very perplexing - urn:ietf:params:acme:error:accountDoesNotExist / "account does not exist". It's not saying that the email identifier of the client doesn't exist, but that the provisioner doesn't exist.

Root-caused - hadn't provided an outside-of-docker store for secrets etcetera, so an upgrade of the image lost all the generated configuration.

Other root-caused - acme.sh has cached credentials for the certificate, and now that account doesn't exist either. Happens when the filesystem resets post upgrade, and after restoring the acme provider.

It's easily fixed by re-adding the provisioner to the Step CA setup, but then a whole new set of CA certs may need distributing - I had to do this at least, and the dates on the certs had changed. Without doing this, Firefox will show SEC_ERROR_BAD_SIGNATURE.