chore(letsencrypt): create valid certificates

This commit is contained in:
Michele Cereda
2024-05-08 18:46:32 +02:00
parent 21d2c1865c
commit 81d263417b
20 changed files with 3477 additions and 6 deletions

View File

@@ -2,3 +2,4 @@ ansible/playbooks/aws.ec2.enable-ssm-agent.yml package-latest
ansible/playbooks/keybase.register-device.yml no-changed-when
examples/ansible/aws_ec2.yml yaml[comments-indentation]
examples/pulumi/gitlab-omnibus-on-aws-ec2/ansible-playbook.yml package-latest
examples/pulumi/gitlab-omnibus-on-aws-ec2/ansible-playbook.yml role-name[path]

View File

@@ -173,6 +173,7 @@
"kustomization",
"kustomize",
"lefthook",
"letsencrypt",
"libexec",
"lighttpd",
"localdomain",

View File

@@ -0,0 +1,95 @@
---
- name: Create and validate an HTTPS certificate
hosts: all
vars:
common_name: service.example.org
pre_tasks:
- name: Generate private keys for an account and the certificate
community.crypto.openssl_privatekey:
path: "{{ item }}"
type: RSA
size: 4096
with_items:
- /tmp/{{ common_name }}.key
- /tmp/letsencrypt.account.key.pem
# - name: Generate private keys for an account and the certificate - OpenSSH alternative
# community.crypto.openssh_keypair:
# path: "{{ item }}"
# type: rsa
# size: 4096
# with_items:
# - /tmp/{{ common_name }}.key
# - /tmp/letsencrypt.account.key.pem
tasks:
- name: Generate the CRS for the certificate
community.crypto.openssl_csr:
path: /tmp/{{ common_name }}.crs
privatekey_path: /tmp/{{ common_name }}.key
common_name: "{{ common_name }}"
- name: Create the DNS challenge for '{{ common_name }}'
community.crypto.acme_certificate:
challenge: dns-01
acme_version: 2
acme_directory: https://acme-v02.api.letsencrypt.org/directory
account_key_src: /tmp/letsencrypt.account.key.pem
account_email: someone@example.org
csr: /tmp/{{ common_name }}.crs
cert: /tmp/{{ common_name }}.crt
terms_agreed: true
remaining_days: 21
register: dns_challenge
notify: Create TXT records for challenge validation
handlers:
- name: Create TXT records for challenge validation
when: common_name in dns_challenge.challenge_data
amazon.aws.route53:
zone: example.org
record: "{{ dns_challenge.challenge_data[common_name]['dns-01'].record }}"
type: TXT
ttl: 60
state: present
overwrite: true
wait: true
value:
# Value should be enclosed in quotation marks
>-
{{
dns_challenge.challenge_data[common_name]['dns-01'].resource_value
| regex_replace('^(.*)$', '"\1"')
}}
notify: Validate the challenge and create the certificate
- name: Validate the challenge and create the certificate
community.crypto.acme_certificate:
challenge: dns-01
acme_version: 2
acme_directory: https://acme-v02.api.letsencrypt.org/directory
account_key_src: /tmp/letsencrypt.account.key.pem
account_email: someone@example.org
csr: /tmp/{{ common_name }}.crs
cert: /tmp/{{ common_name }}.crt
remaining_days: 21
terms_agreed: true
data: "{{ dns_challenge }}"
post_tasks:
- name: Delete TXT records for challenge validation
vars:
validation_record: "{{ ['_acme-challenge', common_name] | join('.') }}"
when: query('community.dns.lookup', validation_record, type='TXT') != []
amazon.aws.route53:
zone: example.org
record: "{{ validation_record }}"
type: TXT
state: absent
wait: true

View File

@@ -0,0 +1 @@
export PULUMI_CONFIG_PASSPHRASE=test123

View File

@@ -0,0 +1 @@
set -x 'PULUMI_CONFIG_PASSPHRASE' 'test123'

View File

@@ -0,0 +1,2 @@
/bin/
/node_modules/

View File

