Compare commits

...

12 Commits

Author SHA1 Message Date
e10b51c8e3 Attempt to automate registry images
All checks were successful
CI / Lint & Format (push) Successful in 4s
CI / Tests (Pytest + Alembic + Postgres) (push) Successful in 9m25s
CI / Docker Build (push) Successful in 10m28s
2025-09-30 13:27:35 +01:00
48c5731a8a Updated CI.yml
All checks were successful
CI / Lint & Format (push) Successful in 4s
CI / Tests (Pytest + Alembic + Postgres) (push) Successful in 9m25s
CI / Docker Build (push) Successful in 1m12s
2025-09-30 13:15:40 +01:00
5e79768394 Updated CI.yml
Some checks failed
CI / Lint & Format (push) Successful in 3s
CI / Tests (Pytest + Alembic + Postgres) (push) Successful in 9m31s
CI / Docker Build (push) Failing after 3m14s
2025-09-30 12:48:39 +01:00
ef947d9888 Migrated CI health test to new JSON. Refactored NGINX config
Some checks failed
CI / Lint & Format (push) Successful in 3s
CI / Tests (Pytest + Alembic + Postgres) (push) Failing after 4m44s
CI / Docker Build (push) Has been skipped
2025-09-30 12:39:49 +01:00
7054ba7547 Fixed NGINX Config for new z endpoints
Some checks failed
CI / Lint & Format (push) Successful in 4s
CI / Tests (Pytest + Alembic + Postgres) (push) Failing after 4m45s
CI / Docker Build (push) Has been skipped
2025-09-30 12:24:51 +01:00
3a49d82d04 Added healthcheck, Added readyz, livez endpoints
Some checks failed
CI / Lint & Format (push) Successful in 6s
CI / Docker Build (push) Has been cancelled
CI / Tests (Pytest + Alembic + Postgres) (push) Has been cancelled
2025-09-30 12:20:11 +01:00
c93f0ccda6 Standardised Bases across models
Some checks failed
CI / Lint & Format (push) Successful in 4s
CI / Tests (Pytest + Alembic + Postgres) (push) Failing after 4m45s
CI / Docker Build (push) Has been skipped
2025-09-23 19:38:54 +01:00
3eaed88074 Moved models to shared base 2025-09-23 19:27:08 +01:00
7d811fede0 Added SETUP.md
Some checks failed
CI / Lint & Format (push) Successful in 4s
CI / Tests (Pytest + Alembic + Postgres) (push) Failing after 4m46s
CI / Docker Build (push) Has been skipped
2025-09-23 19:08:05 +01:00
f8776911dc Appended .env.example
Some checks failed
CI / Lint & Format (push) Successful in 4s
CI / Tests (Pytest + Alembic + Postgres) (push) Failing after 4m55s
CI / Docker Build (push) Has been skipped
2025-09-23 18:57:26 +01:00
7a672fc079 Merge branch 'main' of https://git.ntbx.io/boris/keywarden
Some checks failed
CI / Lint & Format (push) Successful in 4s
CI / Docker Build (push) Has been cancelled
CI / Tests (Pytest + Alembic + Postgres) (push) Has been cancelled
2025-09-23 18:55:10 +01:00
5aacf17ef2 Exposed docs endpoint, added default env 2025-09-23 18:55:07 +01:00
17 changed files with 245 additions and 75 deletions

24
.env.example Normal file
View File

@@ -0,0 +1,24 @@
DOCKERDIR=/opt/compose/keywarden
## Local Auth
KEYWARDEN_SECRET_KEY=<!GENERATE SECRET HERE!>
KEYWARDEN_ALLOW_LOCAL_LOGIN=true
KEYWARDEN_ACCESS_TOKEN_EXPIRE_MINUTES=60
## Optional OIDC
# KEYWARDEN_OIDC_ENABLED=true
# KEYWARDEN_OIDC_ISSUER=https://auth.example.com/application/o/<slug>
# KEYWARDEN_OIDC_CLIENT_ID=keywarden
# KEYWARDEN_OIDC_AUDIENCE=keywarden-api
# KEYWARDEN_OIDC_JWKS_URL=https://auth.example.com/application/o/<slug>/jwks
## Policy toggles
# KEYWARDEN_REQUIRE_SSO=false # if true, local login is disabled
# KEYWARDEN_AUTO_PROVISION_OIDC=true # JIT user creation
## Postgres
KEYWARDEN_POSTGRES_USER="postgres"
KEYWARDEN_POSTGRES_PASSWORD="postgres"
KEYWARDEN_POSTGRES_HOST="keywarden-db"
KEYWARDEN_POSTGRES_PORT=5432
KEYWARDEN_POSTGRES_DB="keywarden"

