Files
computer-playbook/roles/web-app-openbao/files/playwright/addons/auth-ldap.spec.js
kevinveenbirkenbachandClaude Opus 5.5 c925150848 fix(web-app-openbao): ship the LDAP auth method as an external plugin
OpenBao 2.7 removed the builtin LDAP auth engine from the server binary
(openbao/openbao GH-3882). `bao auth enable ldap` answered
"plugin not found in the catalog: ldap" and the deploy stopped there.

The role now builds its own image. A multi-stage Dockerfile copies the
openbao-plugin-auth-ldap binary out of the image declared as the
`auth-ldap` service entry into /opt/openbao/plugins. A second server
config file, plugins.hcl, names that directory and registers the plugin
declaratively. It sets plugin_auto_register = true explicitly: the image
starts the server with a config directory, and on that path 2.7.1 does
not apply the documented default, so without the line the catalog stays
empty. The Dockerfile sets the file mode in a helper stage instead of
COPY --chmod, which only BuildKit accepts.

The mount is enabled with -plugin-version=latest, so it follows whichever
plugin version the image ships. A mount created by the former builtin
engine is tuned and reloaded onto the plugin, which needs `update` on
sys/plugins/reload/backend in the ansible-admin policy.

The LDAP tasks move into tasks/addons/auth-ldap.yml and the plugin is
declared under meta/addons as a required addon bridged to `ldap`. Its
Playwright spec logs in through the mount and asserts that sys/auth
reports an external running plugin version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 13:25:17 +02:00

46 lines
1.7 KiB
JavaScript

const { test, expect } = require("@playwright/test");
const { normalizeBaseUrl, decodeDotenvQuotedValue } = require("../personas");
const { skipUnlessAddonEnabled } = require("../addon-gating");
const { resolveTimeout } = require("../timeouts");
const baseUrl = normalizeBaseUrl(process.env.OPENBAO_BASE_URL || "");
const adminUsername = decodeDotenvQuotedValue(process.env.ADMIN_USERNAME || "");
const adminPassword = decodeDotenvQuotedValue(process.env.ADMIN_PASSWORD || "");
test.use({ ignoreHTTPSErrors: true });
test("addon auth-ldap: the ldap mount is served by the external plugin", async ({ request }) => {
skipUnlessAddonEnabled("auth-ldap");
const login = await request.post(
`${baseUrl}/v1/auth/ldap/login/${encodeURIComponent(adminUsername)}`,
{ data: { password: adminPassword }, failOnStatusCode: false, timeout: resolveTimeout(30_000) },
);
expect(
login.status(),
`expected the LDAP plugin to accept the administrator, got ${login.status()}`,
).toBe(200);
const token = (await login.json()).auth.client_token;
const mounts = await request.get(`${baseUrl}/v1/sys/auth`, {
headers: { "X-Vault-Token": token },
failOnStatusCode: false,
timeout: resolveTimeout(30_000),
});
expect(
mounts.status(),
`expected the administrator policy to read sys/auth, got ${mounts.status()}`,
).toBe(200);
const ldap = (await mounts.json()).data["ldap/"];
expect(ldap, "sys/auth must list the ldap/ mount").toBeTruthy();
expect(
ldap.plugin_version,
"the mount must follow whichever plugin version the image ships",
).toBe("latest");
expect(
ldap.running_plugin_version,
"an external plugin reports its own semantic version",
).toMatch(/^v\d+\.\d+\.\d+/);
});