@@ -0,0 +1,4 @@
encryptionsalt: v1:rsWIsa8WSik=:v1:D517hSFtoEVILMBz:wB9tX0Bu0Y0WqsXEYenywicAjnTHJw==
config:
acme:serverUrl: https://acme-v02.api.letsencrypt.org/directory

View File

@@ -0,0 +1,9 @@
name: letsencrypt-certificate.dns01
runtime: nodejs
description: DNS01 challenge with ACME leveraging Let's Encrypt
config:
pulumi:tags:
value:
pulumi:template: typescript
backend:
url: file://.

View File

@@ -0,0 +1,82 @@
import * as acme from '@pulumiverse/acme';
import * as cloudinit from "@pulumi/cloudinit";
import * as pulumi from "@pulumi/pulumi";
import * as tls from "@pulumi/tls";
import * as yaml from "yaml";
/**
* LetsEncrypt certificate - start
* -------------------------------------
* Leverage the DNS challenge to keep the instance private at all times.
**/
const privateKey = new tls.PrivateKey(
"privateKey",
{ algorithm: "RSA" },
);
const registration = new acme.Registration(
"registration",
{
accountKeyPem: privateKey.privateKeyPem,
emailAddress: "example@company.com",
},
);
const certificate = new acme.Certificate(
"certificate",
{
accountKeyPem: registration.accountKeyPem,
commonName: "gitlab.company.com",
dnsChallenges: [{
provider: "route53",
}],
},
);
/* LetsEncrypt certificate - end */
/**
* Instance - start
* -------------------------------------
* https://serverfault.com/questions/62496/ssl-certificate-location-on-unix-linux#722646
**/
const userData = new cloudinit.Config(
"cloudConfig",
{
gzip: true,
base64Encode: true,
parts: [
{
contentType: "text/cloud-config",
content: pulumi.all([
certificate.certificateDomain.apply(v => v),
certificate.certificatePem.apply(v => v),
certificate.privateKeyPem.apply(v => v),
]).apply(([domain, certificate, privateKey]) => yaml.stringify({
write_files: [
{
path: `/etc/pki/tls/certs/${domain}.crt`,
content: btoa(certificate),
permissions: "0o600",
encoding: "base64",
defer: true,
},
{
path: `/etc/pki/tls/private/${domain}.key`,
content: btoa(privateKey),
permissions: "0o600",
encoding: "base64",
defer: true,
},
],
})),
filename: "cloud-config.letsencrypt.certificate.yml",
mergeType: "dict(recurse_array,no_replace)+list(append)",
},
],
},
);
/* Instance - end */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
{
"name": "letsencrypt-certificate.dns01",
"main": "index.ts",
"devDependencies": {
"@types/node": "^18"
},
"dependencies": {
"@pulumi/cloudinit": "1.4.3",
"@pulumi/pulumi": "3.115.2",
"@pulumi/tls": "5.0.3",
"@pulumiverse/acme": "0.0.1",
"yaml": "2.4.2"
}
}

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2020",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}

29
knowledge base/acme.md Normal file
View File

