diff --git a/.env.sample b/.env.sample index cb379a9..11f6563 100644 --- a/.env.sample +++ b/.env.sample @@ -39,20 +39,20 @@ ENABLE_BACKUPS=true # LOKI_AWS_REGION=eu-west-1 # LOKI_ACCESS_KEY_ID=bush-debrief-approval-robust-scraggly-molecule # LOKI_BUCKET_NAMES=loki -# SECRET_LOKI_AWS_SECRET_ACCESS_KEY_VERSION=v1 +# SECRET_LOKI_AWS_KEY_VERSION=v1 # ## Grafana # # COMPOSE_FILE="$COMPOSE_FILE:compose.grafana.yml" # GF_SERVER_ROOT_URL=https://monitoring.example.com -# SECRET_GRAFANA_ADMIN_PASSWORD_VERSION=v1 +# SECRET_GF_ADMINPASSWD_VERSION=v1 ## Seperate domain for Grafana #GRAFANA_DOMAIN=grafana.example.com # ## Single-Sign-On with OIDC # COMPOSE_FILE="$COMPOSE_FILE:compose.grafana-oidc.yml" # OIDC_ENABLED=1 -# SECRET_GRAFANA_OIDC_CLIENT_SECRET_VERSION=v1 +# SECRET_GF_OIDC_SECRET_VERSION=v1 # OIDC_CLIENT_ID=grafana # OIDC_AUTH_URL="https://authentik.example.com/application/o/authorize/" # OIDC_API_URL="https://authentik.example.com/application/o/userinfo/" @@ -69,12 +69,12 @@ ENABLE_BACKUPS=true # GF_SMTP_ENABLED=true # GF_SMTP_FROM_ADDRESS=grafana@example.com # GF_SMTP_SKIP_VERIFY=false -# SECRET_GRAFANA_SMTP_PASSWORD_VERSION=v1 +# SECRET_GF_SMTP_PASSWD_VERSION=v1 # ## Grafana Matrix Contact Point (optional) #COMPOSE_FILE="$COMPOSE_FILE:compose.matrix-alertmanager-receiver.yml" -#SECRET_MATRIX_ACCESS_TOKEN_VERSION=v1 +#SECRET_MATRIX_TOKEN_VERSION=v1 #GF_MATRIX_USER_ID="" #GF_MATRIX_ROOM_ID="" #GF_MATRIX_HOMESERVER_URL="" diff --git a/README.md b/README.md index 03d891e..6d1ddb0 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ COMPOSE_FILE="$COMPOSE_FILE:compose.matrix-alertmanager-receiver.yml" 2. Insert the matrix access token secret: ``` -abra app secret insert monitoring.marx.klasse-methode.it matrix_access_token v1 +abra app secret insert monitoring.marx.klasse-methode.it matrix_token v1 ``` 3. Set required configurations: diff --git a/abra.sh b/abra.sh index 7b510c9..173b6d9 100644 --- a/abra.sh +++ b/abra.sh @@ -1,11 +1,11 @@ export ENTRYPOINT_VERSION=v1 -export GRAFANA_DATASOURCES_YML_VERSION=v1 -export GRAFANA_DASHBOARDS_YML_VERSION=v2 -export GRAFANA_SWARM_DASHBOARD_JSON_VERSION=v2 -export GRAFANA_STACKS_DASHBOARD_JSON_VERSION=v2 -export GRAFANA_TRAEFIK_DASHBOARD_JSON_VERSION=v2 -export GRAFANA_BACKUP_DASHBOARD_JSON_VERSION=v1 -export GRAFANA_CUSTOM_INI_VERSION=v4 +export GF_DATASOURCES_VERSION=v1 +export GF_DASHBOARDS_VERSION=v2 +export GF_SWARM_DASH_VERSION=v2 +export GF_STACKS_DASH_VERSION=v2 +export GF_TRAEFIK_DASH_VERSION=v2 +export GF_BACKUP_DASH_VERSION=v1 +export GF_CUSTOM_INI_VERSION=v4 export PROMTAIL_YML_VERSION=v3 export LOKI_YML_VERSION=v3 export PROMETHEUS_YML_VERSION=v2 @@ -22,6 +22,101 @@ add_node(){ cat "/prometheus/scrape_configs/$name.yml" } +# migrates secrets from old names to new names by reading values from the +# running containers on the server and re-inserting them under the new names. +# preview changes: abra app cmd --local migrate_secret_names +# execute changes: abra app cmd --local migrate_secret_names execute +migrate_secret_names() { + if ! command -v jq &> /dev/null; then + echo "jq is required on your local machine to migrate secret names" + echo "It could not be found in your PATH, please install jq to proceed." + echo "For example: On a debian/ubuntu system, run `apt install jq`" + exit 1 + fi + + # Hardcoded migration mappings: old_secret_name|new_secret_name + MIGRATIONS=" +grafana_admin_password|gf_adminpasswd +grafana_smtp_password|gf_smtp_passwd +grafana_oidc_client_secret|gf_oidc_secret +matrix_access_token|matrix_token +loki_aws_secret_access_key|loki_aws_key +" + + # Determine which server the app is deployed on + SERVER=$(abra app ls -m | jq -r --arg domain "$APP_NAME" '[.[].apps[] | select(.domain == $domain) | .server] | first' 2>/dev/null) + + if [ -z "$SERVER" ]; then + echo "Error: could not determine server for app '$APP_NAME'" + exit 1 + fi + + # Build a lookup table of all secrets currently mounted in this stack. + # Each line: + LOOKUP=$(ssh "$SERVER" " + docker stack services ${STACK_NAME} --format '{{.Name}}' | while read svc; do + CID=\$(docker ps --no-trunc -q --filter \"name=\${svc}\" | head -1) + docker service inspect \"\$svc\" --format '{{json .Spec.TaskTemplate.ContainerSpec.Secrets}}' | \ + jq -r --arg cid \"\$CID\" '.[]? | .SecretID + \" \" + \$cid + \" \" + .SecretName' + done | sort -k3 -r + " 2>/dev/null) + + echo "Secret migration plan for: $APP_NAME (server: $SERVER)" + echo "" + printf " %-24s %-8s %s\n" "OLD NAME" "FOUND" "ACTION" + printf " %-24s %-8s %s\n" "--------" "-----" "------" + + # Check each old name against the lookup table and display the plan + ANY_FOUND=false + while IFS='|' read -r OLD_NAME NEW_NAME; do + [ -z "$OLD_NAME" ] && continue + MATCH=$(echo "$LOOKUP" | grep " ${STACK_NAME}_${OLD_NAME}_" | head -1) + if [ -n "$MATCH" ]; then + printf " %-24s %-8s %s\n" "$OLD_NAME" "yes" "recreate as '$NEW_NAME' version V1" + ANY_FOUND=true + else + printf " %-24s %-8s %s\n" "$OLD_NAME" "no" "nothing (not found on server)" + fi + done <<< "$MIGRATIONS" + + echo "" + + if [ "$ANY_FOUND" = false ]; then + echo "No old secrets found on server. Nothing to migrate." + return 0 + fi + + if [ "$1" != "execute" ]; then + echo "To apply the above changes, run:" + echo " abra app cmd --local $APP_NAME migrate_secret_names execute" + return 0 + fi + + # read each found secret from its container and re-insert with the new name + while IFS='|' read -r OLD_NAME NEW_NAME; do + [ -z "$OLD_NAME" ] && continue + + MATCH=$(echo "$LOOKUP" | grep " ${STACK_NAME}_${OLD_NAME}_" | head -1) + [ -z "$MATCH" ] && continue + + SECRET_ID=$(echo "$MATCH" | awk '{print $1}') + CID=$(echo "$MATCH" | awk '{print $2}') + SECRET_VALUE=$(ssh "$SERVER" "cat /var/lib/docker/containers/${CID}/mounts/secrets/${SECRET_ID} 2>/dev/null || sudo cat /var/lib/docker/containers/${CID}/mounts/secrets/${SECRET_ID} 2>/dev/null") + + if [ -z "$SECRET_VALUE" ]; then + echo "Error: could not read value for '$OLD_NAME', skipping" + continue + fi + + echo "Migrating: '$OLD_NAME' -> '$NEW_NAME' (v1)" + printf '%s' "$SECRET_VALUE" | abra app secret insert -C "$APP_NAME" "$NEW_NAME" v1 + + done <<< "$MIGRATIONS" + + echo "" + echo "Done." +} + # adds a domain to a scrape config or creates a new one add_domain(){ name=$1 diff --git a/alertmanager-matrix-config.yml.tmpl b/alertmanager-matrix-config.yml.tmpl index 8e6878b..539af76 100644 --- a/alertmanager-matrix-config.yml.tmpl +++ b/alertmanager-matrix-config.yml.tmpl @@ -12,7 +12,7 @@ http: matrix: homeserver-url: "{{ env "GF_MATRIX_HOMESERVER_URL" }}" user-id: "{{ env "GF_MATRIX_USER_ID" }}" - access-token: "{{ secret "matrix_access_token" }}" + access-token: "{{ secret "matrix_token" }}" room-mapping: matrixroom: "{{ env "GF_MATRIX_ROOM_ID" }}" diff --git a/compose.grafana-oidc.yml b/compose.grafana-oidc.yml index a4a4bc8..5ad756b 100644 --- a/compose.grafana-oidc.yml +++ b/compose.grafana-oidc.yml @@ -3,7 +3,7 @@ version: '3.8' services: grafana: secrets: - - grafana_oidc_client_secret + - gf_oidc_secret environment: - OIDC_API_URL - OIDC_AUTH_URL @@ -12,6 +12,6 @@ services: - OIDC_TOKEN_URL secrets: - grafana_oidc_client_secret: + gf_oidc_secret: external: true - name: ${STACK_NAME}_grafana_oidc_client_secret_${SECRET_GRAFANA_OIDC_CLIENT_SECRET_VERSION} + name: ${STACK_NAME}_gf_oidc_secret_${SECRET_GF_OIDC_SECRET_VERSION} diff --git a/compose.grafana-smtp.yml b/compose.grafana-smtp.yml index bd223e4..24d72f3 100644 --- a/compose.grafana-smtp.yml +++ b/compose.grafana-smtp.yml @@ -3,16 +3,16 @@ version: '3.8' services: grafana: secrets: - - grafana_smtp_password + - gf_smtp_passwd environment: - GF_SMTP_HOST - GF_SMTP_USER - - GF_SMTP_PASSWORD__FILE=/run/secrets/grafana_smtp_password + - GF_SMTP_PASSWORD__FILE=/run/secrets/gf_smtp_passwd - GF_SMTP_ENABLED - GF_SMTP_FROM_ADDRESS - GF_SMTP_SKIP_VERIFY secrets: - grafana_smtp_password: + gf_smtp_passwd: external: true - name: ${STACK_NAME}_grafana_smtp_password_${SECRET_GRAFANA_SMTP_PASSWORD_VERSION} + name: ${STACK_NAME}_gf_smtp_passwd_${SECRET_GF_SMTP_PASSWD_VERSION} diff --git a/compose.grafana.yml b/compose.grafana.yml index 987f792..291a2c4 100644 --- a/compose.grafana.yml +++ b/compose.grafana.yml @@ -6,21 +6,21 @@ services: volumes: - grafana-data:/var/lib/grafana:rw secrets: - - grafana_admin_password + - gf_adminpasswd configs: - - source: grafana_custom_ini + - source: gf_custom_ini target: /etc/grafana/grafana.ini - - source: grafana_datasources_yml + - source: gf_datasources target: /etc/grafana/provisioning/datasources/datasources.yml - - source: grafana_dashboards_yml + - source: gf_dashboards target: /etc/grafana/provisioning/dashboards/dashboards.yml - - source: grafana_swarm_dashboard_json + - source: gf_swarm_dash target: /var/lib/grafana/dashboards/docker-swarm-nodes.json - - source: grafana_stacks_dashboard_json + - source: gf_stacks_dash target: /var/lib/grafana/dashboards/docker-swarm-stacks.json - - source: grafana_traefik_dashboard_json + - source: gf_traefik_dash target: /var/lib/grafana/dashboards/traefik.json - - source: grafana_backup_dashboard_json + - source: gf_backup_dash target: /var/lib/grafana/dashboards/backup.json - source: gf_alerts_node target: /etc/grafana/provisioning/alerting/node.yml @@ -29,7 +29,7 @@ services: - internal environment: - GF_SERVER_ROOT_URL - - GF_SECURITY_ADMIN_PASSWORD__FILE=/run/secrets/grafana_admin_password + - GF_SECURITY_ADMIN_PASSWORD__FILE=/run/secrets/gf_adminpasswd - GF_SECURITY_ALLOW_EMBEDDING - GF_INSTALL_PLUGINS - ALERT_NODE_DISK_SPACE_ENABLED @@ -51,27 +51,27 @@ services: start_period: 10s configs: - grafana_custom_ini: + gf_custom_ini: template_driver: golang - name: ${STACK_NAME}_grafana_custom_ini_${GRAFANA_CUSTOM_INI_VERSION} + name: ${STACK_NAME}_gf_custom_ini_${GF_CUSTOM_INI_VERSION} file: grafana_custom.ini - grafana_datasources_yml: - name: ${STACK_NAME}_g_datasources_yml_${GRAFANA_DATASOURCES_YML_VERSION} + gf_datasources: + name: ${STACK_NAME}_gf_datasources_${GF_DATASOURCES_VERSION} file: grafana-datasources.yml - grafana_dashboards_yml: - name: ${STACK_NAME}_g_dashboards_yml_${GRAFANA_DASHBOARDS_YML_VERSION} + gf_dashboards: + name: ${STACK_NAME}_gf_dashboards_${GF_DASHBOARDS_VERSION} file: grafana-dashboards.yml - grafana_swarm_dashboard_json: - name: ${STACK_NAME}_g_swarm_dashboard_json_${GRAFANA_SWARM_DASHBOARD_JSON_VERSION} + gf_swarm_dash: + name: ${STACK_NAME}_gf_swarm_dash_${GF_SWARM_DASH_VERSION} file: grafana-swarm-dashboard.json - grafana_stacks_dashboard_json: - name: ${STACK_NAME}_g_stacks_dashboard_json_${GRAFANA_STACKS_DASHBOARD_JSON_VERSION} + gf_stacks_dash: + name: ${STACK_NAME}_gf_stacks_dash_${GF_STACKS_DASH_VERSION} file: grafana-stacks-dashboard.json - grafana_traefik_dashboard_json: - name: ${STACK_NAME}_g_traefik_dashboard_json_${GRAFANA_TRAEFIK_DASHBOARD_JSON_VERSION} + gf_traefik_dash: + name: ${STACK_NAME}_gf_traefik_dash_${GF_TRAEFIK_DASH_VERSION} file: grafana-traefik-dashboard.json - grafana_backup_dashboard_json: - name: ${STACK_NAME}_g_backup_dashboard_json_${GRAFANA_BACKUP_DASHBOARD_JSON_VERSION} + gf_backup_dash: + name: ${STACK_NAME}_gf_backup_dash_${GF_BACKUP_DASH_VERSION} file: grafana-backup-dashboard.json gf_alerts_node: template_driver: golang @@ -83,6 +83,6 @@ volumes: secrets: - grafana_admin_password: + gf_adminpasswd: external: true - name: ${STACK_NAME}_grafana_admin_password_${SECRET_GRAFANA_ADMIN_PASSWORD_VERSION} + name: ${STACK_NAME}_gf_adminpasswd_${SECRET_GF_ADMINPASSWD_VERSION} diff --git a/compose.loki.yml b/compose.loki.yml index bd4213f..372c2cb 100644 --- a/compose.loki.yml +++ b/compose.loki.yml @@ -12,7 +12,7 @@ services: volumes: - loki-data:/loki # secrets: - # - loki_aws_secret_access_key + # - loki_aws_key environment: - LOKI_ACCESS_KEY_ID - LOKI_AWS_ENDPOINT @@ -47,6 +47,6 @@ volumes: loki-data: # secrets: -# loki_aws_secret_access_key: +# loki_aws_key: # external: true -# name: ${STACK_NAME}_loki_aws_secret_access_key_${SECRET_LOKI_AWS_SECRET_ACCESS_KEY_VERSION} +# name: ${STACK_NAME}_loki_aws_key_${SECRET_LOKI_AWS_KEY_VERSION} diff --git a/compose.matrix-alertmanager-receiver.yml b/compose.matrix-alertmanager-receiver.yml index d53e797..ca0cdc7 100644 --- a/compose.matrix-alertmanager-receiver.yml +++ b/compose.matrix-alertmanager-receiver.yml @@ -4,7 +4,7 @@ services: matrix-alertmanager-receiver: image: metio/matrix-alertmanager-receiver:2026.2.25 secrets: - - matrix_access_token + - matrix_token configs: - source: matrix-alertmanager-receiver-config target: /etc/matrix-alertmanager-receiver/config.yml @@ -23,6 +23,6 @@ configs: file: alertmanager-matrix-config.yml.tmpl secrets: - matrix_access_token: + matrix_token: external: true - name: ${STACK_NAME}_matrix_access_token_${SECRET_MATRIX_ACCESS_TOKEN_VERSION} + name: ${STACK_NAME}_matrix_token_${SECRET_MATRIX_TOKEN_VERSION} diff --git a/grafana_custom.ini b/grafana_custom.ini index e252f8c..6abd532 100644 --- a/grafana_custom.ini +++ b/grafana_custom.ini @@ -21,7 +21,7 @@ tls_skip_verify_insecure = false allow_sign_up = true auto_login = true client_id = {{ env "OIDC_CLIENT_ID" }} -client_secret = {{ secret "grafana_oidc_client_secret" }} +client_secret = {{ secret "gf_oidc_secret" }} auth_url = {{ env "OIDC_AUTH_URL" }} token_url = {{ env "OIDC_TOKEN_URL" }} api_url = {{ env "OIDC_API_URL" }} diff --git a/loki.yml.tmpl b/loki.yml.tmpl index d28e6a0..e399700 100644 --- a/loki.yml.tmpl +++ b/loki.yml.tmpl @@ -86,7 +86,7 @@ storage_config: endpoint: {{ env "LOKI_AWS_ENDPOINT" }} region: {{ env "LOKI_AWS_REGION" }} access_key_id: {{ env "LOKI_ACCESS_KEY_ID" }} - secret_access_key: {{ secret "loki_aws_secret_access_key" }} + secret_access_key: {{ secret "loki_aws_key" }} bucketnames: {{ env "LOKI_BUCKET_NAMES" }} insecure: false sse_encryption: false diff --git a/release/next b/release/next index c05215f..086f284 100644 --- a/release/next +++ b/release/next @@ -6,4 +6,7 @@ COMPOSE_FILE="$COMPOSE_FILE:compose.grafana-oidc.yml" COMPOSE_FILE="$COMPOSE_FILE:compose.grafana-smtp.yml" -3. The scape-config.example.yml file and add_node() command were updated to use a secure endpoint for the traefik metrics instead http. This requires an updated Traefik recipe that publishes the metrics on https. \ No newline at end of file +3. The scrape-config.example.yml file and add_node() command were updated to use a secure endpoint for the traefik metrics instead of http. This requires an updated Traefik recipe that publishes the metrics on https. + +4. Secret and config names were shortened to max 14 characters to prevent going over Docker's 64 character limit when STACK_NAME and VERSION are added to it. +When upgrading, you need to reinsert the secrets with their shorter names. Run `abra app secret list ` to see which secrets aren't created on the server (because their name was shortened) and run `abra app secret insert v1 ` to reinsert them with the shorter name. Or you can use the migrate_secret_names function in abra.sh to reinsert all existing secrets with their shorter name automatically: `abra app cmd --local migrate_secret_names`