#!/bin/sh # Creates or extends OpenSearch's users and passwords, encrypted with SOPS straight away: # no password and no plaintext file ever lands on disk. # apps/logging/opensearch-security.sops.yaml users, roles and role mappings # apps/logging/opensearch-dashboards-account.sops.yaml the account Dashboards logs in with # apps/logging/vector-opensearch.sops.yaml the write-only account Vector uses # apps/falco/falco-opensearch.sops.yaml the write-only account Falcosidekick uses # Safe to run again: stored passwords and hashes are kept, only missing ones are generated, # and a file is rewritten only when its decrypted content changes. # It prints the admin password only when it creates one: put it in your password manager. set -eu cd "$(dirname "$0")/.." SEC=apps/logging/opensearch-security.sops.yaml DASH=apps/logging/opensearch-dashboards-account.sops.yaml VEC=apps/logging/vector-opensearch.sops.yaml FALCO=apps/falco/falco-opensearch.sops.yaml # 1. What already exists. A failed decryption stops the script (set -e): it must never # fall through to "missing" and silently generate new passwords. decrypt() { [ -f "$1" ] || return 0; sops -d "$1"; } SEC_PLAIN=$(decrypt "$SEC") DASH_PLAIN=$(decrypt "$DASH") VEC_PLAIN=$(decrypt "$VEC") FALCO_PLAIN=$(decrypt "$FALCO") # field PLAINTEXT KEY: one decoded value from a decrypted Secret, empty if absent field() { printf '%s\n' "$1" | yq ".data.\"$2\" // \"\"" | base64 --decode; } USERS_PLAIN=$(field "$SEC_PLAIN" internal_users.yml) # old_hash USER: that user's stored bcrypt hash, empty if absent old_hash() { printf '%s\n' "$USERS_PLAIN" | yq ".\"$1\".hash // \"\""; } hash() { htpasswd -bnBC 12 "" "$1" | tr -d ':\n'; } # 2. Passwords and hashes: keep what is stored, generate what is missing. # A newly generated password always gets a new hash, so the two never disagree. NEW_ADMIN_PW="" ADMIN_HASH=$(old_hash admin) if [ -z "$ADMIN_HASH" ]; then NEW_ADMIN_PW=$(openssl rand -base64 24 | tr -d '/+=') ADMIN_HASH=$(hash "$NEW_ADMIN_PW") fi DASH_PW=$(field "$DASH_PLAIN" password) if [ -z "$DASH_PW" ]; then DASH_PW=$(openssl rand -hex 24); DASH_HASH=$(hash "$DASH_PW") else DASH_HASH=$(old_hash kibanaserver); [ -n "$DASH_HASH" ] || DASH_HASH=$(hash "$DASH_PW"); fi VECTOR_PW=$(field "$VEC_PLAIN" password) if [ -z "$VECTOR_PW" ]; then VECTOR_PW=$(openssl rand -hex 24); VECTOR_HASH=$(hash "$VECTOR_PW") else VECTOR_HASH=$(old_hash vector); [ -n "$VECTOR_HASH" ] || VECTOR_HASH=$(hash "$VECTOR_PW"); fi FALCO_PW=$(field "$FALCO_PLAIN" ELASTICSEARCH_PASSWORD) if [ -z "$FALCO_PW" ]; then FALCO_PW=$(openssl rand -hex 24); FALCO_HASH=$(hash "$FALCO_PW") else FALCO_HASH=$(old_hash falco); [ -n "$FALCO_HASH" ] || FALCO_HASH=$(hash "$FALCO_PW"); fi COOKIE=$(field "$DASH_PLAIN" cookie) [ -n "$COOKIE" ] || COOKIE=$(openssl rand -hex 32) # 3. OpenSearch's security configuration INTERNAL_USERS=$(cat < "$1" echo "written $1" } put "$SEC" "$(kubectl create secret generic opensearch-security -n logging \ --from-literal=internal_users.yml="$INTERNAL_USERS" \ --from-literal=roles.yml="$ROLES" \ --from-literal=roles_mapping.yml="$ROLES_MAPPING" \ --dry-run=client -o yaml)" put "$DASH" "$(kubectl create secret generic opensearch-dashboards-account -n logging \ --from-literal=username=kibanaserver --from-literal=password="$DASH_PW" \ --from-literal=cookie="$COOKIE" \ --dry-run=client -o yaml)" put "$VEC" "$(kubectl create secret generic vector-opensearch -n logging \ --from-literal=username=vector --from-literal=password="$VECTOR_PW" \ --dry-run=client -o yaml)" put "$FALCO" "$(kubectl create secret generic falco-opensearch -n falco \ --from-literal=ELASTICSEARCH_USERNAME=falco --from-literal=ELASTICSEARCH_PASSWORD="$FALCO_PW" \ --dry-run=client -o yaml)" # 5. The one password you need, only when it was just created if [ -n "$NEW_ADMIN_PW" ]; then echo "New OpenSearch admin password: $NEW_ADMIN_PW" echo "Put it in your password manager now." fi