Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -302,14 +302,23 @@ jobs:
with:
sparse-checkout: e2e

- name: UI Smoke Tests (Playwright) - HttpOnly disabled
env:
EXPECT_COOKIE_HTTPONLY: "false"
- name: Install Playwright
run: |
cd e2e
npm init -y
npm install @playwright/test
npx playwright install chromium --with-deps

# MIT Kerberos client for the Kerberos spec (kinit, klist); libgssapi_krb5 is already on the runner.
# No apt-get update: playwright install --with-deps has just run it.
- name: Install MIT Kerberos client
run: sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends krb5-user gettext-base

- name: UI Smoke Tests (Playwright) - HttpOnly disabled
env:
EXPECT_COOKIE_HTTPONLY: "false"
run: |
cd e2e
npx playwright test --reporter=list

- name: Enable HttpOnly session cookie on OpenAM IDP and restart
Expand Down Expand Up @@ -349,6 +358,8 @@ jobs:
shell: bash
run: |
docker stop openam-idp opendj-idp opendj-sp openam-sp
# The Kerberos spec removes its KDC in afterAll; this covers a run that did not get there.
docker rm -f openam-kdc >/dev/null 2>&1 || true
docker network rm test-openam

- name: Docker multi-server test
Expand Down
3 changes: 2 additions & 1 deletion e2e/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ node_modules/
package-lock.json
package.json
playwright-report/
test-results/
test-results/
kerberos/.work/
35 changes: 35 additions & 0 deletions e2e/common/openam-commons.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* Copyright 2026 3A Systems, LLC.
*/

import { expect } from "@playwright/test";

export const OPENAM_BASE = process.env.OPENAM_BASE_URL ?? "http://openam.example.org:8080/openam";
export const ADMIN_USER = process.env.OPENAM_ADMIN_USER ?? "amadmin";
export const ADMIN_PASS = process.env.OPENAM_ADMIN_PASS ?? "ampassword";
Expand All @@ -37,4 +39,37 @@ export async function getAuthToken(request, username, password) {
});
const json = await resp.json();
return json.tokenId;
}

// XUI / LESS-based OpenAM login form selectors
export const XUI_LOGIN = {
usernameInput: "#idToken1",
passwordInput: "#idToken2",
// The submit button id varies between XUI builds (loginButton / loginButton_0 / none),
// so match by submit type as the working SAML spec does.
loginButton: "#loginButton, input[type=\"submit\"], button[type=\"submit\"]",
};

/** Fill in and submit the XUI login form once the page shows it. */
export async function submitXuiLogin(page, username, password) {
await expect(page.locator(XUI_LOGIN.usernameInput)).toBeVisible({ timeout: 30_000 });
await page.fill(XUI_LOGIN.usernameInput, username);
await page.fill(XUI_LOGIN.passwordInput, password);
await page.locator(XUI_LOGIN.loginButton).first().click();
}

/**
* Resolve the id of the active session from the (auto-sent) session cookie, or from the given token.
* Returns null when there is no valid session.
*/
export async function idFromSession(request, tokenId) {
const headers = { "Accept-API-Version": "protocol=1.0,resource=2.0" };
if (tokenId) {
headers.iPlanetDirectoryPro = tokenId;
}
const resp = await request.post(`${OPENAM_BASE}/json/users?_action=idFromSession`, { headers });
if (!resp.ok()) {
return null;
}
return (await resp.json()).id;
}
155 changes: 155 additions & 0 deletions e2e/kerberos/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/bin/bash

# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions copyright [year] [name of copyright owner]".
#
# Copyright 2026 3A Systems, LLC.

# Prepares the Kerberos (WindowsDesktopSSO) e2e test:
# 1. starts a KDC through the provider selected by KRB_PROVIDER (providers/<name>.sh);
# 2. installs the service keytab into the OpenAM container;
# 3. configures the WindowsDesktopSSO module and the chain "module -> DataStore" with ssoadm;
# 4. obtains a TGT for the test user into .work/krb5cc for the browser and curl;
# 5. writes the resolved values to .work/kerberos.json for the spec.
#
# Usage: bootstrap.sh set everything up; safe to run again
# bootstrap.sh logs print the KDC and module logs
# bootstrap.sh stop stop the KDC
#
# Every setting is an environment variable; see kerberos.env and providers/*.sh for defaults.

