diff --git a/.github/actions/e2e-tests/set-up-ssh-keys/action.yml b/.github/actions/e2e-tests/set-up-ssh-keys/action.yml new file mode 100644 index 0000000000..830a3fce6d --- /dev/null +++ b/.github/actions/e2e-tests/set-up-ssh-keys/action.yml @@ -0,0 +1,42 @@ +name: Set up SSH keys for VM provisioning +description: "Sets up SSH keys for VM provisioning" +inputs: + E2E_VM_SSH_PRIV_KEY: + description: "Private SSH key for VM provisioning" + required: true + E2E_VM_SSH_PUB_KEY: + description: "Public SSH key for VM provisioning" + required: true +outputs: + keys-path: + description: "Path to the SSH keys" + value: "${{ steps.set-ssh-keys.outputs.keys-path }}" +runs: + using: "composite" + steps: + - name: Set GitHub Path + run: echo "$GITHUB_ACTION_PATH" >> $GITHUB_PATH + shell: bash + env: + GITHUB_ACTION_PATH: ${{ github.action_path }} + + - name: Set up SSH keys for the VM provisioning + id: set-ssh-keys + shell: bash + run: | + set -eu + + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + + echo "${{ inputs.E2E_VM_SSH_PRIV_KEY }}" > "${HOME}/.ssh/id_rsa" + chmod 600 "${HOME}/.ssh/id_rsa" + + echo "${{ inputs.E2E_VM_SSH_PUB_KEY }}" > "${HOME}/.ssh/id_rsa.pub" + chmod 644 "${HOME}/.ssh/id_rsa.pub" + + eval "$(ssh-agent -s)" + + ssh-add "${HOME}/.ssh/id_rsa" + + echo "keys-path=${HOME}/.ssh" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml new file mode 100644 index 0000000000..b80d0cb49e --- /dev/null +++ b/.github/workflows/e2e-tests.yaml @@ -0,0 +1,42 @@ +name: e2e-tests + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * 1' # Runs every Monday at midnight + pull_request: + paths: + - '**' + - '!.github/workflows/**' + - '.github/workflows/run-e2e-tests.yaml' + - '.github/workflows/provision-and-run-e2e-tests.yaml' + - '.github/workflows/e2e-tests.yaml' + - '!.gitignore' + - '!.golangci.yaml' + - '!AGENTS.md' + - '!CODE_OF_CONDUCT.md' + - '!CONTRIBUTING.md' + - '!COPYING' + - '!COPYING.LESSER' + - '!README.md' + - '!SECURITY.md' + - "!authd-oidc-brokers/po/**" + - "!authd-oidc-brokers/.gitignore" + - '!docs/**' + - '!examplebroker/**' + - '!gotestcov' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + provision-and-run-e2e-tests: + strategy: + matrix: + release: [noble, questing] + fail-fast: false + uses: ./.github/workflows/provision-and-run-e2e-tests.yaml + with: + release: ${{ matrix.release }} + secrets: inherit diff --git a/.github/workflows/provision-and-run-e2e-tests.yaml b/.github/workflows/provision-and-run-e2e-tests.yaml new file mode 100644 index 0000000000..f08425bb9f --- /dev/null +++ b/.github/workflows/provision-and-run-e2e-tests.yaml @@ -0,0 +1,127 @@ +name: provision-e2e-test (reusable) + +on: + workflow_call: + inputs: + release: + required: true + type: string + secrets: + E2E_VM_SSH_PRIV_KEY: + required: true + E2E_VM_SSH_PUB_KEY: + required: true + +env: + DEBIAN_FRONTEND: noninteractive + VM_NAME_BASE: e2e-runner + ARTIFACTS_DIR: /tmp/e2e-artifacts + E2E_TESTS_DIR: e2e-tests + +jobs: + provision-vm: + name: Provision VM + runs-on: ubuntu-latest + permissions: + contents: read + actions: write + packages: write + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Restore cached VM image (if available) + id: restore-cache + uses: actions/cache/restore@v4 + with: + path: ${{ env.ARTIFACTS_DIR }}/${{ env.VM_NAME_BASE }}-${{ inputs.release }}.qcow2 + key: >- + e2e-runner-vm-${{ inputs.release }}-${{ hashFiles( + 'e2e-tests/vm/**', + '.github/actions/e2e-tests/set-up-ssh-keys/**', + '.github/workflows/provision-and-run-e2e-tests.yaml' + ) }} + fail-on-cache-miss: false + + - uses: canonical/desktop-engineering/gh-actions/common/dpkg-install-speedup@main + if: steps.restore-cache.outputs.cache-hit != 'true' + + - name: Install APT dependencies for VM provisioning + if: steps.restore-cache.outputs.cache-hit != 'true' + run: ./${{ env.E2E_TESTS_DIR }}/vm/install-provision-deps.sh + + - name: Set up SSH keys for the VM provisioning + id: set-ssh-keys + if: steps.restore-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/e2e-tests/set-up-ssh-keys + with: + E2E_VM_SSH_PRIV_KEY: ${{ secrets.E2E_VM_SSH_PRIV_KEY }} + E2E_VM_SSH_PUB_KEY: ${{ secrets.E2E_VM_SSH_PUB_KEY }} + + - name: Provision the VM + id: provision-vm + if: steps.restore-cache.outputs.cache-hit != 'true' + run: | + set -eu + export PATH="$(realpath "${{ env.E2E_TESTS_DIR }}/vm/helpers"):${PATH}" + mkdir -p ${{ env.ARTIFACTS_DIR }} + env VM_NAME_BASE="${{ env.VM_NAME_BASE }}" \ + RELEASE="${{ inputs.release }}" \ + ARTIFACTS_DIR="${{ env.ARTIFACTS_DIR }}" \ + SSH_PUBLIC_KEY_FILE="${{ steps.set-ssh-keys.outputs.keys-path }}/id_rsa.pub" \ + ./${{ env.E2E_TESTS_DIR }}/vm/provision-ubuntu.sh + + - name: Clean previous cache + if: always() && steps.provision-vm.outcome == 'success' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: | + set -eu + gh cache delete e2e-runner-${{ inputs.release }} || true + echo "Previous cache (if any) deleted" + + - name: Cache the VM image + uses: actions/cache/save@v4 + if: always() && steps.provision-vm.outcome == 'success' + with: + path: ${{ env.ARTIFACTS_DIR }}/${{ env.VM_NAME_BASE }}-${{ inputs.release }}.qcow2 + key: >- + e2e-runner-vm-${{ inputs.release }}-${{ hashFiles( + 'e2e-tests/vm/**', + '.github/actions/e2e-tests/set-up-ssh-keys/**', + '.github/workflows/provision-and-run-e2e-tests.yaml' + ) }} + + - uses: oras-project/setup-oras@v1 + - name: Upload VM image as OCI artifact + if: steps.provision-vm.outcome == 'success' || steps.restore-cache.outputs.cache-hit == 'true' + env: + IMAGE_PATH: ${{ env.ARTIFACTS_DIR }}/${{ env.VM_NAME_BASE }}-${{ inputs.release }}.qcow2 + OCI_REPO: ghcr.io/${{ github.repository }}/e2e-runner + OCI_TAG: ${{ inputs.release }} + run: | + set -euo pipefail + + echo "${{ secrets.GITHUB_TOKEN }}" \ + | oras login ghcr.io -u "${{ github.actor }}" --password-stdin + + # Chunk qcow2 into stable fixed-size layers (64 MiB) + WORKDIR=$(mktemp -d) + split -b 64M "$IMAGE_PATH" "$WORKDIR/chunk-" + + # Push as a layered OCI artifact (each chunk becomes a layer) + cd "$WORKDIR" + oras push "${OCI_REPO}:${OCI_TAG}" chunk-* --artifact-type application/vnd.qemu.qcow2 + + e2e-test: + needs: provision-vm + strategy: + matrix: + broker: [authd-msentraid, authd-google] + fail-fast: false + uses: ./.github/workflows/run-e2e-tests.yaml + with: + release: ${{ inputs.release }} + broker: ${{ matrix.broker }} + secrets: inherit diff --git a/.github/workflows/run-e2e-tests.yaml b/.github/workflows/run-e2e-tests.yaml new file mode 100644 index 0000000000..bbbeba3172 --- /dev/null +++ b/.github/workflows/run-e2e-tests.yaml @@ -0,0 +1,164 @@ +name: run-e2e-test (reusable) + +on: + workflow_call: + inputs: + release: + required: true + type: string + broker: + required: true + type: string + secrets: + E2E_VM_SSH_PRIV_KEY: + required: true + E2E_VM_SSH_PUB_KEY: + required: true + E2E_MSENTRA_ISSUER_ID: + required: false + E2E_MSENTRA_CLIENT_ID: + required: false + E2E_MSENTRA_USERNAME: + required: false + E2E_MSENTRA_PASSWORD: + required: false + E2E_MSENTRA_TOTP_SECRET: + required: false + E2E_GOOGLE_CLIENT_ID: + required: false + E2E_GOOGLE_CLIENT_SECRET: + required: false + E2E_GOOGLE_USERNAME: + required: false + E2E_GOOGLE_PASSWORD: + required: false + E2E_GOOGLE_TOTP_SECRET: + required: false + +env: + DEBIAN_FRONTEND: noninteractive + VM_NAME_BASE: e2e-runner + ARTIFACTS_DIR: /tmp/e2e-artifacts + E2E_TESTS_DIR: e2e-tests + OUTPUT_DIR: /tmp/e2e-${{ inputs.broker }}-${{ inputs.release }}/output + +jobs: + run-tests: + name: Run e2e-tests (${{ inputs.broker }}) + runs-on: ubuntu-latest + permissions: + contents: read + actions: write + packages: read + steps: + - uses: oras-project/setup-oras@v1 + - name: Download VM image OCI artifact + env: + OCI_REPO: ghcr.io/${{ github.repository }}/e2e-runner + OCI_TAG: ${{ inputs.release }} + IMAGE_PATH: ${{ env.ARTIFACTS_DIR }}/${{ env.VM_NAME_BASE }}-${{ inputs.release }}.qcow2 + run: | + set -euo pipefail + + mkdir -p "$(dirname "$IMAGE_PATH")" + + echo "${{ secrets.GITHUB_TOKEN }}" \ + | oras login ghcr.io -u "${{ github.actor }}" --password-stdin + + oras pull "${OCI_REPO}:${OCI_TAG}" + cat chunk-* > "$IMAGE_PATH" + rm chunk-* + + - name: Checkout repo + uses: actions/checkout@v6 + + - uses: canonical/desktop-engineering/gh-actions/common/dpkg-install-speedup@main + + - name: Install APT dependencies for provisioning + run: ${{ env.E2E_TESTS_DIR }}/vm/install-provision-deps.sh + + - name: Set up SSH keys for the VM provisioning + id: set-ssh-keys + uses: ./.github/actions/e2e-tests/set-up-ssh-keys + with: + E2E_VM_SSH_PRIV_KEY: ${{ secrets.E2E_VM_SSH_PRIV_KEY }} + E2E_VM_SSH_PUB_KEY: ${{ secrets.E2E_VM_SSH_PUB_KEY }} + + - name: Provision authd and the broker + run: | + set -eux + export PATH="$(realpath "${{ env.E2E_TESTS_DIR }}/vm/helpers"):${PATH}" + + # Generate the libvirt domain XML file + template="${{ env.E2E_TESTS_DIR }}/vm/e2e-runner-template.xml" + env \ + IMAGE_FILE=${{ env.ARTIFACTS_DIR }}/${{ env.VM_NAME_BASE }}-${{ inputs.release }}.qcow2 \ + VM_NAME=${{ env.VM_NAME_BASE }}-${{ inputs.release }} \ + envsubst \ + < "${template}" \ + > "${{ env.ARTIFACTS_DIR }}/${{ env.VM_NAME_BASE }}.xml" + + # Create the provisioning config file + cat > "${{ env.E2E_TESTS_DIR }}/vm/config.sh" <<-EOF + export SSH_PUBLIC_KEY_FILE=${{ steps.set-ssh-keys.outputs.keys-path }}/id_rsa.pub + export VM_NAME_BASE=${{ env.VM_NAME_BASE }} + export RELEASE=${{ inputs.release }} + export BROKERS=${{ inputs.broker }} + + export AUTHD_MSENTRAID_ISSUER_ID=${{ secrets.E2E_MSENTRA_ISSUER_ID }} + export AUTHD_MSENTRAID_CLIENT_ID=${{ secrets.E2E_MSENTRA_CLIENT_ID }} + export AUTHD_MSENTRAID_USER=${{ secrets.E2E_MSENTRA_USERNAME }} + + export AUTHD_GOOGLE_CLIENT_ID=${{ secrets.E2E_GOOGLE_CLIENT_ID }} + export AUTHD_GOOGLE_CLIENT_SECRET=${{ secrets.E2E_GOOGLE_CLIENT_SECRET }} + export AUTHD_GOOGLE_USER=${{ secrets.E2E_GOOGLE_USERNAME }} + EOF + + # Provision the VM + ${{ env.E2E_TESTS_DIR }}/vm/provision-authd.sh + + - name: Checkout YARF repo + uses: actions/checkout@v6 + with: + repository: adombeck/yarf + path: ${{ env.E2E_TESTS_DIR }}/.yarf + + - name: Install APT dependencies for running the E2E tests + run: ${{ env.E2E_TESTS_DIR }}/install-deps.sh + + - name: Configure YARF + run: ${{ env.E2E_TESTS_DIR }}/setup-yarf.sh + + - name: Run tests + id: run-tests + run: | + set -eux + export PATH="$(realpath "${{ env.E2E_TESTS_DIR }}/vm/helpers"):${PATH}" + + # Set the credentials for the broker + if [ "${{ inputs.broker }}" = "authd-msentraid" ]; then + E2E_USER=${{ secrets.E2E_MSENTRA_USERNAME }} + E2E_PASSWORD=${{ secrets.E2E_MSENTRA_PASSWORD }} + TOTP_SECRET=${{ secrets.E2E_MSENTRA_TOTP_SECRET }} + elif [ "${{ inputs.broker }}" = "authd-google" ]; then + E2E_USER=${{ secrets.E2E_GOOGLE_USERNAME }} + E2E_PASSWORD=${{ secrets.E2E_GOOGLE_PASSWORD }} + TOTP_SECRET=${{ secrets.E2E_GOOGLE_TOTP_SECRET }} + fi + + # Run the tests + ${{ env.E2E_TESTS_DIR }}/run-tests.sh \ + --user "${E2E_USER}" \ + --password "${E2E_PASSWORD}" \ + --totp-secret "${TOTP_SECRET}" \ + --release "${{ inputs.release }}" \ + --broker "${{ inputs.broker }}" \ + --output-dir "${{ env.OUTPUT_DIR }}" + + - name: Upload test results + id: upload-results + if: always() + uses: actions/upload-artifact@v6 + with: + name: e2e-${{ inputs.broker }}-output-${{ inputs.release }}-${{ github.run_id }} + path: ${{ env.OUTPUT_DIR }} diff --git a/.gitignore b/.gitignore index e03372947d..8c35dcd098 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ snap/icon.svg snap/snapcraft.yaml authd-oidc-brokers/conf/authd.conf authd-oidc-brokers/conf/broker.conf + +# Useful for running e2e-tests locally +e2e-tests/e2e-tests.env diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/Journal.py b/authd-oidc-brokers/e2e-tests/resources/authd/Journal.py deleted file mode 100644 index 576fba7695..0000000000 --- a/authd-oidc-brokers/e2e-tests/resources/authd/Journal.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -from ansi2html import Ansi2HTMLConverter - -from robot.api import logger -from robot.api.deco import keyword, library # type: ignore -from robot.libraries.BuiltIn import BuiltIn - -import ExecUtils - -HOST_CID = 2 # 2 always refers to the host -PORT = 55000 - -@library -class Journal: - process = None - output_dir = None - - @keyword - async def start_receiving_journal(self) -> None: - """ - Start receiving journal entries from the VM via vsock. - """ - if self.process: - return - - output_dir = BuiltIn().get_variable_value('${OUTPUT DIR}', '.') - suite_name = BuiltIn().get_variable_value('${SUITE NAME}', 'unknown') - self.output_dir = os.path.join(output_dir, suite_name, "journal") - os.makedirs(self.output_dir, exist_ok=True) - - self.process = ExecUtils.Popen( - [ - "/lib/systemd/systemd-journal-remote", - f"--listen-raw=vsock:{HOST_CID}:{PORT}", - f"--output={self.output_dir}" - ], - ) - - @keyword - async def stop_receiving_journal(self) -> None: - """ - Stop receiving journal entries from the VM. - """ - if self.process: - self.process.terminate() - self.process.wait() - self.process = None - - @keyword - async def log_journal(self) -> None: - """ - Log the journal entries received from the VM. - """ - output = ExecUtils.check_output( - [ - 'journalctl', - '--no-pager', - '--directory', self.output_dir, - ], - env={'SYSTEMD_COLORS': 'true'}, - text=True, - ) - - html_output = Ansi2HTMLConverter(inline=True).convert(output, full=False) - logger.info(html_output, html=True) diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/generate_totp.py b/authd-oidc-brokers/e2e-tests/resources/authd/generate_totp.py deleted file mode 100644 index 5d4e6a0c9b..0000000000 --- a/authd-oidc-brokers/e2e-tests/resources/authd/generate_totp.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import base64 -import hashlib -import hmac -import struct -import time - -def generate_totp(secret: str) -> str: - key = base64.b32decode(secret, True) - msg = struct.pack(">Q", int(time.time()) // 30) - hashed_obj = hmac.new(key, msg, hashlib.sha1).digest() - o = hashed_obj[19] & 15 - - totp_code = str((struct.unpack(">I", hashed_obj[o:o + 4])[0] & 0x7fffffff) % 1000000) - while len(totp_code) != 6: - totp_code += '0' - - return totp_code - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("totp_secret") - args = parser.parse_args() - - print(generate_totp(args.totp_secret)) diff --git a/authd-oidc-brokers/e2e-tests/.gitignore b/e2e-tests/.gitignore similarity index 100% rename from authd-oidc-brokers/e2e-tests/.gitignore rename to e2e-tests/.gitignore diff --git a/authd-oidc-brokers/e2e-tests/install-deps.sh b/e2e-tests/install-deps.sh similarity index 82% rename from authd-oidc-brokers/e2e-tests/install-deps.sh rename to e2e-tests/install-deps.sh index e0723f8e74..d0b9032eb0 100755 --- a/authd-oidc-brokers/e2e-tests/install-deps.sh +++ b/e2e-tests/install-deps.sh @@ -3,7 +3,7 @@ set -euo pipefail # Install packages required for running the e2e tests -sudo apt-get -y install \ +sudo apt-get update && sudo apt-get -y install \ bsdutils \ ffmpeg \ gir1.2-webkit2-4.1 \ @@ -17,4 +17,5 @@ sudo apt-get -y install \ python3-gi \ python3-tk \ socat \ + systemd-journal-remote \ xvfb diff --git a/authd-oidc-brokers/e2e-tests/resources/authd-google/Browser.py b/e2e-tests/resources/authd-google/Browser.py similarity index 86% rename from authd-oidc-brokers/e2e-tests/resources/authd-google/Browser.py rename to e2e-tests/resources/authd-google/Browser.py index 894dc68bdd..76f5cf1fa8 100644 --- a/authd-oidc-brokers/e2e-tests/resources/authd-google/Browser.py +++ b/e2e-tests/resources/authd-google/Browser.py @@ -1,18 +1,18 @@ import os +import subprocess from robot.api.deco import keyword, library # type: ignore -from robot.libraries.Process import Process from robot.api import logger SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) def run_command(args): - result = Process().run_process(args[0], *args[1:]) - if result.rc == 0: + result = subprocess.run(args) + if result.returncode == 0: return cmd = " ".join(args) - logger.error(f"Command '{cmd}' failed:\n{result.stderr}") + logger.error(f"Command '{cmd}' failed with code {result.returncode}:\n{result.stderr}") raise RuntimeError(f"Command '{cmd}' failed") diff --git a/authd-oidc-brokers/e2e-tests/resources/authd-google/broker.resource b/e2e-tests/resources/authd-google/broker.resource similarity index 88% rename from authd-oidc-brokers/e2e-tests/resources/authd-google/broker.resource rename to e2e-tests/resources/authd-google/broker.resource index d1e3ed2ee5..be717fd450 100644 --- a/authd-oidc-brokers/e2e-tests/resources/authd-google/broker.resource +++ b/e2e-tests/resources/authd-google/broker.resource @@ -35,9 +35,7 @@ Log In With Remote User Through CLI: QR Code Start Log In With Remote User Through CLI: QR Code [Arguments] ${username} - Hid.Type String machinectl login - Hid.Keys Combo Return - Match Text ubuntu login: 60 + Try machinectl login Prompt Hid.Type String ${username} Hid.Keys Combo Return @@ -56,7 +54,7 @@ Select Provider Regenerate QR Code # As long as we are in the login process, we can regenerate the QR code - Match Text Request new login code 30 + Match Text Request new login code 120 Hid.Keys Combo Return Match Text google.com/device 15 @@ -79,7 +77,7 @@ Continue Log In With Remote User Through CLI: Define Local Password [Arguments] ${username} ${local_password} # The terminal should now be visible and focused again. # Check that we're prompted to set a local password. - Match Text New password: 60 + Match Text New password: 120 # Set a local password Hid.Type String ${local_password} @@ -91,23 +89,21 @@ Continue Log In With Remote User Through CLI: Define Local Password Hid.Keys Combo Return # Wait for the login to complete - ${timeout_sec} = Set Variable 30 + ${timeout_sec} = Set Variable 120 Match Text ${username}@ubuntu ${timeout_sec} Log In With Remote User Through CLI: Local Password [Arguments] ${username} ${local_password} - Hid.Type String machinectl login - Hid.Keys Combo Return - Match Text ubuntu login: 60 + Try machinectl login Prompt Hid.Type String ${username} Hid.Keys Combo Return Builtin.Sleep 2 - Match Text Enter your local password: 30 + Match Text Enter your local password: 120 Hid.Type String ${local_password} Hid.Keys Combo Return Builtin.Sleep 2 - Match Text ${username}@ubuntu:~$ 30 + Match Text ${username}@ubuntu:~$ 120 # Uses sed to change the broker configuration. @@ -153,39 +149,39 @@ Start Log In With Remote User Through SSH: QR Code Select Provider through SSH - Match Text 2. Google 30 - Match Text Choose your provider: 30 + Match Text 2. Google 120 + Match Text Choose your provider: 120 Hid.Type String 2 Hid.Keys Combo Return Continue Log In With Remote User Through SSH: QR Code - Match Text Choose action: 30 + Match Text Choose action: 120 Hid.Type String 1 Hid.Keys Combo Return Continue Log In With Remote User Through SSH: Define Local Password [Arguments] ${username} ${local_password} - Match Text Create a local password: 30 + Match Text Create a local password: 120 Hid.Type String ${local_password} Hid.Keys Combo Return - Match Text Confirm Password: 30 + Match Text Confirm Password: 120 Hid.Type String ${local_password} Hid.Keys Combo Return - Match Text ${username}@ubuntu:~$ 30 + Match Text ${username}@ubuntu:~$ 120 Log In With Remote User Through SSH: Local Password [Arguments] ${username} ${local_password} Hid.Type String ssh ${username}@localhost Hid.Keys Combo Return - Match Text Enter your local password: 30 + Match Text Enter your local password: 120 Hid.Type String ${local_password} Hid.Keys Combo Return - Match Text ${username}@ubuntu:~$ 30 + Match Text ${username}@ubuntu:~$ 120 Log In With Remote User Through GDM: QR Code @@ -210,7 +206,7 @@ Start Log In With Remote User Through GDM: QR Code Select Broker Through GDM - Match Text Select the broker 30 + Match Text Select the broker 120 Move Pointer To Google Left Button Click Builtin.Sleep 1 @@ -218,11 +214,11 @@ Select Broker Through GDM Continue Log In With Remote User Through GDM: Define Local Password [Arguments] ${local_password} - Match Text Create a local password 30 + Match Text Create a local password 120 Hid.Type String ${local_password} Hid.Keys Combo Return - Match Text Please, type the new passphrase again 30 + Match Text Please, type the new passphrase again 120 Hid.Type String ${local_password} Hid.Keys Combo Return @@ -255,7 +251,7 @@ Check If User Was Added Properly Check User Information [Arguments] ${username} - Match Text ${username}:x: 30 + Match Text ${username}:x: 120 Check Configuration Value @@ -266,28 +262,28 @@ Check Configuration Value Hid.Keys Combo Shift_L | Hid.Type String grep ${config_key} Hid.Keys Combo Return - Match Text ${expected_value} 30 + Match Text ${expected_value} 120 Check If Owner Was Registered [Arguments] ${username} Hid.Type String cat ${GOOGLE_BROKER_CFG_DIR}/20-owner-autoregistration.conf Hid.Keys Combo Return - Match Text owner = ${username} 30 + Match Text owner = ${username} 120 Check Home Directory [Arguments] ${username} Hid.Type String echo $HOME Hid.Keys Combo Return - Match Text /home/${username} 30 + Match Text /home/${username} 120 Hid.Type String stat -c %U:%G $HOME Hid.Keys Combo Return Match Text ${username}:${username} Check That Remote User Is Not Allowed To Log In - Match Text authentication failure: user not allowed in broker configuration 60 + Match Text authentication failure: user not allowed in broker configuration 120 Check That Login Is Handled By PAM Unix diff --git a/authd-oidc-brokers/e2e-tests/resources/authd-google/browser_login.py b/e2e-tests/resources/authd-google/browser_login.py similarity index 92% rename from authd-oidc-brokers/e2e-tests/resources/authd-google/browser_login.py rename to e2e-tests/resources/authd-google/browser_login.py index 52db47c174..94b9b08d55 100755 --- a/authd-oidc-brokers/e2e-tests/resources/authd-google/browser_login.py +++ b/e2e-tests/resources/authd-google/browser_login.py @@ -93,12 +93,18 @@ def login(browser, username: str, password: str, device_code: str, totp_secret: browser.send_key_taps( ascii_string_to_key_events(password) + [Gdk.KEY_Return]) + browser.wait_for_pattern("2-Step Verification") + browser.wait_for_stable_page() + browser.capture_snapshot(screenshot_dir, "device-login-enter-totp-code") + browser.send_key_taps( + ascii_string_to_key_events(generate_totp(totp_secret)) + [Gdk.KEY_Return]) + browser.wait_for_pattern("Choose an account") browser.wait_for_stable_page() browser.capture_snapshot(screenshot_dir, "device-login-choose-account") browser.send_key_taps([Gdk.KEY_Return]) - browser.wait_for_pattern("signing back in") + browser.wait_for_pattern("signing back in", timeout_ms=20000) browser.wait_for_stable_page() browser.capture_snapshot(screenshot_dir, "device-login-confirmation") # Sadly, just pressing Enter is not enough here, we need to tab to the correct button. diff --git a/authd-oidc-brokers/e2e-tests/resources/authd-msentraid/Browser.py b/e2e-tests/resources/authd-msentraid/Browser.py similarity index 86% rename from authd-oidc-brokers/e2e-tests/resources/authd-msentraid/Browser.py rename to e2e-tests/resources/authd-msentraid/Browser.py index 894dc68bdd..4b0af08a11 100644 --- a/authd-oidc-brokers/e2e-tests/resources/authd-msentraid/Browser.py +++ b/e2e-tests/resources/authd-msentraid/Browser.py @@ -1,18 +1,19 @@ import os +import subprocess from robot.api.deco import keyword, library # type: ignore -from robot.libraries.Process import Process from robot.api import logger + SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) def run_command(args): - result = Process().run_process(args[0], *args[1:]) - if result.rc == 0: + result = subprocess.run(args) + if result.returncode == 0: return cmd = " ".join(args) - logger.error(f"Command '{cmd}' failed:\n{result.stderr}") + logger.error(f"Command '{cmd}' failed with code {result.returncode}:\n{result.stderr}") raise RuntimeError(f"Command '{cmd}' failed") diff --git a/authd-oidc-brokers/e2e-tests/resources/authd-msentraid/broker.resource b/e2e-tests/resources/authd-msentraid/broker.resource similarity index 88% rename from authd-oidc-brokers/e2e-tests/resources/authd-msentraid/broker.resource rename to e2e-tests/resources/authd-msentraid/broker.resource index a2c8dd9610..111e47eb76 100644 --- a/authd-oidc-brokers/e2e-tests/resources/authd-msentraid/broker.resource +++ b/e2e-tests/resources/authd-msentraid/broker.resource @@ -39,9 +39,7 @@ Log In With Remote User Through CLI: QR Code Start Log In With Remote User Through CLI: QR Code [Arguments] ${username} - Hid.Type String machinectl login - Hid.Keys Combo Return - Match Text ubuntu login: 60 + Try machinectl login Prompt Hid.Type String ${username} Hid.Keys Combo Return @@ -60,7 +58,7 @@ Select Provider Regenerate QR Code # As long as we are in the login process, we can regenerate the QR code - Match Text Request new login code 30 + Match Text Request new login code 120 Hid.Keys Combo Return Match Text microsoft.com/devicelogin 15 @@ -83,7 +81,7 @@ Continue Log In With Remote User Through CLI: Define Local Password [Arguments] ${username} ${local_password} # The terminal should now be visible and focused again. # Check that we're prompted to set a local password. - Match Text New password: 60 + Match Text New password: 120 # Set a local password Hid.Type String ${local_password} @@ -95,23 +93,21 @@ Continue Log In With Remote User Through CLI: Define Local Password Hid.Keys Combo Return # Wait for the login to complete - ${timeout_sec} = Set Variable 30 + ${timeout_sec} = Set Variable 120 Match Text ${username}@ubuntu ${timeout_sec} Log In With Remote User Through CLI: Local Password [Arguments] ${username} ${local_password} - Hid.Type String machinectl login - Hid.Keys Combo Return - Match Text ubuntu login: 60 + Try machinectl login Prompt Hid.Type String ${username} Hid.Keys Combo Return Builtin.Sleep 2 - Match Text Enter your local password: 30 + Match Text Enter your local password: 120 Hid.Type String ${local_password} Hid.Keys Combo Return Builtin.Sleep 2 - Match Text ${username}@ubuntu:~$ 30 + Match Text ${username}@ubuntu:~$ 120 # Uses sed to change the broker configuration. @@ -157,39 +153,39 @@ Start Log In With Remote User Through SSH: QR Code Select Provider through SSH - Match Text 2. Microsoft Entra ID 30 - Match Text Choose your provider: 30 + Match Text 2. Microsoft Entra ID 120 + Match Text Choose your provider: 120 Hid.Type String 2 Hid.Keys Combo Return Continue Log In With Remote User Through SSH: QR Code - Match Text Choose action: 30 + Match Text Choose action: 120 Hid.Type String 1 Hid.Keys Combo Return Continue Log In With Remote User Through SSH: Define Local Password [Arguments] ${username} ${local_password} - Match Text Create a local password: 30 + Match Text Create a local password: 120 Hid.Type String ${local_password} Hid.Keys Combo Return - Match Text Confirm Password: 30 + Match Text Confirm Password: 120 Hid.Type String ${local_password} Hid.Keys Combo Return - Match Text ${username}@ubuntu:~$ 30 + Match Text ${username}@ubuntu:~$ 120 Log In With Remote User Through SSH: Local Password [Arguments] ${username} ${local_password} Hid.Type String ssh ${username}@localhost Hid.Keys Combo Return - Match Text Enter your local password: 30 + Match Text Enter your local password: 120 Hid.Type String ${local_password} Hid.Keys Combo Return - Match Text ${username}@ubuntu:~$ 30 + Match Text ${username}@ubuntu:~$ 120 Log In With Remote User Through GDM: QR Code @@ -214,7 +210,7 @@ Start Log In With Remote User Through GDM: QR Code Select Broker Through GDM - Match Text Select the broker 30 + Match Text Select the broker 120 Move Pointer To Microsoft Entra ID Left Button Click Builtin.Sleep 1 @@ -222,11 +218,11 @@ Select Broker Through GDM Continue Log In With Remote User Through GDM: Define Local Password [Arguments] ${local_password} - Match Text Create a local password 30 + Match Text Create a local password 120 Hid.Type String ${local_password} Hid.Keys Combo Return - Match Text Please, type the new passphrase again 30 + Match Text Please, type the new passphrase again 120 Hid.Type String ${local_password} Hid.Keys Combo Return @@ -262,12 +258,12 @@ Check If User Was Added Properly Check User Information [Arguments] ${username} - Match Text ${username}:x: 30 + Match Text ${username}:x: 120 Check User Groups [Arguments] ${username} ${remote_group} - Match Text ${username} sudo ${remote_group} 30 + Match Text ${username} sudo ${remote_group} 120 Check Configuration Value @@ -278,28 +274,28 @@ Check Configuration Value Hid.Keys Combo Shift_L | Hid.Type String grep ${config_key} Hid.Keys Combo Return - Match Text ${expected_value} 30 + Match Text ${expected_value} 120 Check If Owner Was Registered [Arguments] ${username} Hid.Type String cat ${ENTRAID_BROKER_CFG_DIR}/20-owner-autoregistration.conf Hid.Keys Combo Return - Match Text owner = ${username} 30 + Match Text owner = ${username} 120 Check Home Directory [Arguments] ${username} Hid.Type String echo $HOME Hid.Keys Combo Return - Match Text /home/${username} 30 + Match Text /home/${username} 120 Hid.Type String stat -c %U:%G $HOME Hid.Keys Combo Return Match Text ${username}:${username} Check That Remote User Is Not Allowed To Log In - Match Text authentication failure: user not allowed in broker configuration 60 + Match Text authentication failure: user not allowed in broker configuration 120 Check That Login Is Handled By PAM Unix diff --git a/authd-oidc-brokers/e2e-tests/resources/authd-msentraid/browser_login.py b/e2e-tests/resources/authd-msentraid/browser_login.py similarity index 100% rename from authd-oidc-brokers/e2e-tests/resources/authd-msentraid/browser_login.py rename to e2e-tests/resources/authd-msentraid/browser_login.py diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/ExecUtils.py b/e2e-tests/resources/authd/ExecUtils.py similarity index 100% rename from authd-oidc-brokers/e2e-tests/resources/authd/ExecUtils.py rename to e2e-tests/resources/authd/ExecUtils.py diff --git a/e2e-tests/resources/authd/Journal.py b/e2e-tests/resources/authd/Journal.py new file mode 100644 index 0000000000..93639780e4 --- /dev/null +++ b/e2e-tests/resources/authd/Journal.py @@ -0,0 +1,132 @@ +import os +from ansi2html import Ansi2HTMLConverter +import select +import subprocess +import time + +from robot.api import logger +from robot.api.deco import keyword, library # type: ignore +from robot.libraries.BuiltIn import BuiltIn + +import ExecUtils +import VMUtils + +HOST_CID = 2 # 2 always refers to the host +PORT = 55000 + + +@library +class Journal: + process = None + output_dir = None + + @keyword + async def start_receiving_journal(self) -> None: + """ + Start receiving journal entries from the VM via vsock. + """ + if self.process: + return + + output_dir = BuiltIn().get_variable_value('${OUTPUT DIR}', '.') + suite_name = BuiltIn().get_variable_value('${SUITE NAME}', 'unknown') + self.output_dir = os.path.join(output_dir, suite_name, "journal") + os.makedirs(self.output_dir, exist_ok=True) + + if os.getenv("SYSTEMD_SUPPORTS_VSOCK"): + self.process = ExecUtils.Popen( + [ + "/lib/systemd/systemd-journal-remote", + f"--listen-raw=vsock:{HOST_CID}:{PORT}", + f"--output={self.output_dir}", + ], + ) + else: + self.process = stream_journal_from_vm_via_tcp(output_dir=self.output_dir) + + @keyword + async def stop_receiving_journal(self) -> None: + """ + Stop receiving journal entries from the VM. + """ + if self.process: + self.process.terminate() + self.process.wait() + self.process = None + + @keyword + async def log_journal(self) -> None: + """ + Log the journal entries received from the VM. + """ + output = ExecUtils.check_output( + [ + 'journalctl', + '--no-pager', + '--directory', self.output_dir, + ], + env={'SYSTEMD_COLORS': 'true'}, + text=True, + ) + + html_output = Ansi2HTMLConverter(inline=True).convert(output, full=False) + logger.info(html_output, html=True) + +def stream_journal_from_vm_via_tcp(output_dir, timeout=60): + vm_name = VMUtils.vm_name() + vm_ip = VMUtils.vm_ip() + deadline = time.time() + timeout + + while time.time() < deadline: + # Start socat to connect to the VM's TCP port + socat = subprocess.Popen( + ["socat", "-d", "-d", f"TCP:{vm_ip}:{PORT}", "-"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, + ) + + connected = False + stderr_buf = [] + + # Read socat's stderr until we see a successful connection or timeout + while True: + r, _, _ = select.select([socat.stderr], [], [], 1) + if not r: + if socat.poll() is not None: + break + continue + + line = socat.stderr.readline() + if not line: + break + + stderr_buf.append(line) + + if "successfully connected" in line: + connected = True + break + + if not connected: + logger.error("".join(stderr_buf)) + socat.kill() + time.sleep(1) + continue + + # TCP connection confirmed + journal_remote = subprocess.Popen( + [ + "/lib/systemd/systemd-journal-remote", + f"--output={output_dir}/{vm_name}.journal", + "-", + ], + stdin=socat.stdout, + ) + + socat.stdout.close() + return journal_remote + + raise RuntimeError( + f"Failed to connect to VM journal stream within {timeout}s" + ) diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/SSH.py b/e2e-tests/resources/authd/SSH.py similarity index 100% rename from authd-oidc-brokers/e2e-tests/resources/authd/SSH.py rename to e2e-tests/resources/authd/SSH.py diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/Snapshot.py b/e2e-tests/resources/authd/Snapshot.py similarity index 53% rename from authd-oidc-brokers/e2e-tests/resources/authd/Snapshot.py rename to e2e-tests/resources/authd/Snapshot.py index 2f46157cc3..6951b54ca9 100644 --- a/authd-oidc-brokers/e2e-tests/resources/authd/Snapshot.py +++ b/e2e-tests/resources/authd/Snapshot.py @@ -1,17 +1,8 @@ -import os - -from robot.api import logger from robot.api.deco import keyword, library # type: ignore import ExecUtils +import VMUtils -VM_NAME_BASE="e2e-runner" - -def vm_name() -> str: - release = os.environ.get("RELEASE") - if not release: - raise Exception("RELEASE environment variable is not set") - return f"{VM_NAME_BASE}-{release}" @library class Snapshot: @@ -23,7 +14,8 @@ async def restore(self, name: str) -> None: Args: name: The name of the snapshot to revert to. """ + vm_name = VMUtils.vm_name() ExecUtils.run( - ["virsh", "snapshot-revert", vm_name(), name], + ["virsh", "snapshot-revert", vm_name, name], check=True, ) diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/StringUtils.py b/e2e-tests/resources/authd/StringUtils.py similarity index 100% rename from authd-oidc-brokers/e2e-tests/resources/authd/StringUtils.py rename to e2e-tests/resources/authd/StringUtils.py diff --git a/e2e-tests/resources/authd/VMUtils.py b/e2e-tests/resources/authd/VMUtils.py new file mode 100644 index 0000000000..28d282aa38 --- /dev/null +++ b/e2e-tests/resources/authd/VMUtils.py @@ -0,0 +1,34 @@ +import os +import subprocess +import time + +VM_NAME_BASE="e2e-runner" + +def vm_name() -> str: + release = os.environ.get("RELEASE") + if not release: + raise Exception("RELEASE environment variable is not set") + return f"{VM_NAME_BASE}-{release}" + + +def vm_ip(timeout=60): + deadline = time.time() + timeout + + while time.time() < deadline: + p = subprocess.run( + ["virsh", "domifaddr", "--domain", vm_name(), "--source", "agent"], + stdout=subprocess.PIPE, + text=True, + check=False, + ) + + for line in p.stdout.splitlines(): + parts = line.split() + if len(parts) >= 4 and parts[2] == "ipv4": + ip = parts[3].split("/")[0] + if ip != "127.0.0.1": + return ip + + time.sleep(1) + + raise RuntimeError(f"Timed out waiting for IPv4 address of VM '{vm_name()}'") diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/VideoLogger.py b/e2e-tests/resources/authd/VideoLogger.py similarity index 100% rename from authd-oidc-brokers/e2e-tests/resources/authd/VideoLogger.py rename to e2e-tests/resources/authd/VideoLogger.py diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/authd.resource b/e2e-tests/resources/authd/authd.resource similarity index 88% rename from authd-oidc-brokers/e2e-tests/resources/authd/authd.resource rename to e2e-tests/resources/authd/authd.resource index f9af984d62..d6edcd6142 100644 --- a/authd-oidc-brokers/e2e-tests/resources/authd/authd.resource +++ b/e2e-tests/resources/authd/authd.resource @@ -56,29 +56,27 @@ Change Password Try Log In With Remote User [Arguments] ${username} - Hid.Type String machinectl login - Hid.Keys Combo Return - Match Text ubuntu login: 60 + Try machinectl login Prompt Hid.Type String ${username} Hid.Keys Combo Return # Conditional checks Check That Remote User Has No Available Authentication Modes - Match Text could not get authentication modes: no authentication modes available for user 30 + Match Text could not get authentication modes: Error connecting to provider. Check your network connection. 120 Check That Log In Fails Because Authd Is Disabled - Match Text could not connect to unix:///run/authd.sock 30 + Match Text could not connect to unix:///run/authd.sock 120 Check That User Is Redirected To Local Broker - Match Text Password: 30 + Match Text Password: 120 Check That Authenticated User Does Not Match Requested User [Arguments] ${requested_user} - Match Text Authentication failure: requested username "${requested_user}" does not match the authenticated username 30 + Match Text Authentication failure: requested username "${requested_user}" does not match the authenticated username 120 Check That Remote User Can Run Sudo Commands diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/browser_window.py b/e2e-tests/resources/authd/browser_window.py similarity index 99% rename from authd-oidc-brokers/e2e-tests/resources/authd/browser_window.py rename to e2e-tests/resources/authd/browser_window.py index 513532ab3f..e0abf78259 100644 --- a/authd-oidc-brokers/e2e-tests/resources/authd/browser_window.py +++ b/e2e-tests/resources/authd/browser_window.py @@ -61,7 +61,6 @@ def on_event(_, event): and event.type != Gdk.EventType.KEY_RELEASE ): return - print(f"event: ({event}, {event.keyval})") return False self.web_view.add_events( @@ -428,6 +427,7 @@ def ascii_string_to_key_events(string): def render_video(screenshot_dir: str, video_path: str, framerate: int = 1): ExecUtils.check_call([ "ffmpeg", + "-loglevel", "warning", "-y", "-framerate", str(framerate), "-pattern_type", "glob", diff --git a/e2e-tests/resources/authd/generate_totp.py b/e2e-tests/resources/authd/generate_totp.py new file mode 100644 index 0000000000..6edb79364b --- /dev/null +++ b/e2e-tests/resources/authd/generate_totp.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +import argparse +import base64 +import hashlib +import hmac +import struct +import time + +TIME_WINDOW = 5 + +def generate_totp(secret: str) -> str: + # The code is generated according to the current time and is valid for 30 seconds. + # This means that if we generate the code just before the time window changes, + # it might be invalid by the time we use it. To avoid this, we make sure the time + # is safely within a new window before generating the code. + while time.time() % 30 > (30 - TIME_WINDOW): + continue + + key = base64.b32decode(secret, True) + msg = struct.pack(">Q", int(time.time()) // 30) + hashed_obj = hmac.new(key, msg, hashlib.sha1).digest() + o = hashed_obj[19] & 15 + + totp_code = (struct.unpack(">I", hashed_obj[o:o + 4])[0] & 0x7fffffff) % 1000000 + + return f"{totp_code:06d}" + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("totp_secret") + args = parser.parse_args() + + print(generate_totp(args.totp_secret)) diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/ssh.sh b/e2e-tests/resources/authd/ssh.sh similarity index 100% rename from authd-oidc-brokers/e2e-tests/resources/authd/ssh.sh rename to e2e-tests/resources/authd/ssh.sh diff --git a/authd-oidc-brokers/e2e-tests/resources/authd/utils.resource b/e2e-tests/resources/authd/utils.resource similarity index 82% rename from authd-oidc-brokers/e2e-tests/resources/authd/utils.resource rename to e2e-tests/resources/authd/utils.resource index f0ac5db531..dfe5dac07a 100644 --- a/authd-oidc-brokers/e2e-tests/resources/authd/utils.resource +++ b/e2e-tests/resources/authd/utils.resource @@ -23,7 +23,7 @@ Log In Wait Until User Selected - Wait Until Keyword Succeeds 30 sec 1 sec + Wait Until Keyword Succeeds 120 sec 1 sec ... Try Select User @@ -51,10 +51,10 @@ Try Desktop Ready Open Terminal Run Command x-terminal-emulator Hid.Move Pointer To Proportional 1 1 - Match Text @ubuntu:~$ 30 + Match Text @ubuntu:~$ 120 Hid.Keys Combo F11 Hid.Move Pointer To Proportional 1 1 - Match Text @ubuntu:~$ 30 + Match Text @ubuntu:~$ 120 Open Terminal In Sudo Mode @@ -67,10 +67,10 @@ Enter Sudo Mode In Terminal Hid.Keys Combo Return Hid.Type String sudo -s Hid.Keys Combo Return - Match Text root@ubuntu 30 + Match Text root@ubuntu 120 Hid.Type String clear Hid.Keys Combo Return - Match Text root@ubuntu 30 + Match Text root@ubuntu 120 Close Focused Window @@ -80,14 +80,14 @@ Close Focused Window Close Terminal In Sudo Mode Hid.Type String clear Hid.Keys Combo Return - Match Text root@ubuntu 30 + Match Text root@ubuntu 120 Hid.Type String exit Hid.Keys Combo Return Close Focused Window Log Out From Terminal Session - Match Text @ubuntu 30 + Match Text @ubuntu 120 # We are in a machinectl session, so we need to ^] thrice to exit properly Hid.Keys Combo Control_L ] Hid.Keys Combo Control_L ] @@ -97,7 +97,7 @@ Log Out From Terminal Session Log Out From SSH Session Hid.Type String exit Hid.Keys Combo Return - Match Text ubuntu@ubuntu 30 + Match Text ubuntu@ubuntu 120 Run Command In Terminal @@ -154,6 +154,23 @@ Wait Until System Time Synced ... Sync System Time +Try machinectl login Prompt + VAR ${tries} 3 + WHILE ${tries} > 0 + Hid.Type String machinectl login + Hid.Keys Combo Return + ${match} = Run Keyword And Return Status + ... Match Text ubuntu login: 120 + IF '${match}' == 'True' + BREAK + END + Hid.Keys Combo Control_L ] + Hid.Keys Combo Control_L ] + Hid.Keys Combo Control_L ] + ${tries} = Evaluate ${tries} - 1 + END + + ### Setup ### Restore Snapshot diff --git a/authd-oidc-brokers/e2e-tests/resources/broker b/e2e-tests/resources/broker similarity index 100% rename from authd-oidc-brokers/e2e-tests/resources/broker rename to e2e-tests/resources/broker diff --git a/authd-oidc-brokers/e2e-tests/resources/kvm.resource b/e2e-tests/resources/kvm.resource similarity index 100% rename from authd-oidc-brokers/e2e-tests/resources/kvm.resource rename to e2e-tests/resources/kvm.resource diff --git a/authd-oidc-brokers/e2e-tests/resources/video_input_vars.py b/e2e-tests/resources/video_input_vars.py similarity index 100% rename from authd-oidc-brokers/e2e-tests/resources/video_input_vars.py rename to e2e-tests/resources/video_input_vars.py diff --git a/authd-oidc-brokers/e2e-tests/run-tests.sh b/e2e-tests/run-tests.sh similarity index 84% rename from authd-oidc-brokers/e2e-tests/run-tests.sh rename to e2e-tests/run-tests.sh index f9ba46c36a..54916f99e2 100755 --- a/authd-oidc-brokers/e2e-tests/run-tests.sh +++ b/e2e-tests/run-tests.sh @@ -29,8 +29,9 @@ Options: -s, --totp-secret Secret to generate OTP codes for the user's MFA (can also be set via TOTP_SECRET environment variable) -b, --broker Broker to test (can also be set via BROKER environment variable) -r, --release Ubuntu release to test (e.g., 'questing', can also be set via RELEASE environment variable) - --rerunfailed Re-run only the tests that failed in the previous run + -o, --output-dir DIR Directory to store test outputs (default: temporary directory) -h, --help Show this help message and exit + --rerunfailed Re-run only the tests that failed in the previous run EOF } @@ -40,6 +41,7 @@ TEST_RUNS_DIR="${XDG_RUNTIME_DIR}/authd-e2e-test-runs" # Parse command line arguments TESTS_TO_RUN="" +OUTPUT_DIR="" while [[ $# -gt 0 ]]; do key="$1" @@ -68,6 +70,10 @@ while [[ $# -gt 0 ]]; do RERUNFAILED=1 shift ;; + --output-dir|-o) + OUTPUT_DIR="$2" + shift 2 + ;; -h|--help) usage exit 0 @@ -107,6 +113,11 @@ if [ -n "${RERUNFAILED:-}" ] && [ -z "${PREVIOUS_TEST_RUN_DIR}" ]; then exit 1 fi +systemd_ver=$(systemctl --version | awk 'NR==1 {print $2}') +if dpkg --compare-versions "$systemd_ver" "ge" "256"; then + SYSTEMD_SUPPORTS_VSOCK=1 +fi + ROBOT_ARGS=() if [ -n "${RERUNFAILED:-}" ]; then echo "Rerunning failed tests from previous run in ${PREVIOUS_TEST_RUN_DIR}" @@ -129,6 +140,13 @@ TEST_RUN_DIR=$(mktemp -d --tmpdir="${TEST_RUNS_DIR}" "${BROKER}-XXXXXX") ln -sf --no-target-directory "${TEST_RUN_DIR}" "${TEST_RUNS_DIR}/${BROKER}-latest" cd "${TEST_RUN_DIR}" +if [ -z "${OUTPUT_DIR:-}" ]; then + OUTPUT_DIR=output + echo "No output directory specified, using current test run directory." +fi + +mkdir -p "${OUTPUT_DIR}" resources + # Activate YARF environment YARF_DIR="${ROOT_DIR}/.yarf" if [ ! -d "${YARF_DIR}" ]; then @@ -150,18 +168,21 @@ for test_file in $TESTS_TO_RUN; do ln -s "${test_file}" tests done -E2E_USER="$E2E_USER" \ -E2E_PASSWORD="$E2E_PASSWORD" \ -TOTP_SECRET="$TOTP_SECRET" \ -BROKER="$BROKER" \ -RELEASE="$RELEASE" \ -VNC_PORT="$VNC_PORT" \ -robot \ - --loglevel DEBUG \ - --pythonpath "${YARF_DIR}/yarf/rf_libraries/libraries/vnc" \ - "${ROBOT_ARGS[@]}" \ - "$@" \ - tests \ - || test_result=$? +env \ + E2E_USER="$E2E_USER" \ + E2E_PASSWORD="$E2E_PASSWORD" \ + TOTP_SECRET="$TOTP_SECRET" \ + BROKER="$BROKER" \ + RELEASE="$RELEASE" \ + VNC_PORT="$VNC_PORT" \ + SYSTEMD_SUPPORTS_VSOCK="${SYSTEMD_SUPPORTS_VSOCK:-}" \ + robot \ + --loglevel DEBUG \ + --pythonpath "${YARF_DIR}/yarf/rf_libraries/libraries/vnc" \ + --outputdir "${OUTPUT_DIR}" \ + "${ROBOT_ARGS[@]}" \ + "$@" \ + tests \ + || test_result=$? exit "${test_result:-0}" diff --git a/authd-oidc-brokers/e2e-tests/setup-yarf.sh b/e2e-tests/setup-yarf.sh similarity index 100% rename from authd-oidc-brokers/e2e-tests/setup-yarf.sh rename to e2e-tests/setup-yarf.sh diff --git a/authd-oidc-brokers/e2e-tests/testing.md b/e2e-tests/testing.md similarity index 100% rename from authd-oidc-brokers/e2e-tests/testing.md rename to e2e-tests/testing.md diff --git a/authd-oidc-brokers/e2e-tests/tests/authd_disabled.robot b/e2e-tests/tests/authd_disabled.robot similarity index 96% rename from authd-oidc-brokers/e2e-tests/tests/authd_disabled.robot rename to e2e-tests/tests/authd_disabled.robot index a5e3ebbc90..19bdfa285f 100644 --- a/authd-oidc-brokers/e2e-tests/tests/authd_disabled.robot +++ b/e2e-tests/tests/authd_disabled.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/broker_disabled.robot b/e2e-tests/tests/broker_disabled.robot similarity index 96% rename from authd-oidc-brokers/e2e-tests/tests/broker_disabled.robot rename to e2e-tests/tests/broker_disabled.robot index 9014783aa6..52ee2f63c0 100644 --- a/authd-oidc-brokers/e2e-tests/tests/broker_disabled.robot +++ b/e2e-tests/tests/broker_disabled.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/config_issuer_invalid.robot b/e2e-tests/tests/config_issuer_invalid.robot similarity index 96% rename from authd-oidc-brokers/e2e-tests/tests/config_issuer_invalid.robot rename to e2e-tests/tests/config_issuer_invalid.robot index 2ddae0d8d0..ecee62b7a7 100644 --- a/authd-oidc-brokers/e2e-tests/tests/config_issuer_invalid.robot +++ b/e2e-tests/tests/config_issuer_invalid.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/config_owner_auto_update.robot b/e2e-tests/tests/config_owner_auto_update.robot similarity index 96% rename from authd-oidc-brokers/e2e-tests/tests/config_owner_auto_update.robot rename to e2e-tests/tests/config_owner_auto_update.robot index 5bf602ca9d..919b540724 100644 --- a/authd-oidc-brokers/e2e-tests/tests/config_owner_auto_update.robot +++ b/e2e-tests/tests/config_owner_auto_update.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/deny_login_if_user_not_allowed.robot b/e2e-tests/tests/deny_login_if_user_not_allowed.robot similarity index 96% rename from authd-oidc-brokers/e2e-tests/tests/deny_login_if_user_not_allowed.robot rename to e2e-tests/tests/deny_login_if_user_not_allowed.robot index 191154c52c..edf3974aaa 100644 --- a/authd-oidc-brokers/e2e-tests/tests/deny_login_if_user_not_allowed.robot +++ b/e2e-tests/tests/deny_login_if_user_not_allowed.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/deny_login_if_username_does_not_match.robot b/e2e-tests/tests/deny_login_if_username_does_not_match.robot similarity index 96% rename from authd-oidc-brokers/e2e-tests/tests/deny_login_if_username_does_not_match.robot rename to e2e-tests/tests/deny_login_if_username_does_not_match.robot index fd7c7fbf81..f7764138a0 100644 --- a/authd-oidc-brokers/e2e-tests/tests/deny_login_if_username_does_not_match.robot +++ b/e2e-tests/tests/deny_login_if_username_does_not_match.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/login.robot b/e2e-tests/tests/login.robot similarity index 96% rename from authd-oidc-brokers/e2e-tests/tests/login.robot rename to e2e-tests/tests/login.robot index 995b12b09a..3bc15af50b 100644 --- a/authd-oidc-brokers/e2e-tests/tests/login.robot +++ b/e2e-tests/tests/login.robot @@ -3,7 +3,7 @@ Resource ./resources/authd/utils.resource Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -11,8 +11,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/login_gdm.robot b/e2e-tests/tests/login_gdm.robot similarity index 96% rename from authd-oidc-brokers/e2e-tests/tests/login_gdm.robot rename to e2e-tests/tests/login_gdm.robot index 7eff3deeb8..a5b4d9c253 100644 --- a/authd-oidc-brokers/e2e-tests/tests/login_gdm.robot +++ b/e2e-tests/tests/login_gdm.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/migration_authd.robot b/e2e-tests/tests/migration_authd.robot similarity index 97% rename from authd-oidc-brokers/e2e-tests/tests/migration_authd.robot rename to e2e-tests/tests/migration_authd.robot index 7be42a410a..5cb2f34e7a 100644 --- a/authd-oidc-brokers/e2e-tests/tests/migration_authd.robot +++ b/e2e-tests/tests/migration_authd.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown diff --git a/authd-oidc-brokers/e2e-tests/tests/migration_authd_broker.robot b/e2e-tests/tests/migration_authd_broker.robot similarity index 97% rename from authd-oidc-brokers/e2e-tests/tests/migration_authd_broker.robot rename to e2e-tests/tests/migration_authd_broker.robot index f824226543..38de3e4339 100644 --- a/authd-oidc-brokers/e2e-tests/tests/migration_authd_broker.robot +++ b/e2e-tests/tests/migration_authd_broker.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown diff --git a/authd-oidc-brokers/e2e-tests/tests/migration_broker.robot b/e2e-tests/tests/migration_broker.robot similarity index 97% rename from authd-oidc-brokers/e2e-tests/tests/migration_broker.robot rename to e2e-tests/tests/migration_broker.robot index d34693031c..5d8d384ddc 100644 --- a/authd-oidc-brokers/e2e-tests/tests/migration_broker.robot +++ b/e2e-tests/tests/migration_broker.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown diff --git a/authd-oidc-brokers/e2e-tests/tests/mixed_case_username.robot b/e2e-tests/tests/mixed_case_username.robot similarity index 97% rename from authd-oidc-brokers/e2e-tests/tests/mixed_case_username.robot rename to e2e-tests/tests/mixed_case_username.robot index 87bf153ab3..9f228b37f2 100644 --- a/authd-oidc-brokers/e2e-tests/tests/mixed_case_username.robot +++ b/e2e-tests/tests/mixed_case_username.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/password_change.robot b/e2e-tests/tests/password_change.robot similarity index 97% rename from authd-oidc-brokers/e2e-tests/tests/password_change.robot rename to e2e-tests/tests/password_change.robot index 344cc7e05d..6d534a6515 100644 --- a/authd-oidc-brokers/e2e-tests/tests/password_change.robot +++ b/e2e-tests/tests/password_change.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/regenerate_qrcode.robot b/e2e-tests/tests/regenerate_qrcode.robot similarity index 97% rename from authd-oidc-brokers/e2e-tests/tests/regenerate_qrcode.robot rename to e2e-tests/tests/regenerate_qrcode.robot index cb7c2d86f1..de12d7b94a 100644 --- a/authd-oidc-brokers/e2e-tests/tests/regenerate_qrcode.robot +++ b/e2e-tests/tests/regenerate_qrcode.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Test Teardown Journal.Stop Receiving Journal diff --git a/authd-oidc-brokers/e2e-tests/tests/resources b/e2e-tests/tests/resources similarity index 100% rename from authd-oidc-brokers/e2e-tests/tests/resources rename to e2e-tests/tests/resources diff --git a/authd-oidc-brokers/e2e-tests/tests/ssh_deny_login_if_prefix_not_allowed.robot b/e2e-tests/tests/ssh_deny_login_if_prefix_not_allowed.robot similarity index 96% rename from authd-oidc-brokers/e2e-tests/tests/ssh_deny_login_if_prefix_not_allowed.robot rename to e2e-tests/tests/ssh_deny_login_if_prefix_not_allowed.robot index 420d7418b7..c99f0e2c98 100644 --- a/authd-oidc-brokers/e2e-tests/tests/ssh_deny_login_if_prefix_not_allowed.robot +++ b/e2e-tests/tests/ssh_deny_login_if_prefix_not_allowed.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Change Broker Configuration ssh_allowed_suffixes_first_auth %{E2E_USER} Test Teardown diff --git a/authd-oidc-brokers/e2e-tests/tests/ssh_login.robot b/e2e-tests/tests/ssh_login.robot similarity index 97% rename from authd-oidc-brokers/e2e-tests/tests/ssh_login.robot rename to e2e-tests/tests/ssh_login.robot index c84106f1f4..a3e02c4977 100644 --- a/authd-oidc-brokers/e2e-tests/tests/ssh_login.robot +++ b/e2e-tests/tests/ssh_login.robot @@ -4,7 +4,7 @@ Resource ./resources/authd/authd.resource Resource ./resources/broker/broker.resource -Test Tags robot:exit-on-failure +# Test Tags robot:exit-on-failure Test Setup Test Setup Test Teardown Test Teardown @@ -12,8 +12,8 @@ Test Teardown Test Teardown *** Keywords *** Test Setup - Journal.Start Receiving Journal Restore Snapshot %{BROKER}-edge-configured + Journal.Start Receiving Journal Change Broker Configuration ssh_allowed_suffixes_first_auth %{E2E_USER} Test Teardown diff --git a/authd-oidc-brokers/e2e-tests/vm/cloud-init-template-noble.yaml b/e2e-tests/vm/cloud-init-template-noble.yaml similarity index 89% rename from authd-oidc-brokers/e2e-tests/vm/cloud-init-template-noble.yaml rename to e2e-tests/vm/cloud-init-template-noble.yaml index d582eb22ba..76e811f300 100644 --- a/authd-oidc-brokers/e2e-tests/vm/cloud-init-template-noble.yaml +++ b/e2e-tests/vm/cloud-init-template-noble.yaml @@ -27,7 +27,9 @@ packages: - gsettings-ubuntu-schemas - openssh-server - policykit-desktop-privileges - # Required to bind sshd to VSOCK socket + # Required to get the VM's IP address + - qemu-guest-agent + # Required to bind sshd to VSOCK socket and to forward the journal to the host - socat # Required for add-apt-repository - software-properties-common @@ -81,11 +83,11 @@ write_files: [Install] WantedBy=multi-user.target - # Export the journal via VSOCK - - path: /etc/systemd/system/journal-vsock-export.service + # Export the journal to the host + - path: /etc/systemd/system/journal-export.service content: | [Unit] - Description=Exports the journal over VSOCK + Description=Exports the journal to the host After=systemd-journald.service After=network.target Requires=network.target @@ -94,7 +96,7 @@ write_files: [Service] Type=simple - ExecStart=/bin/bash -o pipefail -c 'journalctl -b --lines=all -o export -f | socat - VSOCK-CONNECT:2:55000' + ExecStart=/bin/bash -o pipefail -c 'journalctl -b --lines=all -o export -f | socat - ${SOCAT_ADDRESS}' OOMScoreAdjust=-1000 Restart=always RestartSec=1 @@ -114,7 +116,7 @@ runcmd: - sudo systemctl mask systemd-networkd-wait-online.service # Enable our systemd services - sudo systemctl enable sshd-vsock.service - - sudo systemctl enable journal-vsock-export.service + - sudo systemctl enable journal-export.service # Avoid APT downloads and upgrades during the tests - sudo systemctl mask apt-daily.service - sudo systemctl mask apt-daily.timer diff --git a/authd-oidc-brokers/e2e-tests/vm/cloud-init-template-questing.yaml b/e2e-tests/vm/cloud-init-template-questing.yaml similarity index 76% rename from authd-oidc-brokers/e2e-tests/vm/cloud-init-template-questing.yaml rename to e2e-tests/vm/cloud-init-template-questing.yaml index cbf40a13bb..3a1eb731b1 100644 --- a/authd-oidc-brokers/e2e-tests/vm/cloud-init-template-questing.yaml +++ b/e2e-tests/vm/cloud-init-template-questing.yaml @@ -26,8 +26,12 @@ packages: - gsettings-ubuntu-schemas - openssh-server - policykit-desktop-privileges + # Required to get the VM's IP address + - qemu-guest-agent # Default terminal on questing - ptyxis + # Required to forward the journal to the host + - socat - spice-vdagent # Required for add-apt-repository - software-properties-common @@ -65,11 +69,26 @@ write_files: Match User *@* KbdInteractiveAuthentication yes - # Export the journal via VSOCK - - path: /etc/systemd/journald.conf.d/00-forward-to-vsock.conf + # Export the journal to the host + - path: /etc/systemd/system/journal-export.service content: | - [Journal] - ForwardToSocket=vsock:2:55000 + [Unit] + Description=Exports the journal to the host + After=systemd-journald.service + After=network.target + Requires=network.target + # Disable rate limiting + StartLimitIntervalSec=0 + + [Service] + Type=simple + ExecStart=/bin/bash -o pipefail -c 'journalctl -b --lines=all -o export -f | socat - ${SOCAT_ADDRESS}' + OOMScoreAdjust=-1000 + Restart=always + RestartSec=1 + + [Install] + WantedBy=basic.target runcmd: # Disable apport @@ -81,6 +100,8 @@ runcmd: - dconf update # Disable systemd-networkd-wait-online to speed up boot time - sudo systemctl mask systemd-networkd-wait-online.service + # Enable our systemd services + - sudo systemctl enable journal-export.service # Avoid APT downloads and upgrades during the tests - sudo systemctl mask apt-daily.service - sudo systemctl mask apt-daily.timer diff --git a/authd-oidc-brokers/e2e-tests/vm/config.sh.template b/e2e-tests/vm/config.sh.template similarity index 100% rename from authd-oidc-brokers/e2e-tests/vm/config.sh.template rename to e2e-tests/vm/config.sh.template diff --git a/authd-oidc-brokers/e2e-tests/vm/e2e-runner-template.xml b/e2e-tests/vm/e2e-runner-template.xml similarity index 90% rename from authd-oidc-brokers/e2e-tests/vm/e2e-runner-template.xml rename to e2e-tests/vm/e2e-runner-template.xml index 64d771a4f3..ca8d990dea 100644 --- a/authd-oidc-brokers/e2e-tests/vm/e2e-runner-template.xml +++ b/e2e-tests/vm/e2e-runner-template.xml @@ -36,6 +36,10 @@
+ + +
+ @@ -62,4 +66,5 @@ + diff --git a/e2e-tests/vm/helpers/virsh b/e2e-tests/vm/helpers/virsh new file mode 100755 index 0000000000..be0c547990 --- /dev/null +++ b/e2e-tests/vm/helpers/virsh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +exec sudo -E /usr/bin/virsh "$@" diff --git a/authd-oidc-brokers/e2e-tests/vm/install-provision-deps.sh b/e2e-tests/vm/install-provision-deps.sh similarity index 67% rename from authd-oidc-brokers/e2e-tests/vm/install-provision-deps.sh rename to e2e-tests/vm/install-provision-deps.sh index 26f82edbd5..39a5491969 100755 --- a/authd-oidc-brokers/e2e-tests/vm/install-provision-deps.sh +++ b/e2e-tests/vm/install-provision-deps.sh @@ -3,10 +3,14 @@ set -euo pipefail # Install packages required for provisioning the e2e-tests VM -sudo apt-get -y install \ +sudo apt-get update && sudo apt-get -y install \ bsdutils \ cloud-image-utils \ + guestfish \ libvirt-clients-qemu \ libvirt-daemon-system \ qemu-kvm \ + retry \ + socat \ + xvfb \ wget diff --git a/authd-oidc-brokers/e2e-tests/vm/lib/libprovision.sh b/e2e-tests/vm/lib/libprovision.sh similarity index 97% rename from authd-oidc-brokers/e2e-tests/vm/lib/libprovision.sh rename to e2e-tests/vm/lib/libprovision.sh index ff491a63ec..4af6cd6a28 100755 --- a/authd-oidc-brokers/e2e-tests/vm/lib/libprovision.sh +++ b/e2e-tests/vm/lib/libprovision.sh @@ -61,7 +61,7 @@ function wait_for_system_running() { retry --times 30 --delay 3 -- "$SSH" -- true # shellcheck disable=SC2016 local cmd='output=$(systemctl is-system-running --wait) || [ $output = degraded ]' - retry --times 3 --delay 3 -- timeout 30 -- "$SSH" -- "$cmd" + retry --times 3 --delay 3 -- timeout 30 "$SSH" -- "$cmd" } function reboot_system() { diff --git a/authd-oidc-brokers/e2e-tests/vm/provision-authd.sh b/e2e-tests/vm/provision-authd.sh similarity index 95% rename from authd-oidc-brokers/e2e-tests/vm/provision-authd.sh rename to e2e-tests/vm/provision-authd.sh index a17b7b499a..a1ab50ac9f 100755 --- a/authd-oidc-brokers/e2e-tests/vm/provision-authd.sh +++ b/e2e-tests/vm/provision-authd.sh @@ -63,14 +63,14 @@ assert_env_vars RELEASE VM_NAME_BASE BROKERS IFS=',' read -r -a BROKER_ARRAY <<< "${BROKERS}" -ARTIFACTS_DIR="${DATA_DIR}/${RELEASE}" +ARTIFACTS_DIR="${ARTIFACTS_DIR:-${DATA_DIR}/${RELEASE}}" if [ -z "${VM_NAME:-}" ]; then VM_NAME="${VM_NAME_BASE}-${RELEASE}" fi # Check if we have all required artifacts -IMAGE="${ARTIFACTS_DIR}/${VM_NAME_BASE}.qcow2" +IMAGE="${ARTIFACTS_DIR}/${VM_NAME}.qcow2" if [ ! -f "${IMAGE}" ]; then echo "Image not found: ${IMAGE}. Please run e2e-tests/vm/provision-ubuntu.sh first." exit 1 @@ -158,15 +158,14 @@ else # Ensure the VM is running to perform initial setup boot_system # Create a pre-authd setup snapshot - PRE_AUTHD_SNAPSHOT="" force_create_snapshot "$PRE_AUTHD_SNAPSHOT" fi # Install authd stable and create a snapshot -retry --times 3 --delay 1 -- timeout 30 -- "$SSH" -- \ +retry --times 3 --delay 1 -- timeout 30 "$SSH" -- \ "sudo add-apt-repository -y ppa:ubuntu-enterprise-desktop/authd" -timeout 600 -- \ +timeout 600 \ "$SSH" -- \ 'sudo apt-get install -y authd && \ sudo mkdir -p /etc/systemd/system/authd.service.d && \ @@ -187,10 +186,10 @@ virsh snapshot-delete --domain "${VM_NAME}" --snapshotname "authd-stable-install restore_snapshot_and_sync_time "$PRE_AUTHD_SNAPSHOT" # Install authd edge and create a snapshot -retry --times 3 --delay 1 -- timeout 30 -- "$SSH" -- \ +retry --times 3 --delay 1 -- timeout 30 "$SSH" -- \ "sudo add-apt-repository -y ppa:ubuntu-enterprise-desktop/authd-edge" -timeout 600 -- \ +timeout 600 \ "$SSH" -- \ 'sudo apt-get install -y authd && \ sudo mkdir -p /etc/systemd/system/authd.service.d && \ diff --git a/authd-oidc-brokers/e2e-tests/vm/provision-ubuntu.sh b/e2e-tests/vm/provision-ubuntu.sh similarity index 82% rename from authd-oidc-brokers/e2e-tests/vm/provision-ubuntu.sh rename to e2e-tests/vm/provision-ubuntu.sh index d3c6f1ba61..dddf32d723 100755 --- a/authd-oidc-brokers/e2e-tests/vm/provision-ubuntu.sh +++ b/e2e-tests/vm/provision-ubuntu.sh @@ -90,7 +90,7 @@ sudo -v # Installing all the packages can take some time, so we set the timeout to 15 minutes CLOUT_INIT_TIMEOUT=900 -ARTIFACTS_DIR="${DATA_DIR}/${RELEASE}" +ARTIFACTS_DIR="${ARTIFACTS_DIR:-${DATA_DIR}/${RELEASE}}" CLOUD_INIT_TEMPLATE="${SCRIPT_DIR}/cloud-init-template-${RELEASE}.yaml" if [ -z "${VM_NAME:-}" ]; then @@ -102,6 +102,15 @@ function cloud_init_finished() { sudo guestfish --ro -a "${image}" -i stat /var/lib/cloud/instance/boot-finished &>/dev/null } +function cloud_init_successful() { + local image=$1 + if ! sudo guestfish --ro -a "${image}" -i cat /var/lib/cloud/data/result.json | grep -q '"errors": \[\]'; then + # Print the result.json to show the errors + sudo guestfish --ro -a "${image}" -i cat /var/lib/cloud/data/result.json + return 1 + fi +} + # Print executed commands to ease debugging set -x @@ -110,7 +119,7 @@ IMAGE_URL="https://cloud-images.ubuntu.com/${RELEASE}/current/${RELEASE}-server- SOURCE_IMAGE="${CACHE_DIR}/$(basename "${IMAGE_URL}")" if [ ! -f "${SOURCE_IMAGE}" ]; then mkdir -p "${CACHE_DIR}" - wget -O "${SOURCE_IMAGE}" "${IMAGE_URL}" + wget "${CI:+--progress=dot:giga}" -O "${SOURCE_IMAGE}" "${IMAGE_URL}" else echo "Source image already exists: ${SOURCE_IMAGE}" fi @@ -131,7 +140,7 @@ if [ "${FORCE:-}" = true ]; then fi # Copy and resize the image -IMAGE="${ARTIFACTS_DIR}/${VM_NAME_BASE}.qcow2" +IMAGE="${ARTIFACTS_DIR}/${VM_NAME}.qcow2" if [ ! -f "${IMAGE}" ]; then mkdir -p "${ARTIFACTS_DIR}" cp "${SOURCE_IMAGE}" "${IMAGE}" @@ -147,8 +156,16 @@ if [ ! -f "${CLOUD_INIT_ISO}" ]; then CLOUD_INIT_DIR="$(mktemp -d)" trap 'rm -rf ${CLOUD_INIT_DIR}' EXIT + systemd_ver=$(systemctl --version | awk 'NR==1 {print $2}') + if dpkg --compare-versions "$systemd_ver" "ge" "256"; then + SOCAT_ADDRESS="VSOCK-CONNECT:2:55000" + else + SOCAT_ADDRESS="TCP-LISTEN:55000,bind=0.0.0.0,reuseaddr" + fi + SSH_PUBLIC_KEY=$(cat "${SSH_PUBLIC_KEY_FILE}") \ - envsubst < "${CLOUD_INIT_TEMPLATE}" > "${CLOUD_INIT_DIR}/user-data" + SOCAT_ADDRESS="${SOCAT_ADDRESS}" \ + envsubst < "${CLOUD_INIT_TEMPLATE}" > "${CLOUD_INIT_DIR}/user-data" cloud-localds "${CLOUD_INIT_ISO}" "${CLOUD_INIT_DIR}/user-data" else @@ -193,9 +210,18 @@ if ! cloud_init_finished "${IMAGE}"; then echo "Waiting for VM to finish cloud-init setup..." script -q -e -f /dev/null -c "virsh console $VM_NAME" & VM_CONSOLE_PID=$! - virsh await "${VM_NAME}" --condition domain-inactive --timeout "${CLOUT_INIT_TIMEOUT}" + + timeout "${CLOUT_INIT_TIMEOUT}" retry --delay 1 -- \ + sh -c "sudo virsh domstate \"${VM_NAME}\" | grep -q '^shut off'" + kill "${VM_CONSOLE_PID}" || true + # Check if the cloud-init finished successfully + if ! cloud_init_successful "${IMAGE}"; then + echo "cloud-init did not finish successfully." >&2 + exit 1 + fi + # Detach the cloud-init ISO virsh detach-disk "${VM_NAME}" vdb --config diff --git a/authd-oidc-brokers/e2e-tests/vm/provision.sh b/e2e-tests/vm/provision.sh similarity index 100% rename from authd-oidc-brokers/e2e-tests/vm/provision.sh rename to e2e-tests/vm/provision.sh diff --git a/authd-oidc-brokers/e2e-tests/vm/ssh.sh b/e2e-tests/vm/ssh.sh similarity index 100% rename from authd-oidc-brokers/e2e-tests/vm/ssh.sh rename to e2e-tests/vm/ssh.sh