View File

@@ -11,9 +11,13 @@ permissions:
env: env:
PYTHON_VERSION: "3.11" PYTHON_VERSION: "3.11"
# Used by tests / alembic; matches docker-compose-style DSN IMAGE_NAME: keywarden-api
TEST_POSTGRES_DSN: postgresql+asyncpg://postgres:postgres@localhost:5432/keywarden # Used by tests / alembic; matches docker compose environment
KEYWARDEN_POSTGRES_USER: postgres
KEYWARDEN_POSTGRES_PASSWORD: postgres
KEYWARDEN_POSTGRES_HOST: localhost
KEYWARDEN_POSTGRES_PORT: 5432
KEYWARDEN_POSTGRES_DB: keywarden
jobs: jobs:
lint: lint:
name: Lint & Format name: Lint & Format
@@ -68,31 +72,69 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ hashFiles('**/requirements.txt') }} key: pip-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ hashFiles('**/requirements*.txt', 'pyproject.toml') }}
restore-keys: | restore-keys: |
pip-${{ runner.os }}-${{ env.PYTHON_VERSION }}- pip-${{ runner.os }}-${{ env.PYTHON_VERSION }}-
- name: Install dependencies - name: Install deps
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r requirements.txt pip install -r requirements.txt
# optional: extras/dev
if [ -f pyproject.toml ]; then pip install -e .[dev] || pip install -e . ; fi
- name: Set PYTHONPATH - name: Set PYTHONPATH
run: echo "PYTHONPATH=${GITHUB_WORKSPACE}" >> $GITHUB_ENV run: echo "PYTHONPATH=${GITHUB_WORKSPACE}" >> $GITHUB_ENV
- name: Create .env for tests (optional for app runtime) # Gitea ACT Runner needs this for some reason.
- name: Select Postgres host for runner
run: | run: |
printf "KEYWARDEN_POSTGRES_DSN=%s\nKEYWARDEN_SECRET_KEY=%s\n" \ if [ "${ACT:-}" = "true" ]; then
"${{ env.TEST_POSTGRES_DSN }}" "testsecret" > .env echo "KEYWARDEN_POSTGRES_HOST=postgres" >> "$GITHUB_ENV" # Gitea (act_runner)
else
echo "KEYWARDEN_POSTGRES_HOST=127.0.0.1" >> "$GITHUB_ENV" # GitHub Actions
fi
- name: Echo DB target
run: echo "DB → ${KEYWARDEN_POSTGRES_HOST:-unset}:${{ env.KEYWARDEN_POSTGRES_PORT }}"
# Explicit wait (removes race on act_runner)
- name: Wait for Postgres
run: |
for i in {1..60}; do
python - <<'PY'
import os, socket, sys
h=os.environ.get("KEYWARDEN_POSTGRES_HOST","127.0.0.1"); p=int(os.environ.get("KEYWARDEN_POSTGRES_PORT","5432"))
s=socket.socket(); s.settimeout(1)
try:
s.connect((h,p)); print("Postgres is up:", h,p); sys.exit(0)
except Exception as e:
print("waiting:", e); sys.exit(1)
finally:
s.close()
PY
if [ $? -eq 0 ]; then break; fi
sleep 2
done
- name: Run Alembic migrations - name: Run Alembic migrations
env: env:
KEYWARDEN_POSTGRES_DSN: ${{ env.TEST_POSTGRES_DSN }} KEYWARDEN_POSTGRES_USER: ${{ env.KEYWARDEN_POSTGRES_USER }}
KEYWARDEN_POSTGRES_PASSWORD: ${{ env.KEYWARDEN_POSTGRES_PASSWORD }}
KEYWARDEN_POSTGRES_HOST: ${{ env.KEYWARDEN_POSTGRES_HOST }}
KEYWARDEN_POSTGRES_PORT: ${{ env.KEYWARDEN_POSTGRES_PORT }}
KEYWARDEN_POSTGRES_DB: ${{ env.KEYWARDEN_POSTGRES_DB }}
KEYWARDEN_POSTGRES_SSL: ${{ env.KEYWARDEN_POSTGRES_SSL }}
run: alembic upgrade head run: alembic upgrade head
- name: Pytest - name: Pytest
env: env:
KEYWARDEN_POSTGRES_DSN: ${{ env.TEST_POSTGRES_DSN }} KEYWARDEN_POSTGRES_USER: ${{ env.KEYWARDEN_POSTGRES_USER }}
KEYWARDEN_POSTGRES_PASSWORD: ${{ env.KEYWARDEN_POSTGRES_PASSWORD }}
KEYWARDEN_POSTGRES_HOST: ${{ env.KEYWARDEN_POSTGRES_HOST }}
KEYWARDEN_POSTGRES_PORT: ${{ env.KEYWARDEN_POSTGRES_PORT }}
KEYWARDEN_POSTGRES_DB: ${{ env.KEYWARDEN_POSTGRES_DB }}
KEYWARDEN_POSTGRES_SSL: ${{ env.KEYWARDEN_POSTGRES_SSL }}
run: | run: |
pytest -q tests pytest -q tests
docker-build: docker-build:
@@ -103,15 +145,47 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
# Choose Buildx cache backend: gha on GitHub, local on act_runner
- name: Select Buildx cache backend
run: |
if [ "${ACT:-}" = "true" ]; then
echo "CACHE_TO=type=local,dest=/tmp/.buildx-cache,mode=max" >> $GITHUB_ENV
echo "CACHE_FROM=type=local,src=/tmp/.buildx-cache" >> $GITHUB_ENV
else
echo "CACHE_TO=type=gha,mode=max" >> $GITHUB_ENV
echo "CACHE_FROM=type=gha" >> $GITHUB_ENV
fi
- name: Prepare local cache dir (act_runner only)
if: ${{ env.ACT == 'true' }}
run: mkdir -p /tmp/.buildx-cache
- name: Set image reference (Gitea)
run: |
echo "GT_IMAGE=${{ secrets.GITEA_REGISTRY }}/${{ secrets.GITEA_NAMESPACE }}/${{ env.IMAGE_NAME }}" >> $GITHUB_ENV
- name: Set up QEMU (optional)
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Build image (no push) - name: Set image reference
run: echo "GT_IMAGE=${{ secrets.REGISTRY_HOST }}/${{ secrets.REGISTRY_NAMESPACE }}/${{ env.IMAGE_NAME }}" >> $GITHUB_ENV
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.REGISTRY_HOST }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build & push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
push: false push: true
tags: keywarden:ci tags: |
# speeds up builds by caching layers on GH Actions ${{ env.GT_IMAGE }}:${{ github.ref_name }}
cache-from: type=gha ${{ env.GT_IMAGE }}:sha-${{ github.sha }}
cache-to: type=gha,mode=max ${{ env.GT_IMAGE }}:latest

