diff --git a/.gitignore b/.gitignore index 139bb76..4117529 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .idea log tmp -temp \ No newline at end of file +temp +*.log \ No newline at end of file diff --git a/eureka-cli/README.md b/eureka-cli/README.md index efd8a27..71885ec 100644 --- a/eureka-cli/README.md +++ b/eureka-cli/README.md @@ -825,7 +825,11 @@ The environment may fail to add Vault secrets during tenant entitlement. If a se - Add the missing `mod-users` secret ```bash -./add_missing_secret.sh -t diku -u mod-users +# Single user +./add_missing_secret.sh -t diku -u admin + +# Multiple users (space-delimited) +./add_missing_secret.sh -t diku -u "mod-users mod-roles-keycloak mod-inventory" ``` > This script upserts the secret to Vault and resets the associated Keycloak user password in the specified realm (it is idempotent) diff --git a/eureka-cli/add_missing_secret.sh b/eureka-cli/add_missing_secret.sh index 56f3689..3388c03 100644 --- a/eureka-cli/add_missing_secret.sh +++ b/eureka-cli/add_missing_secret.sh @@ -8,9 +8,9 @@ set -euo pipefail # Default values KEYCLOAK_URL="http://keycloak.eureka:8080" TENANT="" -USERNAME="" -VAULT_TOKEN=$(eureka-cli getVaultRootToken) +USERNAMES=() VAULT_URL="http://localhost:8200" +VAULT_TOKEN="" # Display usage usage() { @@ -22,12 +22,13 @@ Set Vault secret and reset Keycloak user password. OPTIONS: -h, --help Display this help message -t, --tenant TENANT Tenant name (e.g., diku) [required] - -u, --username USERNAME Username to reset [required] + -u, --username USERNAME(S) Username(s) to reset (space-delimited) [required] --keycloak-url URL Keycloak URL (default: http://keycloak.eureka:8080) --vault-url URL Vault URL (default: http://localhost:8200) -EXAMPLE: +EXAMPLES: $0 -t diku -u admin + $0 -t diku -u "mod-users mod-roles-keycloak mod-scheduler" EOF exit 1 } @@ -43,7 +44,8 @@ while [[ $# -gt 0 ]]; do shift 2 ;; -u|--username) - USERNAME="$2" + # Split space-delimited usernames into array + read -ra USERNAMES <<< "$2" shift 2 ;; --keycloak-url) @@ -67,40 +69,138 @@ if [[ -z "$TENANT" ]]; then usage fi -if [[ -z "$USERNAME" ]]; then - echo "Error: Username is required" +if [[ ${#USERNAMES[@]} -eq 0 ]]; then + echo "Error: At least one username is required" usage fi -# Generate secure 32-character alphanumeric password -echo -e "\nGenerating secure password..." -if command -v openssl &> /dev/null; then - NEW_PASSWORD=$(openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 32) -else - NEW_PASSWORD=$(cat /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 32) +# Check for required commands +for cmd in jq curl; do + if ! command -v $cmd &> /dev/null; then + echo "Error: Required command '$cmd' is not installed" + exit 1 + fi +done + +if ! command -v openssl &> /dev/null && [[ ! -r /dev/urandom ]]; then + echo "Error: Neither 'openssl' nor '/dev/urandom' is available for password generation" + exit 1 fi -echo -e "\nSetting password for user '$USERNAME' in tenant '$TENANT'" +# Get Vault token +echo "Getting Vault root token..." +VAULT_TOKEN=$(eureka-cli getVaultRootToken) + +if [[ -z "$VAULT_TOKEN" || "$VAULT_TOKEN" == "null" ]]; then + echo "Error: Failed to get Vault root token" + exit 1 +fi -# Set secret in Vault (upsert - creates or updates) -echo -e "\nSetting secret in Vault..." +echo -e "\nProcessing ${#USERNAMES[@]} user(s) for tenant '$TENANT'..." -# Read existing secrets from Vault +# Read existing secrets from Vault once echo -e "\nReading existing secrets from Vault..." VAULT_READ_RESPONSE=$(curl -s --header "X-Vault-Token: $VAULT_TOKEN" \ "$VAULT_URL/v1/secret/data/folio/$TENANT") echo -e "\nVault read response:\n\n$VAULT_READ_RESPONSE" EXISTING_SECRETS=$(echo "$VAULT_READ_RESPONSE" | jq -r '.data.data // {}') + +if [[ -z "$EXISTING_SECRETS" ]]; then + echo "Error: Failed to parse existing secrets from Vault" + exit 1 +fi + echo -e "\nExisting secrets:\n\n$EXISTING_SECRETS" -# Merge new secret with existing ones -MERGED_SECRETS=$(echo "$EXISTING_SECRETS" | jq --arg username "$USERNAME" --arg password "$NEW_PASSWORD" \ - '. + {($username): $password}') +# Get Keycloak access token once +echo -e "\nGetting Keycloak access token..." +ACCESS_TOKEN=$(eureka-cli getKeycloakAccessToken --tokenType master-admin-cli) + +if [[ -z "$ACCESS_TOKEN" || "$ACCESS_TOKEN" == "null" ]]; then + echo "Error: Failed to get Keycloak access token" + exit 1 +fi + +# Store results for summary +declare -A USER_PASSWORDS +FAILED_USERS=() +MERGED_SECRETS="$EXISTING_SECRETS" + +# Process each username +for USERNAME in "${USERNAMES[@]}"; do + echo "" + echo "==========================================" + echo "Processing user: $USERNAME" + echo "==========================================" + + # Generate secure 32-character alphanumeric password + echo -e "\nGenerating secure password..." + if command -v openssl &> /dev/null; then + NEW_PASSWORD=$(openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 32) + else + NEW_PASSWORD=$(cat /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 32) + fi + + # Validate password was generated + if [[ -z "$NEW_PASSWORD" || ${#NEW_PASSWORD} -lt 32 ]]; then + echo "Error: Failed to generate secure password for user '$USERNAME'" + exit 1 + fi + + # Merge new secret with existing ones + MERGED_SECRETS=$(echo "$MERGED_SECRETS" | jq --arg username "$USERNAME" --arg password "$NEW_PASSWORD" \ + '. + {($username): $password}') + + if [[ -z "$MERGED_SECRETS" || "$MERGED_SECRETS" == "null" ]]; then + echo "Error: Failed to merge secrets for user '$USERNAME'" + exit 1 + fi + + # Get user ID from Keycloak + echo -e "\nLooking up user in Keycloak realm '$TENANT'..." + USER_LOOKUP_RESPONSE=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$TENANT/users?username=$USERNAME&exact=true" \ + -H "Authorization: Bearer $ACCESS_TOKEN") + echo -e "\nUser lookup response:\n\n$USER_LOOKUP_RESPONSE" + + USER_ID=$(echo "$USER_LOOKUP_RESPONSE" | jq -r '.[0].id') + + if [[ "$USER_ID" == "null" || -z "$USER_ID" ]]; then + echo "⚠ Warning: User '$USERNAME' not found in realm '$TENANT' - skipping Keycloak password reset" + FAILED_USERS+=("$USERNAME (not found in Keycloak)") + USER_PASSWORDS[$USERNAME]=$NEW_PASSWORD + continue + fi + + echo -e "\n✓ Found user with ID: $USER_ID" + + # Reset user password in Keycloak + echo -e "\nResetting password in Keycloak..." + PASSWORD_RESET_RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \ + "$KEYCLOAK_URL/admin/realms/$TENANT/users/$USER_ID/reset-password" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"type\":\"password\",\"value\":\"$NEW_PASSWORD\",\"temporary\":false}") + + HTTP_CODE=$(echo "$PASSWORD_RESET_RESPONSE" | tail -n 1) + + if [[ "$HTTP_CODE" != "204" && "$HTTP_CODE" != "200" ]]; then + echo "⚠ Warning: Failed to reset password in Keycloak (HTTP $HTTP_CODE)" + FAILED_USERS+=("$USERNAME (Keycloak password reset failed)") + USER_PASSWORDS[$USERNAME]=$NEW_PASSWORD + continue + fi + + echo -e "\n✓ Password reset in Keycloak for user: $USERNAME" + USER_PASSWORDS[$USERNAME]=$NEW_PASSWORD +done + +# Write all merged secrets back to Vault in one call +echo -e "\n==========================================" +echo "Writing all secrets back to Vault..." +echo "==========================================" echo -e "\nMerged secrets:\n\n$MERGED_SECRETS" -# Write merged secrets back to Vault -echo -e "\nWriting secrets back to Vault..." VAULT_RESPONSE=$(curl -s -w "\n%{http_code}" --header "X-Vault-Token: $VAULT_TOKEN" \ --request POST \ --data "{\"data\": $MERGED_SECRETS}" \ @@ -112,62 +212,37 @@ echo -e "\nVault write response (HTTP $HTTP_CODE): $RESPONSE_BODY" if [[ "$HTTP_CODE" == "403" ]]; then echo "Error: Vault authentication failed - invalid or insufficient token permissions" - echo "Please provide a valid Vault token using: -v YOUR_VAULT_TOKEN" exit 1 elif [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "204" ]]; then - echo "Error: Failed to set Vault secret (HTTP $HTTP_CODE)" + echo "Error: Failed to set Vault secrets (HTTP $HTTP_CODE)" echo "$RESPONSE_BODY" exit 1 fi -echo -e "\n✓ Secret set in Vault at: secret/data/folio/$TENANT (key: $USERNAME)" +echo -e "\n✓ All secrets set in Vault at: secret/data/folio/$TENANT" -# Get Keycloak access token -echo "Getting Keycloak access token..." -ACCESS_TOKEN=$(eureka-cli getKeycloakAccessToken --tokenType master-admin-cli) - -if [[ -z "$ACCESS_TOKEN" || "$ACCESS_TOKEN" == "null" ]]; then - echo "Error: Failed to get Keycloak access token" - exit 1 -fi - -# Get user ID from Keycloak -echo -e "\nLooking up user in Keycloak realm '$TENANT'..." -USER_LOOKUP_RESPONSE=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$TENANT/users?username=$USERNAME&exact=true" \ - -H "Authorization: Bearer $ACCESS_TOKEN") -echo -e "\nUser lookup response:\n\n$USER_LOOKUP_RESPONSE" - -USER_ID=$(echo "$USER_LOOKUP_RESPONSE" | jq -r '.[0].id') - -if [[ "$USER_ID" == "null" || -z "$USER_ID" ]]; then - echo "Error: User '$USERNAME' not found in realm '$TENANT'" - exit 1 -fi - -echo -e "\n✓ Found user with ID: $USER_ID" - -# Reset user password in Keycloak -echo -e "\nResetting password in Keycloak..." -PASSWORD_RESET_RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \ - "$KEYCLOAK_URL/admin/realms/$TENANT/users/$USER_ID/reset-password" \ - -H "Authorization: Bearer $ACCESS_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"type\":\"password\",\"value\":\"$NEW_PASSWORD\",\"temporary\":false}") +# Print summary +echo "" +echo "==========================================" +echo "SUMMARY: Password Update Results" +echo "==========================================" +echo "Tenant: $TENANT" +echo "" +echo "Passwords generated and stored in Vault:" -HTTP_CODE=$(echo "$PASSWORD_RESET_RESPONSE" | tail -n 1) +# Sort usernames for consistent output +for USERNAME in $(echo "${!USER_PASSWORDS[@]}" | tr ' ' '\n' | sort); do + echo " - $USERNAME: ${USER_PASSWORDS[$USERNAME]}" +done -if [[ "$HTTP_CODE" != "204" && "$HTTP_CODE" != "200" ]]; then - echo "Error: Failed to reset password in Keycloak (HTTP $HTTP_CODE)" - echo "$RESPONSE_BODY" - exit 1 +if [[ ${#FAILED_USERS[@]} -gt 0 ]]; then + echo "" + echo "⚠ Warnings (passwords stored in Vault but Keycloak updates failed):" + for FAILED in "${FAILED_USERS[@]}"; do + echo " - $FAILED" + done + echo "" + echo "Note: These users' passwords were saved to Vault but require manual Keycloak update." fi -echo -e "\n✓ Password reset in Keycloak" -echo "" -echo "==========================================" -echo "SUCCESS: Password updated successfully" -echo "==========================================" -echo "Tenant: $TENANT" -echo "Username: $USERNAME" -echo "Password: $NEW_PASSWORD" echo "==========================================" \ No newline at end of file diff --git a/eureka-cli/config.combined-native.yaml b/eureka-cli/config.combined-native.yaml index 686d4eb..e77bca7 100644 --- a/eureka-cli/config.combined-native.yaml +++ b/eureka-cli/config.combined-native.yaml @@ -181,11 +181,14 @@ backend-modules: cpus: 4 memory: 900 mod-login-keycloak: + # Versions <3.1.0-SNAPSHOT.156 are unusable, do not use until addressed + version: 3.1.0-SNAPSHOT.156 use-vault: true port: 9905 environment: X_OKAPI_TOKEN_HEADER_ENABLED: "false" LOGIN_COOKIE_SAMESITE: "NONE" + KC_ADMIN_TOKEN_TTL: 895s KAFKA_PRODUCER_TENANT_COLLECTION: "true" mod-roles-keycloak: use-vault: true @@ -206,6 +209,7 @@ backend-modules: mod-permissions: mod-configuration: mod-users: + disable-system-user: true mod-roles: mod-password-validator: mod-settings: @@ -214,12 +218,15 @@ backend-modules: mod-inventory-storage: mod-inventory: private-port: 9403 + disable-system-user: true mod-organizations-storage: mod-organizations: mod-invoice-storage: mod-invoice: + disable-system-user: true mod-orders-storage: mod-orders: + disable-system-user: true mod-finance: mod-finance-storage: use-vault: true diff --git a/eureka-cli/config.combined.yaml b/eureka-cli/config.combined.yaml index 7e30d2c..af48e8e 100644 --- a/eureka-cli/config.combined.yaml +++ b/eureka-cli/config.combined.yaml @@ -177,11 +177,14 @@ backend-modules: cpus: 4 memory: 900 mod-login-keycloak: + # Versions <3.1.0-SNAPSHOT.156 are unusable, do not use until addressed + version: 3.1.0-SNAPSHOT.156 use-vault: true port: 9905 environment: X_OKAPI_TOKEN_HEADER_ENABLED: "false" LOGIN_COOKIE_SAMESITE: "NONE" + KC_ADMIN_TOKEN_TTL: 895s KAFKA_PRODUCER_TENANT_COLLECTION: "true" mod-roles-keycloak: use-vault: true @@ -202,6 +205,7 @@ backend-modules: mod-permissions: mod-configuration: mod-users: + disable-system-user: true mod-roles: mod-password-validator: mod-settings: @@ -210,12 +214,15 @@ backend-modules: mod-inventory-storage: mod-inventory: private-port: 9403 + disable-system-user: true mod-organizations-storage: mod-organizations: mod-invoice-storage: mod-invoice: + disable-system-user: true mod-orders-storage: mod-orders: + disable-system-user: true mod-finance: mod-finance-storage: use-vault: true diff --git a/eureka-cli/config.ecs-single.yaml b/eureka-cli/config.ecs-single.yaml index efe67fc..355304f 100644 --- a/eureka-cli/config.ecs-single.yaml +++ b/eureka-cli/config.ecs-single.yaml @@ -207,11 +207,14 @@ backend-modules: cpus: 4 memory: 900 mod-login-keycloak: + # Versions <3.1.0-SNAPSHOT.156 are unusable, do not use until addressed + version: 3.1.0-SNAPSHOT.156 use-vault: true port: 9905 environment: X_OKAPI_TOKEN_HEADER_ENABLED: "false" LOGIN_COOKIE_SAMESITE: "NONE" + KC_ADMIN_TOKEN_TTL: 895s KAFKA_PRODUCER_TENANT_COLLECTION: "true" mod-roles-keycloak: use-vault: true @@ -249,6 +252,7 @@ backend-modules: mod-permissions: mod-configuration: mod-users: + disable-system-user: true mod-roles: mod-password-validator: mod-settings: @@ -257,12 +261,15 @@ backend-modules: mod-inventory-storage: mod-inventory: private-port: 9403 + disable-system-user: true mod-organizations-storage: mod-organizations: mod-invoice-storage: mod-invoice: + disable-system-user: true mod-orders-storage: mod-orders: + disable-system-user: true mod-finance: mod-finance-storage: use-vault: true diff --git a/eureka-cli/config.ecs.yaml b/eureka-cli/config.ecs.yaml index 15434bb..cdbb8c7 100644 --- a/eureka-cli/config.ecs.yaml +++ b/eureka-cli/config.ecs.yaml @@ -246,11 +246,14 @@ backend-modules: cpus: 4 memory: 900 mod-login-keycloak: + # Versions <3.1.0-SNAPSHOT.156 are unusable, do not use until addressed + version: 3.1.0-SNAPSHOT.156 use-vault: true port: 9905 environment: X_OKAPI_TOKEN_HEADER_ENABLED: "false" LOGIN_COOKIE_SAMESITE: "NONE" + KC_ADMIN_TOKEN_TTL: 895s KAFKA_PRODUCER_TENANT_COLLECTION: "true" mod-roles-keycloak: use-vault: true @@ -288,6 +291,7 @@ backend-modules: mod-permissions: mod-configuration: mod-users: + disable-system-user: true mod-roles: mod-password-validator: mod-settings: @@ -296,12 +300,15 @@ backend-modules: mod-inventory-storage: mod-inventory: private-port: 9403 + disable-system-user: true mod-organizations-storage: mod-organizations: mod-invoice-storage: mod-invoice: + disable-system-user: true mod-orders-storage: mod-orders: + disable-system-user: true mod-finance: mod-finance-storage: use-vault: true diff --git a/eureka-cli/config.import.yaml b/eureka-cli/config.import.yaml index e8def2d..cbd0ec7 100644 --- a/eureka-cli/config.import.yaml +++ b/eureka-cli/config.import.yaml @@ -190,11 +190,14 @@ backend-modules: cpus: 4 memory: 900 mod-login-keycloak: + # Versions <3.1.0-SNAPSHOT.156 are unusable, do not use until addressed + version: 3.1.0-SNAPSHOT.156 use-vault: true port: 9905 environment: X_OKAPI_TOKEN_HEADER_ENABLED: "false" LOGIN_COOKIE_SAMESITE: "NONE" + KC_ADMIN_TOKEN_TTL: 895s KAFKA_PRODUCER_TENANT_COLLECTION: "true" mod-roles-keycloak: use-vault: true @@ -215,6 +218,7 @@ backend-modules: mod-permissions: mod-configuration: mod-users: + disable-system-user: true mod-roles: mod-password-validator: mod-settings: @@ -224,6 +228,7 @@ backend-modules: mod-inventory-storage: mod-inventory: private-port: 9403 + disable-system-user: true mod-quick-marc: ### Data Import Modules mod-data-import: @@ -241,8 +246,10 @@ backend-modules: ### Acquisition Modules mod-orders-storage: mod-orders: + disable-system-user: true mod-invoice-storage: mod-invoice: + disable-system-user: true mod-organizations-storage: mod-organizations: mod-finance: