mirror of
https://github.com/TronoSfera/Law.git
synced 2026-05-18 18:13:46 +03:00
157 lines
3.6 KiB
Bash
Executable file
157 lines
3.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ENV_FILE=".env"
|
|
KID=""
|
|
DATA_SECRET=""
|
|
CHAT_SECRET=""
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage:
|
|
scripts/ops/rotate_encryption_kid.sh [options]
|
|
|
|
Options:
|
|
--env-file <path> Env file to update (default: .env)
|
|
--kid <kid> KID to activate (default: kYYYYMMDDHHMM)
|
|
--data-secret <value> DATA key secret (default: generated)
|
|
--chat-secret <value> CHAT key secret (default: same as data secret)
|
|
-h, --help
|
|
|
|
Result:
|
|
- Updates DATA_ENCRYPTION_KEYS / CHAT_ENCRYPTION_KEYS
|
|
- Sets DATA_ENCRYPTION_ACTIVE_KID / CHAT_ENCRYPTION_ACTIVE_KID
|
|
|
|
After updating env:
|
|
1) restart backend/chat/worker with new env
|
|
2) run re-encryption:
|
|
docker compose exec -T backend python -m app.scripts.reencrypt_with_active_kid --apply
|
|
USAGE
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--env-file)
|
|
ENV_FILE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--kid)
|
|
KID="${2:-}"
|
|
shift 2
|
|
;;
|
|
--data-secret)
|
|
DATA_SECRET="${2:-}"
|
|
shift 2
|
|
;;
|
|
--chat-secret)
|
|
CHAT_SECRET="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "[ERROR] Unknown option: $1" >&2
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
echo "[ERROR] Env file not found: $ENV_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v openssl >/dev/null 2>&1; then
|
|
echo "[ERROR] openssl not found" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "$KID" ]]; then
|
|
KID="k$(date -u +%Y%m%d%H%M)"
|
|
fi
|
|
|
|
if [[ -z "$DATA_SECRET" ]]; then
|
|
DATA_SECRET="$(openssl rand -base64 64 | tr -d '\n' | tr '+/' 'AZ' | tr -dc 'A-Za-z0-9' | cut -c1-64)"
|
|
fi
|
|
|
|
if [[ -z "$CHAT_SECRET" ]]; then
|
|
CHAT_SECRET="$DATA_SECRET"
|
|
fi
|
|
|
|
read_env_var() {
|
|
local key="$1"
|
|
grep -E "^${key}=" "$ENV_FILE" | tail -n1 | cut -d= -f2- || true
|
|
}
|
|
|
|
upsert_env_var() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local tmp
|
|
tmp="$(mktemp)"
|
|
awk -v k="$key" -v v="$value" '
|
|
BEGIN { done = 0 }
|
|
$0 ~ ("^" k "=") { print k "=" v; done = 1; next }
|
|
{ print }
|
|
END { if (!done) print k "=" v }
|
|
' "$ENV_FILE" > "$tmp"
|
|
mv "$tmp" "$ENV_FILE"
|
|
}
|
|
|
|
merge_kid_secret() {
|
|
local csv="$1"
|
|
local kid="$2"
|
|
local secret="$3"
|
|
local out=""
|
|
local found="0"
|
|
IFS=',' read -r -a parts <<< "$csv"
|
|
for part in "${parts[@]}"; do
|
|
local token key value
|
|
token="$(echo "$part" | xargs)"
|
|
[[ -z "$token" ]] && continue
|
|
if [[ "$token" == *=* ]]; then
|
|
key="${token%%=*}"
|
|
value="${token#*=}"
|
|
key="$(echo "$key" | xargs)"
|
|
value="$(echo "$value" | xargs)"
|
|
if [[ "$key" == "$kid" ]]; then
|
|
token="${kid}=${secret}"
|
|
found="1"
|
|
fi
|
|
fi
|
|
if [[ -n "$out" ]]; then
|
|
out+="${out:+,}${token}"
|
|
else
|
|
out="$token"
|
|
fi
|
|
done
|
|
if [[ "$found" != "1" ]]; then
|
|
if [[ -n "$out" ]]; then
|
|
out+=",${kid}=${secret}"
|
|
else
|
|
out="${kid}=${secret}"
|
|
fi
|
|
fi
|
|
echo "$out"
|
|
}
|
|
|
|
current_data_keys="$(read_env_var DATA_ENCRYPTION_KEYS)"
|
|
current_chat_keys="$(read_env_var CHAT_ENCRYPTION_KEYS)"
|
|
new_data_keys="$(merge_kid_secret "$current_data_keys" "$KID" "$DATA_SECRET")"
|
|
new_chat_keys="$(merge_kid_secret "$current_chat_keys" "$KID" "$CHAT_SECRET")"
|
|
|
|
upsert_env_var "DATA_ENCRYPTION_KEYS" "$new_data_keys"
|
|
upsert_env_var "CHAT_ENCRYPTION_KEYS" "$new_chat_keys"
|
|
upsert_env_var "DATA_ENCRYPTION_ACTIVE_KID" "$KID"
|
|
upsert_env_var "CHAT_ENCRYPTION_ACTIVE_KID" "$KID"
|
|
|
|
echo "[OK] Updated $ENV_FILE"
|
|
echo " DATA_ENCRYPTION_ACTIVE_KID=$KID"
|
|
echo " CHAT_ENCRYPTION_ACTIVE_KID=$KID"
|
|
echo
|
|
echo "Next steps:"
|
|
echo " 1) restart backend/chat/worker with updated env"
|
|
echo " 2) re-encrypt historical data:"
|
|
echo " docker compose exec -T backend python -m app.scripts.reencrypt_with_active_kid --apply"
|