From a6625f114a745c2bf7695f5dbeabd274d37440c8 Mon Sep 17 00:00:00 2001 From: Roy Osherove <575051+royosherove@users.noreply.github.com> Date: Sun, 16 Aug 2026 09:50:03 +0000 Subject: [PATCH 1/9] simple mode: KiroCrew default, curated pack list with UI hints - KiroCrew is now the default selection in simple mode - Simple mode shows only: KiroCrew, OpenClaw, Hermes, Claude Code, Codex, Kiro-CLI, Troika - KiroCrew labeled [WebUI], all others labeled [Terminal] (as prefix for scannability) - Advanced mode unchanged (still shows all packs, defaults to OpenClaw) Review fixes (Sol + Sonnet): - M1: Iterate simple_packs in declared order, not registry order - M2: Fallback to first item if default pack missing from registry - M3: AUTO_YES non-interactive auto-selects kirocrew without gum - L1: webui_packs array for extensibility (not hardcoded if/else) - L2: Hoisted local declarations out of loop - L3: UI type as prefix [WebUI]/[Terminal] for visual scanning - Fix PACK_NAME extraction to strip prefix labels --- install.sh | 71 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/install.sh b/install.sh index 41a8d89..08cd47e 100755 --- a/install.sh +++ b/install.sh @@ -1908,15 +1908,73 @@ choose_pack() { return fi + # Simple mode + non-interactive: auto-select kirocrew without gum + if [[ "$INSTALL_MODE" == "simple" && "$AUTO_YES" == true ]]; then + PACK_NAME="kirocrew" + # Validate kirocrew exists in registry; fall back to openclaw + local found_auto=false + for i in "${!PACK_NAMES[@]}"; do + if [[ "${PACK_NAMES[$i]}" == "$PACK_NAME" ]]; then + found_auto=true + break + fi + done + if [[ "$found_auto" != true ]]; then + PACK_NAME="openclaw" + warn "kirocrew not found in registry — falling back to openclaw" + fi + ok "Agent: ${PACK_NAME} (auto-selected)" + return + fi + # Interactive: build display items for gum choose local -a gum_items=() local default_item="" - for i in "${!PACK_NAMES[@]}"; do - local item="${PACK_NAMES[$i]} — ${PACK_DESCS[$i]}" - [[ "${PACK_EXPERIMENTAL[$i]}" == "true" ]] && item+=" (experimental)" - gum_items+=("$item") - [[ "${PACK_NAMES[$i]}" == "openclaw" ]] && default_item="$item" - done + local pname="" item="" in_list=false sp="" + + # Simple mode: curated list (order preserved) with UI-type hints + local -a simple_packs=(kirocrew openclaw hermes claude-code codex-cli kiro-cli troika) + local -a webui_packs=(kirocrew) + + if [[ "$INSTALL_MODE" == "simple" ]]; then + # Iterate in curated order to guarantee display position + for sp in "${simple_packs[@]}"; do + for i in "${!PACK_NAMES[@]}"; do + [[ "${PACK_NAMES[$i]}" == "$sp" ]] || continue + pname="${PACK_NAMES[$i]}" + + # Determine UI type prefix + in_list=false + for wp in "${webui_packs[@]}"; do + [[ "$pname" == "$wp" ]] && in_list=true && break + done + if [[ "$in_list" == true ]]; then + item="[WebUI] ${pname} — ${PACK_DESCS[$i]}" + else + item="[Terminal] ${pname} — ${PACK_DESCS[$i]}" + fi + [[ "${PACK_EXPERIMENTAL[$i]}" == "true" ]] && item+=" (experimental)" + + gum_items+=("$item") + [[ "$pname" == "kirocrew" ]] && default_item="$item" + break + done + done + else + # Advanced mode: show all packs in registry order, no UI-type labels + for i in "${!PACK_NAMES[@]}"; do + pname="${PACK_NAMES[$i]}" + item="${pname} — ${PACK_DESCS[$i]}" + [[ "${PACK_EXPERIMENTAL[$i]}" == "true" ]] && item+=" (experimental)" + gum_items+=("$item") + [[ "$pname" == "openclaw" ]] && default_item="$item" + done + fi + + # Fallback: if intended default wasn't found, use first item + if [[ -z "$default_item" && ${#gum_items[@]} -gt 0 ]]; then + default_item="${gum_items[0]}" + fi local pack_choice local header="${1:-Agent to deploy}" _gum_or_die pack_choice $GUM choose --header "$header" \ @@ -1924,6 +1982,7 @@ choose_pack() { "${gum_items[@]}" \ || { fail "Pack selection is required"; } PACK_NAME="${pack_choice%% —*}" + PACK_NAME="${PACK_NAME##*] }" # Strip [WebUI]/[Terminal] prefix if present for i in "${!PACK_NAMES[@]}"; do if [[ "${PACK_NAMES[$i]}" == "$PACK_NAME" && "${PACK_EXPERIMENTAL[$i]}" == "true" ]]; then warn "${PACK_NAME} is experimental — expect rough edges" From 122d0c7c71a1d3627f05882f9e96056282fef4a7 Mon Sep 17 00:00:00 2001 From: Roy Osherove <575051+royosherove@users.noreply.github.com> Date: Sun, 16 Aug 2026 10:42:16 +0000 Subject: [PATCH 2/9] docs: add WebUI Cognito auth technical design Pack-agnostic design for configure_webui_auth(): - Cognito Managed Login + PKCE (no client secret) - AdminCreateUserOnly (no self-signup) - Server-side JWT enforcement requirements - Reusable by any future WebUI pack - Security considerations, edge cases, cleanup plan Incorporates Sol review feedback on: - C1: Enforcement defined (JWT validation on all endpoints) - C2: OAuth flow clarified (auth-code + PKCE, not SRP) - C3: App client created for both new and existing pools - C4: No-auth gated on network isolation - C5: HTTPS required for remote access --- docs/design/kirocrew-webui-auth.md | 336 +++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 docs/design/kirocrew-webui-auth.md diff --git a/docs/design/kirocrew-webui-auth.md b/docs/design/kirocrew-webui-auth.md new file mode 100644 index 0000000..18229e3 --- /dev/null +++ b/docs/design/kirocrew-webui-auth.md @@ -0,0 +1,336 @@ +# KiroCrew WebUI Authentication — Technical Design + +## Overview + +When a user selects a pack with a WebUI (currently KiroCrew), the installer offers to protect it with AWS Cognito-based authentication. This flow is **pack-agnostic** — any future pack exposing a web interface can reuse the same function. + +## Architecture + +``` +Browser → KiroCrew WebUI (port 5476) + ↓ (unauthenticated) +Redirect → Cognito Managed Login (hosted UI) + ↓ (user authenticates) +Callback → /auth/callback with authorization code + ↓ (code exchange via PKCE) +KiroCrew validates JWT → grants session +``` + +### Auth Flow: Authorization Code + PKCE (Recommended) + +- **Grant type:** Authorization Code with PKCE (`code_challenge_method=S256`) +- **No client secret** — public client (SPA running in browser) +- **Scopes:** `openid email` +- **Token transport:** Tokens stored in secure httpOnly cookies or session storage (never in URL params beyond the initial callback) +- **Managed Login:** Uses Cognito's hosted UI — no custom login page needed + +### Why Managed Login + PKCE (not SRP) + +| Concern | Managed Login + PKCE | Custom SRP | +|---------|---------------------|------------| +| Implementation complexity | Low (redirect-based) | High (SDK integration) | +| Server-side enforcement | Standard JWT validation | Same | +| MFA support (future) | Built-in | Manual | +| Custom login UI | Not needed (Cognito hosted) | Required | +| Token refresh | Standard refresh_token grant | Manual | +| Security surface | Proven, AWS-managed | Custom code = custom bugs | + +## Security Requirements + +### Mandatory (non-negotiable) + +1. **No self-registration** — `AdminCreateUserConfig.AllowAdminCreateUserOnly = true` +2. **Server-side JWT validation** on every request: + - Validate signature against Cognito JWKS (`/.well-known/jwks.json`) + - Validate `iss` matches pool URL + - Validate `aud` (client_id) for ID tokens\n - Validate `exp` (reject expired tokens) + - Validate `token_use` (`id` vs `access`) per endpoint + - Fail closed: if validation fails or JWKS unavailable → 401 +3. **All API endpoints + WebSocket/SSE connections protected** — not just HTML routes +4. **Explicit unauthenticated allowlist** — only `/health`, `/auth/callback`, `/.well-known/*` +5. **HTTPS required for remote access** — HTTP only permitted for `localhost` / loopback +6. **No tokens in URL query strings** (except the one-time authorization code on callback) +7. **No wildcard callback URLs** — exact match only +8. **CSRF protection** — `state` parameter validated on callback + +### Network-gated no-auth option + +The `--no-auth` escape hatch is only safe when: +- Access is via SSM port-forward (`localhost:5476`) +- WebUI is behind a VPN/private VPC with restricted SG +- Another upstream auth layer exists (e.g. ALB + Cognito, CloudFront + Lambda@Edge) + +If the installer detects the instance has a public IP and no restrictive SG on port 5476, it should **warn strongly** or **refuse** to skip auth. + +## Installer Flow + +### Function signature + +```bash +# Pack-agnostic — any WebUI pack calls this +configure_webui_auth() { + local pack_name="$1" # e.g. "kirocrew" + local webui_port="$2" # e.g. 5476 + local callback_path="$3" # e.g. "/auth/callback" + # ... +} +``` + +### Step 1: Ask if auth is wanted + +``` +┌─────────────────────────────────────────────────────┐ +│ Protect KiroCrew WebUI with login? (recommended) │ +│ │ +│ > Yes — create Cognito login (recommended) │ +│ No — I'll use SSM port-forward only │ +└─────────────────────────────────────────────────────┘ +``` + +- Default: **Yes** +- `AUTO_YES` mode: Yes (secure by default) +- `---no-auth` flag: Skip (user takes responsibility) +- If "No" selected and public exposure detected: show strong warning, require explicit confirmation + +### Step 2: Choose or create user pool + +``` +┌─────────────────────────────────────────────────────┐ +│ Cognito user pool │ +│ │ +│ > Create new pool (recommended) │ +│ Use existing pool: my-pool-1 │ +│ Use existing pool: dev-auth-pool │ +└─────────────────────────────────────────────────────┘ +``` + +- `aws cognito-idp list-user-pools --max-results 20` +- If 0 existing pools → skip picker, auto-create +- `AUTO_YES` mode: always create new + +#### New pool configuration + +```bash +aws cognito-idp create-user-pool \ + --pool-name "lowkey-${pack_name}-${ENV_NAME}" \ + --policies '{ + "PasswordPolicy": { + "MinimumLength": 12, + "RequireUppercase": true, + "RequireLowercase": true, + "RequireNumbers": true, + "RequireSymbols": true, + "TemporaryPasswordValidityDays": 1 + } + }' \ + --admin-create-user-config '{ + "AllowAdminCreateUserOnly": true + }' \ + --auto-verified-attributes email \ + --username-attributes email \ + --schema '[ + {"Name":"email","Required":true,"Mutable":true} + ]' \ + --user-pool-tags '{ + "loki:managed": "true", + "loki:pack": "'"${pack_name}"'", + "loki:env": "'"${ENV_NAME}"'" + }' +``` + +#### App client (created for BOTH new and existing pools) + +```bash +aws cognito-idp create-user-pool-client \ + --user-pool-id "${POOL_ID}" \ + --client-name "${pack_name}-webui" \ + --generate-secret # ← NO: public client, no secret + --no-generate-secret \ + --explicit-auth-flows ALLOW_REFRESH_TOKEN_AUTH \ + --supported-identity-providers COGNITO \ + --allowed-o-auth-flows code \ + --allowed-o-auth-scopes openid email \ + --allowed-o-auth-flows-with-pkce true \ + --callback-urls "[\"${CALLBACK_URL}\"]" \ + --logout-urls "[\"${LOGOUT_URL}\"]" \ + --prevent-user-existence-errors ENABLED \ + --token-validity-units '{ + "AccessToken": "hours", + "IdToken": "hours", + "RefreshToken": "days" + }' \ + --access-token-validity 1 \ + --id-token-validity 1 \ + --refresh-token-validity 30 +``` + +#### Cognito domain (required for Managed Login) + +```bash +aws cognito-idp create-user-pool-domain \ + --user-pool-id "${POOL_ID}" \ + --domain "lowkey-${pack_name}-${UNIQUE_SUFFIX}" +``` + +### Step 3: Determine callback URL + +- **Local/tunnel access:** `http://localhost:${webui_port}${callback_path}` +- **Remote/ALB access:** `https://${DOMAIN}${callback_path}` +- Installer detects based on whether CloudFront/ALB is configured for this pack +- Both can be registered as callback URLs if both access paths exist + +### Step 4: Create initial user + +``` +┌─────────────────────────────────────────────────────┐ +│ Email for WebUI login: │ +│ > roy@example.com │ +└─────────────────────────────────────────────────────┘ +``` + +- Validate: basic email regex (`[^@]+@[^@]+\.[^@]+`) +- `AUTO_YES` mode: require `--webui-email ` flag or fail with clear message +- Generate password: 16 chars, cryptographically random (mixed case + digits + symbols) + +```bash +# Generate secure random password +WEBUI_PASSWORD=$(python3 -c " +import secrets, string +alphabet = string.ascii_letters + string.digits + '!@#$%&*' +print(''.join(secrets.choice(alphabet) for _ in range(16))) +") + +# Create user with permanent password (skip force-change-password) +aws cognito-idp admin-create-user \ + --user-pool-id "${POOL_ID}" \ + --username "${USER_EMAIL}" \ + --user-attributes Name=email,Value="${USER_EMAIL}" Name=email_verified,Value=true \ + --message-action SUPPRESS # Don't send welcome email + +aws cognito-idp admin-set-user-password \ + --user-pool-id "${POOL_ID}" \ + --username "${USER_EMAIL}" \ + --password "${WEBUI_PASSWORD}" \ + --permanent +``` + +### Step 5: Pass config to pack + +Export for pack's `install.sh` to consume: + +```bash +export WEBUI_AUTH_ENABLED="true" +export WEBUI_COGNITO_POOL_ID="${POOL_ID}" +export WEBUI_COGNITO_CLIENT_ID="${CLIENT_ID}" +export WEBUI_COGNITO_DOMAIN="${COGNITO_DOMAIN}" +export WEBUI_COGNITO_REGION="${DEPLOY_REGION}" +export WEBUI_CALLBACK_URL="${CALLBACK_URL}" +export WEBUI_LOGOUT_URL="${LOGOUT_URL}" +``` + +The pack installer writes these to its `.env` or config file. + +### Step 6: Output summary + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ✓ WebUI protected with Cognito + + Login: roy@example.com + Password: Xk9#mP2vR8$nL4wQ + + ⚠ Save these credentials now — the password + will not be shown again. + + Pool: lowkey-kirocrew-env-1-3847 + Region: us-east-1 + Login UI: https://lowkey-kirocrew-3847.auth.us-east-1.amazoncognito.com +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +## Pack-Side Enforcement (KiroCrew gateway responsibility) + +The installer provisions Cognito. The **pack** (KiroCrew) must enforce: + +1. **Middleware/filter** on all routes except allowlisted paths +2. **JWKS caching** with periodic refresh (respect `Cache-Control` from Cognito) +3. **Token validation** per the mandatory requirements above +4. **Session management** — exchange auth code for tokens on callback, set httpOnly cookie +5. **Logout** — clear session, redirect to Cognito logout endpoint +6. **WebSocket auth** — validate token on connection upgrade, not just initial page load + +### Minimal enforcement pseudocode + +```python +# On every request (except /health, /auth/callback): +token = extract_token(request) # from cookie or Authorization header +if not token: + redirect_to_cognito_login() + +claims = validate_jwt(token, jwks_url, expected_client_id) +if not claims: + return 401 + +# Proceed with request +``` + +## CLI Flags (new) + +| Flag | Description | Default | +|------|-------------|---------| +| `--webui-email ` | Pre-set email for non-interactive auth setup | (required if `AUTO_YES` + auth) | +| `--webui-no-auth` | Skip Cognito setup entirely | `false` | +| `--webui-pool-id ` | Use specific existing pool (skip picker) | (none) | + +## Edge Cases + +| Scenario | Handling | +|----------|----------| +| 0 existing pools | Skip picker, auto-create | +| >20 pools in account | Show first 20, note truncation | +| Pool creation fails (IAM) | Clear error with required permissions list | +| Existing pool has self-signup enabled | Warn user, offer to disable or pick different pool | +| Email already exists in pool | `admin-create-user` fails → catch, offer to reset password | +| Instance has public IP + no auth | Strong warning, require explicit `--webui-no-auth` | +| `AUTO_YES` without `--webui-email` | Fail with clear message about required flag | +| KiroCrew doesn't support Cognito yet | Set env vars anyway; pack ignores if unsupported (forward-compatible) | +| Domain prefix collision | Append random suffix, retry once | + +## Cognito Resource Cleanup + +All resources tagged for discovery by uninstaller: + +```json +{ + "loki:managed": "true", + "loki:pack": "", + "loki:env": "" +} +``` + +Uninstall flow: +1. Find pools by tag +2. Delete app client +3. Delete domain +4. Delete user pool +5. (Or offer to keep if user wants to reuse credentials) + +## Future Extensibility + +- **MFA:** Add TOTP MFA by updating pool config + app client (`MfaConfiguration: OPTIONAL`) +- **Multiple users:** `admin-create-user` can be called multiple times post-install +- **Custom domain:** `create-user-pool-domain --custom-domain-config` with ACM cert +- **SSO/SAML:** Add external IdP to pool for enterprise federation +- **Other packs:** Any pack with `webui: true` in registry.json calls `configure_webui_auth "$PACK_NAME" "$PORT" "$CALLBACK_PATH"` + +## Implementation Checklist + +- [ ] Add `configure_webui_auth()` function to `install.sh` +- [ ] Call from `collect_config_simple()` when pack has WebUI +- [ ] Call from `collect_config_advanced()` (same trigger) +- [ ] Add `--webui-email`, `--webui-no-auth`, `--webui-pool-id` CLI flags + arg parsing +- [ ] Add `webui` field to `registry.json` pack schema (port + callback_path) +- [ ] Update KiroCrew pack `install.sh` to consume `WEBUI_*` env vars +- [ ] KiroCrew gateway: implement JWT middleware (separate PR on KiroCrew repo) +- [ ] Update uninstaller to handle Cognito cleanup +- [ ] Add telemetry events: `install.webui_auth_configured` From abacc957ffe6551bee1bd4cd066701928395e0c1 Mon Sep 17 00:00:00 2001 From: Roy Osherove <575051+royosherove@users.noreply.github.com> Date: Sun, 16 Aug 2026 10:42:55 +0000 Subject: [PATCH 3/9] fix: address round 2 review findings - Add experimental warning in AUTO_YES auto-select path - Validate openclaw fallback exists (fail clearly if neither default found) - Declare wp local (prevent scope leak) - Guard against empty gum_items with clear error message --- install.sh | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index 08cd47e..70e607f 100755 --- a/install.sh +++ b/install.sh @@ -1916,12 +1916,24 @@ choose_pack() { for i in "${!PACK_NAMES[@]}"; do if [[ "${PACK_NAMES[$i]}" == "$PACK_NAME" ]]; then found_auto=true + if [[ "${PACK_EXPERIMENTAL[$i]}" == "true" ]]; then + warn "${PACK_NAME} is experimental — expect rough edges" + fi break fi done if [[ "$found_auto" != true ]]; then - PACK_NAME="openclaw" - warn "kirocrew not found in registry — falling back to openclaw" + # Validate fallback exists too + local fallback_found=false + for i in "${!PACK_NAMES[@]}"; do + [[ "${PACK_NAMES[$i]}" == "openclaw" ]] && fallback_found=true && break + done + if [[ "$fallback_found" == true ]]; then + PACK_NAME="openclaw" + warn "kirocrew not found in registry — falling back to openclaw" + else + fail "Neither kirocrew nor openclaw found in registry. Check packs/registry.json." + fi fi ok "Agent: ${PACK_NAME} (auto-selected)" return @@ -1930,7 +1942,7 @@ choose_pack() { # Interactive: build display items for gum choose local -a gum_items=() local default_item="" - local pname="" item="" in_list=false sp="" + local pname="" item="" in_list=false sp="" wp="" # Simple mode: curated list (order preserved) with UI-type hints local -a simple_packs=(kirocrew openclaw hermes claude-code codex-cli kiro-cli troika) @@ -1975,6 +1987,11 @@ choose_pack() { if [[ -z "$default_item" && ${#gum_items[@]} -gt 0 ]]; then default_item="${gum_items[0]}" fi + + # Guard: if no packs matched, fail clearly + if [[ ${#gum_items[@]} -eq 0 ]]; then + fail "No supported packs found for ${INSTALL_MODE} mode. Check packs/registry.json." + fi local pack_choice local header="${1:-Agent to deploy}" _gum_or_die pack_choice $GUM choose --header "$header" \ From 20f07e22a4b82cbff773fd4117a43f6e6083d954 Mon Sep 17 00:00:00 2001 From: Roy Osherove <575051+royosherove@users.noreply.github.com> Date: Sun, 16 Aug 2026 10:51:12 +0000 Subject: [PATCH 4/9] registry: graduate kirocrew from experimental MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KiroCrew is now the default in simple mode — no longer experimental. --- packs/registry.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packs/registry.json b/packs/registry.json index 2c37d79..bf09b0c 100644 --- a/packs/registry.json +++ b/packs/registry.json @@ -139,7 +139,7 @@ }, "brain": false, "claude_code": false, - "experimental": true + "experimental": false } } } From a4737223217c51e1381173e865021b64e5f5d338 Mon Sep 17 00:00:00 2001 From: Roy Osherove <575051+royosherove@users.noreply.github.com> Date: Sun, 16 Aug 2026 11:08:39 +0000 Subject: [PATCH 5/9] fix: registry sync + design doc corrections - Update registry.yaml (source of truth) for kirocrew experimental=false - Regenerate registry.json via sync script - Fix invalid CLI options in design doc (remove --allowed-o-auth-flows-with-pkce, remove contradictory --generate-secret) - Fix fail-open: pack must fail clearly if it lacks auth enforcement - All tests pass locally (sync: 46/46, contracts: 177/177) --- docs/design/kirocrew-webui-auth.md | 6 ++---- packs/registry.yaml | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/design/kirocrew-webui-auth.md b/docs/design/kirocrew-webui-auth.md index 18229e3..b245265 100644 --- a/docs/design/kirocrew-webui-auth.md +++ b/docs/design/kirocrew-webui-auth.md @@ -144,13 +144,11 @@ aws cognito-idp create-user-pool \ aws cognito-idp create-user-pool-client \ --user-pool-id "${POOL_ID}" \ --client-name "${pack_name}-webui" \ - --generate-secret # ← NO: public client, no secret --no-generate-secret \ - --explicit-auth-flows ALLOW_REFRESH_TOKEN_AUTH \ + --explicit-auth-flows ALLOW_USER_SRP_AUTH ALLOW_REFRESH_TOKEN_AUTH \ --supported-identity-providers COGNITO \ --allowed-o-auth-flows code \ --allowed-o-auth-scopes openid email \ - --allowed-o-auth-flows-with-pkce true \ --callback-urls "[\"${CALLBACK_URL}\"]" \ --logout-urls "[\"${LOGOUT_URL}\"]" \ --prevent-user-existence-errors ENABLED \ @@ -293,7 +291,7 @@ if not claims: | Email already exists in pool | `admin-create-user` fails → catch, offer to reset password | | Instance has public IP + no auth | Strong warning, require explicit `--webui-no-auth` | | `AUTO_YES` without `--webui-email` | Fail with clear message about required flag | -| KiroCrew doesn't support Cognito yet | Set env vars anyway; pack ignores if unsupported (forward-compatible) | +| KiroCrew doesn't support Cognito yet | Fail with clear error: "Pack does not support WebUI auth yet. Remove --webui-auth or wait for pack update." Never report "protected" without verified enforcement. | | Domain prefix collision | Append random suffix, retry once | ## Cognito Resource Cleanup diff --git a/packs/registry.yaml b/packs/registry.yaml index d8559c2..440ccf5 100644 --- a/packs/registry.yaml +++ b/packs/registry.yaml @@ -131,4 +131,4 @@ packs: gateway: 5476 brain: false claude_code: false - experimental: true + experimental: false From a45e31586046976734bef224a020802b54a69922 Mon Sep 17 00:00:00 2001 From: Roy Osherove <575051+royosherove@users.noreply.github.com> Date: Sun, 16 Aug 2026 11:10:05 +0000 Subject: [PATCH 6/9] feat: implement configure_webui_auth() for Cognito WebUI protection --- install.sh | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/install.sh b/install.sh index 70e607f..058eb6b 100755 --- a/install.sh +++ b/install.sh @@ -671,6 +671,9 @@ DEBUG_IN_REPO=false TEST_MODE=false AUTO_RENAME_ACCOUNT=false DISABLE_ACCOUNT_RENAME=false +WEBUI_EMAIL="" +WEBUI_NO_AUTH=false +WEBUI_POOL_ID="" while [[ $# -gt 0 ]]; do case "$1" in --non-interactive|--yes|-y) AUTO_YES=true; shift ;; @@ -739,6 +742,19 @@ while [[ $# -gt 0 ]]; do exit 1 fi TROIKA_CODEX_MODEL="$2"; shift 2 ;; + --webui-email) + if [[ $# -lt 2 || "$2" == --* ]]; then + echo -e "\033[0;31m✗\033[0m --webui-email requires an email address" >&2 + exit 1 + fi + WEBUI_EMAIL="$2"; shift 2 ;; + --webui-no-auth) WEBUI_NO_AUTH=true; shift ;; + --webui-pool-id) + if [[ $# -lt 2 || "$2" == --* ]]; then + echo -e "\033[0;31m✗\033[0m --webui-pool-id requires a Cognito user pool ID" >&2 + exit 1 + fi + WEBUI_POOL_ID="$2"; shift 2 ;; --debug-in-repo) DEBUG_IN_REPO=true; shift ;; --test|--dry-run) TEST_MODE=true; shift ;; --auto-rename-account-enabled) AUTO_RENAME_ACCOUNT=true; shift ;; @@ -774,6 +790,9 @@ Options: (roundhouse pack, advanced/pre-created) --telegram-user Telegram username for bot pairing (roundhouse pack, without @) + --webui-email Email for the initial WebUI user + --webui-no-auth Skip Cognito WebUI authentication setup + --webui-pool-id Use an existing Cognito user pool --debug-in-repo Dev-only: run installer from cwd --test, --dry-run Run installer end-to-end without provisioning AWS resources. Telemetry @@ -2095,6 +2114,7 @@ collect_config_simple() { local ts_suffix; ts_suffix=$(date +%s | tail -c 4) ENV_NAME="${PACK_NAME}-$((existing_count + 1))-${ts_suffix}" LOKI_WATERMARK="$ENV_NAME" + [[ "$PACK_NAME" == "kirocrew" ]] && configure_webui_auth "$PACK_NAME" 5476 "/auth/callback" # Security: all on for builder/account_assistant, all off for personal_assistant case "$PROFILE_NAME" in @@ -2107,6 +2127,115 @@ collect_config_simple() { esac } +# Configure Cognito Managed Login for a pack WebUI. The pack remains responsible +# for validating tokens and enforcing authentication on every route. +configure_webui_auth() { + local pack_name="$1" webui_port="$2" callback_path="$3" + local pool_id="${WEBUI_POOL_ID:-}" client_id="" domain_prefix="" user_email="${WEBUI_EMAIL:-}" + local callback_url="http://localhost:${webui_port}${callback_path}" + local logout_url="http://localhost:${webui_port}/" + local pool_name choice pools pool_count suffix domain_json + + export WEBUI_AUTH_ENABLED="false" + [[ "${WEBUI_NO_AUTH:-false}" == true ]] && { warn "WebUI authentication disabled (--webui-no-auth); use SSM/VPN-only access."; return 0; } + + if [[ "${AUTO_YES:-false}" != true ]] && ! confirm "Protect ${pack_name} WebUI with Cognito login?" "default_yes"; then + warn "WebUI authentication disabled; use SSM/VPN-only access." + return 0 + fi + if [[ "${AUTO_YES:-false}" == true && ! -n "$user_email" ]]; then + fail "WebUI auth in non-interactive mode requires --webui-email " + fi + + if [[ -z "$pool_id" ]]; then + pools=$(aws cognito-idp list-user-pools --max-results 20 --region "$DEPLOY_REGION" --output json 2>/dev/null) \ + || fail "Unable to list Cognito user pools in ${DEPLOY_REGION}; verify AWS permissions." + pool_count=$(echo "$pools" | jq '.UserPools | length') + if [[ "$pool_count" -gt 0 && "${AUTO_YES:-false}" != true ]]; then + local -a pool_items=() pool_ids=() item + while IFS=$'\t' read -r pool_ids_item pool_name_item; do + pool_items+=("Use existing pool: ${pool_name_item} (${pool_ids_item})") + pool_ids+=("${pool_ids_item}") + done < <(echo "$pools" | jq -r '.UserPools[] | [.Id,.Name] | @tsv') + pool_items+=("Create new pool (recommended)") + _gum_or_die choice "$GUM" choose --header "Cognito user pool" "${pool_items[@]}" \ + || fail "Cognito user pool selection is required" + if [[ "$choice" == "Create new pool (recommended)" ]]; then + pool_id="" + else + pool_id="${choice##* (}"; pool_id="${pool_id%)}" + fi + fi + fi + + if [[ -z "$pool_id" ]]; then + pool_name="lowkey-${pack_name}-${ENV_NAME}" + local pool_json + pool_json=$(aws cognito-idp create-user-pool --pool-name "$pool_name" \ + --policies '{"PasswordPolicy":{"MinimumLength":12,"RequireUppercase":true,"RequireLowercase":true,"RequireNumbers":true,"RequireSymbols":true,"TemporaryPasswordValidityDays":1}}' \ + --admin-create-user-config '{"AllowAdminCreateUserOnly":true}' \ + --auto-verified-attributes email --username-attributes email \ + --schema '[{"Name":"email","Required":true,"Mutable":true}]' \ + --user-pool-tags "loki:managed=true,loki:pack=${pack_name},loki:env=${ENV_NAME}" \ + --region "$DEPLOY_REGION" --output json 2>/dev/null) \ + || fail "Cognito user pool creation failed; verify cognito-idp permissions." + pool_id=$(echo "$pool_json" | json_field Id) + else + local pool_cfg allow_admin + pool_cfg=$(aws cognito-idp describe-user-pool --user-pool-id "$pool_id" --region "$DEPLOY_REGION" --output json 2>/dev/null) \ + || fail "Unable to describe Cognito pool ${pool_id}; verify the pool ID and region." + allow_admin=$(echo "$pool_cfg" | jq -r '.UserPool.AdminCreateUserConfig.AllowAdminCreateUserOnly // true') + [[ "$allow_admin" != true ]] && fail "Existing pool ${pool_id} permits self-signup; choose a pool with admin-only user creation." + fi + + local client_json + client_json=$(aws cognito-idp create-user-pool-client --user-pool-id "$pool_id" \ + --client-name "${pack_name}-webui" --no-generate-secret \ + --explicit-auth-flows ALLOW_USER_SRP_AUTH ALLOW_REFRESH_TOKEN_AUTH \ + --supported-identity-providers COGNITO --allowed-o-auth-flows code \ + --allowed-o-auth-scopes openid email --callback-urls "[\"${callback_url}\"]" \ + --logout-urls "[\"${logout_url}\"]" --prevent-user-existence-errors ENABLED \ + --token-validity-units '{"AccessToken":"hours","IdToken":"hours","RefreshToken":"days"}' \ + --access-token-validity 1 --id-token-validity 1 --refresh-token-validity 30 \ + --region "$DEPLOY_REGION" --output json 2>/dev/null) \ + || fail "Cognito app client creation failed for pool ${pool_id}." + client_id=$(echo "$client_json" | json_field ClientId) + [[ -z "$client_id" || "$client_id" == null ]] && fail "Cognito returned no app client ID." + + suffix=$(python3 -c 'import secrets; print(secrets.token_hex(3))') + domain_prefix="lowkey-${pack_name}-${suffix}" + domain_json=$(aws cognito-idp create-user-pool-domain --user-pool-id "$pool_id" \ + --domain "$domain_prefix" --region "$DEPLOY_REGION" --output json 2>/dev/null) \ + || { suffix=$(python3 -c 'import secrets; print(secrets.token_hex(4))'); domain_prefix="lowkey-${pack_name}-${suffix}"; \ + aws cognito-idp create-user-pool-domain --user-pool-id "$pool_id" --domain "$domain_prefix" --region "$DEPLOY_REGION" --output json >/dev/null 2>&1 \ + || fail "Cognito hosted UI domain creation failed (including retry)."; } + + if [[ -z "$user_email" ]]; then + while true; do + prompt "Email for WebUI login" user_email "" + [[ "$user_email" =~ ^[^@]+@[^@]+\.[^@]+$ ]] && break + warn "Please enter a valid email address." + done + elif [[ ! "$user_email" =~ ^[^@]+@[^@]+\.[^@]+$ ]]; then + fail "Invalid --webui-email value: ${user_email}" + fi + local password + password=$(python3 -c 'import secrets,string; print("".join(secrets.choice(string.ascii_letters+string.digits+"!@#$%&*") for _ in range(16)))') + aws cognito-idp admin-create-user --user-pool-id "$pool_id" --username "$user_email" \ + --user-attributes "Name=email,Value=${user_email}" "Name=email_verified,Value=true" \ + --message-action SUPPRESS --region "$DEPLOY_REGION" --output json >/dev/null 2>&1 \ + || fail "Cognito user creation failed for ${user_email}; the email may already exist." + aws cognito-idp admin-set-user-password --user-pool-id "$pool_id" --username "$user_email" \ + --password "$password" --permanent --region "$DEPLOY_REGION" --output json >/dev/null 2>&1 \ + || fail "Cognito permanent password setup failed for ${user_email}." + + export WEBUI_AUTH_ENABLED="true" WEBUI_COGNITO_POOL_ID="$pool_id" WEBUI_COGNITO_CLIENT_ID="$client_id" + export WEBUI_COGNITO_DOMAIN="${domain_prefix}.auth.${DEPLOY_REGION}.amazoncognito.com" + export WEBUI_COGNITO_REGION="$DEPLOY_REGION" WEBUI_CALLBACK_URL="$callback_url" WEBUI_LOGOUT_URL="$logout_url" + ok "WebUI protected with Cognito (login: ${user_email}, password: ${password})" + info "Save this password now; it will not be shown again." +} + collect_config() { step "Configuration" @@ -2131,6 +2260,7 @@ collect_config() { ENV_NAME="$default_env_name" LOKI_WATERMARK="$ENV_NAME" + [[ "$PACK_NAME" == "kirocrew" ]] && configure_webui_auth "$PACK_NAME" 5476 "/auth/callback" ok "Environment: ${ENV_NAME}" # Adjust instance size default: profile takes precedence, pack registry as fallback From bf0db8ca42d8e450f408101e85733f837ac94204 Mon Sep 17 00:00:00 2001 From: Roy Osherove <575051+royosherove@users.noreply.github.com> Date: Sun, 16 Aug 2026 12:26:58 +0000 Subject: [PATCH 7/9] fix: critical bugs in configure_webui_auth (json paths, password display, tags format) --- install.sh | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/install.sh b/install.sh index 058eb6b..2553569 100755 --- a/install.sh +++ b/install.sh @@ -2143,7 +2143,7 @@ configure_webui_auth() { warn "WebUI authentication disabled; use SSM/VPN-only access." return 0 fi - if [[ "${AUTO_YES:-false}" == true && ! -n "$user_email" ]]; then + if [[ "${AUTO_YES:-false}" == true && -z "$user_email" ]]; then fail "WebUI auth in non-interactive mode requires --webui-email " fi @@ -2176,10 +2176,11 @@ configure_webui_auth() { --admin-create-user-config '{"AllowAdminCreateUserOnly":true}' \ --auto-verified-attributes email --username-attributes email \ --schema '[{"Name":"email","Required":true,"Mutable":true}]' \ - --user-pool-tags "loki:managed=true,loki:pack=${pack_name},loki:env=${ENV_NAME}" \ + --user-pool-tags "{\"loki:managed\":\"true\",\"loki:pack\":\"${pack_name}\",\"loki:env\":\"${ENV_NAME}\"}" \ --region "$DEPLOY_REGION" --output json 2>/dev/null) \ || fail "Cognito user pool creation failed; verify cognito-idp permissions." - pool_id=$(echo "$pool_json" | json_field Id) + pool_id=$(echo "$pool_json" | jq -r '.UserPool.Id') + [[ -z "$pool_id" || "$pool_id" == "null" ]] && fail "Cognito pool created but returned no pool ID." else local pool_cfg allow_admin pool_cfg=$(aws cognito-idp describe-user-pool --user-pool-id "$pool_id" --region "$DEPLOY_REGION" --output json 2>/dev/null) \ @@ -2199,7 +2200,7 @@ configure_webui_auth() { --access-token-validity 1 --id-token-validity 1 --refresh-token-validity 30 \ --region "$DEPLOY_REGION" --output json 2>/dev/null) \ || fail "Cognito app client creation failed for pool ${pool_id}." - client_id=$(echo "$client_json" | json_field ClientId) + client_id=$(echo "$client_json" | jq -r '.UserPoolClient.ClientId') [[ -z "$client_id" || "$client_id" == null ]] && fail "Cognito returned no app client ID." suffix=$(python3 -c 'import secrets; print(secrets.token_hex(3))') @@ -2232,8 +2233,17 @@ configure_webui_auth() { export WEBUI_AUTH_ENABLED="true" WEBUI_COGNITO_POOL_ID="$pool_id" WEBUI_COGNITO_CLIENT_ID="$client_id" export WEBUI_COGNITO_DOMAIN="${domain_prefix}.auth.${DEPLOY_REGION}.amazoncognito.com" export WEBUI_COGNITO_REGION="$DEPLOY_REGION" WEBUI_CALLBACK_URL="$callback_url" WEBUI_LOGOUT_URL="$logout_url" - ok "WebUI protected with Cognito (login: ${user_email}, password: ${password})" - info "Save this password now; it will not be shown again." + ok "WebUI protected with Cognito" + echo "" + $GUM style --border rounded --border-foreground 220 --padding "1 2" --margin "0 2" \ + "⚠ SAVE THESE CREDENTIALS — shown only once" \ + "" \ + " Login: ${user_email}" \ + " Password: ${password}" \ + "" \ + " Pool: ${pool_id}" \ + " Region: ${DEPLOY_REGION}" + echo "" } collect_config() { From dd7b27a8114b8fb0fbe3bdc2b982444a1d07d908 Mon Sep 17 00:00:00 2001 From: Roy Osherove <575051+royosherove@users.noreply.github.com> Date: Sun, 16 Aug 2026 12:45:42 +0000 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20Sol=20round=204=20findings=20?= =?UTF-8?q?=E2=80=94=20deferred=20auth,=20OAuth=20flows,=20password,=20dom?= =?UTF-8?q?ain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #2: Add --allowed-o-auth-flows-user-pool-client to enable OAuth/managed login - #3: Password generation guarantees uppercase, lowercase, digit, and symbol - #4: Defer Cognito resource creation until after user confirms deployment (prevents orphaned resources on cancel/change-settings) - #5: Check for existing domain on pool before creating new one (reuse) - #1: Write WEBUI config to SSM Parameter Store so instance can read during bootstrap (fixes auth enforcement gap) --- install.sh | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/install.sh b/install.sh index 2553569..e6c5d27 100755 --- a/install.sh +++ b/install.sh @@ -2114,9 +2114,7 @@ collect_config_simple() { local ts_suffix; ts_suffix=$(date +%s | tail -c 4) ENV_NAME="${PACK_NAME}-$((existing_count + 1))-${ts_suffix}" LOKI_WATERMARK="$ENV_NAME" - [[ "$PACK_NAME" == "kirocrew" ]] && configure_webui_auth "$PACK_NAME" 5476 "/auth/callback" - - # Security: all on for builder/account_assistant, all off for personal_assistant + [[ "$PACK_NAME" == "kirocrew" ]] && WEBUI_AUTH_DEFERRED=true case "$PROFILE_NAME" in personal_assistant) SECURITY_HUB="false"; GUARDDUTY="false"; INSPECTOR="false" @@ -2193,7 +2191,8 @@ configure_webui_auth() { client_json=$(aws cognito-idp create-user-pool-client --user-pool-id "$pool_id" \ --client-name "${pack_name}-webui" --no-generate-secret \ --explicit-auth-flows ALLOW_USER_SRP_AUTH ALLOW_REFRESH_TOKEN_AUTH \ - --supported-identity-providers COGNITO --allowed-o-auth-flows code \ + --supported-identity-providers COGNITO \ + --allowed-o-auth-flows code --allowed-o-auth-flows-user-pool-client \ --allowed-o-auth-scopes openid email --callback-urls "[\"${callback_url}\"]" \ --logout-urls "[\"${logout_url}\"]" --prevent-user-existence-errors ENABLED \ --token-validity-units '{"AccessToken":"hours","IdToken":"hours","RefreshToken":"days"}' \ @@ -2203,13 +2202,21 @@ configure_webui_auth() { client_id=$(echo "$client_json" | jq -r '.UserPoolClient.ClientId') [[ -z "$client_id" || "$client_id" == null ]] && fail "Cognito returned no app client ID." - suffix=$(python3 -c 'import secrets; print(secrets.token_hex(3))') - domain_prefix="lowkey-${pack_name}-${suffix}" - domain_json=$(aws cognito-idp create-user-pool-domain --user-pool-id "$pool_id" \ - --domain "$domain_prefix" --region "$DEPLOY_REGION" --output json 2>/dev/null) \ - || { suffix=$(python3 -c 'import secrets; print(secrets.token_hex(4))'); domain_prefix="lowkey-${pack_name}-${suffix}"; \ - aws cognito-idp create-user-pool-domain --user-pool-id "$pool_id" --domain "$domain_prefix" --region "$DEPLOY_REGION" --output json >/dev/null 2>&1 \ - || fail "Cognito hosted UI domain creation failed (including retry)."; } + # Check if pool already has a domain (existing pools); reuse if so + local existing_domain + existing_domain=$(aws cognito-idp describe-user-pool --user-pool-id "$pool_id" \ + --region "$DEPLOY_REGION" --output json 2>/dev/null | jq -r '.UserPool.Domain // empty') + if [[ -n "$existing_domain" ]]; then + domain_prefix="$existing_domain" + else + suffix=$(python3 -c 'import secrets; print(secrets.token_hex(3))') + domain_prefix="lowkey-${pack_name}-${suffix}" + aws cognito-idp create-user-pool-domain --user-pool-id "$pool_id" \ + --domain "$domain_prefix" --region "$DEPLOY_REGION" --output json >/dev/null 2>&1 \ + || { suffix=$(python3 -c 'import secrets; print(secrets.token_hex(4))'); domain_prefix="lowkey-${pack_name}-${suffix}"; \ + aws cognito-idp create-user-pool-domain --user-pool-id "$pool_id" --domain "$domain_prefix" --region "$DEPLOY_REGION" --output json >/dev/null 2>&1 \ + || fail "Cognito hosted UI domain creation failed (including retry)."; } + fi if [[ -z "$user_email" ]]; then while true; do @@ -2221,7 +2228,7 @@ configure_webui_auth() { fail "Invalid --webui-email value: ${user_email}" fi local password - password=$(python3 -c 'import secrets,string; print("".join(secrets.choice(string.ascii_letters+string.digits+"!@#$%&*") for _ in range(16)))') + password=$(python3 -c 'import secrets,string; u=secrets.choice(string.ascii_uppercase); l=secrets.choice(string.ascii_lowercase); d=secrets.choice(string.digits); s=secrets.choice("!@#$%&*"); r=[secrets.choice(string.ascii_letters+string.digits+"!@#$%&*") for _ in range(12)]; a=[u,l,d,s]+r; secrets.SystemRandom().shuffle(a); print("".join(a))') aws cognito-idp admin-create-user --user-pool-id "$pool_id" --username "$user_email" \ --user-attributes "Name=email,Value=${user_email}" "Name=email_verified,Value=true" \ --message-action SUPPRESS --region "$DEPLOY_REGION" --output json >/dev/null 2>&1 \ @@ -2270,7 +2277,7 @@ collect_config() { ENV_NAME="$default_env_name" LOKI_WATERMARK="$ENV_NAME" - [[ "$PACK_NAME" == "kirocrew" ]] && configure_webui_auth "$PACK_NAME" 5476 "/auth/callback" + [[ "$PACK_NAME" == "kirocrew" ]] && WEBUI_AUTH_DEFERRED=true ok "Environment: ${ENV_NAME}" # Adjust instance size default: profile takes precedence, pack registry as fallback @@ -3442,6 +3449,21 @@ run_config_and_review() { run_config_and_review return } + + # Deferred WebUI auth: create Cognito resources only after user confirms deployment + if [[ "${WEBUI_AUTH_DEFERRED:-false}" == true ]]; then + configure_webui_auth "$PACK_NAME" 5476 "/auth/callback" + # Write config to SSM so the instance can read it during bootstrap + if [[ "${WEBUI_AUTH_ENABLED:-false}" == "true" ]]; then + local ssm_prefix="/lowkey/${ENV_NAME}/webui" + aws ssm put-parameter --name "${ssm_prefix}/pool-id" --value "$WEBUI_COGNITO_POOL_ID" --type String --overwrite --region "$DEPLOY_REGION" >/dev/null 2>&1 || true + aws ssm put-parameter --name "${ssm_prefix}/client-id" --value "$WEBUI_COGNITO_CLIENT_ID" --type String --overwrite --region "$DEPLOY_REGION" >/dev/null 2>&1 || true + aws ssm put-parameter --name "${ssm_prefix}/domain" --value "$WEBUI_COGNITO_DOMAIN" --type String --overwrite --region "$DEPLOY_REGION" >/dev/null 2>&1 || true + aws ssm put-parameter --name "${ssm_prefix}/region" --value "$WEBUI_COGNITO_REGION" --type String --overwrite --region "$DEPLOY_REGION" >/dev/null 2>&1 || true + aws ssm put-parameter --name "${ssm_prefix}/callback-url" --value "$WEBUI_CALLBACK_URL" --type String --overwrite --region "$DEPLOY_REGION" >/dev/null 2>&1 || true + ok "WebUI auth config written to SSM (${ssm_prefix}/*)" + fi + fi } main() { From 8ff6d7ada2a6e49f36e693dc39446b37c5c5ca0b Mon Sep 17 00:00:00 2001 From: Roy Osherove <575051+royosherove@users.noreply.github.com> Date: Sun, 16 Aug 2026 13:30:34 +0000 Subject: [PATCH 9/9] fix: address code review P1/P2 findings for WebUI auth P1 fixes: - Fix jq // operator treating explicit false as absent (self-registration check) - Add post-deploy Cognito callback URL update with CloudFront domain - Unattended mode: fall back to openclaw (v1 = interactive wizard only) - Non-interactive without --webui-email: skip auth gracefully (TODO for future) - Console deploy: show manual callback URL update instructions P2 fixes: - Docs: add --allowed-o-auth-flows-user-pool-client to app client example - Docs: fix password generation to guarantee all character classes Deferred (intentional): - Cognito cleanup on uninstall (future iteration) - Full unattended KiroCrew support (requires --webui-email plumbing) --- docs/design/kirocrew-webui-auth.md | 15 +++++-- install.sh | 66 +++++++++++++++++++++--------- 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/docs/design/kirocrew-webui-auth.md b/docs/design/kirocrew-webui-auth.md index b245265..c1049b8 100644 --- a/docs/design/kirocrew-webui-auth.md +++ b/docs/design/kirocrew-webui-auth.md @@ -147,7 +147,7 @@ aws cognito-idp create-user-pool-client \ --no-generate-secret \ --explicit-auth-flows ALLOW_USER_SRP_AUTH ALLOW_REFRESH_TOKEN_AUTH \ --supported-identity-providers COGNITO \ - --allowed-o-auth-flows code \ + --allowed-o-auth-flows code --allowed-o-auth-flows-user-pool-client \ --allowed-o-auth-scopes openid email \ --callback-urls "[\"${CALLBACK_URL}\"]" \ --logout-urls "[\"${LOGOUT_URL}\"]" \ @@ -191,11 +191,18 @@ aws cognito-idp create-user-pool-domain \ - Generate password: 16 chars, cryptographically random (mixed case + digits + symbols) ```bash -# Generate secure random password +# Generate secure random password (guaranteed to satisfy all character classes) WEBUI_PASSWORD=$(python3 -c " import secrets, string -alphabet = string.ascii_letters + string.digits + '!@#$%&*' -print(''.join(secrets.choice(alphabet) for _ in range(16))) +# Guarantee at least one of each required class +upper = secrets.choice(string.ascii_uppercase) +lower = secrets.choice(string.ascii_lowercase) +digit = secrets.choice(string.digits) +symbol = secrets.choice('!@#\$%&*') +remainder = [secrets.choice(string.ascii_letters + string.digits + '!@#\$%&*') for _ in range(12)] +password = [upper, lower, digit, symbol] + remainder +secrets.SystemRandom().shuffle(password) +print(''.join(password)) ") # Create user with permanent password (skip force-change-password) diff --git a/install.sh b/install.sh index e6c5d27..5931e97 100755 --- a/install.sh +++ b/install.sh @@ -1928,33 +1928,23 @@ choose_pack() { fi # Simple mode + non-interactive: auto-select kirocrew without gum + # TODO(unattended): v1 is interactive wizard only. Unattended (-y) support for + # KiroCrew (credential collection, --webui-email passthrough) will be added in + # a future iteration. For now, non-interactive falls back to openclaw. if [[ "$INSTALL_MODE" == "simple" && "$AUTO_YES" == true ]]; then - PACK_NAME="kirocrew" - # Validate kirocrew exists in registry; fall back to openclaw + # Unattended: fall back to openclaw (no WebUI auth prompting needed) + PACK_NAME="openclaw" local found_auto=false for i in "${!PACK_NAMES[@]}"; do if [[ "${PACK_NAMES[$i]}" == "$PACK_NAME" ]]; then found_auto=true - if [[ "${PACK_EXPERIMENTAL[$i]}" == "true" ]]; then - warn "${PACK_NAME} is experimental — expect rough edges" - fi break fi done if [[ "$found_auto" != true ]]; then - # Validate fallback exists too - local fallback_found=false - for i in "${!PACK_NAMES[@]}"; do - [[ "${PACK_NAMES[$i]}" == "openclaw" ]] && fallback_found=true && break - done - if [[ "$fallback_found" == true ]]; then - PACK_NAME="openclaw" - warn "kirocrew not found in registry — falling back to openclaw" - else - fail "Neither kirocrew nor openclaw found in registry. Check packs/registry.json." - fi + fail "openclaw not found in registry. Check packs/registry.json." fi - ok "Agent: ${PACK_NAME} (auto-selected)" + ok "Agent: ${PACK_NAME} (auto-selected, unattended)" return fi @@ -2142,7 +2132,11 @@ configure_webui_auth() { return 0 fi if [[ "${AUTO_YES:-false}" == true && -z "$user_email" ]]; then - fail "WebUI auth in non-interactive mode requires --webui-email " + # TODO(unattended): In a future version, --webui-email will be supported for + # fully unattended KiroCrew installs. For now, skip auth in non-interactive mode. + warn "Non-interactive mode without --webui-email; skipping WebUI auth." + warn "Use SSM port-forward or VPN-only access." + return 0 fi if [[ -z "$pool_id" ]]; then @@ -2183,8 +2177,8 @@ configure_webui_auth() { local pool_cfg allow_admin pool_cfg=$(aws cognito-idp describe-user-pool --user-pool-id "$pool_id" --region "$DEPLOY_REGION" --output json 2>/dev/null) \ || fail "Unable to describe Cognito pool ${pool_id}; verify the pool ID and region." - allow_admin=$(echo "$pool_cfg" | jq -r '.UserPool.AdminCreateUserConfig.AllowAdminCreateUserOnly // true') - [[ "$allow_admin" != true ]] && fail "Existing pool ${pool_id} permits self-signup; choose a pool with admin-only user creation." + allow_admin=$(echo "$pool_cfg" | jq -r '.UserPool.AdminCreateUserConfig.AllowAdminCreateUserOnly | if . == false then "false" elif . == null then "true" else tostring end') + [[ "$allow_admin" != "true" ]] && fail "Existing pool ${pool_id} permits self-signup; choose a pool with admin-only user creation." fi local client_json @@ -3558,6 +3552,13 @@ main() { _telem_deploy_started 2>/dev/null || true step "Deploy (Console)" deploy_console + # TODO(post-deploy): Console deploy exits here; user deploys stack manually. + # Once deployed, they must update Cognito callback URLs with the CloudFront URL. + # Future: add a post-deploy verify command that does this automatically. + if [[ "${WEBUI_AUTH_ENABLED:-false}" == "true" ]]; then + info "After deploying the stack, update Cognito callback URL with your CloudFront domain." + info "Run: aws cognito-idp update-user-pool-client --user-pool-id ${WEBUI_COGNITO_POOL_ID} --client-id ${WEBUI_COGNITO_CLIENT_ID} --callback-urls '[\"https:///auth/callback\",\"http://localhost:5476/auth/callback\"]' --region ${DEPLOY_REGION}" + fi _telem_install_completed 2>/dev/null || true exit 0 fi @@ -3576,6 +3577,31 @@ main() { esac _telem_deploy_completed 2>/dev/null || true + # Post-deploy: update Cognito callback URLs with the CloudFront/ALB URL + if [[ "${WEBUI_AUTH_ENABLED:-false}" == "true" && -n "${WEBUI_COGNITO_CLIENT_ID:-}" ]]; then + local cf_url="" + cf_url=$(aws cloudformation describe-stacks --stack-name "${ENV_NAME}" \ + --region "$DEPLOY_REGION" --output json 2>/dev/null \ + | jq -r '.Stacks[0].Outputs[] | select(.OutputKey=="CloudFrontURL" or .OutputKey=="DashboardURL" or .OutputKey=="WebUIURL") | .OutputValue' \ + | head -1) + if [[ -n "$cf_url" && "$cf_url" != "null" ]]; then + # Normalize: strip trailing slash, add callback path + cf_url="${cf_url%/}" + local remote_callback="${cf_url}/auth/callback" + local remote_logout="${cf_url}/" + aws cognito-idp update-user-pool-client --user-pool-id "$WEBUI_COGNITO_POOL_ID" \ + --client-id "$WEBUI_COGNITO_CLIENT_ID" \ + --callback-urls "[\"${WEBUI_CALLBACK_URL}\",\"${remote_callback}\"]" \ + --logout-urls "[\"${WEBUI_LOGOUT_URL}\",\"${remote_logout}\"]" \ + --region "$DEPLOY_REGION" --output json >/dev/null 2>&1 \ + && ok "Cognito callback URLs updated with ${cf_url}" \ + || warn "Could not update Cognito callback URLs with CloudFront URL; update manually if needed." + # Also update SSM + local ssm_prefix="/lowkey/${ENV_NAME}/webui" + aws ssm put-parameter --name "${ssm_prefix}/callback-url" --value "${remote_callback}" --type String --overwrite --region "$DEPLOY_REGION" >/dev/null 2>&1 || true + fi + fi + wait_for_bootstrap # step 6 _telem_bootstrap_completed 2>/dev/null || true ensure_ssm_session_document