16
SETUP.md Normal file
View File

@@ -0,0 +1,16 @@
# NGINX - External Proxy
For setups behind an external reverse proxy (heavily recommended), using a local CA and self-signed certificates is not required, but also recommended.
After installing `mkcert` through your system package manager:
```bash
mkcert -install
mkcert abc.domain.xyz, bcd.domain.xyz
mv domain.xyz+X-key.pem nginx/certs/key.pem
mv domain.xyz.pem nginx/certs/certificate.pem
```
NGINX will find these certificates automatically and use them when proxying the application. Unless you know what you are doing, editing files under `nginx/configs/` is not recommended.
If preferred, NGINX can be used as a reverse proxy, however an additional `certbot/certbot:latest` container would be required unless other valid SSL certificates are provided under `nginx/certs`.

View File

@@ -25,11 +25,13 @@ if config.config_file_name is not None:
target_metadata = Base.metadata target_metadata = Base.metadata
# Get DB URL from env (prefer KEYWARDEN_ prefix, fall back to unprefixed, then a sane default for local) # Get DB URL from env (prefer KEYWARDEN_ prefix, fall back to unprefixed, then a sane default for local)
DB_URL = ( DB_USER = os.getenv("KEYWARDEN_POSTGRES_USER", "postgres")
os.getenv("KEYWARDEN_POSTGRES_DSN") DB_PASS = os.getenv("KEYWARDEN_POSTGRES_PASSWORD", "postgres")
or os.getenv("POSTGRES_DSN") DB_HOST = os.getenv("KEYWARDEN_POSTGRES_HOST", "keywarden-db")
or "postgresql+asyncpg://postgres:postgres@localhost:5432/keywarden" DB_PORT = os.getenv("KEYWARDEN_POSTGRES_PORT", "5432")
) DB_NAME = os.getenv("KEYWARDEN_POSTGRES_DB", "keywarden")
DB_URL = f"postgresql+asyncpg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
def run_migrations_offline(): def run_migrations_offline():

View File