@@ -0,0 +1,29 @@
# Automated Certificate Management Environment
Protocol allowing for automation of issuance and renewal of certificates.
1. [Further readings](#further-readings)
1. [Sources](#sources)
## Further readings
- [Let's Encrypt]
### Sources
- [What is ACME protocol and how does it work?]
<!--
Reference
═╬═Time══
-->
<!-- In-article sections -->
<!-- Knowledge base -->
[let's encrypt]: letsencrypt.md
<!-- Files -->
<!-- Upstream -->
[what is acme protocol and how does it work?]: https://www.keyfactor.com/blog/what-is-acme-protocol-and-how-does-it-work/
<!-- Others -->

View File

@@ -1,3 +0,0 @@
# Automated Certificate Management Environment
[]: https://www.keyfactor.com/blog/what-is-acme-protocol-and-how-does-it-work/

View File

@@ -281,6 +281,7 @@ Examples:
- [Date & time policy conditions at AWS - 1-minute IAM lesson]
- [IAM JSON policy elements: Sid]
- [Elastic IP addresses]
- [Using IAM policy conditions for fine-grained access control to manage resource record sets]
<!--
References
@@ -316,6 +317,7 @@ Examples:
[nat gateways]: https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html
[services that publish cloudwatch metrics]: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html
[subnets for your vpc]: https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html
[using iam policy conditions for fine-grained access control to manage resource record sets]: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/specifying-rrset-conditions.html
[using service-linked roles]: https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html
[what is amazon vpc?]: https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html
[what is aws config?]: https://docs.aws.amazon.com/config/latest/developerguide/WhatIsConfig.html

View File

@@ -32,6 +32,9 @@ export AWS_PROFILE='work'
aws configure set 'cli_auto_prompt' 'on-partial'
export AWS_CLI_AUTO_PROMPT='on'
# Check the current configuration.
aws configure list
# Clear cached credentials.
rm -r ~'/.aws/cli/cache'
```
@@ -102,6 +105,10 @@ aws rds describe-db-instances
aws rds describe-db-instances --output 'json' --query "DBInstances[?(DBInstanceIdentifier=='master-prod')]"
# List hosted zones.
aws route53 list-hosted-zones
# List all SageMaker EndpointConfigurations' names.
aws sagemaker list-endpoint-configs --output 'yaml-stream' | yq -r '.[].EndpointConfigs[].EndpointConfigName' -
aws sagemaker list-endpoint-configs --output 'yaml-stream' --query 'EndpointConfigs[].EndpointConfigName' | yq -r '.[].[]' -

View File

@@ -0,0 +1,57 @@
# Let's Encrypt
1. [Challenges](#challenges)
1. [DNS-01 challenge](#dns-01-challenge)
1. [Further readings](#further-readings)
1. [Sources](#sources)
## Challenges
### DNS-01 challenge
Requires one to prove one has control over the DNS for one's domain name.<br/>
This also allows one to issue wildcard certificates for the domain name in question.
Proof is achieved by creating a TXT record with a specific value under that domain name.
The procedure is as follows:
1. The ACME client requests Let's Encrypt a token.
1. The client, or anything else capable, creates the TXT record in the DNS at `_acme-challenge.{{ domain name }}`.<br/>
The value of the record needs to be derived from the token and one's account key.
1. The client requests Let's Encrypt to query the DNS system for the TXT record.
1. If Let's Encrypt finds a match, one can proceed to issue a certificate.
This process kinda only makes sense to leverage the DNS-01 challenge type if one's DNS provider allows for automation.
Let's Encrypt follows the DNS standards when looking up TXT records for DNS-01 validation.<br/>
As such, one can use CNAME or NS records to delegate answering the challenge to other DNS zones, meaning this can be
used to delegate the `_acme-challenge` subdomain to a validation-specific server or zone.
One can have multiple TXT records in place for the same name.<br/>
However, make sure to clean up old TXT records: Let's Encrypt will start rejecting the request if the response size from
the DNS gets too big.
## Further readings
- [Website]
- [ACME]
### Sources
- [Challenge types]
<!--
Reference
═╬═Time══
-->
<!-- In-article sections -->
<!-- Knowledge base -->
[acme]: acme.placeholder
<!-- Files -->
<!-- Upstream -->
[challenge types]: https://letsencrypt.org/docs/challenge-types/
[website]: https://letsencrypt.org/
<!-- Others -->

View File

@@ -48,8 +48,9 @@ openssl req -text -noout -verify -in 'request.csr'
# Check existing keys and verify their consistency.
openssl rsa -check -in 'file.key'
# Check certificates and return information about them.
# Check certificates or keys and return information about them.
openssl x509 -text -noout -in 'certificate.crt'
openssl rsa -text -noout -in 'private.key'
# Verify certificate chains.
# If a certificate is its own issuer, it is assumed to be the root CA.
@@ -76,6 +77,14 @@ openssl x509 -noout -modulus -in 'certificate.crt' | openssl md5
# Remove password protection from keys.
openssl rsa -in 'protected.key' -out 'unprotected.key'
# Protect keys with passwords.
openssl rsa -aes192 -in 'unprotected.key' -out 'protected.key'
# Print the public key corresponding to private ones.
openssl rsa -in private.pem -pubout
openssl rsa -in private.pem -pubout -out public.pem
# Convert DER-formatted files (.crt .cer .der) to the PEM format.
openssl x509 -inform 'der' -in 'certificate.cer' -out 'certificate.pem'
@@ -206,6 +215,7 @@ All the references in the [further readings] section, plus the following:
- [How to generate a self-signed SSL certificate using OpenSSL]
- [OpenSSL unable to verify the first certificate for Experian URL]
- [Verify certificate chain with OpenSSL]
- [How to put domain correctly in CSR?]
<!--
References
@@ -219,6 +229,7 @@ All the references in the [further readings] section, plus the following:
[create a self signed certificate]: https://stackoverflow.com/questions/10175812/how-to-create-a-self-signed-certificate-with-openssl#10176685
[display the contents of a ssl certificate]: https://support.qacafe.com/knowledge-base/how-do-i-display-the-contents-of-a-ssl-certificate/
[how to generate a self-signed ssl certificate using openssl]: https://stackoverflow.com/questions/10175812/how-to-generate-a-self-signed-ssl-certificate-using-openssl#10176685
[how to put domain correctly in csr?]: https://www.namecheap.com/support/knowledgebase/article.aspx/9641/2290/how-to-put-domain-correctly-in-csr/
[openssl commands to check and verify your ssl certificate, key and csr]: https://www.ibm.com/support/pages/openssl-commands-check-and-verify-your-ssl-certificate-key-and-csr
[openssl unable to verify the first certificate for experian url]: https://stackoverflow.com/questions/7587851/openssl-unable-to-verify-the-first-certificate-for-experian-url
[the most common openssl commands]: https://www.sslshopper.com/article-most-common-openssl-commands.html

View File

@@ -61,12 +61,16 @@ ssh-copy-id -i "${HOME}/.ssh/id_rsa.pub" 'user@host.fqdn'
# Preload trusted keys.
ssh-keyscan 'host.fqdn' >> "${HOME}/.ssh/known_hosts"
# Connect to an unreachable host tunnelling the session through a bastion.
# Connect to a directly unreachable host by tunnelling sessions.
ssh -t 'bastion-host' ssh 'unreachable-host'
# Mount a remote folder.
# Mount remote folders.
sshfs 'nas.lan:/mnt/data' 'Data' \
-o 'auto_cache,reconnect,defer_permissions,noappledouble,volname=Data'
# Validate keys.
ssh-keygen -yef 'path/to/key'
openssl rsa -check -in 'path/to/key' -noout
```
## Server installation on Windows
@@ -359,6 +363,7 @@ Solution: update the SSH server.
- [Get started with OpenSSH for Windows]
- [Restrict SSH login to a specific IP or host]
- [Stick with security: YubiKey, SSH, GnuPG, macOS]
- [How to check if an RSA public / private key pair match]
<!--
References
@@ -379,6 +384,7 @@ Solution: update the SSH server.
<!-- Others -->
[get started with openssh for windows]: https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse?tabs=gui
[how to check if an rsa public / private key pair match]: https://serverfault.com/questions/426394/how-to-check-if-an-rsa-public-private-key-pair-match#426429
[how to enable ssh access using a gpg key for authentication]: https://opensource.com/article/19/4/gpg-subkeys-ssh
[how to list keys added to ssh-agent with ssh-add?]: https://unix.stackexchange.com/questions/58969/how-to-list-keys-added-to-ssh-agent-with-ssh-add
[how to perform hostname canonicalization]: https://sleeplessbeastie.eu/2020/08/24/how-to-perform-hostname-canonicalization/

View File

@@ -0,0 +1,7 @@
---
# Directories are created recursively.
- name: Create a whole directory tree
ansible.builtin.file:
path: /tmp/path/to/final/dir
state: directory