diff --git a/.github/workflows/ca-renewal-system-certs-test.yml b/.github/workflows/ca-renewal-system-certs-test.yml new file mode 100644 index 00000000000..c19dcae2772 --- /dev/null +++ b/.github/workflows/ca-renewal-system-certs-test.yml @@ -0,0 +1,466 @@ +name: CA system certs renewal +# https://github.com/dogtagpki/pki/wiki/Renewing-System-Certificates +# https://github.com/dogtagpki/pki/wiki/Renewing-Admin-Certificate + +on: workflow_call + +env: + DB_IMAGE: ${{ vars.DB_IMAGE || 'quay.io/389ds/dirsrv' }} + +jobs: + test: + name: Test + runs-on: ubuntu-latest + env: + SHARED: /tmp/workdir/pki + steps: + - name: Clone repository + uses: actions/checkout@v3 + + - name: Retrieve PKI images + uses: actions/cache@v3 + with: + key: pki-images-${{ github.sha }} + path: pki-images.tar + + - name: Load PKI images + run: docker load --input pki-images.tar + + - name: Create network + run: docker network create example + + - name: Set up DS container + run: | + tests/bin/ds-container-create.sh ds + env: + IMAGE: ${{ env.DB_IMAGE }} + HOSTNAME: ds.example.com + PASSWORD: Secret.123 + + - name: Connect DS container to network + run: docker network connect example ds --alias ds.example.com + + - name: Set up PKI container + run: | + tests/bin/runner-init.sh pki + env: + HOSTNAME: pki.example.com + + - name: Connect PKI container to network + run: docker network connect example pki --alias pki.example.com + + - name: Configure short-lived SSL server cert profile + run: | + # set cert validity to 3 minute + VALIDITY_DEFAULT="2.default.params" + docker exec pki sed -i \ + -e "s/^$VALIDITY_DEFAULT.range=.*$/$VALIDITY_DEFAULT.range=3/" \ + -e "/^$VALIDITY_DEFAULT.range=.*$/a $VALIDITY_DEFAULT.rangeUnit=minute" \ + /usr/share/pki/ca/conf/rsaServerCert.profile + + docker exec pki cat /usr/share/pki/ca/conf/rsaServerCert.profile + + - name: Configure short-lived subsystem cert profile + run: | + # set cert validity to 3 minute + VALIDITY_DEFAULT="2.default.params" + docker exec pki sed -i \ + -e "s/^$VALIDITY_DEFAULT.range=.*$/$VALIDITY_DEFAULT.range=3/" \ + -e "/^$VALIDITY_DEFAULT.range=.*$/a $VALIDITY_DEFAULT.rangeUnit=minute" \ + /usr/share/pki/ca/conf/rsaSubsystemCert.profile + + docker exec pki cat /usr/share/pki/ca/conf/rsaSubsystemCert.profile + + - name: Configure short-lived audit signing cert profile + run: | + # set cert validity to 3 minute + VALIDITY_DEFAULT="2.default.params" + docker exec pki sed -i \ + -e "s/^$VALIDITY_DEFAULT.range=.*$/$VALIDITY_DEFAULT.range=3/" \ + -e "/^$VALIDITY_DEFAULT.range=.*$/a $VALIDITY_DEFAULT.rangeUnit=minute" \ + /usr/share/pki/ca/conf/caAuditSigningCert.profile + + docker exec pki cat /usr/share/pki/ca/conf/caAuditSigningCert.profile + + - name: Configure short-lived OCSP signing cert profile + run: | + # set cert validity to 3 minute + VALIDITY_DEFAULT="2.default.params" + docker exec pki sed -i \ + -e "s/^$VALIDITY_DEFAULT.range=.*$/$VALIDITY_DEFAULT.range=3/" \ + -e "/^$VALIDITY_DEFAULT.range=.*$/a $VALIDITY_DEFAULT.rangeUnit=minute" \ + /usr/share/pki/ca/conf/caOCSPCert.profile + + # check updated profile + docker exec pki cat /usr/share/pki/ca/conf/caOCSPCert.profile + + - name: Configure short-lived admin cert profile + run: | + # set cert validity to 3 minute + VALIDITY_DEFAULT="2.default.params" + docker exec pki sed -i \ + -e "s/^$VALIDITY_DEFAULT.range=.*$/$VALIDITY_DEFAULT.range=3/" \ + -e "/^$VALIDITY_DEFAULT.range=.*$/a $VALIDITY_DEFAULT.rangeUnit=minute" \ + /usr/share/pki/ca/conf/rsaAdminCert.profile + + docker exec pki cat /usr/share/pki/ca/conf/rsaAdminCert.profile + + - name: Install CA + run: | + docker exec pki pkispawn \ + -f /usr/share/pki/server/examples/installation/ca.cfg \ + -s CA \ + -D pki_ds_url=ldap://ds.example.com:3389 \ + -v + + docker exec pki pki-server cert-find + + - name: Check system cert keys + run: | + echo "Secret.123" > password.txt + docker exec pki certutil \ + -K -d /etc/pki/pki-tomcat/alias \ + -f $SHARED/password.txt | tee keys.orig + + - name: Run PKI healthcheck + run: | + # healthcheck should generate warnings + docker exec pki pki-healthcheck --failures-only \ + > >(tee stdout) 2> >(tee stderr >&2) || true + + echo "Expiring in a day: ocsp_signing" > expected + echo "Expiring in a day: sslserver" >> expected + echo "Expiring in a day: subsystem" >> expected + echo "Expiring in a day: audit_signing" >> expected + diff expected stderr + + - name: Check CA admin + run: | + docker exec pki pki-server cert-export ca_signing --cert-file ca_signing.crt + docker exec pki pki client-cert-import ca_signing --ca-cert ca_signing.crt + docker exec pki pki pkcs12-import \ + --pkcs12 /root/.dogtag/pki-tomcat/ca_admin_cert.p12 \ + --pkcs12-password Secret.123 + docker exec pki pki nss-cert-show caadmin + + docker exec pki pki -n caadmin ca-user-show caadmin + + - name: Restart PKI server with expired certs + run: | + # wait for SSL server cert to expire + sleep 180 + + docker exec pki pki-server restart --wait \ + > >(tee stdout) 2> >(tee stderr >&2) + + - name: Run PKI healthcheck + run: | + # healthcheck should fail + docker exec pki pki-healthcheck --failures-only \ + > >(tee stdout) 2> >(tee stderr >&2) || true + + echo "Expired Cert: ocsp_signing" > expected + echo "Expired Cert: sslserver" >> expected + echo "Expired Cert: subsystem" >> expected + echo "Expired Cert: audit_signing" >> expected + echo "Internal server error 404 Client Error: for url: http://pki.example.com:8080/ca/rest/securityDomain/domainInfo" >> expected + echo "Internal server error 404 Client Error: for url: https://pki.example.com:8443/ca/admin/ca/getStatus" >> expected + diff expected stderr + + - name: Check CA admin + run: | + # client should fail + docker exec pki pki -n caadmin ca-user-show caadmin \ + > >(tee stdout) 2> >(tee stderr >&2) || true + + echo "ERROR: EXPIRED_CERTIFICATE encountered on 'CN=pki.example.com,OU=pki-tomcat,O=EXAMPLE' results in a denied SSL server cert!" > expected + grep "^ERROR:" stderr > actual + diff expected actual + + - name: Create temp SSL server cert + # https://github.com/dogtagpki/pki/wiki/Creating-Temporary-SSL-Server-Certificate + run: | + # create temp cert + docker exec pki pki-server cert-create sslserver --temp + + # delete current cert + docker exec pki pki-server cert-del sslserver + + # import temp cert + docker exec pki pki-server cert-import sslserver + + docker exec pki pki-server cert-show sslserver + + - name: Restart PKI server with temp SSL server cert + run: | + # disable selftests + docker exec pki pki-server selftest-disable + + # restart server + docker exec pki pki-server restart --wait + + - name: Run PKI healthcheck + run: | + # healthcheck should fail + docker exec pki pki-healthcheck --failures-only \ + > >(tee stdout) 2> >(tee stderr >&2) || true + + echo "Expired Cert: ocsp_signing" > expected + echo "Expired Cert: subsystem" >> expected + echo "Expired Cert: audit_signing" >> expected + diff expected stderr + + - name: Check PKI client + run: | + # client should work + docker exec pki pki info + + - name: Renew SSL server cert + # https://github.com/dogtagpki/pki/wiki/Renewing-SSL-Server-Certificate + run: | + # get current serial number + docker exec pki pki-server cert-show sslserver | tee output + CERT_ID=$(sed -n "s/^\s*Serial Number:\s*\(\S*\)$/\1/p" output) + + # submit renewal request + docker exec pki pki ca-cert-request-submit \ + --profile caManualRenewal \ + --serial $CERT_ID \ + --renewal | tee output + REQUEST_ID=$(sed -n "s/^\s*Request ID:\s*\(\S*\)$/\1/p" output) + + # approve renewal request + docker exec pki pki \ + -u caadmin \ + -w Secret.123 \ + ca-cert-request-approve \ + $REQUEST_ID \ + --force | tee output + CERT_ID=$(sed -n "s/^\s*Certificate ID:\s*\(\S*\)$/\1/p" output) + + # export new cert + docker exec pki pki ca-cert-export $CERT_ID --output-file sslserver.crt + + # delete current cert + docker exec pki pki-server cert-del sslserver + + # install new cert + docker exec pki pki-server cert-import sslserver --input sslserver.crt + + docker exec pki pki-server cert-show sslserver + + - name: Renew subsystem cert + # https://github.com/dogtagpki/pki/wiki/Renewing-Subsystem-Certificate + run: | + # get current serial number + docker exec pki pki-server cert-show subsystem | tee output + CERT_ID=$(sed -n "s/^\s*Serial Number:\s*\(\S*\)$/\1/p" output) + + # submit renewal request + docker exec pki pki ca-cert-request-submit \ + --profile caManualRenewal \ + --serial $CERT_ID \ + --renewal | tee output + REQUEST_ID=$(sed -n "s/^\s*Request ID:\s*\(\S*\)$/\1/p" output) + + # approve renewal request + docker exec pki pki \ + -u caadmin \ + -w Secret.123 \ + ca-cert-request-approve \ + $REQUEST_ID \ + --force | tee output + CERT_ID=$(sed -n "s/^\s*Certificate ID:\s*\(\S*\)$/\1/p" output) + + # export new cert + docker exec pki pki ca-cert-export $CERT_ID --output-file subsystem.crt + + # delete current cert + docker exec pki pki-server cert-del subsystem + + # install new cert + docker exec pki pki-server cert-import subsystem --input subsystem.crt + + docker exec pki pki-server cert-show subsystem + + - name: Update subsystem user cert + # https://github.com/dogtagpki/pki/wiki/Renewing-Subsystem-Certificate + # this is needed by pkidestroy to remove the subsystem from security domain + run: | + # get cert ID + docker exec pki pki-server ca-user-cert-find CA-pki.example.com-8443 | tee output + CERT_ID=$(sed -n "s/^\s*Cert ID:\s*\(.*\)$/\1/p" output) + echo "CERT_ID: $CERT_ID" + + # remove current cert + docker exec pki pki-server ca-user-cert-del CA-pki.example.com-8443 "$CERT_ID" + + # install new cert + docker exec pki pki-server ca-user-cert-add CA-pki.example.com-8443 --cert subsystem.crt + + docker exec pki pki-server ca-user-cert-find CA-pki.example.com-8443 + + - name: Renew audit signing cert + # https://github.com/dogtagpki/pki/wiki/Renewing-Audit-Signing-Certificate + run: | + # get current serial number + docker exec pki pki-server cert-show ca_audit_signing | tee output + CERT_ID=$(sed -n "s/^\s*Serial Number:\s*\(\S*\)$/\1/p" output) + + # submit renewal request + docker exec pki pki ca-cert-request-submit \ + --profile caManualRenewal \ + --serial $CERT_ID \ + --renewal | tee output + REQUEST_ID=$(sed -n "s/^\s*Request ID:\s*\(\S*\)$/\1/p" output) + + # approve renewal request + docker exec pki pki \ + -u caadmin \ + -w Secret.123 \ + ca-cert-request-approve \ + $REQUEST_ID \ + --force | tee output + CERT_ID=$(sed -n "s/^\s*Certificate ID:\s*\(\S*\)$/\1/p" output) + + # export new cert + docker exec pki pki ca-cert-export $CERT_ID --output-file ca_audit_signing.crt + + # delete current cert + docker exec pki pki-server cert-del ca_audit_signing + + # install new cert + docker exec pki pki-server cert-import ca_audit_signing --input ca_audit_signing.crt + + docker exec pki pki-server cert-show ca_audit_signing + + - name: Renew OCSP signing cert + # https://github.com/dogtagpki/pki/wiki/Renewing-OCSP-Signing-Certificate + run: | + # get current serial number + docker exec pki pki-server cert-show ca_ocsp_signing | tee output + CERT_ID=$(sed -n "s/^\s*Serial Number:\s*\(\S*\)$/\1/p" output) + + # submit renewal request + docker exec pki pki ca-cert-request-submit \ + --profile caManualRenewal \ + --serial $CERT_ID \ + --renewal | tee output + REQUEST_ID=$(sed -n "s/^\s*Request ID:\s*\(\S*\)$/\1/p" output) + + # approve renewal request + docker exec pki pki \ + -u caadmin \ + -w Secret.123 \ + ca-cert-request-approve \ + $REQUEST_ID \ + --force | tee output + CERT_ID=$(sed -n "s/^\s*Certificate ID:\s*\(\S*\)$/\1/p" output) + + # export new cert + docker exec pki pki ca-cert-export $CERT_ID --output-file ca_ocsp_signing.crt + + # delete current cert + docker exec pki pki-server cert-del ca_ocsp_signing + + # install new cert + docker exec pki pki-server cert-import ca_ocsp_signing --input ca_ocsp_signing.crt + + docker exec pki pki-server cert-show ca_ocsp_signing + + - name: Renew admin cert + # https://github.com/dogtagpki/pki/wiki/Renewing-Admin-Certificate + run: | + # get current serial number + docker exec pki pki nss-cert-show caadmin | tee output + CERT_ID=$(sed -n "s/^\s*Serial Number:\s*\(\S*\)$/\1/p" output) + + # submit renewal request + docker exec pki pki ca-cert-request-submit \ + --profile caManualRenewal \ + --serial $CERT_ID \ + --renewal | tee output + REQUEST_ID=$(sed -n "s/^\s*Request ID:\s*\(\S*\)$/\1/p" output) + + # approve renewal request + docker exec pki pki \ + -u caadmin \ + -w Secret.123 \ + ca-cert-request-approve \ + $REQUEST_ID \ + --force | tee output + CERT_ID=$(sed -n "s/^\s*Certificate ID:\s*\(\S*\)$/\1/p" output) + + # export new cert + docker exec pki pki ca-cert-export $CERT_ID --output-file caadmin.crt + + # delete current cert + # TODO: add pki nss-cert-del command + docker exec pki certutil -D -d /root/.dogtag/nssdb -n caadmin + + # install new cert + docker exec pki pki nss-cert-import caadmin --cert caadmin.crt + + docker exec pki pki nss-cert-show caadmin + + - name: Update admin user cert + # https://github.com/dogtagpki/pki/wiki/Renewing-Admin-Certificate + # this is needed by admin to access CA with client cert auth + run: | + # get cert ID + docker exec pki pki-server ca-user-cert-find caadmin | tee output + CERT_ID=$(sed -n "s/^\s*Cert ID:\s*\(.*\)$/\1/p" output) + echo "CERT_ID: $CERT_ID" + + # remove current cert + docker exec pki pki-server ca-user-cert-del caadmin "$CERT_ID" + + # install new cert + docker exec pki pki-server ca-user-cert-add caadmin --cert caadmin.crt + + docker exec pki pki-server ca-user-cert-find caadmin + + - name: Restart PKI server with renewed certs + run: | + # enable selftests + docker exec pki pki-server selftest-enable + + docker exec pki pki-server restart --wait + + - name: Check cert keys after renewal + run: | + # the keys should not change + docker exec pki certutil \ + -K -d /etc/pki/pki-tomcat/alias \ + -f $SHARED/password.txt | tee keys.after + diff keys.orig keys.after + + - name: Run PKI healthcheck + run: | + # healthcheck should not fail + docker exec pki pki-healthcheck --failures-only + + - name: Check CA admin + run: | + # client should not fail + docker exec pki pki -n caadmin ca-user-show caadmin + + - name: Check systemd journal + if: always() + run: | + docker exec pki journalctl -x --no-pager -u pki-tomcatd@pki-tomcat.service + + - name: Check CA debug log + if: always() + run: | + docker exec pki find /var/log/pki/pki-tomcat/ca -name "debug.*" -exec cat {} \; + + - name: Check CA selftests log + if: always() + run: | + docker exec pki cat /var/log/pki/pki-tomcat/ca/selftests.log + + - name: Remove CA + run: | + # pkidestroy should not fail + docker exec pki pkidestroy -i pki-tomcat -s CA -v diff --git a/.github/workflows/ca-tests2.yml b/.github/workflows/ca-tests2.yml index 9df818d9dcf..dd0c56d9d6d 100644 --- a/.github/workflows/ca-tests2.yml +++ b/.github/workflows/ca-tests2.yml @@ -18,6 +18,11 @@ jobs: needs: build uses: ./.github/workflows/ca-profile-caServerCert-test.yml + ca-renewal-system-certs-test: + name: CA system certs renewal + needs: build + uses: ./.github/workflows/ca-renewal-system-certs-test.yml + ca-secure-ds-test: name: CA with secure DS needs: build diff --git a/base/server/python/pki/server/instance.py b/base/server/python/pki/server/instance.py index 34b5a4c4786..e3608aa34e7 100644 --- a/base/server/python/pki/server/instance.py +++ b/base/server/python/pki/server/instance.py @@ -707,6 +707,10 @@ def cert_file(self, cert_id): """Compute name of certificate under instance cert folder.""" return os.path.join(self.cert_folder, cert_id + '.crt') + def csr_file(self, cert_id): + """Compute name of CSR under instance cert folder.""" + return os.path.join(self.cert_folder, cert_id + '.csr') + def nssdb_import_cert(self, cert_id, cert_file=None): """ Add cert from cert_file to NSS db with appropriate trust flags @@ -783,8 +787,7 @@ def nssdb_import_cert(self, cert_id, cert_file=None): def cert_import(self, cert_id, cert_file=None): """ - Import cert from cert_file into NSS db with appropriate trust flags and update - all corresponding subsystem's CS.cfg + Import cert from cert_file into NSS db with appropriate trust :param cert_id: Cert ID :type cert_id: str @@ -793,8 +796,7 @@ def cert_import(self, cert_id, cert_file=None): :return: None :rtype: None """ - updated_cert = self.nssdb_import_cert(cert_id, cert_file) - self.cert_update_config(cert_id, updated_cert) + self.nssdb_import_cert(cert_id, cert_file) def cert_create( self, cert_id=None, diff --git a/base/server/python/pki/server/subsystem.py b/base/server/python/pki/server/subsystem.py index 41abad2ce88..cda9371d56b 100644 --- a/base/server/python/pki/server/subsystem.py +++ b/base/server/python/pki/server/subsystem.py @@ -933,35 +933,17 @@ def set_startup_test_criticality(self, critical, test=None): target_tests[testID] = critical self.set_startup_tests(target_tests) - def setup_temp_renewal(self, tmpdir, cert_tag): + def setup_temp_renewal(self, tmpdir): """ - Retrieve CA's cert, Subject Key Identifier (SKI aka AKI) and CSR for - the *cert_id* provided + Retrieve CA signing cert info and Subject Key Identifier (SKI aka AKI) - :param tmpdir: Path to temp dir to write cert's .csr and CA's .crt file + :param tmpdir: Path to temp dir to write CA signing cert file :type tmpdir: str - :param cert_tag: Cert for which CSR is requested - :type cert_tag: str - :return: (ca_signing_cert, aki, csr_file) + :return: (ca_signing_cert, aki) """ - csr_file = os.path.join(tmpdir, cert_tag + '.csr') ca_cert_file = os.path.join(tmpdir, 'ca_certificate.crt') - logger.debug('Exporting CSR for %s cert', cert_tag) - - # Retrieve CSR for cert_id - cert_request = self.get_subsystem_cert(cert_tag).get('request') - if cert_request is None: - raise pki.server.PKIServerException('Unable to find CSR for %s cert' % cert_tag) - - logger.debug('Retrieved CSR: %s', cert_request) - - csr_data = pki.nssdb.convert_csr(cert_request, 'base64', 'pem') - with open(csr_file, 'w', encoding='utf-8') as f: - f.write(csr_data) - logger.info('CSR for %s has been written to %s', cert_tag, csr_file) - logger.debug('Extracting SKI from CA cert') # TODO: Support remote CA. @@ -997,7 +979,7 @@ def setup_temp_renewal(self, tmpdir, cert_tag): aki = '0x' + aki.strip().replace(':', '') logger.info('AKI: %s', aki) - return ca_signing_cert, aki, csr_file + return ca_signing_cert, aki def temp_cert_create(self, nssdb, tmpdir, cert_tag, serial, new_cert_file): """ @@ -1024,8 +1006,10 @@ def temp_cert_create(self, nssdb, tmpdir, cert_tag, serial, new_cert_file): raise pki.server.PKIServerException( 'Temp cert for %s is not supported yet.' % cert_tag) - ca_signing_cert, aki, csr_file = \ - self.setup_temp_renewal(tmpdir=tmpdir, cert_tag=cert_tag) + ca_signing_cert, aki = self.setup_temp_renewal(tmpdir=tmpdir) + + csr_file = self.instance.csr_file(cert_tag) + logger.debug('Reusing existing CSR in %s', csr_file) # --keyUsage key_usage_ext = {