recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit)
Sanitized single-commit public mirror of recipe-maintainer. - Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders. - Removed plans/ and planned-updates/ (deployment-planning docs) so no client/ deployment domains appear in the public repo. - All other secret stores were already gitignored. - docs.coopcloud.tech retained as a submodule (public upstream).
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Keycloak Test Dependencies
|
||||
|
||||
## lasuite-docs
|
||||
|
||||
La Suite Docs is used as an OIDC relying party for integration testing. The `oidc_integration.py` test verifies that Keycloak can issue tokens and that a real application accepts them.
|
||||
|
||||
- **Do not undeploy** lasuite-docs during `/test-context-reset` when testing keycloak.
|
||||
- The OIDC integration is configured via `recipe-info/lasuite-docs/setup_keycloak_integration.py`.
|
||||
- Test credentials are stored in `recipe-info/lasuite-docs/keycloak-test-credentials.<DOMAIN_SUFFIX>.toml`.
|
||||
@@ -0,0 +1,5 @@
|
||||
name = "keycloak"
|
||||
|
||||
[dependencies]
|
||||
requires = []
|
||||
test_requires = ["lasuite-docs"] # oidc_integration test needs lasuite-docs deployed
|
||||
@@ -0,0 +1,30 @@
|
||||
# Keycloak — First-Time Setup
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- DNS: `keycloak.<domain_suffix>` must resolve to the server
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Create the app:**
|
||||
```bash
|
||||
abra app new keycloak --server <SERVER> --domain keycloak.<DOMAIN_SUFFIX> --no-input
|
||||
```
|
||||
|
||||
2. **Generate secrets:**
|
||||
```bash
|
||||
abra app secret generate keycloak.<DOMAIN_SUFFIX> --all -m --no-input
|
||||
```
|
||||
Save output to `recipe-info/testsecrets/keycloak.<DOMAIN_SUFFIX>`.
|
||||
|
||||
3. **Deploy:**
|
||||
```bash
|
||||
abra app deploy keycloak.<DOMAIN_SUFFIX> --chaos --force --no-input
|
||||
```
|
||||
|
||||
4. **Verify:** curl `https://keycloak.<DOMAIN_SUFFIX>/realms/master` returns HTTP 200.
|
||||
|
||||
## Notes
|
||||
|
||||
- Keycloak health check uses `/realms/master` (root `/` returns 302).
|
||||
- Admin credentials: username `admin`, password from `admin_password` secret.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Keycloak Tests
|
||||
|
||||
## Requires
|
||||
|
||||
- lasuite-docs
|
||||
|
||||
## Target
|
||||
|
||||
- **URL:** `https://keycloak.<DOMAIN_SUFFIX>`
|
||||
|
||||
## Automated Checks
|
||||
|
||||
Run the scripts in `tests/` to perform automated testing:
|
||||
|
||||
- `health_check.py` — Confirms the instance is reachable and returns HTTP 200.
|
||||
- `oidc_integration.py` — Full OIDC integration test using La Suite Docs as the relying party. Verifies token issuance, OIDC discovery, and JWT validation by authenticating a test user through Keycloak and calling the Docs API with the resulting token. Requires lasuite-docs to be deployed. Set `SKIP_INTEGRATION=1` to skip.
|
||||
|
||||
## Manual Verification
|
||||
|
||||
1. Open `https://keycloak.<DOMAIN_SUFFIX>` in a browser.
|
||||
2. Confirm the Keycloak login/welcome page loads without errors.
|
||||
3. Log in with the temporary admin credentials (username: `admin`, password in `recipe-info/keycloak/secrets.json` under `admin_password`).
|
||||
4. Verify the Keycloak admin console loads and is functional.
|
||||
5. Create a real admin user with 2FA and delete the temporary admin (see recipe README for full steps).
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Health check for Keycloak."""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
|
||||
from utils.tests.helpers import http_get, resolve_domain
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--domain', default=os.environ.get('TEST_DOMAIN'))
|
||||
args = parser.parse_args()
|
||||
domain = args.domain or resolve_domain('keycloak')
|
||||
url = f"https://{domain}/realms/master"
|
||||
|
||||
print(f"Checking Keycloak at {url} ...")
|
||||
status, _ = http_get(url)
|
||||
if status == 200:
|
||||
print(f"PASS: Keycloak returned HTTP {status}")
|
||||
else:
|
||||
print(f"FAIL: Keycloak returned HTTP {status} (expected 200)")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keycloak OIDC integration test — validates Keycloak can issue tokens
|
||||
that are accepted by La Suite Docs."""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
|
||||
from utils.tests.helpers import (
|
||||
http_get, http_post, load_toml_credentials, resolve_domain,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
if os.environ.get('SKIP_INTEGRATION') == '1':
|
||||
print("SKIP: SKIP_INTEGRATION=1 is set, skipping OIDC integration test")
|
||||
sys.exit(0)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--domain', default=os.environ.get('TEST_DOMAIN'))
|
||||
args = parser.parse_args()
|
||||
|
||||
kc_domain = args.domain or resolve_domain('keycloak')
|
||||
docs_domain = resolve_domain('lasuite-docs')
|
||||
kc_url = f"https://{kc_domain}"
|
||||
docs_url = f"https://{docs_domain}"
|
||||
|
||||
# Load credentials from lasuite-docs recipe dir
|
||||
creds_path = os.path.join(os.path.dirname(__file__), '..', '..', 'lasuite-docs')
|
||||
creds = load_toml_credentials(creds_path, 'keycloak')
|
||||
if creds is None:
|
||||
print("FAIL: Credentials file not found: lasuite-docs/keycloak-test-credentials.<domain_suffix>.toml")
|
||||
print("Run recipe-info/lasuite-docs/setup_keycloak_integration.py first.")
|
||||
sys.exit(1)
|
||||
|
||||
print("Testing Keycloak OIDC integration with La Suite Docs")
|
||||
print()
|
||||
|
||||
# Step 1: Check that La Suite Docs is deployed and reachable
|
||||
print("Step 1: Checking La Suite Docs is reachable ...")
|
||||
status, _ = http_get(docs_url)
|
||||
if status == 0 or status >= 500:
|
||||
print(f" FAIL: La Suite Docs at {docs_url} returned HTTP {status}")
|
||||
print(" Make sure lasuite-docs is deployed before running this test.")
|
||||
sys.exit(1)
|
||||
print(f" PASS: La Suite Docs is reachable (HTTP {status})")
|
||||
|
||||
# Step 2: Verify OIDC discovery endpoint
|
||||
print("Step 2: Checking Keycloak OIDC discovery endpoint ...")
|
||||
discovery_url = f"{kc_url}/realms/{creds['kc_realm']}/.well-known/openid-configuration"
|
||||
status, data = http_get(discovery_url)
|
||||
if status != 200:
|
||||
print(f" FAIL: OIDC discovery endpoint returned HTTP {status} (expected 200)")
|
||||
sys.exit(1)
|
||||
issuer = (data or {}).get("issuer", "")
|
||||
print(f" PASS: OIDC discovery endpoint OK (issuer: {issuer})")
|
||||
|
||||
# Step 3: Obtain token from Keycloak
|
||||
print("Step 3: Obtaining token from Keycloak ...")
|
||||
token_url = f"{kc_url}/realms/{creds['kc_realm']}/protocol/openid-connect/token"
|
||||
status, data = http_post(
|
||||
token_url,
|
||||
data={
|
||||
"grant_type": "password",
|
||||
"client_id": creds["kc_client_id"],
|
||||
"client_secret": creds["kc_client_secret"],
|
||||
"username": creds["kc_test_user"],
|
||||
"password": creds["kc_test_pass"],
|
||||
"scope": "openid email",
|
||||
},
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
access_token = (data or {}).get("access_token", "")
|
||||
if not access_token:
|
||||
error = (data or {}).get("error_description", (data or {}).get("error", "unknown"))
|
||||
print(f" FAIL: Could not obtain token from Keycloak: {error}")
|
||||
sys.exit(1)
|
||||
print(f" PASS: Obtained access token from Keycloak ({len(access_token)} chars)")
|
||||
|
||||
# Step 4: Use token to authenticate against Docs API
|
||||
print("Step 4: Accessing Docs API with Keycloak token ...")
|
||||
status, body = http_get(
|
||||
f"{docs_url}/api/v1.0/users/me/",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
)
|
||||
if status != 200:
|
||||
print(f" FAIL: Docs API returned HTTP {status} (expected 200)")
|
||||
sys.exit(1)
|
||||
|
||||
user_email = (body or {}).get("email", "")
|
||||
expected_email = creds.get("kc_test_email", f"{creds['kc_test_user']}@test.example.com")
|
||||
if user_email == expected_email:
|
||||
print(f" PASS: Docs API returned user with email '{user_email}'")
|
||||
else:
|
||||
print(f" FAIL: Expected email '{expected_email}', got '{user_email}'")
|
||||
sys.exit(1)
|
||||
|
||||
print()
|
||||
print("PASS: Keycloak OIDC integration test passed — tokens accepted by Docs")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,14 @@
|
||||
# Keycloak Upstream
|
||||
|
||||
## Main Project
|
||||
|
||||
- **Repository:** https://github.com/keycloak/keycloak
|
||||
- **Releases:** https://github.com/keycloak/keycloak/releases
|
||||
- **Website:** https://www.keycloak.org
|
||||
|
||||
## Images
|
||||
|
||||
| Service | Image | Release Notes |
|
||||
|---------|-------|---------------|
|
||||
| app | `keycloak/keycloak` | https://github.com/keycloak/keycloak/releases |
|
||||
| db | `mariadb` | https://mariadb.com/kb/en/release-notes/ |
|
||||
Reference in New Issue
Block a user