diff --git a/.github/workflows/python-ca-rest-api-v1-test.yml b/.github/workflows/python-ca-rest-api-v1-test.yml new file mode 100644 index 00000000000..ee4a3f9a026 --- /dev/null +++ b/.github/workflows/python-ca-rest-api-v1-test.yml @@ -0,0 +1,646 @@ +name: CA Python API with REST API v1 + +on: workflow_call + +env: + DS_IMAGE: ${{ vars.DS_IMAGE || 'quay.io/389ds/dirsrv' }} + +jobs: + # https://github.com/dogtagpki/pki/wiki/Deploying-CA-Container + test: + name: Test + runs-on: ubuntu-latest + env: + SHARED: /tmp/workdir/pki + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Retrieve PKI images + uses: actions/cache@v4 + 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 client container + run: | + tests/bin/runner-init.sh \ + --hostname=client.example.com \ + --network=example \ + client + + #################################################################################################### + # Create system certs + + - name: Create CA signing cert + run: | + mkdir certs + + docker exec client pki \ + nss-cert-request \ + --subject "CN=CA Signing Certificate" \ + --ext /usr/share/pki/server/certs/ca_signing.conf \ + --csr $SHARED/certs/ca_signing.csr + + docker exec client pki \ + nss-cert-issue \ + --csr $SHARED/certs/ca_signing.csr \ + --ext /usr/share/pki/server/certs/ca_signing.conf \ + --cert $SHARED/certs/ca_signing.crt + + docker exec client pki \ + nss-cert-import \ + --cert $SHARED/certs/ca_signing.crt \ + --trust CT,C,C \ + ca_signing + + docker exec client pki \ + nss-cert-show \ + ca_signing + + - name: Create OCSP signing cert + run: | + docker exec client pki \ + nss-cert-request \ + --subject "CN=OCSP Signing Certificate" \ + --ext /usr/share/pki/server/certs/ocsp_signing.conf \ + --csr $SHARED/certs/ocsp_signing.csr + + docker exec client pki \ + nss-cert-issue \ + --issuer ca_signing \ + --csr $SHARED/certs/ocsp_signing.csr \ + --ext /usr/share/pki/server/certs/ocsp_signing.conf \ + --cert $SHARED/certs/ocsp_signing.crt + + docker exec client pki \ + nss-cert-import \ + --cert $SHARED/certs/ocsp_signing.crt \ + ocsp_signing + + docker exec client pki \ + nss-cert-show \ + ocsp_signing + + - name: Create audit signing cert + run: | + docker exec client pki \ + nss-cert-request \ + --subject "CN=Audit Signing Certificate" \ + --ext /usr/share/pki/server/certs/audit_signing.conf \ + --csr $SHARED/certs/audit_signing.csr + + docker exec client pki \ + nss-cert-issue \ + --issuer ca_signing \ + --csr $SHARED/certs/audit_signing.csr \ + --ext /usr/share/pki/server/certs/audit_signing.conf \ + --cert $SHARED/certs/audit_signing.crt + + docker exec client pki \ + nss-cert-import \ + --cert $SHARED/certs/audit_signing.crt \ + --trust ,,P \ + audit_signing + + docker exec client pki \ + nss-cert-show \ + audit_signing + + - name: Create subsystem cert + run: | + docker exec client pki \ + nss-cert-request \ + --subject "CN=Subsystem Certificate" \ + --ext /usr/share/pki/server/certs/subsystem.conf \ + --csr $SHARED/certs/subsystem.csr + + docker exec client pki \ + nss-cert-issue \ + --issuer ca_signing \ + --csr $SHARED/certs/subsystem.csr \ + --ext /usr/share/pki/server/certs/subsystem.conf \ + --cert $SHARED/certs/subsystem.crt + + docker exec client pki \ + nss-cert-import \ + --cert $SHARED/certs/subsystem.crt \ + subsystem + + docker exec client pki \ + nss-cert-show \ + subsystem + + - name: Create SSL server cert + run: | + docker exec client pki \ + nss-cert-request \ + --subject "CN=ca.example.com" \ + --ext /usr/share/pki/server/certs/sslserver.conf \ + --csr $SHARED/certs/sslserver.csr + + docker exec client pki \ + nss-cert-issue \ + --issuer ca_signing \ + --csr $SHARED/certs/sslserver.csr \ + --ext /usr/share/pki/server/certs/sslserver.conf \ + --cert $SHARED/certs/sslserver.crt + + docker exec client pki \ + nss-cert-import \ + --cert $SHARED/certs/sslserver.crt \ + sslserver + + docker exec client pki \ + nss-cert-show \ + sslserver + + - name: Create admin cert + run: | + docker exec client pki \ + nss-cert-request \ + --subject "CN=Administrator" \ + --ext /usr/share/pki/server/certs/admin.conf \ + --csr $SHARED/certs/admin.csr + + docker exec client pki \ + nss-cert-issue \ + --issuer ca_signing \ + --csr $SHARED/certs/admin.csr \ + --ext /usr/share/pki/server/certs/admin.conf \ + --cert $SHARED/certs/admin.crt + + docker exec client pki \ + nss-cert-import \ + --cert $SHARED/certs/admin.crt \ + admin + + docker exec client pki \ + nss-cert-show \ + admin + + - name: "Export system certs and keys to PKCS #12 file" + run: | + docker exec client pki pkcs12-export \ + --pkcs12 $SHARED/certs/server.p12 \ + --password Secret.123 \ + ca_signing \ + ocsp_signing \ + audit_signing \ + subsystem \ + sslserver + + - name: "Export admin cert and key to PKCS #12 file" + run: | + docker exec client pki pkcs12-export \ + --pkcs12 $SHARED/certs/admin.p12 \ + --password Secret.123 \ + admin + + - name: "Export admin key to PEM file" + run: | + docker exec client openssl pkcs12 \ + -in $SHARED/certs/admin.p12 \ + -passin pass:Secret.123 \ + -out $SHARED/certs/admin.key \ + -nodes \ + -nocerts + + #################################################################################################### + # Set up CA database + + - name: Set up DS container + run: | + tests/bin/ds-create.sh \ + --image=${{ env.DS_IMAGE }} \ + --hostname=ds.example.com \ + --network=example \ + --network-alias=ds.example.com \ + --password=Secret.123 \ + ds + + # https://github.com/dogtagpki/pki/wiki/Setting-up-CA-Database + - name: Configure DS database + run: | + docker exec ds ldapadd \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 \ + -f $SHARED/base/server/database/ds/config.ldif + + - name: Add PKI schema + run: | + docker exec ds ldapmodify \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 \ + -f $SHARED/base/server/database/ds/schema.ldif + + - name: Add CA base entry + run: | + docker exec -i ds ldapadd \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 << EOF + dn: dc=ca,dc=pki,dc=example,dc=com + objectClass: dcObject + dc: ca + EOF + + - name: Add CA database entries + run: | + sed \ + -e 's/{rootSuffix}/dc=ca,dc=pki,dc=example,dc=com/g' \ + base/ca/database/ds/create.ldif \ + | tee create.ldif + docker exec ds ldapadd \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 \ + -f $SHARED/create.ldif + + - name: Add CA search indexes + run: | + sed \ + -e 's/{database}/userroot/g' \ + base/ca/database/ds/index.ldif \ + | tee index.ldif + docker exec ds ldapadd \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 \ + -f $SHARED/index.ldif + + - name: Rebuild CA search indexes + run: | + # start rebuild task + sed \ + -e 's/{database}/userroot/g' \ + base/ca/database/ds/indextasks.ldif \ + | tee indextasks.ldif + docker exec ds ldapadd \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 \ + -f $SHARED/indextasks.ldif + + # wait for task to complete + while true; do + sleep 1 + + docker exec ds ldapsearch \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 \ + -b "cn=index1160589770, cn=index, cn=tasks, cn=config" \ + -LLL \ + nsTaskExitCode \ + | tee output + + sed -n -e 's/nsTaskExitCode:\s*\(.*\)/\1/p' output > nsTaskExitCode + cat nsTaskExitCode + + if [ -s nsTaskExitCode ]; then + break + fi + done + + echo "0" > expected + diff expected nsTaskExitCode + + - name: Add CA ACL resources + run: | + sed \ + -e 's/{rootSuffix}/dc=ca,dc=pki,dc=example,dc=com/g' \ + base/ca/database/ds/acl.ldif \ + | tee acl.ldif + docker exec ds ldapadd \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 \ + -f $SHARED/acl.ldif + + - name: Add CA VLV indexes + run: | + sed \ + -e 's/{instanceId}/pki-tomcat/g' \ + -e 's/{database}/userroot/g' \ + -e 's/{rootSuffix}/dc=ca,dc=pki,dc=example,dc=com/g' \ + base/ca/database/ds/vlv.ldif \ + | tee vlv.ldif + docker exec ds ldapadd \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 \ + -f $SHARED/vlv.ldif + + - name: Rebuild CA VLV indexes + run: | + # start rebuild task + sed \ + -e 's/{database}/userroot/g' \ + -e 's/{instanceId}/pki-tomcat/g' \ + base/ca/database/ds/vlvtasks.ldif \ + | tee vlvtasks.ldif + docker exec ds ldapadd \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 \ + -f $SHARED/vlvtasks.ldif + + # wait for task to complete + while true; do + sleep 1 + + docker exec ds ldapsearch \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 \ + -b "cn=index1160589769, cn=index, cn=tasks, cn=config" \ + -LLL \ + nsTaskExitCode \ + | tee output + + sed -n -e 's/nsTaskExitCode:\s*\(.*\)/\1/p' output > nsTaskExitCode + cat nsTaskExitCode + + if [ -s nsTaskExitCode ]; then + break + fi + done + + echo "0" > expected + diff expected nsTaskExitCode + + #################################################################################################### + # Set up admin user + + # https://github.com/dogtagpki/pki/wiki/Setting-up-CA-Admin-User + - name: Add admin user + run: | + docker exec -i ds ldapadd \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 << EOF + dn: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + objectClass: person + objectClass: organizationalPerson + objectClass: inetOrgPerson + objectClass: cmsuser + cn: admin + sn: admin + uid: admin + mail: admin@example.com + userPassword: Secret.123 + userState: 1 + userType: adminType + EOF + + - name: Assign admin cert to admin user + run: | + # convert cert from PEM to DER + openssl x509 -outform der -in certs/admin.crt -out certs/admin.der + + # get serial number + openssl x509 -text -noout -in certs/admin.crt | tee output + SERIAL=$(sed -En 'N; s/^ *Serial Number:\n *(.*)$/\1/p; D' output) + echo "SERIAL: $SERIAL" + HEX_SERIAL=$(echo "$SERIAL" | tr -d ':') + echo "HEX_SERIAL: $HEX_SERIAL" + DEC_SERIAL=$(python -c "print(int('$HEX_SERIAL', 16))") + echo "DEC_SERIAL: $DEC_SERIAL" + + docker exec -i ds ldapmodify \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 << EOF + dn: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + changetype: modify + add: description + description: 2;$DEC_SERIAL;CN=CA Signing Certificate;CN=Administrator + - + add: userCertificate + userCertificate:< file:$SHARED/certs/admin.der + - + EOF + + - name: Add admin user into CA groups + run: | + docker exec -i ds ldapmodify \ + -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" \ + -w Secret.123 << EOF + dn: cn=Administrators,ou=groups,dc=ca,dc=pki,dc=example,dc=com + changetype: modify + add: uniqueMember + uniqueMember: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + - + + dn: cn=Certificate Manager Agents,ou=groups,dc=ca,dc=pki,dc=example,dc=com + changetype: modify + add: uniqueMember + uniqueMember: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + - + + dn: cn=Security Domain Administrators,ou=groups,dc=ca,dc=pki,dc=example,dc=com + changetype: modify + add: uniqueMember + uniqueMember: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + - + + dn: cn=Enterprise CA Administrators,ou=groups,dc=ca,dc=pki,dc=example,dc=com + changetype: modify + add: uniqueMember + uniqueMember: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + - + + dn: cn=Enterprise KRA Administrators,ou=groups,dc=ca,dc=pki,dc=example,dc=com + changetype: modify + add: uniqueMember + uniqueMember: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + - + + dn: cn=Enterprise RA Administrators,ou=groups,dc=ca,dc=pki,dc=example,dc=com + changetype: modify + add: uniqueMember + uniqueMember: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + - + + dn: cn=Enterprise TKS Administrators,ou=groups,dc=ca,dc=pki,dc=example,dc=com + changetype: modify + add: uniqueMember + uniqueMember: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + - + + dn: cn=Enterprise OCSP Administrators,ou=groups,dc=ca,dc=pki,dc=example,dc=com + changetype: modify + add: uniqueMember + uniqueMember: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + - + + dn: cn=Enterprise TPS Administrators,ou=groups,dc=ca,dc=pki,dc=example,dc=com + changetype: modify + add: uniqueMember + uniqueMember: uid=admin,ou=people,dc=ca,dc=pki,dc=example,dc=com + - + EOF + + #################################################################################################### + # Install CA that only supports REST API v1 + + - name: Create PKI CA 11.4 Dockerfile + run: | + # create a new Dockerfile to disable access log buffer + cat > Dockerfile-pki-ca-11.4 < expected << EOF + GET /pki/v2/info HTTP/1.1 404 - + GET /pki/rest/info HTTP/1.1 200 - + EOF + + diff expected output + + - name: Check CA certs + run: | + docker exec client python /usr/share/pki/tests/ca/bin/pki-ca-cert-find.py \ + -U https://ca.example.com:8443 \ + --ca-bundle $SHARED/certs/ca_signing.crt \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec ca find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -3 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should fall back to REST API v1 + cat > expected << EOF + GET /pki/v2/info HTTP/1.1 404 - + GET /pki/rest/info HTTP/1.1 200 - + POST /ca/rest/certs/search HTTP/1.1 200 - + EOF + + - name: Check CA users + run: | + docker exec client python /usr/share/pki/tests/ca/bin/pki-ca-user-find.py \ + -U https://ca.example.com:8443 \ + --ca-bundle $SHARED/certs/ca_signing.crt \ + --client-cert $SHARED/certs/admin.crt \ + --client-key $SHARED/certs/admin.key \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec ca find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -5 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should fall back to REST API v1 + cat > expected << EOF + GET /pki/v2/info HTTP/1.1 404 - + GET /pki/rest/info HTTP/1.1 200 - + GET /ca/rest/account/login HTTP/1.1 200 admin + GET /ca/rest/admin/users HTTP/1.1 200 admin + GET /ca/rest/account/logout HTTP/1.1 204 admin + EOF + + diff expected output + + - name: Check DS server systemd journal + if: always() + run: | + docker exec ds journalctl -x --no-pager -u dirsrv@localhost.service + + - name: Check DS container logs + if: always() + run: | + docker logs ds + + - name: Check PKI server access log + if: always() + run: | + docker exec ca find /var/log/pki/pki-tomcat -name "localhost_access_log.*" -exec cat {} \; + + - name: Check CA container logs + if: always() + run: | + docker logs ca diff --git a/.github/workflows/python-ca-test.yml b/.github/workflows/python-ca-test.yml new file mode 100644 index 00000000000..02cbdd59408 --- /dev/null +++ b/.github/workflows/python-ca-test.yml @@ -0,0 +1,281 @@ +name: CA Python API + +on: workflow_call + +env: + DS_IMAGE: ${{ vars.DS_IMAGE || 'quay.io/389ds/dirsrv' }} + +jobs: + # docs/installation/ca/Installing_CA.md + test: + name: Test + runs-on: ubuntu-latest + env: + SHARED: /tmp/workdir/pki + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Retrieve PKI images + uses: actions/cache@v4 + 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 + + #################################################################################################### + # Install CA that supports both REST API v1 and v2 + + - name: Set up DS container + run: | + tests/bin/ds-create.sh \ + --image=${{ env.DS_IMAGE }} \ + --hostname=ds.example.com \ + --network=example \ + --network-alias=ds.example.com \ + --password=Secret.123 \ + ds + + - name: Set up PKI container + run: | + tests/bin/runner-init.sh \ + --hostname=pki.example.com \ + --network=example \ + --network-alias=pki.example.com \ + pki + + - 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 + + - name: Update PKI server configuration + run: | + docker exec pki dnf install -y xmlstarlet + + # disable access log buffer + docker exec pki xmlstarlet edit --inplace \ + -u "//Valve[@className='org.apache.catalina.valves.AccessLogValve']/@buffered" \ + -v "false" \ + -i "//Valve[@className='org.apache.catalina.valves.AccessLogValve' and not(@buffered)]" \ + -t attr \ + -n "buffered" \ + -v "false" \ + /etc/pki/pki-tomcat/server.xml + + # restart PKI server + docker exec pki pki-server restart --wait + + - name: Set up client + run: | + # export CA signing cert + docker exec pki pki-server cert-export \ + --cert-file $SHARED/ca_signing.crt \ + ca_signing + + # export admin cert + docker exec pki openssl pkcs12 \ + -in /root/.dogtag/pki-tomcat/ca_admin_cert.p12 \ + -passin pass:Secret.123 \ + -out admin.crt \ + -clcerts \ + -nokeys + + # export admin key + docker exec pki openssl pkcs12 \ + -in /root/.dogtag/pki-tomcat/ca_admin_cert.p12 \ + -passin pass:Secret.123 \ + -out admin.key \ + -nodes \ + -nocerts + + #################################################################################################### + # Check Python API + + - name: Check PKI server info + run: | + docker exec pki python /usr/share/pki/tests/bin/pki-info.py \ + -U https://pki.example.com:8443 \ + --ca-bundle $SHARED/ca_signing.crt \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec pki find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -1 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should use REST API v2 by default + cat > expected << EOF + GET /pki/v2/info HTTP/1.1 200 - + EOF + + diff expected output + + - name: Check PKI server info with REST API v1 + run: | + docker exec pki python /usr/share/pki/tests/bin/pki-info.py \ + -U https://pki.example.com:8443 \ + --ca-bundle $SHARED/ca_signing.crt \ + --api v1 \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec pki find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -1 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should use REST API v1 as specified + cat > expected << EOF + GET /pki/v1/info HTTP/1.1 200 - + EOF + + - name: Check CA certs + run: | + docker exec pki python /usr/share/pki/tests/ca/bin/pki-ca-cert-find.py \ + -U https://pki.example.com:8443 \ + --ca-bundle $SHARED/ca_signing.crt \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec pki find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -2 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should use REST API v2 by default + cat > expected << EOF + GET /pki/v2/info HTTP/1.1 200 - + POST /ca/v2/certs/search HTTP/1.1 200 - + EOF + + diff expected output + + - name: Check CA certs with REST API v1 + run: | + docker exec pki python /usr/share/pki/tests/ca/bin/pki-ca-cert-find.py \ + -U https://pki.example.com:8443 \ + --ca-bundle $SHARED/ca_signing.crt \ + --api v1 \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec pki find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -1 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should use REST API v1 as specified + cat > expected << EOF + POST /ca/v1/certs/search HTTP/1.1 200 - + EOF + + diff expected output + + - name: Check CA users + run: | + docker exec pki python /usr/share/pki/tests/ca/bin/pki-ca-user-find.py \ + -U https://pki.example.com:8443 \ + --ca-bundle $SHARED/ca_signing.crt \ + --client-cert admin.crt \ + --client-key admin.key \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec pki find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -4 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should use REST API v2 by default + cat > expected << EOF + GET /pki/v2/info HTTP/1.1 200 - + GET /ca/v2/account/login HTTP/1.1 200 caadmin + GET /ca/v2/admin/users HTTP/1.1 200 caadmin + GET /ca/v2/account/logout HTTP/1.1 204 caadmin + EOF + + diff expected output + + - name: Check CA users with REST API v1 + run: | + docker exec pki python /usr/share/pki/tests/ca/bin/pki-ca-user-find.py \ + -U https://pki.example.com:8443 \ + --ca-bundle $SHARED/ca_signing.crt \ + --client-cert admin.crt \ + --client-key admin.key \ + --api v1 \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec pki find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -3 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should use REST API v1 as specified + cat > expected << EOF + GET /ca/v1/account/login HTTP/1.1 200 caadmin + GET /ca/v1/admin/users HTTP/1.1 200 caadmin + GET /ca/v1/account/logout HTTP/1.1 204 caadmin + EOF + + diff expected output + + - name: Check DS server systemd journal + if: always() + run: | + docker exec ds journalctl -x --no-pager -u dirsrv@localhost.service + + - name: Check DS container logs + if: always() + run: | + docker logs ds + + - name: Check PKI server systemd journal + if: always() + run: | + docker exec pki journalctl -x --no-pager -u pki-tomcatd@pki-tomcat.service + + - name: Check PKI server access log + if: always() + run: | + docker exec pki find /var/log/pki/pki-tomcat -name "localhost_access_log.*" -exec cat {} \; + + - name: Check CA debug log + if: always() + run: | + docker exec pki find /var/lib/pki/pki-tomcat/logs/ca -name "debug.*" -exec cat {} \; diff --git a/.github/workflows/python-kra-test.yml b/.github/workflows/python-kra-test.yml new file mode 100644 index 00000000000..0ffa609e438 --- /dev/null +++ b/.github/workflows/python-kra-test.yml @@ -0,0 +1,244 @@ +name: KRA Python API + +on: workflow_call + +env: + DS_IMAGE: ${{ vars.DS_IMAGE || 'quay.io/389ds/dirsrv' }} + +jobs: + # docs/installation/ca/Installing_CA.md + test: + name: Test + runs-on: ubuntu-latest + env: + SHARED: /tmp/workdir/pki + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Retrieve PKI images + uses: actions/cache@v4 + 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 + + #################################################################################################### + # Install KRA that supports both REST API v1 and v2 + + - name: Set up DS container + run: | + tests/bin/ds-create.sh \ + --image=${{ env.DS_IMAGE }} \ + --hostname=ds.example.com \ + --network=example \ + --network-alias=ds.example.com \ + --password=Secret.123 \ + ds + + - name: Set up PKI container + run: | + tests/bin/runner-init.sh \ + --hostname=pki.example.com \ + --network=example \ + --network-alias=pki.example.com \ + pki + + - 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 + + - name: Install KRA + run: | + docker exec pki pkispawn \ + -f /usr/share/pki/server/examples/installation/kra.cfg \ + -s KRA \ + -D pki_ds_url=ldap://ds.example.com:3389 \ + -v + + - name: Update PKI server configuration + run: | + docker exec pki dnf install -y xmlstarlet + + # disable access log buffer + docker exec pki xmlstarlet edit --inplace \ + -u "//Valve[@className='org.apache.catalina.valves.AccessLogValve']/@buffered" \ + -v "false" \ + -i "//Valve[@className='org.apache.catalina.valves.AccessLogValve' and not(@buffered)]" \ + -t attr \ + -n "buffered" \ + -v "false" \ + /etc/pki/pki-tomcat/server.xml + + # restart PKI server + docker exec pki pki-server restart --wait + + - name: Set up client + run: | + # export CA signing cert + docker exec pki pki-server cert-export \ + --cert-file $SHARED/ca_signing.crt \ + ca_signing + + # export admin cert + docker exec pki openssl pkcs12 \ + -in /root/.dogtag/pki-tomcat/ca_admin_cert.p12 \ + -passin pass:Secret.123 \ + -out admin.crt \ + -clcerts \ + -nokeys + + # export admin key + docker exec pki openssl pkcs12 \ + -in /root/.dogtag/pki-tomcat/ca_admin_cert.p12 \ + -passin pass:Secret.123 \ + -out admin.key \ + -nodes \ + -nocerts + + #################################################################################################### + # Check Python API + + - name: Check PKI server info + run: | + docker exec pki python /usr/share/pki/tests/bin/pki-info.py \ + -U https://pki.example.com:8443 \ + --ca-bundle $SHARED/ca_signing.crt \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec pki find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -1 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should use REST API v2 by default + cat > expected << EOF + GET /pki/v2/info HTTP/1.1 200 - + EOF + + diff expected output + + - name: Check PKI server info with REST API v1 + run: | + docker exec pki python /usr/share/pki/tests/bin/pki-info.py \ + -U https://pki.example.com:8443 \ + --ca-bundle $SHARED/ca_signing.crt \ + --api v1 \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec pki find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -1 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should use REST API v1 as specified + cat > expected << EOF + GET /pki/v1/info HTTP/1.1 200 - + EOF + + - name: Check KRA users + run: | + docker exec pki python /usr/share/pki/tests/kra/bin/pki-kra-user-find.py \ + -U https://pki.example.com:8443 \ + --ca-bundle $SHARED/ca_signing.crt \ + --client-cert admin.crt \ + --client-key admin.key \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec pki find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -4 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should use REST API v2 by default + cat > expected << EOF + GET /pki/v2/info HTTP/1.1 200 - + GET /kra/v2/account/login HTTP/1.1 200 kraadmin + GET /kra/v2/admin/users HTTP/1.1 200 kraadmin + GET /kra/v2/account/logout HTTP/1.1 204 kraadmin + EOF + + diff expected output + + - name: Check KRA users with REST API v1 + run: | + docker exec pki python /usr/share/pki/tests/kra/bin/pki-kra-user-find.py \ + -U https://pki.example.com:8443 \ + --ca-bundle $SHARED/ca_signing.crt \ + --client-cert admin.crt \ + --client-key admin.key \ + --api v1 \ + -v + + sleep 1 + + # check HTTP methods, paths, protocols, status, and authenticated users + docker exec pki find /var/log/pki/pki-tomcat \ + -name "localhost_access_log.*" \ + -exec cat {} \; \ + | tail -3 \ + | sed -e 's/^.* .* \(.*\) \[.*\] "\(.*\)" \(.*\) .*$/\2 \3 \1/' \ + | tee output + + # Python API should use REST API v1 as specified + cat > expected << EOF + GET /kra/v1/account/login HTTP/1.1 200 kraadmin + GET /kra/v1/admin/users HTTP/1.1 200 kraadmin + GET /kra/v1/account/logout HTTP/1.1 204 kraadmin + EOF + + diff expected output + + - name: Check DS server systemd journal + if: always() + run: | + docker exec ds journalctl -x --no-pager -u dirsrv@localhost.service + + - name: Check DS container logs + if: always() + run: | + docker logs ds + + - name: Check PKI server systemd journal + if: always() + run: | + docker exec pki journalctl -x --no-pager -u pki-tomcatd@pki-tomcat.service + + - name: Check PKI server access log + if: always() + run: | + docker exec pki find /var/log/pki/pki-tomcat -name "localhost_access_log.*" -exec cat {} \; + + - name: Check CA debug log + if: always() + run: | + docker exec pki find /var/lib/pki/pki-tomcat/logs/ca -name "debug.*" -exec cat {} \; + + - name: Check KRA debug log + if: always() + run: | + docker exec pki find /var/lib/pki/pki-tomcat/logs/kra -name "debug.*" -exec cat {} \; diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index a01ea01593b..7ac797a495b 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -12,3 +12,18 @@ jobs: name: Python lint needs: build uses: ./.github/workflows/python-lint-test.yml + + python-ca-test: + name: CA Python API + needs: build + uses: ./.github/workflows/python-ca-test.yml + + python-ca-rest-api-v1-test: + name: CA Python API with REST API v1 + needs: build + uses: ./.github/workflows/python-ca-rest-api-v1-test.yml + + python-kra-test: + name: KRA Python API + needs: build + uses: ./.github/workflows/python-kra-test.yml diff --git a/base/common/python/pki/account.py b/base/common/python/pki/account.py index 86c8951bcad..3662a4caae9 100644 --- a/base/common/python/pki/account.py +++ b/base/common/python/pki/account.py @@ -18,9 +18,15 @@ # Copyright (C) 2013 Red Hat, Inc. # All rights reserved. # -from __future__ import absolute_import + +import inspect +import json +import logging + import pki +logger = logging.getLogger(__name__) + class AccountClient: """ @@ -35,27 +41,43 @@ class AccountClient: * call logout() to invalidate the session. """ - def __init__(self, connection, subsystem=None): + def __init__(self, parent, subsystem=None): """ Creates an AccountClient for the connection. - :param connection: connection to be associated with the AccountClient - :type connection: pki.PKIConnection + :param parent: PKIClient object + :type parent: pki.client.PKIClient :returns: AccountClient """ - self.connection = connection + if isinstance(parent, pki.client.PKIConnection): - self.login_url = '/rest/account/login' - self.logout_url = '/rest/account/logout' + logger.warning( + '%s:%s: The PKIConnection parameter in AccountClient.__init__() has been deprecated. ' + 'Provide PKIClient instead.', + inspect.stack()[1].filename, inspect.stack()[1].lineno) - if connection.subsystem is None: + self.subsystem_client = None + self.pki_client = None + self.connection = parent - if subsystem is None: + # in legacy code the subsystem name is specified in AccountClient + # in PKIConnection + if subsystem: + self.subsystem_name = subsystem + elif self.connection.subsystem: + self.subsystem_name = self.connection.subsystem + else: raise Exception('Missing subsystem for AccountClient') - self.login_url = '/' + subsystem + self.login_url - self.logout_url = '/' + subsystem + self.logout_url + else: + self.subsystem_client = parent + self.pki_client = self.subsystem_client.parent + self.connection = self.pki_client.connection + + # in newer code the subsystem name is specified in subsystem client + # (e.g. CAClient, KRAClient) + self.subsystem_name = self.subsystem_client.name @pki.handle_exceptions() def login(self): @@ -65,7 +87,26 @@ def login(self): :returns: None """ - self.connection.get(self.login_url) + + if self.pki_client: + api_path = self.pki_client.get_api_path() + else: + api_path = 'rest' + + path = '/%s/account/login' % api_path + + # in legacy code the PKIConnection object might already have the subsystem name + # in newer code the subsystem name needs to be included in the path + if not self.connection.subsystem: + path = '/' + self.subsystem_name + path + + response = self.connection.get(path) + + json_response = response.json() + logger.debug('Response:\n%s', json.dumps(json_response, indent=4)) + + # TODO: return Account object instead of JSON/XML + return json_response @pki.handle_exceptions() def logout(self): @@ -75,4 +116,17 @@ def logout(self): :returns: None """ - self.connection.get(self.logout_url) + + if self.pki_client: + api_path = self.pki_client.get_api_path() + else: + api_path = 'rest' + + path = '/%s/account/logout' % api_path + + # in legacy code the PKIConnection object might already have the subsystem name + # in newer code the subsystem name needs to be included in the path + if not self.connection.subsystem: + path = '/' + self.subsystem_name + path + + self.connection.get(path) diff --git a/base/common/python/pki/ca.py b/base/common/python/pki/ca.py new file mode 100644 index 00000000000..4ef02caf6fa --- /dev/null +++ b/base/common/python/pki/ca.py @@ -0,0 +1,17 @@ +# +# Copyright Red Hat, Inc. +# +# SPDX-License-Identifier: GPL-2.0-or-later +# + +import logging + +logger = logging.getLogger(__name__) + + +class CAClient(object): + + def __init__(self, parent): + + self.name = 'ca' + self.parent = parent diff --git a/base/common/python/pki/cert.py b/base/common/python/pki/cert.py index b0e0772e06a..33718e0c602 100644 --- a/base/common/python/pki/cert.py +++ b/base/common/python/pki/cert.py @@ -19,15 +19,15 @@ # Abhishek Koneru # Ade Lee -from __future__ import absolute_import -from __future__ import print_function import copy +import inspect import json import logging from six import iteritems import pki +import pki.ca import pki.client as client import pki.encoder as encoder import pki.profile as profile @@ -600,17 +600,31 @@ class CertClient(object): Java interface class defining the REST API for Certificate resources. """ - def __init__(self, connection): + def __init__(self, parent): """ Constructor """ - self.connection = connection + if isinstance(parent, pki.client.PKIConnection): + + logger.warning( + '%s:%s: The PKIConnection parameter in CertClient.__init__() has been deprecated. ' + 'Provide PKIClient instead.', + inspect.stack()[1].filename, inspect.stack()[1].lineno) + + self.ca_client = None + self.pki_client = None + self.connection = parent + + else: + self.ca_client = parent + self.pki_client = self.ca_client.parent + self.connection = self.pki_client.connection self.cert_url = '/rest/certs' self.agent_cert_url = '/rest/agent/certs' self.cert_requests_url = '/rest/certrequests' self.agent_cert_requests_url = '/rest/agent/certrequests' - if connection.subsystem is None: + if not self.connection.subsystem: self.cert_url = '/ca' + self.cert_url self.agent_cert_url = '/ca' + self.agent_cert_url self.cert_requests_url = '/ca' + self.cert_requests_url @@ -642,14 +656,28 @@ def list_certs(self, max_results=None, max_time=None, start=None, size=None, the certificates that satisfy the search criteria. If cert_search_request=None, returns all the certificates. """ - url = self.cert_url + '/search' + + if self.pki_client: + api_path = self.pki_client.get_api_path() + else: + api_path = 'rest' + + path = '/%s/certs/search' % api_path + + # in legacy code the PKIConnection object might already have the subsystem name + # in newer code the subsystem name needs to be included in the path + if not self.connection.subsystem: + path = '/ca' + path + + logger.info('Getting CA certs from %s', path) + query_params = {"maxResults": max_results, "maxTime": max_time, "start": start, "size": size} cert_search_request = CertSearchRequest(**cert_search_params) search_request = json.dumps(cert_search_request, cls=encoder.CustomTypeEncoder, sort_keys=True) - response = self.connection.post(url, search_request, self.headers, + response = self.connection.post(path, search_request, self.headers, query_params) json_response = response.json() diff --git a/base/common/python/pki/client.py b/base/common/python/pki/client.py index 64c72a01dd1..6e6ff31f59d 100644 --- a/base/common/python/pki/client.py +++ b/base/common/python/pki/client.py @@ -29,6 +29,7 @@ import os import ssl import warnings +import urllib import requests from requests import adapters @@ -38,6 +39,9 @@ except ImportError: from urllib3.exceptions import InsecureRequestWarning +import pki.info +import pki.util + logger = logging.getLogger(__name__) @@ -169,7 +173,10 @@ def __init__(self, protocol='http', hostname='localhost', port='8080', self.protocol = protocol self.hostname = hostname - self.port = port + self.port = str(port) + + # TODO: remove subsystem name from PKIConnection once all supported code + # has been changed to use PKIClient. self.subsystem = subsystem self.rootURI = self.protocol + '://' + self.hostname @@ -366,6 +373,103 @@ def delete(self, path, headers=None, use_root_uri=False): return r +class PKIClient: + + def __init__( + self, + url, + trust_env=None, + verify=True, + ca_bundle=None, + api_version=None): + + self.url = urllib.parse.urlparse(url) + + if self.url.port: + port = self.url.port + elif self.url.scheme == 'http': + port = 80 + elif self.url.scheme == 'https': + port = 443 + else: + raise Exception('Unsupported URL scheme: %s' % self.url.scheme) + + self.info = None + self.server_version = None + self.api_version = api_version + + if api_version == 'v1': + self.api_path = 'rest' + else: + self.api_path = api_version + + # TODO: do not hard-code message format + self.connection = PKIConnection( + protocol=self.url.scheme, + hostname=self.url.hostname, + port=port, + accept='application/json', + trust_env=trust_env, + verify=verify, + cert_paths=ca_bundle + ) + + self.info_client = pki.info.InfoClient(self) + + def set_client_auth(self, client_cert, client_key=None): + self.connection.set_authentication_cert(client_cert, client_key) + + def connect(self): + + logger.info('Connecting to %s', urllib.parse.urlunparse(self.url)) + + self.info = self.info_client.get_info() + + self.server_version = pki.util.Version(self.info.version) + logger.debug('- server version: %s', self.server_version) + + # if not specified, set REST API version and path based on server version + if not self.api_version: + if self.server_version >= pki.util.Version('11.6.0'): + # use REST API v2 for PKI 11.6.0 or later + self.api_version = 'v2' + self.api_path = 'v2' + else: + self.api_version = 'v1' + self.api_path = 'rest' + + logger.debug('- API version: %s', self.api_version) + logger.debug('- API path: %s', self.api_path) + + def get_info(self): + + if not self.info: + self.connect() + + return self.info + + def get_server_version(self): + + if not self.server_version: + self.connect() + + return self.server_version + + def get_api_version(self): + + if not self.api_version: + self.connect() + + return self.api_version + + def get_api_path(self): + + if not self.api_path: + self.connect() + + return self.api_path + + def main(): """ Test code for the PKIConnection class. diff --git a/base/common/python/pki/info.py b/base/common/python/pki/info.py index 03b7927ace8..fe69f9ec10d 100644 --- a/base/common/python/pki/info.py +++ b/base/common/python/pki/info.py @@ -21,11 +21,11 @@ """ Module containing the Python client classes for the InfoClient """ -from __future__ import absolute_import -from __future__ import print_function +import inspect import json import logging +import requests.exceptions from six import iteritems @@ -103,20 +103,56 @@ class InfoClient(object): server Info resources. """ - def __init__(self, connection): + def __init__(self, parent): """ Constructor """ - self.connection = connection + if isinstance(parent, pki.client.PKIConnection): - self.info_url = '/pki/v2/info' + logger.warning( + '%s:%s: The PKIConnection parameter in InfoClient.__init__() has been deprecated. ' + 'Provide PKIClient instead.', + inspect.stack()[1].filename, inspect.stack()[1].lineno) + + self.pki_client = None + self.connection = parent + + else: + self.pki_client = parent + self.connection = self.pki_client.connection @pki.handle_exceptions() def get_info(self): """ Return an Info object form a PKI server """ + if self.pki_client and self.pki_client.api_path: + # use REST API path specified in PKIClient + api_paths = [self.pki_client.api_path] + + else: + # try all possible REST API paths + api_paths = ['v2', 'rest'] + headers = {'Content-type': 'application/json', 'Accept': 'application/json'} - response = self.connection.get(self.info_url, headers) + + response = None + + for api_path in api_paths: + try: + path = '/pki/%s/info' % api_path + logger.info('Getting PKI server info from %s', path) + + response = self.connection.get(path, headers) + # REST API path available -> done + break + + except requests.exceptions.HTTPError as e: + if e.response.status_code != 404: + raise + # REST API path not available -> try another + + if not response: + raise Exception('Unable to get PKI server info') json_response = response.json() logger.debug('Response:\n%s', json.dumps(json_response, indent=4)) diff --git a/base/common/python/pki/kra.py b/base/common/python/pki/kra.py index 01d738342f3..701ffe83675 100644 --- a/base/common/python/pki/kra.py +++ b/base/common/python/pki/kra.py @@ -25,11 +25,15 @@ KeyRequestResource REST APIs. """ -from __future__ import absolute_import -from pki.info import InfoClient -import pki.key as key +import inspect +import logging -from pki.systemcert import SystemCertClient +import pki.client +import pki.info +import pki.key +import pki.systemcert + +logger = logging.getLogger(__name__) class KRAClient(object): @@ -38,7 +42,7 @@ class KRAClient(object): KeyRequest REST APIs. """ - def __init__(self, connection, crypto, transport_cert_nick=None): + def __init__(self, parent, crypto=None, transport_cert_nick=None): """ Constructor :param connection - PKIConnection object with DRM connection info. @@ -52,13 +56,36 @@ def __init__(self, connection, crypto, transport_cert_nick=None): Note that for NSS databases, the database must have been initialized beforehand. """ - self.connection = connection - self.crypto = crypto - self.info = InfoClient(connection) - self.keys = key.KeyClient( - connection, - crypto, - transport_cert_nick, - self.info - ) - self.system_certs = SystemCertClient(connection) + + self.name = 'kra' + self.parent = parent + + if isinstance(parent, pki.client.PKIConnection): + + logger.warning( + '%s:%s: The PKIConnection parameter in KRAClient.__init__() has been deprecated. ' + 'Provide PKIClient instead.', + inspect.stack()[1].filename, inspect.stack()[1].lineno) + + self.connection = parent + + self.crypto = crypto + self.info = pki.info.InfoClient(self.connection) + + self.keys = pki.key.KeyClient( + self.connection, + crypto, + transport_cert_nick, + self.info) + + self.system_certs = pki.systemcert.SystemCertClient(self.connection) + + else: + self.connection = parent.connection + + # do not automatically create these objects in KRAClient. + # client application should create them as needed. + self.crypto = None + self.info = None + self.keys = None + self.system_certs = None diff --git a/base/common/python/pki/user.py b/base/common/python/pki/user.py new file mode 100644 index 00000000000..d2f9fda349f --- /dev/null +++ b/base/common/python/pki/user.py @@ -0,0 +1,37 @@ +# +# Copyright Red Hat, Inc. +# +# SPDX-License-Identifier: GPL-2.0-or-later +# + +import json +import logging + +logger = logging.getLogger(__name__) + + +class UserClient(object): + + def __init__(self, parent): + + self.subsystem_client = parent + self.pki_client = self.subsystem_client.parent + self.connection = self.pki_client.connection + + def find_users(self): + + api_path = self.pki_client.get_api_path() + + # the UserClient doesn't support legacy code so the subsystem name + # needs to be included in the path + path = '/%s/%s/admin/users' % (self.subsystem_client.name, api_path) + + logger.info('Getting %s users from %s', self.subsystem_client.name.upper(), path) + + response = self.connection.get(path) + + json_response = response.json() + logger.debug('Response:\n%s', json.dumps(json_response, indent=4)) + + # TODO: return UserCollection object instead of JSON/XML + return json_response diff --git a/base/server/healthcheck/pki/server/healthcheck/meta/connectivity.py b/base/server/healthcheck/pki/server/healthcheck/meta/connectivity.py index 00b561c09c4..d7c6d5e9fd7 100644 --- a/base/server/healthcheck/pki/server/healthcheck/meta/connectivity.py +++ b/base/server/healthcheck/pki/server/healthcheck/meta/connectivity.py @@ -1,11 +1,14 @@ import logging -from pki.server.healthcheck.meta.plugin import MetaPlugin, registry from ipahealthcheck.core.plugin import Result, duration from ipahealthcheck.core import constants -from pki.client import PKIConnection -from pki.cert import CertClient -from pki.systemcert import SystemCertClient + +import pki.ca +import pki.cert +import pki.client +import pki.systemcert + +from pki.server.healthcheck.meta.plugin import MetaPlugin, registry logger = logging.getLogger(__name__) @@ -43,12 +46,15 @@ def check(self): # Make a plain HTTPS GET to "find" one certificate, to test that # the server is up AND is able to respond back - connection = PKIConnection(protocol='https', - hostname='localhost', - port=https_port, - verify=False) + server_url = 'https://localhost:' + https_port + + pki_client = pki.client.PKIClient( + url=server_url, + verify=False) + + ca_client = pki.ca.CAClient(pki_client) - cert_client = CertClient(connection) + cert_client = pki.cert.CertClient(ca_client) cert = cert_client.list_certs(size=1) cert_info = cert.cert_data_info_list[0] if cert_info: @@ -63,13 +69,13 @@ def check(self): yield Result(self, constants.ERROR, msg="Unable to read serial number from retrieved cert", cert_info=cert_info, - serverURI=connection.serverURI, + serverURI=server_url, cert_url=cert_client.cert_url) else: logger.info("Request was made but none of the certs were retrieved") yield Result(self, constants.ERROR, msg="PKI server is up. But, unable to retrieve any certs", - serverURI=connection.serverURI, + serverURI=server_url, rest_path=cert_client.cert_url) else: @@ -118,12 +124,12 @@ def check(self): # Make a plain HTTPS GET to retrieve KRA transport cert, to test that # the server is up AND is able to respond back - connection = PKIConnection(protocol='https', + connection = pki.client.PKIConnection(protocol='https', hostname='localhost', port=https_port, verify=False) - system_cert_client = SystemCertClient(connection) + system_cert_client = pki.systemcert.SystemCertClient(connection) # This gets the KRA cert from CS.cfg via REST API. In future, the system # certs will be moved into LDAP. This means that even if LDAP is down diff --git a/tests/bin/pki-info.py b/tests/bin/pki-info.py new file mode 100755 index 00000000000..9cf308b830e --- /dev/null +++ b/tests/bin/pki-info.py @@ -0,0 +1,58 @@ +# +# Copyright Red Hat, Inc. +# +# SPDX-License-Identifier: GPL-2.0-or-later +# + +import argparse +import logging + +import pki.ca +import pki.cert +import pki.client + +logger = logging.getLogger(__name__) +logging.basicConfig(format='%(levelname)s: %(message)s') + +parser = argparse.ArgumentParser() +parser.add_argument( + '-U', + help='Server URL', + dest='url') +parser.add_argument( + '--ca-bundle', + help='Path to CA bundle', + dest='ca_bundle') +parser.add_argument( + '--api', + help='API version: v1, v2', + dest='api_version') +parser.add_argument( + '-v', + '--verbose', + help='Run in verbose mode.', + dest='verbose', + action='store_true') +parser.add_argument( + '--debug', + help='Run in debug mode.', + dest='debug', + action='store_true') + +args = parser.parse_args() + +if args.debug: + logging.getLogger().setLevel(logging.DEBUG) + +elif args.verbose: + logging.getLogger().setLevel(logging.INFO) + +pki_client = pki.client.PKIClient( + url=args.url, + ca_bundle=args.ca_bundle, + api_version=args.api_version) + +info = pki_client.get_info() + +print(' Server Version: %s' % pki_client.get_server_version()) +print(' API Version: %s' % pki_client.get_api_version()) diff --git a/tests/ca/bin/pki-ca-cert-find.py b/tests/ca/bin/pki-ca-cert-find.py new file mode 100755 index 00000000000..33df0cc58bb --- /dev/null +++ b/tests/ca/bin/pki-ca-cert-find.py @@ -0,0 +1,71 @@ +# +# Copyright Red Hat, Inc. +# +# SPDX-License-Identifier: GPL-2.0-or-later +# + +import argparse +import logging + +import pki.ca +import pki.cert +import pki.client + +logger = logging.getLogger(__name__) +logging.basicConfig(format='%(levelname)s: %(message)s') + +parser = argparse.ArgumentParser() +parser.add_argument( + '-U', + help='Server URL', + dest='url') +parser.add_argument( + '--ca-bundle', + help='Path to CA bundle', + dest='ca_bundle') +parser.add_argument( + '--api', + help='API version: v1, v2', + dest='api_version') +parser.add_argument( + '-v', + '--verbose', + help='Run in verbose mode.', + dest='verbose', + action='store_true') +parser.add_argument( + '--debug', + help='Run in debug mode.', + dest='debug', + action='store_true') + +args = parser.parse_args() + +if args.debug: + logging.getLogger().setLevel(logging.DEBUG) + +elif args.verbose: + logging.getLogger().setLevel(logging.INFO) + +pki_client = pki.client.PKIClient( + url=args.url, + ca_bundle=args.ca_bundle, + api_version=args.api_version) + +ca_client = pki.ca.CAClient(pki_client) +cert_client = pki.cert.CertClient(ca_client) + +certs = cert_client.list_certs() + +first = True + +for cert in certs: + + if first: + first = False + else: + print() + + print(' Serial Number: ' + cert.serial_number) + print(' Subject DN: ' + cert.subject_dn) + print(' Status: ' + cert.status) diff --git a/tests/ca/bin/pki-ca-user-find.py b/tests/ca/bin/pki-ca-user-find.py new file mode 100755 index 00000000000..122808a4a00 --- /dev/null +++ b/tests/ca/bin/pki-ca-user-find.py @@ -0,0 +1,88 @@ +# +# Copyright Red Hat, Inc. +# +# SPDX-License-Identifier: GPL-2.0-or-later +# + +import argparse +import logging + +import pki.ca +import pki.account +import pki.client +import pki.user + +logger = logging.getLogger(__name__) +logging.basicConfig(format='%(levelname)s: %(message)s') + +parser = argparse.ArgumentParser() +parser.add_argument( + '-U', + help='Server URL', + dest='url') +parser.add_argument( + '--ca-bundle', + help='Path to CA bundle', + dest='ca_bundle') +parser.add_argument( + '--client-cert', + help='Path to client certificate', + dest='client_cert') +parser.add_argument( + '--client-key', + help='Path to client key', + dest='client_key') +parser.add_argument( + '--api', + help='API version: v1, v2', + dest='api_version') +parser.add_argument( + '-v', + '--verbose', + help='Run in verbose mode.', + dest='verbose', + action='store_true') +parser.add_argument( + '--debug', + help='Run in debug mode.', + dest='debug', + action='store_true') + +args = parser.parse_args() + +if args.debug: + logging.getLogger().setLevel(logging.DEBUG) + +elif args.verbose: + logging.getLogger().setLevel(logging.INFO) + +pki_client = pki.client.PKIClient( + url=args.url, + ca_bundle=args.ca_bundle, + api_version=args.api_version) + +pki_client.set_client_auth( + client_cert=args.client_cert, + client_key=args.client_key) + +ca_client = pki.ca.CAClient(pki_client) + +account_client = pki.account.AccountClient(ca_client) +account_client.login() + +user_client = pki.user.UserClient(ca_client) +users = user_client.find_users() + +first = True + +for user in users['entries']: + + if first: + first = False + else: + print() + + print(' User ID: %s' % user['UserID']) + print(' Full name: %s' % user['FullName']) + +account_client.logout() diff --git a/tests/kra/bin/pki-kra-user-find.py b/tests/kra/bin/pki-kra-user-find.py new file mode 100755 index 00000000000..b918c04181c --- /dev/null +++ b/tests/kra/bin/pki-kra-user-find.py @@ -0,0 +1,88 @@ +# +# Copyright Red Hat, Inc. +# +# SPDX-License-Identifier: GPL-2.0-or-later +# + +import argparse +import logging + +import pki.kra +import pki.account +import pki.client +import pki.user + +logger = logging.getLogger(__name__) +logging.basicConfig(format='%(levelname)s: %(message)s') + +parser = argparse.ArgumentParser() +parser.add_argument( + '-U', + help='Server URL', + dest='url') +parser.add_argument( + '--ca-bundle', + help='Path to CA bundle', + dest='ca_bundle') +parser.add_argument( + '--client-cert', + help='Path to client certificate', + dest='client_cert') +parser.add_argument( + '--client-key', + help='Path to client key', + dest='client_key') +parser.add_argument( + '--api', + help='API version: v1, v2', + dest='api_version') +parser.add_argument( + '-v', + '--verbose', + help='Run in verbose mode.', + dest='verbose', + action='store_true') +parser.add_argument( + '--debug', + help='Run in debug mode.', + dest='debug', + action='store_true') + +args = parser.parse_args() + +if args.debug: + logging.getLogger().setLevel(logging.DEBUG) + +elif args.verbose: + logging.getLogger().setLevel(logging.INFO) + +pki_client = pki.client.PKIClient( + url=args.url, + ca_bundle=args.ca_bundle, + api_version=args.api_version) + +pki_client.set_client_auth( + client_cert=args.client_cert, + client_key=args.client_key) + +kra_client = pki.kra.KRAClient(pki_client) + +account_client = pki.account.AccountClient(kra_client) +account_client.login() + +user_client = pki.user.UserClient(kra_client) +users = user_client.find_users() + +first = True + +for user in users['entries']: + + if first: + first = False + else: + print() + + print(' User ID: %s' % user['UserID']) + print(' Full name: %s' % user['FullName']) + +account_client.logout()