diff --git a/.drone.yml b/.drone.yml index 20d3ed7..623114f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -100,6 +100,53 @@ volumes: host: path: /tmp/drone_cache +--- +kind: pipeline +type: docker +name: build-and-publish + +trigger: + branch: + - main + event: + - push + - tag + +steps: + - name: build-and-publish-container + image: plugins/docker + settings: + registry: git.local-it.org + repo: git.local-it.org/local-it/mitgliederverwaltung + username: + from_secret: DRONE_REGISTRY_USERNAME + password: + from_secret: DRONE_REGISTRY_TOKEN + auto_tag: true + auto_tag_suffix: ${DRONE_COMMIT_SHA:0:8} + when: + event: + - tag + + - name: build-and-publish-container-branch + image: plugins/docker + settings: + registry: git.local-it.org + repo: git.local-it.org/local-it/mitgliederverwaltung + username: + from_secret: DRONE_REGISTRY_USERNAME + password: + from_secret: DRONE_REGISTRY_TOKEN + tags: + - latest + - ${DRONE_COMMIT_SHA:0:8} + when: + event: + - push + +depends_on: + - check + --- kind: pipeline type: docker diff --git a/.env.example b/.env.example index ccbd6ed..7559b0a 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,16 @@ -OIDC_CLIENT_SECRET= +# Production Environment Variables for docker-compose.prod.yml +# Copy this file to .env and fill in the actual values + +# Required: Phoenix secrets (generate with: mix phx.gen.secret) +SECRET_KEY_BASE=changeme-run-mix-phx.gen.secret +TOKEN_SIGNING_SECRET=changeme-run-mix-phx.gen.secret + +# Required: Hostname for URL generation +PHX_HOST=localhost + +# Optional: OIDC Configuration +# These have defaults in docker-compose.prod.yml, only override if needed +# OIDC_CLIENT_ID=mv +# OIDC_BASE_URL=http://localhost:8080/auth/v1 +# OIDC_REDIRECT_URI=http://localhost:4001/auth/user/rauthy/callback +# OIDC_CLIENT_SECRET=your-rauthy-client-secret diff --git a/Dockerfile b/Dockerfile index 5503e2e..88468a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ ARG BUILDER_IMAGE="hexpm/elixir:1.18.3-erlang-27.3-debian-bullseye-20250317-slim" ARG RUNNER_IMAGE="debian:bullseye-20250317-slim" -FROM ${BUILDER_IMAGE} as builder +FROM ${BUILDER_IMAGE} AS builder # install build dependencies RUN apt-get update -y && apt-get install -y build-essential git \ @@ -70,9 +70,9 @@ RUN apt-get update -y && \ # Set the locale RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US:en +ENV LC_ALL=en_US.UTF-8 WORKDIR "/app" RUN chown nobody /app diff --git a/README.md b/README.md index 60bb115..cf70b81 100644 --- a/README.md +++ b/README.md @@ -183,12 +183,70 @@ Useful `just` commands: - `just reset-database` — reset local DB - `just regen-migrations ` — regenerate migrations -## 📦 Deployment +## 📦 Production Deployment -A production release image is built via the provided `Dockerfile`. -Entrypoint: `/app/bin/server`. +### Local Production Testing + +For testing the production Docker build locally: + +1. **Generate secrets:** + ```bash + mix phx.gen.secret # for SECRET_KEY_BASE + mix phx.gen.secret # for TOKEN_SIGNING_SECRET + ``` + +2. **Create `.env` file:** + ```bash + # Copy template and edit + cp .env.example .env + nano .env + + # Required variables: + SECRET_KEY_BASE= + TOKEN_SIGNING_SECRET= + PHX_HOST=localhost + + # Optional (have defaults in docker-compose.prod.yml): + # OIDC_CLIENT_ID=mv + # OIDC_BASE_URL=http://localhost:8080/auth/v1 + # OIDC_REDIRECT_URI=http://localhost:4001/auth/user/rauthy/callback + # OIDC_CLIENT_SECRET= + ``` + +3. **Start development environment** (for Rauthy): + ```bash + docker compose up -d + ``` + +4. **Start production environment:** + ```bash + docker compose -f docker-compose.prod.yml up + ``` + +5. **Run database migrations:** + ```bash + docker compose -f docker-compose.prod.yml exec app /app/bin/mv eval "Mv.Release.migrate" + ``` + +6. **Access the production app:** + - Production App: http://localhost:4001 + - Uses same Rauthy instance as dev (localhost:8080) + +**Note:** The local production setup uses `network_mode: host` to share localhost with the development Rauthy instance. For real production deployment, configure an external OIDC provider and remove `network_mode: host`. + +### Real Production Deployment + +For actual production deployment: + +1. **Use an external OIDC provider** (not the local Rauthy) +2. **Update `docker-compose.prod.yml`:** + - Remove `network_mode: host` + - Set `OIDC_BASE_URL` to your production OIDC provider + - Configure proper Docker networks +3. **Set up SSL/TLS** (e.g., via reverse proxy like Nginx/Traefik) +4. **Use secure secrets management** (environment variables, Docker secrets, vault) +5. **Configure database backups** -More detailed deployment docs are planned. ## 🤝 Contributing diff --git a/config/prod.exs b/config/prod.exs index 70ea4b0..e0d0670 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -16,5 +16,16 @@ config :swoosh, local: false # Do not print debug messages in production config :logger, level: :info +# AshAuthentication production configuration +# These must be set at compile-time (not in runtime.exs) because +# Application.compile_env!/3 is used in lib/accounts/user.ex +config :mv, :session_identifier, :jti + +config :mv, :require_token_presence_for_authentication, true + +# Token signing secret - using a placeholder that MUST be overridden +# at runtime via environment variable in config/runtime.exs +config :mv, :token_signing_secret, "REPLACE_ME_AT_RUNTIME" + # Runtime production configuration, including reading # of environment variables, is done on config/runtime.exs. diff --git a/config/runtime.exs b/config/runtime.exs index 464d8cd..c50356c 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -53,12 +53,24 @@ if config_env() == :prod do config :mv, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") - config :mv, :rauthy, redirect_uri: "http://localhost:4000/auth/user/rauthy/callback" + # Rauthy OIDC configuration + config :mv, :rauthy, + client_id: System.get_env("OIDC_CLIENT_ID") || "mv", + base_url: System.get_env("OIDC_BASE_URL") || "http://localhost:8080/auth/v1", + client_secret: System.get_env("OIDC_CLIENT_SECRET"), + redirect_uri: + System.get_env("OIDC_REDIRECT_URI") || "http://#{host}:#{port}/auth/user/rauthy/callback" - # AshAuthentication production configuration - config :mv, :session_identifier, :jti + # Token signing secret from environment variable + # This overrides the placeholder value set in prod.exs + token_signing_secret = + System.get_env("TOKEN_SIGNING_SECRET") || + raise """ + environment variable TOKEN_SIGNING_SECRET is missing. + You can generate one by calling: mix phx.gen.secret + """ - config :mv, :require_token_presence_for_authentication, true + config :mv, :token_signing_secret, token_signing_secret config :mv, MvWeb.Endpoint, url: [host: host, port: 443, scheme: "https"], @@ -70,7 +82,13 @@ if config_env() == :prod do ip: {0, 0, 0, 0, 0, 0, 0, 0}, port: port ], - secret_key_base: secret_key_base + secret_key_base: secret_key_base, + # Allow connections from localhost and 127.0.0.1 + check_origin: [ + "//#{host}", + "//localhost:#{port}", + "//127.0.0.1:#{port}" + ] # ## SSL Support # diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..0bb2840 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,38 @@ +services: + app: + image: git.local-it.org/local-it/mitgliederverwaltung:latest + container_name: mv-prod-app + # Use host network for local testing to access localhost:8080 (Rauthy) + # In real production, remove this and use external OIDC provider + network_mode: host + environment: + DATABASE_URL: "ecto://postgres:postgres@localhost:5001/mv_prod" + SECRET_KEY_BASE: "${SECRET_KEY_BASE}" + TOKEN_SIGNING_SECRET: "${TOKEN_SIGNING_SECRET}" + PHX_HOST: "${PHX_HOST}" + PORT: "4001" + PHX_SERVER: "true" + # Rauthy OIDC config - uses localhost because of host network mode + OIDC_CLIENT_ID: "mv" + OIDC_BASE_URL: "http://localhost:8080/auth/v1" + OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET:-}" + OIDC_REDIRECT_URI: "http://localhost:4001/auth/user/rauthy/callback" + depends_on: + - db-prod + restart: unless-stopped + + db-prod: + image: postgres:16-alpine + container_name: mv-prod-db + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: mv_prod + volumes: + - postgres_data_prod:/var/lib/postgresql/data + ports: + - "5001:5432" + restart: unless-stopped + +volumes: + postgres_data_prod: diff --git a/docker-compose.yml b/docker-compose.yml index 297b4af..56876f2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,6 @@ networks: local: rauthy-dev: - driver: bridge services: db: @@ -39,12 +38,8 @@ services: - LISTEN_SCHEME=http - PUB_URL=localhost:8080 - BOOTSTRAP_ADMIN_PASSWORD_PLAIN=RauthyTest12345 - #- HIQLITE=false - #- PG_HOST=db - #- PG_PORT=5432 - #- PG_USER=postgres - #- PG_PASSWORD=postgres - #- PG_DB_NAME=mv_dev + # Disable strict IP validation to allow access from multiple Docker networks + - SESSION_VALIDATE_IP=false ports: - "8080:8080" depends_on: