70 lines
2.1 KiB
Python
70 lines
2.1 KiB
Python
"""Persist per-deployment secrets in a Docker volume before starting uvicorn."""
|
|
|
|
import fcntl
|
|
import os
|
|
import re
|
|
import secrets
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from dotenv import dotenv_values
|
|
|
|
SECRET_NAMES = ("IDENTITY_SECRET", "SERVICE_TOKEN")
|
|
|
|
|
|
def bootstrap(path: Path):
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
lock = path.with_suffix(".lock")
|
|
descriptor = os.open(lock, os.O_WRONLY | os.O_CREAT, 0o600)
|
|
with os.fdopen(descriptor, "w") as handle:
|
|
fcntl.flock(handle, fcntl.LOCK_EX)
|
|
existing = dotenv_values(path) if path.exists() else {}
|
|
selected = {
|
|
name: os.environ.get(name)
|
|
or existing.get(name)
|
|
or secrets.token_urlsafe(48)
|
|
for name in SECRET_NAMES
|
|
}
|
|
if any(
|
|
not re.fullmatch(r"[A-Za-z0-9_-]{32,}", value)
|
|
for value in selected.values()
|
|
):
|
|
raise ValueError("Container secrets must have 32+ URL-safe characters")
|
|
if any(existing.get(name) != value for name, value in selected.items()):
|
|
temporary = None
|
|
try:
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", dir=path.parent, prefix=".identity-", delete=False
|
|
) as output:
|
|
temporary = Path(output.name)
|
|
os.chmod(temporary, 0o600)
|
|
for name, value in selected.items():
|
|
output.write(name + "=" + value + "\n")
|
|
output.flush()
|
|
os.fsync(output.fileno())
|
|
os.replace(temporary, path)
|
|
finally:
|
|
if temporary is not None:
|
|
temporary.unlink(missing_ok=True)
|
|
os.chmod(path, 0o600)
|
|
return selected
|
|
|
|
|
|
def main():
|
|
path = Path(os.getenv("IDENTITY_BOOTSTRAP_FILE", "/config/identity.env"))
|
|
os.environ.update(bootstrap(path))
|
|
command = sys.argv[1:] or [
|
|
"uvicorn",
|
|
"adcloud_server.main:app",
|
|
"--host",
|
|
"0.0.0.0",
|
|
"--port",
|
|
"8000",
|
|
]
|
|
os.execvp(command[0], command)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|