Skip to content
Open
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.idea
log
tmp
temp
temp
*.log
6 changes: 5 additions & 1 deletion eureka-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
213 changes: 144 additions & 69 deletions eureka-cli/add_missing_secret.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
}
Expand All @@ -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)
Expand All @@ -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}" \
Expand All @@ -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 "=========================================="
7 changes: 7 additions & 0 deletions eureka-cli/config.combined-native.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -206,6 +209,7 @@ backend-modules:
mod-permissions:
mod-configuration:
mod-users:
disable-system-user: true
mod-roles:
mod-password-validator:
mod-settings:
Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions eureka-cli/config.combined.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -202,6 +205,7 @@ backend-modules:
mod-permissions:
mod-configuration:
mod-users:
disable-system-user: true
mod-roles:
mod-password-validator:
mod-settings:
Expand All @@ -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
Expand Down
Loading
Loading