# TLS certificates in Infra
## Common naming convention.
Ansible roles are using the following logic to distribute .key/.cert files
```
* {{ public_name }}.key : TLS private key
* {{ public_name }}.crt : signed TLS certificate
* {{ public_name }}-CAchain.crt : Trusted chain from CA (usually a symlink in pkistore is enough as we have a very few)
```
## Internal certificates
### IPA/dogtag (central authentication)
While IPA enrolled nodes can directly request TLS certificates, for CentOS infra, due to the fact that almost *all* nodes *can't* be enrolled (no direct link with IPA infra), we have to delegate this on an enrolled node where we can then be granted "delegation" rights in IPA, so that a "fake" enrolled node (created for the real target server that will need a TLS cert) can be "managed by" the enrolled node.
From that enrolled node, we'll then be able to retrieve the TLS cert/keytab and also then export/import into `pkistore` , following the same naming convention as described above.
Depending on the Env (Prod vs STG), you can find which node[s] is/are enrolled (through applied `ipa-client` client role applied with also the `ipa_client_tls_delegated_host` boolean set to `True` (needed for the following script to be present in the ipa-client role).
Pre-requisites:
* one node enrolled in the correct REALM we want to generate/retrieve TLS cert (and keytab) for
* an IPA account that has enough privileges to add hosts/services and local sudo rights on the intermediate enrolled node
* `ipa-client` role applied with correct script deployed
Once we have shell access on such enrolled node, we can proceed like this :
```
/usr/libexec/centos/generate_ipa_tls_krb5
You need to call the script like this : /usr/libexec/centos/generate_ipa_tls_krb5 -arguments
-n : node name / fqdn ([REQUIRED], example 'ppc64-01.cbs.centos.org')
-d : Description for that host ([REQUIRED], example 'cbs koji builder')
-s : service for principal ([REQUIRED], example 'compile' would create compile/ppc64-01.cbs.centos.org service in IPA)
-h : display this help
You also need a valid kerberos ticket otherwise script will exit
```
Here is an output example for the command with arguments:
```
/usr/libexec/centos/generate_ipa_tls_krb5 -n mbs.mbox.stg.centos.org -d 'Koji mbox STG mbs' -s 'HTTP'
[+] Adding host in IPA and adding delegation to retrieve certs/keytab ...
------------------------------------
Added host "mbs.mbox.stg.centos.org"
------------------------------------
Host name: mbs.mbox.stg.centos.org
Description: Koji mbox STG mbs
Principal name: host/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Principal alias: host/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Password: False
Keytab: False
Managed by: mbs.mbox.stg.centos.org
Host name: mbs.mbox.stg.centos.org
Description: Koji mbox STG mbs
Principal name: host/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Principal alias: host/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Managed by: mbs.mbox.stg.centos.org, <modified>.fedoraproject.org
-------------------------
Number of members added 1
-------------------------
------------------------------------------------------------------
Added service "HTTP/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG"
------------------------------------------------------------------
Principal name: HTTP/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Principal alias: HTTP/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Managed by: mbs.mbox.stg.centos.org
Principal name: HTTP/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Principal alias: HTTP/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Managed by: mbs.mbox.stg.centos.org, <modified>.fedoraproject.org
-------------------------
Number of members added 1
-------------------------
Host name: mbs.mbox.stg.centos.org
Description: Koji mbox STG mbs
Principal name: host/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Principal alias: host/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Managed by: mbs.mbox.stg.centos.org, <modified>.fedoraproject.org
Hosts allowed to retrieve keytab: <modified>.fedoraproject.org
-------------------------
Number of members added 1
-------------------------
Principal name: HTTP/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Principal alias: HTTP/mbs.mbox.stg.centos.org@STG.FEDORAPROJECT.ORG
Managed by: mbs.mbox.stg.centos.org, <modified>.fedoraproject.org
Hosts allowed to retrieve keytab: <modified>.fedoraproject.org
-------------------------
Number of members added 1
-------------------------
[+] Retrieving TLS and keytab files ...
New signing request "20210705130958" added.
Keytab successfully retrieved and stored in: /etc/pki/centos/certs/HTTP-mbs.mbox.stg.centos.org.keytab
[+] Validating TLS against IPA CA ...
/etc/pki/centos/certs/mbs.mbox.stg.centos.org.crt: OK
```
!!! tip
You need a valid kerberos ticket for the existing REALM, otherwise same script will exit 1. Worth knowing that if you have enabled OTP on your account (a *must* for admins) you can use the `2fa-kinit` script, also deployed by ansible on each enrolled node by the `ipa-client` ansible role
You can now import into `pkistore` (and correct directory based on role) git-crypted repository the needed .crt and .key files from /etc/pki/centos/certs/ directory
#### TLS service account
While the mentioned above script is probably the one that we'll use the most for nodes, we can also have to create service account, just to retrieve TLS cert used to auth against other services. As we'll just do that on *very* limited use cases, we can just "manually" execute the following snippet, still with first a valid kerberos ticket to be able to add users in IPA (so also on an enrolled machine and ideally the same one we use for the node certificates) :
```
# Let's first define some variables
service_account="mbox_stg_kojira"
full_name="CentOS kojira mbox STG service user"
realm="STG.FEDORAPROJECT.ORG"
# Before next steps we *have* to have ipa rights to create ipa users and valid kerberos ticket
pushd /etc/pki/centos/certs >/dev/null
ipa user-show ${service_account} >/dev/null 2>&1 || ipa user-add --cn="${full_name}" --displayname="${full_name}" --password --first=${service_account} --last=${service_account} ${service_account}
# Now that we have created the account with strong and random password, we can create private key and csr and ask IPA CA to sign it
# Create a private key and csr first
test -e ${service_account}.key || openssl req -new -newkey rsa:2048 -days 3650 -nodes -keyout ${service_account}.key -out ${service_account}.csr -subj "/CN=${service_account}"
# kinit as user (will ask password and also twice to set new password which can be the same one we decided to use
kinit ${service_account}@${realm}
ipa cert-request ${service_account}.csr --principal=${service_account} --profile-id=userCerts --certificate-out=${service_account}.crt && rm ${service_account}.csr
```
You can now push both .key/.crt files into `pkistore` git-crypted repository *and* also record the service account password in the IPA-service-accounts file in that same git-crypted repository
### Red Hat CA (internal setup only)
## Public certificates
### Letsencrypt
!!! warning
This is now the prefered way to retrieve and use TLS certs in the CentOS infra for all public services.
We use one dedicated node to obtain/renew certs for the acme http challenges, and also the same for dns challenges (for internal openshift setup).
Actually that node is `certbot.rdu2.centos.org`.
#### How to obtain new cert (DNS challenge is the preferred way)
##### For dns challenge
We have automated the delegated dynamic zone needed for acme-challenge update with acme.sh
We just have once to add in our zone (main one, so centos.org) a CNAME pointing to the delegated zone acme.centos.org
Example (simple) for forums.centos.org :
```
_acme-challenge.forums IN CNAME _acme-challenge.forums.acme.centos.org.
```
Now on certbot node, we can just ask acme.sh to dynamically update acme.centos.org with our ddns.key file (already present) that is permitted to update the acme.centos.org and instruct acme.sh that while asking for a record in centos.org, it has to update other TXT record for the 'acme-challenge' record
```
acme.sh --issue --dns dns_nsupdate -d forums.centos.org --challenge-alias forums.acme.centos.org
```
Let's see what this produces on our DNS node, basically updating TXT record:
```
Dec 10 10:35:57 ns1 named[28134]: client @0x7fe6240e93a0 8.43.84.3#59340/key ddns: signer "ddns" approved
Dec 10 10:35:57 ns1 named[28134]: client @0x7fe6240e93a0 8.43.84.3#59340/key ddns: updating zone 'acme.centos.org/IN': adding an RR at '_acme-challenge.forums.acme.centos.org' TXT "mKq4hBQnYsbWEmTYEkZu6OjVsQJBGQsXWS0c8Zdu9hQ"
```
And back on the certbot node, where it just updates, then waits and finalizes the acme validation with letsencrypt servers:
```
[Tue 10 Dec 10:35:56 UTC 2019] Single domain='forums.centos.org'
[Tue 10 Dec 10:35:56 UTC 2019] Getting domain auth token for each domain
[Tue 10 Dec 10:35:57 UTC 2019] Getting webroot for domain='forums.centos.org'
[Tue 10 Dec 10:35:57 UTC 2019] Adding txt value: mKq4hBQnYsbWEmTYEkZu6OjVsQJBGQsXWS0c8Zdu9hQ for domain: _acme-challenge.forums.acme.centos.org
[Tue 10 Dec 10:35:57 UTC 2019] adding _acme-challenge.forums.acme.centos.org. 60 in txt "mKq4hBQnYsbWEmTYEkZu6OjVsQJBGQsXWS0c8Zdu9hQ"
[Tue 10 Dec 10:35:57 UTC 2019] The txt record is added: Success.
[Tue 10 Dec 10:35:57 UTC 2019] Let's check each dns records now. Sleep 20 seconds first.
[Tue 10 Dec 10:36:19 UTC 2019] Checking forums.centos.org for _acme-challenge.forums.acme.centos.org
[Tue 10 Dec 10:36:19 UTC 2019] Domain forums.centos.org '_acme-challenge.forums.acme.centos.org' success.
[Tue 10 Dec 10:36:19 UTC 2019] All success, let's return
[Tue 10 Dec 10:36:19 UTC 2019] Verifying: forums.centos.org
[Tue 10 Dec 10:36:22 UTC 2019] Success
[Tue 10 Dec 10:36:22 UTC 2019] Removing DNS records.
[Tue 10 Dec 10:36:22 UTC 2019] Removing txt: mKq4hBQnYsbWEmTYEkZu6OjVsQJBGQsXWS0c8Zdu9hQ for domain: _acme-challenge.forums.acme.centos.org
[Tue 10 Dec 10:36:22 UTC 2019] removing _acme-challenge.forums.acme.centos.org. txt
[Tue 10 Dec 10:36:22 UTC 2019] Removed: Success
[Tue 10 Dec 10:36:22 UTC 2019] Verify finished, start to sign.
[Tue 10 Dec 10:36:22 UTC 2019] Lets finalize the order, Le_OrderFinalize: https://acme-v02.api.letsencrypt.org/acme/finalize/52322595/1718928579
[Tue 10 Dec 10:36:23 UTC 2019] Download cert, Le_LinkCert: https://acme-v02.api.letsencrypt.org/acme/cert/04cd1bdeeef194461a56d1323b11691aeccd
[Tue 10 Dec 10:36:24 UTC 2019] Cert success.
```
PS : for a wildcard, just add multiple -d and `*.domain`
```
acme.sh --issue --dns dns_nsupdate -d stg.centos.org -d '*.stg.centos.org' --challenge-alias stg.acme.centos.org
```
All certs/keys obtained through acme are under /root/.acme.sh/{hostname}/ so you'll then have to import those into this pkistore dir
##### For http challenge
Normally we prefer DNS challenge, but there are corner cases like delegated records for which that would be problematic. That's the case for {buildlogs,cloud,vault}.centos.org nodes (delegated records to pdns/geoip)
You can add multiple SANs in the same certs. Here is one example with mon.centos.org and SAN mon.j7.centos.org, status.centos.org :
```
certbot certonly --webroot --webroot-path /var/www/html --manual-public-ip-logging-ok --agree-tos --email sysadmin@centos.org -d mon.centos.org -d mon.j7.centos.org -d status.centos.org
```
All files (certs/keys) are then available under /etc/letsencrypt/live/ (you'll have to import those into this pkistore dir)
#### How to renew existing certs
##### For DNS challenges (existing records)
For each cert/dns record, we have to ask for a renewal
```
acme.sh --renew-all
```
##### For HTTP challange
For http challenge it's better to run first with --dry-run, then fix eventual issue and then launch it again for real operations
```
time certbot renew --webroot --webroot-path /var/www/html --manual-public-ip-logging-ok --agree-tos --email sysadmin@centos.org --dry-run ; echo return code $?
certbot renew --force-renew --webroot --webroot-path /var/www/html --manual-public-ip-logging-ok --agree-tos --email sysadmin@centos.org
```
### Deploying through ansible
Don't forget to have pushed the new/renewed certs/keys into this pkistore directory first.
Important too : Please validate that the -CAChain.crt is linked to correct CA chain. As LetsEncrypt is rotating also their CA, please validate that your .crt is correctly being validated with correct CAchain with simple provided tool in this repository :
```
./validate_public_chain koji.mbox.centos.org.crt
Validating cert [koji.mbox.centos.org.crt] with CAChain [koji.mbox.centos.org-CAChain.crt] and default trusted ca-bundle
koji.mbox.centos.org.crt: OK
```
Let's consider now three infrastructures and how to push renewed certs :
#### CentOS public infra (including .dev. , .stg. and ci infra)
Once it's committed/pushed to pkistore git repo, the ansible bot will deploy the renewed TLS certs automatically.
You can still "force" the playbook execution if you want, from ansible bot host but should be done automatically and you can see reports through ARA.
#### CentOS Stream infra
Same as for other parts of infra, except that you *have* to encrypt with ansible-vault before git commit/git push operations (important).
Once done :
```
ansible-playbook-stream playbooks/role-haproxy.yml --tags "tls,pki"
```
Also worth knowing that we have to contact Fedora Infra team for the `mirrors.centos.org` one, as it's hosted on their infra so we need to securely transfer needed key/cert files so that they can reload on their setup (mirrormanager)