Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSL certificates documentation #102

Merged
merged 4 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 15 additions & 26 deletions docs/silta-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_:

Expand All @@ -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-----
<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
```

Expand Down
78 changes: 78 additions & 0 deletions docs/ssl_certificates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
## 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-----
<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 [this script](../scripts/pfx-ready.sh).

## 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.
*!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

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).
47 changes: 47 additions & 0 deletions scripts/pfx-ready.sh
Original file line number Diff line number Diff line change
@@ -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"