set -euo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK="$HERE/.work"

# ─── Settings: environment, then common defaults, then the provider, then derived values ──
: "${KRB_PROVIDER:=mit}"
# shellcheck source=kerberos.env
source "$HERE/kerberos.env"

PROVIDER_FILE="$HERE/providers/$KRB_PROVIDER.sh"
if [[ ! "$KRB_PROVIDER" =~ ^[A-Za-z0-9_-]+$ || ! -f "$PROVIDER_FILE" ]]; then
echo "Unknown KRB_PROVIDER '$KRB_PROVIDER'. Available providers:" >&2
for provider in "$HERE"/providers/*.sh; do
echo " $(basename "$provider" .sh)" >&2
done
exit 2
fi
# shellcheck source=providers/mit.sh
source "$PROVIDER_FILE"

: "${KRB_SERVICE_PRINCIPAL:=$KRB_SPN@$KRB_REALM}"
: "${KRB_KDC_HOST:=$KRB_KDC_CONTAINER}"

OPENAM_DEBUG_DIR="/usr/openam/config/openam/debug"

if [[ "${1:-}" == "logs" ]]; then
echo "===== KDC ($KRB_PROVIDER) ====="
kdc_logs || true
echo "===== OpenAM WindowsDesktopSSO debug log ====="
docker exec "$KRB_OPENAM_CONTAINER" sh -c 'cat "$1"/amAuthWindowsDesktopSSO 2>/dev/null' sh "$OPENAM_DEBUG_DIR" || true
exit 0
fi
if [[ "${1:-}" == "stop" ]]; then
kdc_stop
exit 0
fi

ssoadm() {
local subcommand="$1"
shift
docker exec -w /usr/openam/ssoadmintools/openam/bin "$KRB_OPENAM_CONTAINER" \
./ssoadm "$subcommand" --adminid amadmin --password-file pwd.txt "$@"
}

json_string() {
local value="${1//\\/\\\\}"
printf '"%s"' "${value//\"/\\\"}"
}

if ! klist -V 2>/dev/null | grep -q '^Kerberos 5 version'; then
echo "MIT Kerberos client tools are required (kinit, klist; Debian/Ubuntu package krb5-user)" >&2
exit 2
fi
if ! command -v envsubst >/dev/null; then
echo "envsubst is required (Debian/Ubuntu package gettext-base)" >&2
exit 2
fi

mkdir -p "$WORK"

echo "Starting KDC (provider $KRB_PROVIDER, realm $KRB_REALM)"
kdc_start
kdc_wait

echo "Installing the keytab for $KRB_SPN into $KRB_OPENAM_CONTAINER:$KRB_KEYTAB_PATH"
kdc_export_keytab > "$WORK/http.keytab"
docker exec -i "$KRB_OPENAM_CONTAINER" sh -c 'cat > "$1"' sh "$KRB_KEYTAB_PATH" < "$WORK/http.keytab"

# The JDK accepts only AES keys by default (RC4 and DES are disabled since JDK 17).
keytab_entries="$(klist -ke "$WORK/http.keytab")"
echo "$keytab_entries"
if ! grep -F "$KRB_SPN@$KRB_REALM" <<<"$keytab_entries" | grep -Eq '\(aes(128|256)-cts'; then
echo "The keytab has no AES key for $KRB_SPN@$KRB_REALM" >&2
exit 1
fi

echo "Configuring auth module $KRB_MODULE_NAME and chain $KRB_CHAIN_NAME"
instances="$(ssoadm list-auth-instances --realm /)"
if ! grep -Fxq "$KRB_MODULE_NAME, [type=WindowsDesktopSSO]" <<<"$instances"; then
ssoadm create-auth-instance --realm / --name "$KRB_MODULE_NAME" --authtype WindowsDesktopSSO
fi
ssoadm update-auth-instance --realm / --name "$KRB_MODULE_NAME" --attributevalues \
"iplanet-am-auth-windowsdesktopsso-principal-name=$KRB_SERVICE_PRINCIPAL" \
"iplanet-am-auth-windowsdesktopsso-keytab-file=$KRB_KEYTAB_PATH" \
"iplanet-am-auth-windowsdesktopsso-kerberos-realm=$KRB_REALM" \
"iplanet-am-auth-windowsdesktopsso-kdc=$KRB_KDC_HOST" \
"iplanet-am-auth-windowsdesktopsso-returnRealm=false" \
"iplanet-am-auth-windowsdesktopsso-auth-level=1"

chains="$(ssoadm list-auth-cfgs --realm /)"
if ! grep -Fxq "$KRB_CHAIN_NAME" <<<"$chains"; then
ssoadm create-auth-cfg --realm / --name "$KRB_CHAIN_NAME"
fi
# Replaces the whole entry list, so a second run leaves the same chain.
ssoadm update-auth-cfg-entr --realm / --name "$KRB_CHAIN_NAME" \
--entries "$KRB_MODULE_NAME|SUFFICIENT" "DataStore|REQUIRED"

echo "Obtaining a ticket for $KRB_USER@$KRB_REALM"
KRB_REALM="$KRB_REALM" KRB_KDC_CLIENT_ADDR="$KRB_KDC_CLIENT_ADDR" KRB_SPN_HOST="$KRB_SPN_HOST" \
envsubst '${KRB_REALM} ${KRB_KDC_CLIENT_ADDR} ${KRB_SPN_HOST}' \
< "$HERE/krb5.conf.template" > "$WORK/krb5.conf"
export KRB5_CONFIG="$WORK/krb5.conf"
export KRB5CCNAME="FILE:$WORK/krb5cc"
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if printf '%s\n' "$KRB_PASSWORD" | kinit "$KRB_USER@$KRB_REALM" >/dev/null; then
break
fi
if ((attempt == 10)); then
echo "kinit failed" >&2
exit 1
fi
sleep 2
done
klist

{
printf '{\n'
printf ' "provider": %s,\n' "$(json_string "$KRB_PROVIDER")"
printf ' "realm": %s,\n' "$(json_string "$KRB_REALM")"
printf ' "user": %s,\n' "$(json_string "$KRB_USER")"
printf ' "module": %s,\n' "$(json_string "$KRB_MODULE_NAME")"
printf ' "chain": %s\n' "$(json_string "$KRB_CHAIN_NAME")"
printf '}\n'
} > "$WORK/kerberos.json"
echo "Kerberos e2e environment is ready"
39 changes: 39 additions & 0 deletions e2e/kerberos/kerberos.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions copyright [year] [name of copyright owner]".
#
# Copyright 2026 3A Systems, LLC.

# Defaults of the Kerberos e2e test that do not depend on the KDC provider.
#
# bootstrap.sh sources this file after KRB_PROVIDER is resolved and before
# providers/$KRB_PROVIDER.sh, so provider defaults may refer to these variables.
# A value already set in the environment always wins.

# Realm of the KDC, the client krb5.conf and the module's Kerberos realm attribute.
: "${KRB_REALM:=EXAMPLE.ORG}"
# Host name of OpenAM as the browser sees it: SPN, [domain_realm] and --auth-server-allowlist.
: "${KRB_SPN_HOST:=openam.example.org}"
# SPN requested by the browser and curl; the service keytab is exported for it.
: "${KRB_SPN:=HTTP/$KRB_SPN_HOST}"
# Keytab location inside the OpenAM container.
: "${KRB_KEYTAB_PATH:=/usr/openam/http.keytab}"
# Address the KDC port is published on, and the KDC address in the client krb5.conf.
# Docker Desktop on macOS cannot publish port 88; use an unprivileged one there, e.g. 127.0.0.1:10088.
: "${KRB_KDC_CLIENT_ADDR:=127.0.0.1:88}"
# Kerberos user; with returnRealm=false it must match an OpenAM user.
: "${KRB_USER:=demo}"
# Names of the WindowsDesktopSSO module instance and of the chain Kerberos -> DataStore.
: "${KRB_MODULE_NAME:=Kerberos}"
: "${KRB_CHAIN_NAME:=KerberosChain}"
# OpenAM container that runs ssoadm and the Docker network it shares with the KDC.
: "${KRB_OPENAM_CONTAINER:=openam-idp}"
: "${KRB_DOCKER_NETWORK:=test-openam}"
Loading
Loading