name: ACME Tests

on: [push, pull_request]

jobs:
  init:
    name: Initializing workflow
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.init.outputs.matrix }}
      repo: ${{ steps.init.outputs.repo }}
    steps:
      - name: Clone repository
        uses: actions/checkout@v2

      - name: Initialize workflow
        id: init
        env:
          BASE64_MATRIX: ${{ secrets.BASE64_MATRIX }}
          BASE64_REPO: ${{ secrets.BASE64_REPO }}
        run: |
          tests/bin/init-workflow.sh

  # docs/development/Building_PKI.md
  build:
    name: Building PKI
    needs: init
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJSON(needs.init.outputs.matrix) }}
    steps:
      - name: Clone repository
        uses: actions/checkout@v2

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Build runner image
        uses: docker/build-push-action@v2
        with:
          context: .
          build-args: |
            OS_VERSION=${{ matrix.os }}
            COPR_REPO=${{ needs.init.outputs.repo }}
            BUILD_OPTS=--with-pkgs=base,server,ca,acme --with-timestamp --with-commit-id
          tags: pki-runner
          target: pki-runner
          outputs: type=docker,dest=/tmp/pki-runner.tar

      - name: Upload runner image
        uses: actions/upload-artifact@v2
        with:
          name: pki-runner-${{ matrix.os }}
          path: /tmp/pki-runner.tar

      - name: Build ACME image
        uses: docker/build-push-action@v2
        with:
          context: .
          build-args: |
            OS_VERSION=${{ matrix.os }}
            COPR_REPO=${{ needs.init.outputs.repo }}
            BUILD_OPTS=--with-pkgs=base,server,ca,acme --with-timestamp --with-commit-id
          tags: pki-acme
          target: pki-acme
          outputs: type=docker,dest=/tmp/pki-acme.tar

      - name: Upload ACME image
        uses: actions/upload-artifact@v2
        with:
          name: pki-acme-${{ matrix.os }}
          path: /tmp/pki-acme.tar

  # docs/installation/acme/Installing_PKI_ACME_Responder.md
  # docs/user/acme/Using_PKI_ACME_Responder_with_Certbot.md
  acme-certbot-test:
    name: Testing ACME with certbot
    needs: [init, build]
    runs-on: ubuntu-latest
    env:
      PKIDIR: /tmp/workdir/pki
    strategy:
      matrix: ${{ fromJSON(needs.init.outputs.matrix) }}
    steps:
      - name: Clone repository
        uses: actions/checkout@v2

      - name: Download runner image
        uses: actions/download-artifact@v2
        with:
          name: pki-runner-${{ matrix.os }}
          path: /tmp

      - name: Load runner image
        run: docker load --input /tmp/pki-runner.tar

      - name: Create network
        run: docker network create example

      - name: Run PKI container
        run: |
          IMAGE=pki-runner \
          NAME=pki \
          HOSTNAME=pki.example.com \
          tests/bin/runner-init.sh

      - name: Connect PKI container to network
        run: docker network connect example pki --alias pki.example.com

      - name: Install dependencies in PKI container
        run: docker exec pki dnf install -y 389-ds-base

      - name: Install DS in PKI container
        run: docker exec pki ${PKIDIR}/tests/bin/ds-create.sh

      - name: Install CA in PKI container
        run: docker exec pki pkispawn -f /usr/share/pki/server/examples/installation/ca.cfg -s CA -v

      - name: Install ACME in PKI container
        run: |
          docker exec pki pki-server acme-create
          docker exec pki ldapmodify -h pki.example.com \
              -x \
              -D "cn=Directory Manager" \
              -w Secret.123 \
              -f /usr/share/pki/acme/database/ds/schema.ldif
          docker exec pki ldapadd -h pki.example.com \
              -x \
              -D "cn=Directory Manager" \
              -w Secret.123 \
              -f /usr/share/pki/acme/database/ds/create.ldif
          docker exec pki ldapadd -h pki.example.com \
              -x \
              -D "cn=Directory Manager" \
              -w Secret.123 \
              -f /usr/share/pki/acme/realm/ds/create.ldif
          docker exec pki pki-server acme-database-mod --type ds
          docker exec pki pki-server acme-issuer-mod --type pki
          docker exec pki pki-server acme-realm-mod --type ds
          docker exec pki pki-server acme-deploy --wait

      - name: Run PKI healthcheck in PKI container
        run: docker exec pki pki-healthcheck --debug

      - name: Verify admin user in PKI container
        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 client-cert-import \
              --pkcs12 /root/.dogtag/pki-tomcat/ca_admin_cert.p12 \
              --pkcs12-password-file /root/.dogtag/pki-tomcat/ca/pkcs12_password.conf
          docker exec pki pki -n caadmin ca-user-show caadmin

      - name: Verify ACME in PKI container
        run: docker exec pki pki acme-info

      - name: Run client container
        run: |
          IMAGE=pki-runner \
          NAME=client \
          HOSTNAME=client.example.com \
          tests/bin/runner-init.sh

      - name: Connect client container to network
        run: docker network connect example client --alias client.example.com

      - name: Install dependencies in client container
        run: docker exec client dnf install -y certbot

      - name: Verify certbot in client container
        run: |
          docker exec client certbot register \
              --server http://pki.example.com:8080/acme/directory \
              --email user1@example.com \
              --agree-tos \
              --non-interactive
          docker exec client certbot certonly \
              --server http://pki.example.com:8080/acme/directory \
              -d client.example.com \
              --standalone \
              --non-interactive
          docker exec client certbot renew \
              --server http://pki.example.com:8080/acme/directory \
              --cert-name client.example.com \
              --force-renewal \
              --no-random-sleep-on-renew \
              --non-interactive
          docker exec client certbot revoke \
              --server http://pki.example.com:8080/acme/directory \
              --cert-name client.example.com \
              --non-interactive
          docker exec client certbot update_account \
              --server http://pki.example.com:8080/acme/directory \
              --email user2@example.com \
              --non-interactive
          docker exec client certbot unregister \
              --server http://pki.example.com:8080/acme/directory \
              --non-interactive

      - name: Gather artifacts from PKI container
        if: always()
        run: |
          tests/bin/ds-artifacts-save.sh pki
          tests/bin/pki-artifacts-save.sh pki
        continue-on-error: true

      - name: Remove ACME from PKI container
        run: |
          docker exec pki pki-server acme-undeploy --wait
          docker exec pki pki-server acme-remove

      - name: Remove CA from PKI container
        run: docker exec pki pkidestroy -i pki-tomcat -s CA -v

      - name: Remove DS from PKI container
        run: docker exec pki ${PKIDIR}/tests/bin/ds-remove.sh

      - name: Upload artifacts
        if: always()
        uses: actions/upload-artifact@v2
        with:
          name: acme-${{ matrix.os }}
          path: |
            /tmp/artifacts/pki

  # This test verifies that in a cluster the baseURL parameter can be used
  # to replace a server with another server without affecting the client.
  acme-switchover-test:
    name: Testing ACME server switchover
    needs: [init, build]
    runs-on: ubuntu-latest
    env:
      PKIDIR: /tmp/workdir/pki
    strategy:
      matrix: ${{ fromJSON(needs.init.outputs.matrix) }}
    steps:
      - name: Clone repository
        uses: actions/checkout@v2

      - name: Download runner image
        uses: actions/download-artifact@v2
        with:
          name: pki-runner-${{ matrix.os }}
          path: /tmp

      - name: Load runner image
        run: docker load --input /tmp/pki-runner.tar

      - name: Create network
        run: docker network create example

      - name: Run PKI container
        run: |
          IMAGE=pki-runner \
          NAME=pki \
          HOSTNAME=pki.example.com \
          tests/bin/runner-init.sh

      - name: Connect PKI container to network
        run: docker network connect example pki --alias pki.example.com --alias server1.example.com

      - name: Install dependencies in PKI container
        run: docker exec pki dnf install -y 389-ds-base jq

      - name: Install DS in PKI container
        run: docker exec pki ${PKIDIR}/tests/bin/ds-create.sh

      - name: Install CA in PKI container
        run: docker exec pki pkispawn -f /usr/share/pki/server/examples/installation/ca.cfg -s CA -v

      - name: Install ACME in PKI container
        run: |
          docker exec pki pki-server acme-create
          docker exec pki ldapmodify -h pki.example.com \
              -x \
              -D "cn=Directory Manager" \
              -w Secret.123 \
              -f /usr/share/pki/acme/database/ds/schema.ldif
          docker exec pki ldapadd -h pki.example.com \
              -x \
              -D "cn=Directory Manager" \
              -w Secret.123 \
              -f /usr/share/pki/acme/database/ds/create.ldif
          docker exec pki ldapadd -h pki.example.com \
              -x \
              -D "cn=Directory Manager" \
              -w Secret.123 \
              -f /usr/share/pki/acme/realm/ds/create.ldif
          docker exec pki pki-server acme-database-mod --type ds
          docker exec pki pki-server acme-issuer-mod --type pki
          docker exec pki pki-server acme-realm-mod --type ds
          docker exec pki bash -c "echo baseURL=http://server1.example.com:8080/acme >> /etc/pki/pki-tomcat/acme/engine.conf"
          docker exec pki pki-server acme-deploy --wait

      - name: Run client container
        run: |
          IMAGE=pki-runner \
          NAME=client \
          HOSTNAME=client.example.com \
          tests/bin/runner-init.sh

      - name: Connect client container to network
        run: docker network connect example client --alias client1.example.com --alias client2.example.com

      - name: Install dependencies in client container
        run: docker exec client dnf install -y certbot

      - name: Verify ACME directory before switchover
        run: |
          echo http://server1.example.com:8080/acme/new-nonce > expected
          docker exec pki bash -c "curl -s -k http://pki.example.com:8080/acme/directory | jq -r '.newNonce' > ${PKIDIR}/actual"
          diff expected actual

      - name: Verify registration and enrollment before switchover
        run: |
          docker exec client certbot register \
              --server http://pki.example.com:8080/acme/directory \
              --email user1@example.com \
              --agree-tos \
              --non-interactive
          docker exec client certbot certonly \
              --server http://pki.example.com:8080/acme/directory \
              -d client1.example.com \
              --standalone \
              --non-interactive
          docker exec client certbot certonly \
              --server http://pki.example.com:8080/acme/directory \
              -d client2.example.com \
              --standalone \
              --non-interactive

      - name: Simulate ACME server switchover by replacing the baseURL parameter
        run: |
          docker network disconnect example pki
          docker exec pki pki-server acme-undeploy --wait
          docker exec pki sed -i "s/server1.example.com/server2.example.com/g" /etc/pki/pki-tomcat/acme/engine.conf
          docker exec pki pki-server acme-deploy --wait
          docker network connect example pki --alias pki.example.com --alias server2.example.com

      - name: Verify ACME directory after switchover
        run: |
          echo http://server2.example.com:8080/acme/new-nonce > expected
          docker exec pki bash -c "curl -s -k http://pki.example.com:8080/acme/directory | jq -r '.newNonce' > ${PKIDIR}/actual"
          diff expected actual

      - name: Verify renewal, revocation, account update and deactivation after switchover
        run: |
          docker exec client certbot renew \
              --server http://pki.example.com:8080/acme/directory \
              --cert-name client1.example.com \
              --force-renewal \
              --no-random-sleep-on-renew \
              --non-interactive
          docker exec client certbot revoke \
              --server http://pki.example.com:8080/acme/directory \
              --cert-name client2.example.com \
              --non-interactive
          docker exec client certbot update_account \
              --server http://pki.example.com:8080/acme/directory \
              --email user2@example.com \
              --non-interactive
          docker exec client certbot unregister \
              --server http://pki.example.com:8080/acme/directory \
              --non-interactive

      - name: Gather artifacts from PKI container
        if: always()
        run: |
          tests/bin/ds-artifacts-save.sh pki
          tests/bin/pki-artifacts-save.sh pki
        continue-on-error: true

      - name: Remove ACME from PKI container
        run: |
          docker exec pki pki-server acme-undeploy --wait
          docker exec pki pki-server acme-remove

      - name: Remove CA from PKI container
        run: docker exec pki pkidestroy -i pki-tomcat -s CA -v

      - name: Remove DS from PKI container
        run: docker exec pki ${PKIDIR}/tests/bin/ds-remove.sh

      - name: Upload artifacts
        if: always()
        uses: actions/upload-artifact@v2
        with:
          name: acme-switchover-${{ matrix.os }}
          path: |
            /tmp/artifacts/pki

  # docs/installation/podman/Deploying_PKI_ACME_Responder_on_Podman.md
  acme-container-test:
    name: Testing ACME container
    needs: [init, build]
    runs-on: ubuntu-latest
    env:
      PKIDIR: /tmp/workdir/pki
    strategy:
      matrix: ${{ fromJSON(needs.init.outputs.matrix) }}
    steps:
      - name: Clone repository
        uses: actions/checkout@v2

      - name: Download runner image
        uses: actions/download-artifact@v2
        with:
          name: pki-runner-${{ matrix.os }}
          path: /tmp

      - name: Load runner image
        run: docker load --input /tmp/pki-runner.tar

      - name: Download ACME image
        uses: actions/download-artifact@v2
        with:
          name: pki-acme-${{ matrix.os }}
          path: /tmp

      - name: Load ACME image
        run: docker load --input /tmp/pki-acme.tar

      - name: Create network
        run: docker network create example

      - name: Run ACME container
        run: |
          docker run \
              --name server \
              --detach \
              pki-acme

      - name: Connect ACME container to network
        run: docker network connect example server --alias pki.example.com

      - name: Run client container
        run: |
          IMAGE=pki-runner \
          NAME=client \
          HOSTNAME=client.example.com \
          tests/bin/runner-init.sh

      - name: Connect client container to network
        run: docker network connect example client --alias client.example.com

      - name: Install dependencies in client container
        run: docker exec client dnf install -y certbot

      - name: Verify certbot in client container
        run: |
          docker exec client certbot register \
              --server http://pki.example.com:8080/acme/directory \
              --email user1@example.com \
              --agree-tos \
              --non-interactive
          docker exec client certbot certonly \
              --server http://pki.example.com:8080/acme/directory \
              -d client.example.com \
              --standalone \
              --non-interactive
          docker exec client certbot renew \
              --server http://pki.example.com:8080/acme/directory \
              --cert-name client.example.com \
              --force-renewal \
              --no-random-sleep-on-renew \
              --non-interactive
          #
          # By default the pki-acme container uses NSS issuer (instead of
          # PKI issuer) which does not support cert revocation, so the
          # revocation test is disabled.
          #
          # docker exec client certbot revoke \
          #     --server http://pki.example.com:8080/acme/directory \
          #     --cert-name client.example.com \
          #     --non-interactive
          #
          docker exec client certbot update_account \
              --server http://pki.example.com:8080/acme/directory \
              --email user2@example.com \
              --non-interactive
          docker exec client certbot unregister \
              --server http://pki.example.com:8080/acme/directory \
              --non-interactive

      - name: Gather artifacts from server container
        if: always()
        run: |
          mkdir -p /tmp/artifacts/server/var/lib
          docker cp server:/var/lib/tomcats /tmp/artifacts/server/var/lib
          docker logs server > /tmp/artifacts/server/container.log

      - name: Gather artifacts from client container
        if: always()
        run: |
          mkdir -p /tmp/artifacts/client/var/log/letsencrypt
          docker cp client:/var/log/letsencrypt/letsencrypt.log /tmp/artifacts/client/var/log/letsencrypt

      - name: Upload artifacts from server container
        if: always()
        uses: actions/upload-artifact@v2
        with:
          name: acme-container-server-${{ matrix.os }}
          path: /tmp/artifacts/server

      - name: Upload artifacts from client container
        if: always()
        uses: actions/upload-artifact@v2
        with:
          name: acme-container-client-${{ matrix.os }}
          path: /tmp/artifacts/client
