mirror of
https://gitea.com/mcereda/oam.git
synced 2026-02-09 05:44:23 +00:00
chore(letsencrypt): create valid certificates
This commit is contained in:
@@ -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]
|
||||
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -173,6 +173,7 @@
|
||||
"kustomization",
|
||||
"kustomize",
|
||||
"lefthook",
|
||||
"letsencrypt",
|
||||
"libexec",
|
||||
"lighttpd",
|
||||
"localdomain",
|
||||
|
||||
95
examples/ansible/letsencrypt.create-certificate.yml
Normal file
95
examples/ansible/letsencrypt.create-certificate.yml
Normal 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
|
||||
1
examples/pulumi/letsencrypt-certificate.dns01/.env
Normal file
1
examples/pulumi/letsencrypt-certificate.dns01/.env
Normal file
@@ -0,0 +1 @@
|
||||
export PULUMI_CONFIG_PASSPHRASE=test123
|
||||
1
examples/pulumi/letsencrypt-certificate.dns01/.env.fish
Normal file
1
examples/pulumi/letsencrypt-certificate.dns01/.env.fish
Normal file
@@ -0,0 +1 @@
|
||||
set -x 'PULUMI_CONFIG_PASSPHRASE' 'test123'
|
||||
2
examples/pulumi/letsencrypt-certificate.dns01/.gitignore
vendored
Normal file
2
examples/pulumi/letsencrypt-certificate.dns01/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/bin/
|
||||
/node_modules/
|
||||
@@ -0,0 +1,4 @@
|
||||
encryptionsalt: v1:rsWIsa8WSik=:v1:D517hSFtoEVILMBz:wB9tX0Bu0Y0WqsXEYenywicAjnTHJw==
|
||||
|
||||
config:
|
||||
acme:serverUrl: https://acme-v02.api.letsencrypt.org/directory
|
||||
@@ -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://.
|
||||
82
examples/pulumi/letsencrypt-certificate.dns01/index.ts
Normal file
82
examples/pulumi/letsencrypt-certificate.dns01/index.ts
Normal 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 */
|
||||
3127
examples/pulumi/letsencrypt-certificate.dns01/package-lock.json
generated
Normal file
3127
examples/pulumi/letsencrypt-certificate.dns01/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
examples/pulumi/letsencrypt-certificate.dns01/package.json
Normal file
14
examples/pulumi/letsencrypt-certificate.dns01/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
18
examples/pulumi/letsencrypt-certificate.dns01/tsconfig.json
Normal file
18
examples/pulumi/letsencrypt-certificate.dns01/tsconfig.json
Normal 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
29
knowledge base/acme.md
Normal 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 -->
|
||||
@@ -1,3 +0,0 @@
|
||||
# Automated Certificate Management Environment
|
||||
|
||||
[]: https://www.keyfactor.com/blog/what-is-acme-protocol-and-how-does-it-work/
|
||||
@@ -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
|
||||
|
||||
@@ -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 '.[].[]' -
|
||||
|
||||
57
knowledge base/letsencrypt.md
Normal file
57
knowledge base/letsencrypt.md
Normal 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 -->
|
||||
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
7
snippets/ansible.tasks.yml
Normal file
7
snippets/ansible.tasks.yml
Normal 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
|
||||
Reference in New Issue
Block a user