@@ -1,4 +1,4 @@
from pydantic import computed_field
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -8,13 +8,33 @@ class Settings(BaseSettings):
env_prefix="KEYWARDEN_", env_prefix="KEYWARDEN_",
extra="ignore", extra="ignore",
) )
PROJECT_NAME: str = "Keywarden" PROJECT_NAME: str = "Keywarden"
API_V1_STR: str = "/api/v1" API_V1_STR: str = "/api/v1"
SECRET_KEY: str
# Postgres split vars (with defaults)
POSTGRES_USER: str = "postgres"
POSTGRES_PASSWORD: str = "postgres"
POSTGRES_HOST: str = "keywarden-db"
POSTGRES_PORT: int = 5432
POSTGRES_DB: str = "keywarden"
SECRET_KEY: str = "insecure-dev-secret" # default for local dev only
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
POSTGRES_DSN: str
OIDC_ENABLED: bool = False
OIDC_ISSUER: str | None = None OIDC_ISSUER: str | None = None
OIDC_CLIENT_ID: str | None = None OIDC_CLIENT_ID: str | None = None
OIDC_CLIENT_SECRET: str | None = None OIDC_AUDIENCE: str | None = None # optional
OIDC_JWKS_URL: str | None = None # if not set, derive from issuer
@computed_field(return_type=str)
@property
def POSTGRES_DSN(self) -> str:
return (
f"postgresql+asyncpg://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
f"@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
)
settings = Settings() settings = Settings()

View File

@@ -1,9 +1,6 @@
from sqlalchemy.orm import declarative_base from app.db.base_class import Base # noqa: F401
from app.models.access_request import AccessRequest # noqa: F401 from app.models.access_request import AccessRequest # noqa: F401
from app.models.audit import AuditEvent # noqa: F401 from app.models.audit import AuditEvent # noqa: F401
from app.models.server import Server # noqa: F401 from app.models.server import Server # noqa: F401
from app.models.sshkey import SSHKey # noqa: F401 from app.models.sshkey import SSHKey # noqa: F401
from app.models.user import User # noqa: F401 from app.models.user import User # noqa: F401
Base = declarative_base()

13
app/db/base_class.py Normal file
View File

@@ -0,0 +1,13 @@
from sqlalchemy import MetaData
from sqlalchemy.orm import declarative_base
# Optional: naming convention keeps Alembic diffs stable
convention = {
"ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s",
}
metadata = MetaData(naming_convention=convention)
Base = declarative_base(metadata=metadata)

View File

@@ -1,13 +1,37 @@
from fastapi import FastAPI from fastapi import FastAPI
from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from starlette.responses import JSONResponse
from app.api.v1 import auth, keys from app.api.v1 import auth, keys
from app.core.config import settings from app.core.config import settings
from app.db.session import AsyncSessionLocal
app = FastAPI(title=settings.PROJECT_NAME) app = FastAPI(
title=settings.PROJECT_NAME
)
app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["auth"]) app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["auth"])
app.include_router(keys.router, prefix=f"{settings.API_V1_STR}/keys", tags=["keys"]) app.include_router(keys.router, prefix=f"{settings.API_V1_STR}/keys", tags=["keys"])
# Health endpoint (useful for docker, agent and uptime) # Is the API running?
@app.get("/livez")
async def livez():
return {"status": "ok"}
# Is the application ready (including db)?
@app.get("/readyz")
async def readyz():
try:
async with AsyncSessionLocal() as session:
await session.execute(text("SELECT 1"))
return {"status": "ok", "db": "up"}
except SQLAlchemyError:
return JSONResponse(
status_code=503,
content={"status": "degraded", "db": "down"},
)
# Is the application healthy (ready)?
@app.get("/healthz") @app.get("/healthz")
def healthz(): async def healthz():
return {"ok": True} return await readyz() # alias

View File

@@ -1,9 +1,9 @@
from datetime import datetime, timezone #noqa from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, String from sqlalchemy import DateTime, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from app.models.user import Base from app.db.base_class import Base
class AccessRequest(Base): class AccessRequest(Base):

View File

@@ -3,7 +3,7 @@ from datetime import datetime, timezone
from sqlalchemy import DateTime, String from sqlalchemy import DateTime, String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from app.models.user import Base from app.db.base_class import Base
class AuditEvent(Base): class AuditEvent(Base):

View File

@@ -1,7 +1,7 @@
from sqlalchemy import JSON, Boolean, Integer, String from sqlalchemy import JSON, Boolean, Integer, String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from app.models.user import Base from app.db.base_class import Base
class Server(Base): class Server(Base):

View File

