From 21fbff62fe42c8b3b1dd52c8320496a4abc640c5 Mon Sep 17 00:00:00 2001 From: Artis Krumins Date: Thu, 17 Aug 2023 14:23:24 +0300 Subject: [PATCH 1/3] SSL certificates documentation --- docs/silta-examples.md | 41 +++++++++--------------- docs/ssl_certificates.md | 68 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 26 deletions(-) create mode 100644 docs/ssl_certificates.md diff --git a/docs/silta-examples.md b/docs/silta-examples.md index add5ae0..6790f23 100644 --- a/docs/silta-examples.md +++ b/docs/silta-examples.md @@ -465,15 +465,12 @@ If the `smtp` is configured and enabled, but it does not appear to send anything ## Domain names and SSL certificates -All environments are given a hostname by default. It is possible to attach a custom domain name to environment by configuring `exposeDomains` configuration parameter. All hostnames attached to environment are printed in release notes. +All environments are given a hostname by default. It is possible to attach a custom domain name to environment by configuring `exposeDomains` configuration parameter. All hostnames attached to environment are printed in release notes. +You can also use `letsencrypt-staging` issuer to avoid hitting `letsencrypt` [rate limits](https://letsencrypt.org/docs/rate-limits/). -Note: You can also use `letsencrypt-staging` issuer to avoid hitting `letsencrypt` [rate limits](https://letsencrypt.org/docs/rate-limits/). +!NB Deploy `exposeDomains` entries only when DNS entries are changed or are soon to be changed. Otherwise, Letsencrypt validation might eventually get stuck due to retries. -Note 2: For custom certificates it's advised to add CA root certificate to `exposeDomains[].ssl.crt` value. Having it under `exposeDomains[].ssl.ca` is not enough. - -Note 3: Deploy `exposeDomains` entries only when DNS entries are changed or are soon to be changed. Otherwise, Letsencrypt validation might eventually get stuck due to retries. - -Note 4: Put `exposeDomains` in a dedicated configuration yaml file, so only one environment (branch) would be assigned this hostname. Having multiple environments with the same domain will act as a round robin load balancer for all environments and unexpected responses might be returned. +!NB Put `exposeDomains` in a dedicated configuration yaml file, so only one environment (branch) would be assigned this hostname. Having multiple environments with the same domain will act as a round robin load balancer for all environments and unexpected responses might be returned. _Drupal chart and Frontend chart_: @@ -491,43 +488,35 @@ exposeDomains: enabled: true issuer: custom # Encrypt key and certificate. See: docs/encrypting_sensitive_configuration.md - ca: | - -----BEGIN CERTIFICATE----- - < CA CHAIN ROOT > - -----END CERTIFICATE----- - -----BEGIN CERTIFICATE----- - < CA CHAIN RCA > - -----END CERTIFICATE----- - -----BEGIN CERTIFICATE----- - < CA CERTIFICATE > - -----END CERTIFICATE----- key: | -----BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- - crt: | -----BEGIN CERTIFICATE----- - < CERTIFICATE > + < DOMAIN CERTIFICATE > -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - < CA CHAIN ROOT > + < INTERMEDIATE CERTIFICATE > -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - < CA CHAIN RCA > - -----END CERTIFICATE----- - -----BEGIN CERTIFICATE----- - < CA CERTIFICATE > + < ROOT CA CERTIFICATE > -----END CERTIFICATE----- ``` +`key` value is certificates private key. +`crt` value is full chain of certificate. +`ca` value is not required anymore for exposed domains. +[See more information on how to convert and prepare SSL certificate for exposed domains](ssl_certificates.md) If you have same SSL certificate for multiple domains You can reuse `ssl` block. ```yaml exposeDomains: - example-customcert: &shared-ssl + example-domain1: &shared-ssl ssl: [....] - example-anothercert: + example-domain2: + <<: *shared-ssl + example-domain3: <<: *shared-ssl ``` diff --git a/docs/ssl_certificates.md b/docs/ssl_certificates.md new file mode 100644 index 0000000..c6be8fd --- /dev/null +++ b/docs/ssl_certificates.md @@ -0,0 +1,68 @@ +## Basics + +Full chain consists of 3 parts. +`End-Entity (Server) Certificate:` This is your server's SSL/TLS certificate, also known as the end-entity certificate. It is the certificate that identifies your server's domain. +`Intermediate Certificates:` These are the certificates of intermediate Certificate Authorities (CAs) that form the chain between your end-entity certificate and the root CA certificate. Intermediate certificates help build the trust chain between your certificate and a root CA. They are necessary because root CA certificates are typically not distributed widely due to security reasons. +`Root CA Certificate:` This is the certificate of the root Certificate Authority. This certificate is the ultimate trust anchor in the chain. The root CA certificate establishes trust in the entire chain. + +You can have multiple Intermediate Certificates in chain. +```yaml +exposeDomains: + example-customcert: + hostname: ssl-custom.example.com + ssl: + enabled: true + issuer: custom + key: | + -----BEGIN RSA PRIVATE KEY----- + + -----END RSA PRIVATE KEY----- + crt: | + -----BEGIN CERTIFICATE----- + < DOMAIN CERTIFICATE > + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + < INTERMEDIATE CERTIFICATE 1 > + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + < INTERMEDIATE CERTIFICATE 2 > + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + < INTERMEDIATE CERTIFICATE N > + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + < ROOT CA CERTIFICATE > + -----END CERTIFICATE----- +``` + +## PFX to PEM +Extraction (legacy flag is required if older version of PKCS#12 was used to create PFX file): +`openssl pkcs12 -legacy -in custom_cert.pfx -nocerts -nodes | sed -ne '/-BEGIN PRIVATE KEY-/,/-END PRIVATE KEY-/p' > private.key` +`openssl pkcs12 -legacy -in custom_cert.pfx -cacerts -nokeys -chain | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ca.crt` +`openssl pkcs12 -legacy -in custom_cert.pfx -clcerts -nokeys | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > domain.crt` + +Creating full chain: +`cat domain.crt ca.crt > fullchain.crt` + +You can also use [these scripts](https://github.com/wunderio/internal-ops-utils/blob/master/silta-scripts/pfx-readme.md). + +## SSL certificate verification + +You can verify full chain part: +`openssl verify -CAfile fullchain.crt domain.crt` +And then matching with private key +`openssl x509 -noout -modulus -in fullchain.crt | openssl md5` +`openssl rsa -noout -modulus -in private.key | openssl md5` +Output values should match. + +Testing certificate on live server can be done only on different cluster/environment. In this case `/etc/hosts` +should be changed accordingly. +!NB Do not try to test it on Production cluster/environment where production hostname is in use already. + + +## Tips + +PEM strings can be encoded in different formats. Both cases are valid +`-----BEGIN RSA PRIVATE KEY-----` +`-----BEGIN PRIVATE KEY-----` +`openssl` will take care of correct decoding. [List of all supported formats](https://git.openssl.org/?p=openssl.git;a=blob;f=include/openssl/pem.h;hb=HEAD#l35). \ No newline at end of file From 38e4689ba4f116a1887708b9ba795739d9f8d7a3 Mon Sep 17 00:00:00 2001 From: Artis Krumins Date: Thu, 17 Aug 2023 15:05:44 +0300 Subject: [PATCH 2/3] PFX script location moved to public repo --- docs/ssl_certificates.md | 2 +- scripts/pfx-ready.sh | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100755 scripts/pfx-ready.sh diff --git a/docs/ssl_certificates.md b/docs/ssl_certificates.md index c6be8fd..29646ed 100644 --- a/docs/ssl_certificates.md +++ b/docs/ssl_certificates.md @@ -44,7 +44,7 @@ Extraction (legacy flag is required if older version of PKCS#12 was used to crea Creating full chain: `cat domain.crt ca.crt > fullchain.crt` -You can also use [these scripts](https://github.com/wunderio/internal-ops-utils/blob/master/silta-scripts/pfx-readme.md). +You can also use [this script](../scripts/pfx-ready.sh). ## SSL certificate verification diff --git a/scripts/pfx-ready.sh b/scripts/pfx-ready.sh new file mode 100755 index 0000000..d3705e8 --- /dev/null +++ b/scripts/pfx-ready.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +if [ "$#" -ne 2 ]; then + echo -e "Usage: $0 filename prefix" + exit 1 +fi + +filename="$1" +prefix="$2" + +# Check if the specified file exists +if [ ! -f "$filename" ]; then + echo "Error: File '$filename' does not exist." + exit 1 +fi + +# Check if the corresponding .pass file exists +passfile="${prefix}.pass" +if [ ! -f "$passfile" ]; then + echo "Error: Pass file '$passfile' does not exists. Create a password file [prefix].pass and put there password for decoding PFX file (f.ex. mysite.pass)" + exit 1 +fi + +openssl pkcs12 -legacy -in "$filename" -nocerts -nodes -password "file:$prefix.pass" | sed -ne '/-BEGIN PRIVATE KEY-/,/-END PRIVATE KEY-/p' > "${prefix}_private.key" +openssl pkcs12 -legacy -in "$filename" -cacerts -nokeys -password "file:$prefix.pass" | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > "${prefix}_ca.crt" +openssl pkcs12 -legacy -in "$filename" -clcerts -nokeys -password "file:$prefix.pass" | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > "${prefix}_root.crt" + +openssl x509 -in "${prefix}_root.crt" -noout -startdate -enddate + +sed -ne 's/^\( *\)[Ss]ubject[=:] */ \1/p;/X509v3 Subj.*Alt.*Name/{ + N;s/^.*\n//;:a;s/^\( *\)\(.*\), /\1\2\n\1/;ta;p;q; }' < <( + openssl x509 -in "${prefix}_root.crt" -noout -subject -ext subjectAltName) + +echo -e "\n--------------------------\n"; +echo -e "Put this under ssl:\n" +echo -e " key: |" +key_content=$(<"${prefix}_private.key") +key_indented_content=$(echo "$key_content" | sed "s/^/ /") +echo "$key_indented_content" + +echo -e " crt: |" +root_content=$(<"${prefix}_root.crt") +root_indented_content=$(echo "$root_content" | sed "s/^/ /") +echo "$root_indented_content" +ca_content=$(<"${prefix}_ca.crt") +ca_indented_content=$(echo "$ca_content" | sed "s/^/ /") +echo "$ca_indented_content" From 415abdc4de849418dc2327ad69d70794d2fa3f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artis=20Kr=C5=ABmi=C5=86=C5=A1?= Date: Fri, 25 Aug 2023 13:19:54 +0300 Subject: [PATCH 3/3] Update ssl_certificates.md Adding steps to verify SSL certificate on live server --- docs/ssl_certificates.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/ssl_certificates.md b/docs/ssl_certificates.md index 29646ed..b6972a2 100644 --- a/docs/ssl_certificates.md +++ b/docs/ssl_certificates.md @@ -55,9 +55,19 @@ And then matching with private key `openssl rsa -noout -modulus -in private.key | openssl md5` Output values should match. -Testing certificate on live server can be done only on different cluster/environment. In this case `/etc/hosts` -should be changed accordingly. -!NB Do not try to test it on Production cluster/environment where production hostname is in use already. +Testing certificate on live server can be done only on different cluster/environment. +*!NB Do not try to test it on Production cluster/environment where production hostname is in use already.* +#### Steps to test SSL certificate on Development cluster + * Make a new Git branch + * Add SSL certificates domain to Exposed domains in `stila.yml` + * Create secrets file, put relevant structure and encrypt it with cluster's secret key + * Modify `.circleci/config.yml` to decrypt secret and use it in `silta_config` part + * Push branch to trigger deployment + * Verify SSL certificate with `openssl s_client -connect [IP]:443 -servername [hostname]`. Expected result + `SSL handshake has read 7583 bytes and written 408 bytes Verification: OK`. If something is wrong You'll get + `Verification error: unable to verify the first certificate` and/or `Verify return code: 21 (unable to verify the first certificate)` + * You can also change `/etc/hosts` to resolve hostname and verify SSL certificate via browser + * When everything looks good delete the testing branch and proceed with production release. ## Tips @@ -65,4 +75,4 @@ should be changed accordingly. PEM strings can be encoded in different formats. Both cases are valid `-----BEGIN RSA PRIVATE KEY-----` `-----BEGIN PRIVATE KEY-----` -`openssl` will take care of correct decoding. [List of all supported formats](https://git.openssl.org/?p=openssl.git;a=blob;f=include/openssl/pem.h;hb=HEAD#l35). \ No newline at end of file +`openssl` will take care of correct decoding. [List of all supported formats](https://git.openssl.org/?p=openssl.git;a=blob;f=include/openssl/pem.h;hb=HEAD#l35).