feat(auth): optional SEED_ADMIN_PASSWORD to pre-hash seed admin (dev helper)
Terraform gains enable_seed_password + seed_admin_password (sensitive) vars on the customer-instance module; when enabled the password is piped via startup-script into /opt/agnes/.env as SEED_ADMIN_PASSWORD. On first boot app/main.py argon2-hashes it onto the seed user so the admin can log in immediately without going through /auth/bootstrap. Never overwrites an existing password_hash — safe against accidental reset on terraform apply.
This commit is contained in:
parent
e4f6910398
commit
96bd06ba00
5 changed files with 54 additions and 18 deletions
26
app/main.py
26
app/main.py
|
|
@ -90,7 +90,10 @@ def create_app() -> FastAPI:
|
|||
SCHEMA_VERSION,
|
||||
)
|
||||
|
||||
# Seed admin user for testing/CI (when SEED_ADMIN_EMAIL is set)
|
||||
# Seed admin user for testing/CI (when SEED_ADMIN_EMAIL is set).
|
||||
# Optional: SEED_ADMIN_PASSWORD sets password_hash on first seed so the user
|
||||
# can log in immediately without bootstrap. Only applied if the user has no
|
||||
# password_hash yet — never overwrites an existing password.
|
||||
seed_email = os.environ.get("SEED_ADMIN_EMAIL")
|
||||
if seed_email:
|
||||
try:
|
||||
|
|
@ -98,10 +101,25 @@ def create_app() -> FastAPI:
|
|||
from src.repositories.users import UserRepository
|
||||
conn = get_system_db()
|
||||
repo = UserRepository(conn)
|
||||
if not repo.get_by_email(seed_email):
|
||||
seed_password = os.environ.get("SEED_ADMIN_PASSWORD") or None
|
||||
password_hash = None
|
||||
if seed_password:
|
||||
from argon2 import PasswordHasher
|
||||
password_hash = PasswordHasher().hash(seed_password)
|
||||
existing = repo.get_by_email(seed_email)
|
||||
if not existing:
|
||||
import uuid
|
||||
repo.create(id=str(uuid.uuid4()), email=seed_email, name="Admin", role="admin")
|
||||
logger.info("Seeded admin user: %s", seed_email)
|
||||
repo.create(
|
||||
id=str(uuid.uuid4()),
|
||||
email=seed_email,
|
||||
name="Admin",
|
||||
role="admin",
|
||||
password_hash=password_hash,
|
||||
)
|
||||
logger.info("Seeded admin user: %s (password=%s)", seed_email, "yes" if password_hash else "no")
|
||||
elif password_hash and not existing.get("password_hash"):
|
||||
repo.update(id=existing["id"], password_hash=password_hash, role="admin")
|
||||
logger.info("Set password on existing seed admin: %s", seed_email)
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not seed admin: {e}")
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ SESSION_SECRET= # python -c "import secrets; print(secrets.token_he
|
|||
|
||||
# ── BOOTSTRAP (first deploy only) ───────────────────
|
||||
# SEED_ADMIN_EMAIL=admin@example.com
|
||||
# SEED_ADMIN_PASSWORD= # Dev helper only — sets password_hash on seed.
|
||||
# # Never overwrites an existing password.
|
||||
|
||||
# ── EMAIL / SMTP (required for magic link auth) ─────
|
||||
# SMTP_HOST=smtp.gmail.com
|
||||
|
|
|
|||
|
|
@ -209,18 +209,19 @@ resource "google_compute_instance" "vm" {
|
|||
}
|
||||
|
||||
metadata_startup_script = templatefile("${path.module}/startup-script.sh.tpl", {
|
||||
customer_name = var.customer_name
|
||||
image_repo = var.image_repo
|
||||
image_tag = each.value.image_tag
|
||||
upgrade_mode = each.value.upgrade_mode
|
||||
tls_mode = each.value.tls_mode
|
||||
domain = each.value.domain
|
||||
acme_email = var.acme_email != "" ? var.acme_email : var.seed_admin_email
|
||||
data_source = var.data_source
|
||||
keboola_stack_url = var.keboola_stack_url
|
||||
seed_admin_email = var.seed_admin_email
|
||||
role = each.value.role
|
||||
compose_ref = var.compose_ref
|
||||
customer_name = var.customer_name
|
||||
image_repo = var.image_repo
|
||||
image_tag = each.value.image_tag
|
||||
upgrade_mode = each.value.upgrade_mode
|
||||
tls_mode = each.value.tls_mode
|
||||
domain = each.value.domain
|
||||
acme_email = var.acme_email != "" ? var.acme_email : var.seed_admin_email
|
||||
data_source = var.data_source
|
||||
keboola_stack_url = var.keboola_stack_url
|
||||
seed_admin_email = var.seed_admin_email
|
||||
seed_admin_password = var.enable_seed_password ? var.seed_admin_password : ""
|
||||
role = each.value.role
|
||||
compose_ref = var.compose_ref
|
||||
})
|
||||
|
||||
service_account {
|
||||
|
|
@ -289,8 +290,8 @@ resource "google_monitoring_alert_policy" "health_failure" {
|
|||
conditions {
|
||||
display_name = "Uptime check failed > 5 min"
|
||||
condition_threshold {
|
||||
filter = "metric.type=\"monitoring.googleapis.com/uptime_check/check_passed\" AND metric.labels.check_id=\"${google_monitoring_uptime_check_config.health[each.key].uptime_check_id}\" AND resource.type=\"uptime_url\""
|
||||
duration = "300s"
|
||||
filter = "metric.type=\"monitoring.googleapis.com/uptime_check/check_passed\" AND metric.labels.check_id=\"${google_monitoring_uptime_check_config.health[each.key].uptime_check_id}\" AND resource.type=\"uptime_url\""
|
||||
duration = "300s"
|
||||
# ALIGN_FRACTION_TRUE yields fraction of checks that returned true.
|
||||
# If the fraction stays < 1 (i.e. any probe failed) for 5 min → alert.
|
||||
comparison = "COMPARISON_LT"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ ACME_EMAIL="${acme_email}"
|
|||
DATA_SOURCE="${data_source}"
|
||||
KEBOOLA_STACK_URL="${keboola_stack_url}"
|
||||
SEED_ADMIN_EMAIL="${seed_admin_email}"
|
||||
SEED_ADMIN_PASSWORD="${seed_admin_password}"
|
||||
ROLE="${role}"
|
||||
COMPOSE_REF="${compose_ref}"
|
||||
|
||||
|
|
@ -81,6 +82,7 @@ DATA_SOURCE=$DATA_SOURCE
|
|||
KEBOOLA_STORAGE_TOKEN=$KEBOOLA_TOKEN
|
||||
KEBOOLA_STACK_URL=$KEBOOLA_STACK_URL
|
||||
SEED_ADMIN_EMAIL=$SEED_ADMIN_EMAIL
|
||||
SEED_ADMIN_PASSWORD=$SEED_ADMIN_PASSWORD
|
||||
LOG_LEVEL=info
|
||||
DOMAIN=$DOMAIN
|
||||
AGNES_TAG=$IMAGE_TAG
|
||||
|
|
|
|||
|
|
@ -53,6 +53,19 @@ variable "seed_admin_email" {
|
|||
type = string
|
||||
}
|
||||
|
||||
variable "enable_seed_password" {
|
||||
description = "Pokud true, seed admin user dostane hned password_hash ze seed_admin_password (dev helper). Ponech false v prod — admin si heslo nastaví přes /auth/bootstrap nebo Google OAuth."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "seed_admin_password" {
|
||||
description = "Plain-text heslo pro seed admina. Použije se jen když enable_seed_password=true. POZOR: ukládá se do Terraform state."
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "data_source" {
|
||||
description = "Typ data source — keboola | bigquery | csv"
|
||||
type = string
|
||||
|
|
|
|||
Loading…
Reference in a new issue