@@ -1,9 +1,9 @@
from datetime import datetime, timezone # noqa: F401 from datetime import datetime, timezone # noqa: F401
from sqlalchemy import Boolean, DateTime, ForeignKey, String from sqlalchemy import Boolean, DateTime, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship # noqa: F401 from sqlalchemy.orm import Mapped, mapped_column
from app.models.user import Base from app.db.base_class import Base
class SSHKey(Base): class SSHKey(Base):

View File

@@ -1,10 +1,8 @@
from sqlalchemy import Boolean, String from sqlalchemy import Boolean, String
from sqlalchemy.orm import Mapped, declarative_base, mapped_column from sqlalchemy.orm import Mapped, mapped_column
# only for Alembic discovery, not used here from app.db.base_class import Base
from app.db.session import engine # noqa: F401
Base = declarative_base()
class User(Base): class User(Base):
__tablename__ = "users" __tablename__ = "users"

View File

@@ -9,6 +9,7 @@ services:
- ${DOCKERDIR}/nginx/certs/:/certs/ - ${DOCKERDIR}/nginx/certs/:/certs/
- ${DOCKERDIR}/nginx/webdir/:/var/www/ - ${DOCKERDIR}/nginx/webdir/:/var/www/
- ${DOCKERDIR}/nginx/logs:/var/log/nginx/ - ${DOCKERDIR}/nginx/logs:/var/log/nginx/
# - "external:internal", change external to desired port
ports: ports:
- "443:443" - "443:443"
@@ -22,10 +23,13 @@ services:
db: db:
image: postgres:17-alpine image: postgres:17-alpine
environment: environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} POSTGRES_PASSWORD: ${KEYWARDEN_POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: keywarden POSTGRES_DB: ${KEYWARDEN_POSTGRES_DB:-keywarden}
POSTGRES_USER: postgres POSTGRES_USER: ${KEYWARDEN_POSTGRES_USER:-keywarden}
# ports: ["5432:5432"] POSTGRES_PORT: ${KEYWARDEN_POSTGRES_PORT:-5432}
# Do not enable unless debugging, not needed to be exposed outside of docker network
# ports:
# - "5432:5432"
volumes: volumes:
- "pgdata:/var/lib/postgresql/data" - "pgdata:/var/lib/postgresql/data"
@@ -33,13 +37,17 @@ services:
build: . build: .
depends_on: depends_on:
- db - db
environment: env_file:
- SECRET_KEY=[CREATE SECRET KEY] - .env
- POSTGRES_DSN=postgresql+asyncpg://postgres:${POSTGRES_PASSWORD:-postgres}@keywarden-postgres:5432/keywarden # API runs on port 8000, but is unneeded to be external unless using a custom reverse proxy on another machine
- ACCESS_TOKEN_EXPIRE_MINUTES=60 # ports:
ports: # - "8000:8000"
- "8000:8000"
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 command: uvicorn app.main:app --host 0.0.0.0 --port 8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/healthz"]
interval: 10s
timeout: 5s
retries: 5
volumes: volumes:
pgdata: pgdata:

View File

@@ -21,21 +21,6 @@ http {
'"$http_user_agent" "$http_x_forwarded_for"'; '"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main; access_log /var/log/nginx/access.log main;
server_tokens off;
sendfile on;
tcp_nopush on;
keepalive_timeout 60;
tcp_nodelay on;
client_body_timeout 15;
gzip on;
gzip_vary on;
gzip_min_length 1k;
client_max_body_size 10G;
proxy_request_buffering off;
include /etc/nginx/conf.d/*.conf; include /etc/nginx/conf.d/*.conf;
types_hash_bucket_size 128; types_hash_bucket_size 128;

View File

@@ -25,15 +25,24 @@ server {
} }
# NOT FOR PROD vvv
location ~ ^/(docs|openapi.json)$ {
proxy_pass http://keywarden-api:8000;
}
## REMOVE IN PRODUCTION BUILDS ^^^
location /api/v1/ { location /api/v1/ {
proxy_pass http://api:8000; proxy_pass http://keywarden-api:8000;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
} }
location /healthz { location ~ ^/(healthz|readyz|livez)$ {
proxy_pass http://api:8000; proxy_pass http://keywarden-api:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
} }
} }

View File

@@ -8,6 +8,6 @@ from app.main import app
def test_healthz(): def test_healthz():
client = TestClient(app) client = TestClient(app)
r = client.get("/healthz") r = client.get("/readyz")
assert r.status_code == 200 assert r.status_code == 200
assert r.json() == {"ok": True} assert r.json() == {"status": "ok", "db": "up"}