Cleanup, add missing wait_for, support pgbouncer
This commit is contained in:
parent
8053aacf4e
commit
40228e52a1
|
@ -1,4 +1,4 @@
|
|||
job [[ .bounca.instance | toJSON ]] {
|
||||
job [[ .instance | toJSON ]] {
|
||||
|
||||
[[- $c := merge .bounca . ]]
|
||||
|
||||
|
@ -10,7 +10,7 @@ job [[ .bounca.instance | toJSON ]] {
|
|||
}
|
||||
|
||||
service {
|
||||
name = "[[ .bounca.instance ]][[ .consul.suffix ]]"
|
||||
name = "[[ .instance ]][[ .consul.suffix ]]"
|
||||
port = 8749
|
||||
|
||||
[[ template "common/connect.tpl" $c ]]
|
||||
|
@ -20,32 +20,35 @@ job [[ .bounca.instance | toJSON ]] {
|
|||
|
||||
[[- if $c.public.traefik.enabled ]]
|
||||
[[ $p := merge .bounca.public . ]]
|
||||
"[[ $p.traefik.instance ]].http.routers.[[ .bounca.instance ]]-public[[ .consul.suffix ]].rule=Host(`[[ (urlParse .bounca.public_url).Hostname ]]`) && PathPrefix(`[[ (urlParse .bounca.public_url).Path ]]/public/`)",
|
||||
"[[ $p.traefik.instance ]].http.routers.[[ .bounca.instance ]]-public[[ .consul.suffix ]].priority=200",
|
||||
"[[ $p.traefik.instance ]].http.routers.[[ .bounca.instance ]]-public[[ .consul.suffix ]].entrypoints=[[ join $p.traefik.entrypoints "," ]]",
|
||||
"[[ $p.traefik.instance ]].http.routers.[[ .instance ]]-public[[ .consul.suffix ]].rule=Host(`[[ (urlParse .bounca.public_url).Hostname ]]`) && PathPrefix(`[[ (urlParse .bounca.public_url).Path ]]/public/`)",
|
||||
"[[ $p.traefik.instance ]].http.routers.[[ .instance ]]-public[[ .consul.suffix ]].priority=200",
|
||||
"[[ $p.traefik.instance ]].http.routers.[[ .instance ]]-public[[ .consul.suffix ]].entrypoints=[[ join $p.traefik.entrypoints "," ]]",
|
||||
[[- if not (regexp.Match "^/?$" (urlParse .bounca.public_url).Path) ]]
|
||||
"[[ $p.traefik.instance ]].http.middlewares.[[ .bounca.instance ]]-public[[ .consul.suffix ]]-prefix.stripprefix.prefixes=[[ (urlParse .bounca.public_url).Path ]]",
|
||||
"[[ $p.traefik.instance ]].http.routers.[[ .bounca.instance ]]-public[[ .consul.suffix ]].middlewares=[[ .bounca.instance ]]-public[[ .consul.suffix ]]-prefix,[[ template "common/traefik_middlewares.tpl" $p.traefik ]]",
|
||||
"[[ $p.traefik.instance ]].http.middlewares.[[ .instance ]]-public[[ .consul.suffix ]]-prefix.stripprefix.prefixes=[[ (urlParse .bounca.public_url).Path ]]",
|
||||
"[[ $p.traefik.instance ]].http.routers.[[ .instance ]]-public[[ .consul.suffix ]].middlewares=[[ .instance ]]-public[[ .consul.suffix ]]-prefix,[[ template "common/traefik_middlewares.tpl" $p.traefik ]]",
|
||||
[[- else ]]
|
||||
"[[ $p.traefik.instance ]].http.routers.[[ .bounca.instance ]]-public[[ .consul.suffix ]].middlewares=[[ template "common/traefik_middlewares.tpl" $p.traefik ]]",
|
||||
"[[ $p.traefik.instance ]].http.routers.[[ .instance ]]-public[[ .consul.suffix ]].middlewares=[[ template "common/traefik_middlewares.tpl" $p.traefik ]]",
|
||||
[[- end ]]
|
||||
[[- end ]]
|
||||
|
||||
"[[ $c.traefik.instance ]].http.routers.[[ .bounca.instance ]][[ .consul.suffix ]].rule=Host(`[[ (urlParse .bounca.public_url).Hostname ]]`)
|
||||
"[[ $c.traefik.instance ]].http.routers.[[ .instance ]][[ .consul.suffix ]].rule=Host(`[[ (urlParse .bounca.public_url).Hostname ]]`)
|
||||
[[- if not (regexp.Match "^/?$" (urlParse .bounca.public_url).Path) ]] && PathPrefix(`[[ (urlParse .bounca.public_url).Path ]]`)[[ end ]]",
|
||||
"[[ $c.traefik.instance ]].http.routers.[[ .bounca.instance ]][[ .consul.suffix ]].priority=100",
|
||||
"[[ $c.traefik.instance ]].http.routers.[[ .bounca.instance ]][[ .consul.suffix ]].entrypoints=[[ join $c.traefik.entrypoints "," ]]",
|
||||
"[[ $c.traefik.instance ]].http.routers.[[ .instance ]][[ .consul.suffix ]].priority=100",
|
||||
"[[ $c.traefik.instance ]].http.routers.[[ .instance ]][[ .consul.suffix ]].entrypoints=[[ join $c.traefik.entrypoints "," ]]",
|
||||
[[- if not (regexp.Match "^/?$" (urlParse .bounca.public_url).Path) ]]
|
||||
"[[ $c.traefik.instance ]].http.middlewares.[[ .bounca.instance ]][[ .consul.suffix ]]-prefix.stripprefix.prefixes=[[ (urlParse .bounca.public_url).Path ]]",
|
||||
"[[ $c.traefik.instance ]].http.routers.[[ .bounca.instance ]][[ .consul.suffix ]].middlewares=[[ .bounca.instance ]][[ .consul.suffix ]]-prefix,[[ template "common/traefik_middlewares.tpl" $c.traefik ]]",
|
||||
"[[ $c.traefik.instance ]].http.middlewares.[[ .instance ]][[ .consul.suffix ]]-prefix.stripprefix.prefixes=[[ (urlParse .bounca.public_url).Path ]]",
|
||||
"[[ $c.traefik.instance ]].http.routers.[[ .instance ]][[ .consul.suffix ]].middlewares=[[ .instance ]][[ .consul.suffix ]]-prefix,[[ template "common/traefik_middlewares.tpl" $c.traefik ]]",
|
||||
[[- else ]]
|
||||
"[[ $c.traefik.instance ]].http.routers.[[ .bounca.instance ]][[ .consul.suffix ]].middlewares=[[ template "common/traefik_middlewares.tpl" $c.traefik ]]",
|
||||
"[[ $c.traefik.instance ]].http.routers.[[ .instance ]][[ .consul.suffix ]].middlewares=[[ template "common/traefik_middlewares.tpl" $c.traefik ]]",
|
||||
[[- end ]]
|
||||
]
|
||||
|
||||
|
||||
}
|
||||
|
||||
[[ template "common/task.wait_for" $c ]]
|
||||
[[ template "common/postgres_pooler" $c ]]
|
||||
|
||||
task "bounca" {
|
||||
driver = [[ $c.nomad.driver | toJSON ]]
|
||||
user = 8749
|
||||
|
@ -58,11 +61,7 @@ job [[ .bounca.instance | toJSON ]] {
|
|||
volumes = ["local/docker_settings.py:/opt/bounca/bounca/docker_settings.py:ro"]
|
||||
}
|
||||
|
||||
vault {
|
||||
policies = ["[[ .bounca.instance ]][[ .consul.suffix ]]"]
|
||||
disable_file = true
|
||||
env = false
|
||||
}
|
||||
[[ template "common/vault.policies" $c ]]
|
||||
|
||||
env {
|
||||
BOUNCA_MODE = "server"
|
||||
|
@ -79,6 +78,21 @@ _EOT
|
|||
destination = "local/docker_settings.py"
|
||||
}
|
||||
|
||||
template {
|
||||
data =<<_EOT
|
||||
[[- if ne $c.postgres.pooler.engine "none" ]]
|
||||
BOUNCA_DB_USER=[[ .instance ]]
|
||||
BOUNCA_DB_PASSWORD={{ env "NOMAD_ALLOC_ID" }}
|
||||
BOUNCA_DB_PORT=6432
|
||||
[[- else ]]
|
||||
BOUNCA_DB_USER={{ with secret "[[ .vault.prefix ]]/database/creds/[[ .instance ]]" }}{{ .Data.username }}{{ end }}
|
||||
BOUNCA_DB_PASSWORD={{ with secret "[[ .vault.prefix ]]/database/creds/[[ .instance ]]" }}{{ .Data.password }}{{ end }}
|
||||
BOUNCA_DB_PORT=[[ $c.postgres.port ]]
|
||||
[[- end ]]
|
||||
_EOT
|
||||
destination = "secrets/.db.env"
|
||||
}
|
||||
|
||||
[[ template "common/resources.tpl" $c.resources ]]
|
||||
}
|
||||
|
||||
|
@ -98,11 +112,7 @@ _EOT
|
|||
[[ template "common/tmpfs.tpl" dict "target" "/tmp" "size" 1000000 ]]
|
||||
}
|
||||
|
||||
vault {
|
||||
policies = ["[[ .bounca.instance ]][[ .consul.suffix ]]"]
|
||||
disable_file = true
|
||||
env = false
|
||||
}
|
||||
[[ template "common/vault.policies" $c ]]
|
||||
|
||||
env {
|
||||
BOUNCA_MODE = "public-exporter"
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
Kind = "service-defaults"
|
||||
Name = "[[ .bounca.instance ]][[ .consul.suffix ]]"
|
||||
Name = "[[ .instance ]][[ .consul.suffix ]]"
|
||||
Protocol = "http"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
Kind = "service-intentions"
|
||||
Name = "[[ .bounca.instance ]][[ .consul.suffix ]]"
|
||||
Name = "[[ .instance ]][[ .consul.suffix ]]"
|
||||
Sources = [
|
||||
{
|
||||
Name = "[[ .traefik.instance ]]"
|
||||
Name = "[[ (merge .bounca.traefik .traefik).instance ]]"
|
||||
Permissions = [
|
||||
{
|
||||
Action = "allow"
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 dani
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
|||
# bounca
|
||||
|
||||
Create a PKI, sign and revoke server and client X.509 v3 SSL certificates
|
|
@ -0,0 +1,292 @@
|
|||
job "bounca" {
|
||||
|
||||
|
||||
datacenters = ["dc1"]
|
||||
|
||||
|
||||
group "bounca" {
|
||||
network {
|
||||
mode = "bridge"
|
||||
}
|
||||
|
||||
service {
|
||||
name = "bounca"
|
||||
port = 8749
|
||||
|
||||
connect {
|
||||
sidecar_service {
|
||||
proxy {
|
||||
upstreams {
|
||||
destination_name = "postgres"
|
||||
local_bind_port = 5432
|
||||
}
|
||||
}
|
||||
}
|
||||
sidecar_task {
|
||||
resources {
|
||||
cpu = 50
|
||||
memory = 64
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tags = [
|
||||
"traefik.enable=true",
|
||||
|
||||
"traefik.http.routers.bounca-public.rule=Host(`pki.example.org`) && PathPrefix(`/public/`)",
|
||||
"traefik.http.routers.bounca-public.priority=200",
|
||||
"traefik.http.routers.bounca-public.entrypoints=https",
|
||||
"traefik.http.routers.bounca-public.middlewares=rate-limit-std@file,inflight-std@file,security-headers@file,hsts@file,compression@file,csp-relaxed@file",
|
||||
|
||||
"traefik.http.routers.bounca.rule=Host(`pki.example.org`)",
|
||||
"traefik.http.routers.bounca.priority=100",
|
||||
"traefik.http.routers.bounca.entrypoints=https",
|
||||
"traefik.http.routers.bounca.middlewares=rate-limit-std@file,inflight-std@file,security-headers@file,hsts@file,compression@file,csp-relaxed@file",
|
||||
]
|
||||
|
||||
|
||||
}
|
||||
|
||||
# wait for required services tp be ready before starting the main task
|
||||
task "wait-for" {
|
||||
|
||||
driver = "docker"
|
||||
user = 1053
|
||||
|
||||
config {
|
||||
image = "danielberteaud/wait-for:24.1-1"
|
||||
readonly_rootfs = true
|
||||
pids_limit = 20
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
hook = "prestart"
|
||||
}
|
||||
|
||||
env {
|
||||
SERVICE_0 = "master.postgres.service.consul"
|
||||
}
|
||||
|
||||
resources {
|
||||
cpu = 10
|
||||
memory = 10
|
||||
memory_max = 30
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
task "bounca" {
|
||||
driver = "docker"
|
||||
user = 8749
|
||||
|
||||
config {
|
||||
image = "danielberteaud/bounca:latest"
|
||||
pids_limit = 50
|
||||
readonly_rootfs = true
|
||||
|
||||
mount {
|
||||
type = "tmpfs"
|
||||
target = "/tmp"
|
||||
tmpfs_options {
|
||||
size = 1000000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
volumes = ["local/docker_settings.py:/opt/bounca/bounca/docker_settings.py:ro"]
|
||||
}
|
||||
|
||||
|
||||
vault {
|
||||
policies = ["bounca"]
|
||||
env = false
|
||||
disable_file = true
|
||||
}
|
||||
|
||||
|
||||
env {
|
||||
BOUNCA_MODE = "server"
|
||||
BOUNCA_UNIX_SOCKET = "/alloc/data/bounca.sock"
|
||||
BOUNCA_HOST = "pki.example.org"
|
||||
}
|
||||
|
||||
|
||||
# Use a template block instead of env {} so we can fetch values from vault
|
||||
template {
|
||||
data = <<_EOT
|
||||
BOUNCA_DB_NAME=bounca
|
||||
BOUNCA_DJANGO_SECRET={{ with secret "/kv/service/bounca" }}{{ .Data.data.django_secret }}{{ end }}
|
||||
LANG=fr_FR.utf8
|
||||
TZ=Europe/Paris
|
||||
_EOT
|
||||
destination = "secrets/.env"
|
||||
perms = 400
|
||||
env = true
|
||||
}
|
||||
|
||||
|
||||
template {
|
||||
data = <<_EOT
|
||||
from bounca.settings import *
|
||||
|
||||
LOGGING: dict = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"verbose": {
|
||||
"format": "%(levelname)s [%(asctime)s] %(name)s %(message)s",
|
||||
},
|
||||
"simple": {"format": "[%(asctime)s] %(message)s"},
|
||||
},
|
||||
"handlers": {
|
||||
"null": {
|
||||
"class": "logging.NullHandler",
|
||||
},
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "simple",
|
||||
},
|
||||
"mail_admins": {"level": "ERROR", "class": "django.utils.log.AdminEmailHandler"},
|
||||
},
|
||||
"root": {
|
||||
"level": "DEBUG",
|
||||
"handlers": ["console"],
|
||||
},
|
||||
"loggers": {},
|
||||
}
|
||||
|
||||
TIME_ZONE = os.environ.get('TZ')
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
"https://pki.example.org"
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_EOT
|
||||
destination = "local/docker_settings.py"
|
||||
}
|
||||
|
||||
template {
|
||||
data = <<_EOT
|
||||
BOUNCA_DB_USER={{ with secret "/database/creds/bounca" }}{{ .Data.username }}{{ end }}
|
||||
BOUNCA_DB_PASSWORD={{ with secret "/database/creds/bounca" }}{{ .Data.password }}{{ end }}
|
||||
BOUNCA_DB_PORT=5432
|
||||
_EOT
|
||||
destination = "secrets/.db.env"
|
||||
}
|
||||
|
||||
resources {
|
||||
cpu = 200
|
||||
memory = 192
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task "public-exporter" {
|
||||
driver = "docker"
|
||||
user = 8749
|
||||
|
||||
lifecycle {
|
||||
hook = "poststart"
|
||||
sidecar = true
|
||||
}
|
||||
|
||||
config {
|
||||
image = "danielberteaud/bounca:latest"
|
||||
pids_limit = 50
|
||||
readonly_rootfs = true
|
||||
|
||||
mount {
|
||||
type = "tmpfs"
|
||||
target = "/tmp"
|
||||
tmpfs_options {
|
||||
size = 1000000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
vault {
|
||||
policies = ["bounca"]
|
||||
env = false
|
||||
disable_file = true
|
||||
}
|
||||
|
||||
|
||||
env {
|
||||
BOUNCA_MODE = "public-exporter"
|
||||
BOUNCA_PUBLIC_DIR = "/alloc/data/public"
|
||||
}
|
||||
|
||||
|
||||
# Use a template block instead of env {} so we can fetch values from vault
|
||||
template {
|
||||
data = <<_EOT
|
||||
BOUNCA_DB_NAME=bounca
|
||||
BOUNCA_DJANGO_SECRET={{ with secret "/kv/service/bounca" }}{{ .Data.data.django_secret }}{{ end }}
|
||||
LANG=fr_FR.utf8
|
||||
TZ=Europe/Paris
|
||||
_EOT
|
||||
destination = "secrets/.env"
|
||||
perms = 400
|
||||
env = true
|
||||
}
|
||||
|
||||
|
||||
resources {
|
||||
cpu = 10
|
||||
memory = 10
|
||||
memory_max = 20
|
||||
}
|
||||
}
|
||||
|
||||
task "nginx" {
|
||||
driver = "docker"
|
||||
user = 8749
|
||||
|
||||
lifecycle {
|
||||
hook = "poststart"
|
||||
sidecar = true
|
||||
}
|
||||
|
||||
config {
|
||||
image = "danielberteaud/bounca:latest"
|
||||
pids_limit = 30
|
||||
readonly_rootfs = true
|
||||
|
||||
mount {
|
||||
type = "tmpfs"
|
||||
target = "/tmp"
|
||||
tmpfs_options {
|
||||
size = 1000000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
env {
|
||||
BOUNCA_MODE = "front"
|
||||
BOUNCA_BIND_ADDR = "127.0.0.1:8749"
|
||||
BOUNCA_UNIX_SOCKET = "/alloc/data/bounca.sock"
|
||||
BOUNCA_PUBLIC_DIR = "/alloc/data/public"
|
||||
BOUNCA_HOST = "pki.example.org"
|
||||
}
|
||||
|
||||
resources {
|
||||
cpu = 20
|
||||
memory = 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
Kind = "service-defaults"
|
||||
Name = "bounca"
|
||||
Protocol = "http"
|
|
@ -0,0 +1,16 @@
|
|||
Kind = "service-intentions"
|
||||
Name = "bounca"
|
||||
Sources = [
|
||||
{
|
||||
Name = "traefik"
|
||||
Permissions = [
|
||||
{
|
||||
Action = "allow"
|
||||
HTTP {
|
||||
PathPrefix = "/"
|
||||
Methods = ["GET", "HEAD", "POST", "OPTIONS", "PUT", "DELETE", "PATCH"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,83 @@
|
|||
# syntax=docker/dockerfile:labs
|
||||
|
||||
FROM python:3.11-alpine AS builder
|
||||
|
||||
ARG BOUNCA_VERSION=0.4.4
|
||||
|
||||
workdir /opt
|
||||
RUN set -euxo pipefail &&\
|
||||
apk --no-cache add \
|
||||
curl \
|
||||
ca-certificates \
|
||||
rdfind \
|
||||
git \
|
||||
nodejs \
|
||||
npm \
|
||||
make \
|
||||
&&\
|
||||
git clone --depth=1 --branch=release/${BOUNCA_VERSION} https://gitlab.com/bounca/bounca.git &&\
|
||||
cd bounca &&\
|
||||
rm -rf .git &&\
|
||||
python3 -m venv venv &&\
|
||||
source /opt/bounca/venv/bin/activate &&\
|
||||
pip --no-cache-dir install -r requirements.txt &&\
|
||||
pip --no-cache-dir install \
|
||||
gunicorn \
|
||||
&&\
|
||||
mkdir -p /var/log/bounca/ &&\
|
||||
cp /opt/bounca/etc/bounca/services.yaml.example /opt/bounca/etc/bounca/services.yaml &&\
|
||||
./manage.py collectstatic --link --noinput &&\
|
||||
rm -f /opt/bounca/etc/bounca/services.yaml &&\
|
||||
sed -i -E 's|/etc/bounca/services.yaml|/tmp/services.yaml|g' /opt/bounca/bounca/settings.py &&\
|
||||
rm -rf docs &&\
|
||||
find /opt/bounca -type d -name '__pycache__' -exec rm -rf "{}" + &&\
|
||||
cd /opt/bounca/front &&\
|
||||
npm install &&\
|
||||
export NODE_OPTIONS=--openssl-legacy-provider &&\
|
||||
make production &&\
|
||||
find ./ -maxdepth 1 \! -name dist -exec rm -rf "{}" \; &&\
|
||||
rm -f results.txt &&\
|
||||
rdfind /opt
|
||||
|
||||
FROM python:3.11-alpine
|
||||
MAINTAINER Daniel Berteaud <dbd@ehtrace.com>
|
||||
|
||||
ENV PATH=/opt/bounca/venv/bin:${PATH} \
|
||||
BOUNCA_UNIX_SOCKET=/tmp/bounca.sock \
|
||||
BOUNCA_HOST='*' \
|
||||
DJANGO_SETTINGS_MODULE=bounca.docker_settings \
|
||||
BOUNCA_BIND_ADDR=0.0.0.0:8749 \
|
||||
BOUNCA_DB_NAME=bounca \
|
||||
BOUNCA_DB_USER=bounca \
|
||||
BOUNCA_DB_PASSWORD=bounca \
|
||||
BOUNCA_DB_HOST=localhost \
|
||||
BOUNCA_DB_PORT=5432 \
|
||||
BOUNCA_DJANGO_SECRET=changeme \
|
||||
BOUNCA_SMTP_SERVER=127.0.0.1 \
|
||||
BOUNCA_SMTP_PORT=25 \
|
||||
BOUNCA_ADMIN_EMAIL=admin@localhost \
|
||||
BOUNCA_FROM_EMAIL=no-reply@localhost \
|
||||
BOUNCA_ADMIN_USER=admin \
|
||||
BOUNCA_ADMIN_PASSWORD=password \
|
||||
BOUNCA_PUBLIC_DIR=/tmp/public \
|
||||
BOUNCA_MODE=all-in-one
|
||||
|
||||
ADD https://git.lapiole.org/nomad/base_tools.git#master /
|
||||
COPY --from=builder /opt /opt
|
||||
|
||||
RUN set -euxo pipefail &&\
|
||||
apk --no-cache add \
|
||||
tini \
|
||||
curl \
|
||||
gettext \
|
||||
openssl \
|
||||
nginx \
|
||||
supervisor \
|
||||
postgresql15-client
|
||||
|
||||
COPY root/ /
|
||||
|
||||
WORKDIR /opt/bounca
|
||||
EXPOSE 8749
|
||||
ENTRYPOINT ["tini", "--", "/entrypoint.sh"]
|
||||
CMD ["bounca"]
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${BOUNCA_MODE}" = "all-in-one" -o "${BOUNCA_MODE}" = "server" ]; then
|
||||
echo "Populating /tmpservices.yaml from /opt/bounca/etc/bounca/services.yaml.template"
|
||||
envsubst < /opt/bounca/etc/bounca/services.yaml.template > /tmp/services.yaml
|
||||
chmod 600 /tmp/services.yaml
|
||||
fi
|
||||
|
||||
if [ "${BOUNCA_MODE}" = "all-in-one" -o "${BOUNCA_MODE}" = "front" ]; then
|
||||
echo "Populating /tmp/nginx.conf from /opt/bounca/etc/nginx/nginx.conf.template"
|
||||
envsubst < /opt/bounca/etc/nginx/nginx.conf.template > /tmp/nginx.conf
|
||||
fi
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -euo pipefail
|
||||
source /opt/bounca/venv/bin/activate
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${BOUNCA_MODE}" != "all-in-one" -a "${BOUNCA_MODE}" != "server" ]; then
|
||||
echo "Not running migration"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Migrating database"
|
||||
cd /opt/bounca
|
||||
./manage.py migrate
|
||||
|
||||
if [ "${BOUNCA_HOST}" != "*" ]; then
|
||||
echo "Configure site URL"
|
||||
./manage.py site ${BOUNCA_HOST}
|
||||
fi
|
||||
|
||||
if [ -n "${BOUNCA_ADMIN_USER}" -a -n "${BOUNCA_ADMIN_PASSWORD}" ]; then
|
||||
echo "Creating admin user ${BOUNCA_ADMIN_USER}"
|
||||
export DJANGO_SUPERUSER_PASSWORD="${BOUNCA_ADMIN_PASSWORD}"
|
||||
./manage.py createsuperuser --noinput --username ${BOUNCA_ADMIN_USER} --email ${BOUNCA_ADMIN_EMAIL} ||\
|
||||
echo "Failed to create user ${BOUNCA_ADMIN_USER} (maybe it already exists ?)"
|
||||
fi
|
|
@ -0,0 +1,8 @@
|
|||
[supervisord]
|
||||
pidfile=/tmp/supervisord.pi
|
||||
nodaemon=true
|
||||
logfile=/dev/stdout
|
||||
logfile_maxbytes=0
|
||||
|
||||
[include]
|
||||
files = supervisord.d/*.ini
|
|
@ -0,0 +1,10 @@
|
|||
[program:gunicorn]
|
||||
command=sh -c "source /opt/bounca/venv/bin/activate && gunicorn --bind=unix:${BOUNCA_UNIX_SOCKET} --threads=4 --max-requests=10000 bounca.wsgi:application"
|
||||
stdout_logfile=/proc/self/fd/1
|
||||
stdout_logfile_backups=0
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/proc/self/fd/2
|
||||
stderr_logfile_backups=0
|
||||
stderr_logfile_maxbytes=0
|
||||
autostart=true
|
||||
autorestart=true
|
|
@ -0,0 +1,10 @@
|
|||
[program:nginx]
|
||||
command=/usr/sbin/nginx -c /tmp/nginx.conf
|
||||
stdout_logfile=/proc/self/fd/1
|
||||
stdout_logfile_backups=0
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/proc/self/fd/2
|
||||
stderr_logfile_backups=0
|
||||
stderr_logfile_maxbytes=0
|
||||
autostart=true
|
||||
autorestart=true
|
|
@ -0,0 +1,10 @@
|
|||
[program:pub-export]
|
||||
command=/usr/local/bin/bounca-pub-export 300
|
||||
stdout_logfile=/proc/self/fd/1
|
||||
stdout_logfile_backups=0
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/proc/self/fd/2
|
||||
stderr_logfile_backups=0
|
||||
stderr_logfile_maxbytes=0
|
||||
autostart=true
|
||||
autorestart=true
|
|
@ -0,0 +1,2 @@
|
|||
[group:bounca]
|
||||
programs=gunicorn,nginx,pub-export
|
|
@ -0,0 +1,35 @@
|
|||
from bounca.settings import *
|
||||
|
||||
LOGGING: dict = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"verbose": {
|
||||
"format": "%(levelname)s [%(asctime)s] %(name)s %(message)s",
|
||||
},
|
||||
"simple": {"format": "[%(asctime)s] %(message)s"},
|
||||
},
|
||||
"handlers": {
|
||||
"null": {
|
||||
"class": "logging.NullHandler",
|
||||
},
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "simple",
|
||||
},
|
||||
"mail_admins": {"level": "ERROR", "class": "django.utils.log.AdminEmailHandler"},
|
||||
},
|
||||
"root": {
|
||||
"level": "DEBUG",
|
||||
"handlers": ["console"],
|
||||
},
|
||||
"loggers": {},
|
||||
}
|
||||
|
||||
TIME_ZONE = os.environ.get('TZ')
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
"http://localhost:%d" % (os.environ.get('BOUNCA_BIND_ADDR').split(':'))[1],
|
||||
"https://%s" % os.environ.get('BOUNCA_HOST')
|
||||
]
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
psql:
|
||||
dbname: ${BOUNCA_DB_NAME}
|
||||
username: ${BOUNCA_DB_USER}
|
||||
password: ${BOUNCA_DB_PASSWORD}
|
||||
host: ${BOUNCA_DB_HOST}
|
||||
port: ${BOUNCA_DB_PORT}
|
||||
|
||||
admin:
|
||||
enabled: True
|
||||
superuser_signup: False
|
||||
|
||||
django:
|
||||
debug: False
|
||||
secret_key: '${BOUNCA_DJANGO_SECRET}'
|
||||
hosts:
|
||||
# add your hosts here
|
||||
- localhost
|
||||
- 127.0.0.1
|
||||
- bounca
|
||||
- ${BOUNCA_HOST}
|
||||
|
||||
mail:
|
||||
host: ${BOUNCA_SMTP_SERVER}
|
||||
port: ${BOUNCA_SMTP_PORT}
|
||||
# port: 587 optionally, only for tls and ssl
|
||||
# username: <user>
|
||||
# password: <password>
|
||||
# connection: none # allowed values: none, tls, ssl
|
||||
admin: ${BOUNCA_ADMIN_EMAIL}
|
||||
from: ${BOUNCA_FROM_EMAIL}
|
||||
|
||||
certificate-engine:
|
||||
# allowed values: ed25519, rsa
|
||||
# Ed25519 is a a modern, fast and safe key algorithm, however not supported by all operating systems, like MacOS.
|
||||
# Keep the 'rsa' option if unsure. Root and intermediate keys are 4096 bits, client and server certificates
|
||||
# use 2048 bits keys.
|
||||
key_algorithm: rsa
|
||||
|
||||
registration:
|
||||
# allowed values: mandatory, optional, off
|
||||
email_verification: off
|
|
@ -0,0 +1,67 @@
|
|||
worker_processes auto;
|
||||
error_log /dev/stderr warn;
|
||||
pid /tmp/nginx.pid;
|
||||
daemon off;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
|
||||
proxy_temp_path /tmp/proxy_temp;
|
||||
client_body_temp_path /tmp/client_temp;
|
||||
fastcgi_temp_path /tmp/fastcgi_temp;
|
||||
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||
scgi_temp_path /tmp/scgi_temp;
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log /dev/stdout main;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
set_real_ip_from 127.0.0.1;
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
upstream bounca {
|
||||
server unix:${BOUNCA_UNIX_SOCKET};
|
||||
}
|
||||
|
||||
server {
|
||||
listen ${BOUNCA_BIND_ADDR} default_server;
|
||||
server_tokens off;
|
||||
|
||||
location /static {
|
||||
root /opt/bounca/media;
|
||||
}
|
||||
location /public {
|
||||
alias ${BOUNCA_PUBLIC_DIR};
|
||||
autoindex on;
|
||||
autoindex_localtime on;
|
||||
autoindex_exact_size off;
|
||||
}
|
||||
location ~ ^/(api|admin) {
|
||||
proxy_pass http://bounca;
|
||||
}
|
||||
location / {
|
||||
root /opt/bounca/front/dist;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source /opt/bounca/venv/bin/activate
|
||||
|
||||
if [ "${BOUNCA_MODE}" = "all-in-one" ]; then
|
||||
exec supervisor -c /etc/supervisord.conf -n
|
||||
elif [ "${BOUNCA_MODE}" = "server" ]; then
|
||||
exec gunicorn \
|
||||
--bind=unix:/alloc/data/bounca.sock \
|
||||
--threads=4 \
|
||||
--max-requests=10000 \
|
||||
bounca.wsgi:application
|
||||
elif [ "${BOUNCA_MODE}" = "front" ]; then
|
||||
exec nginx -c /tmp/nginx.conf
|
||||
elif [ "${BOUNCA_MODE}" = "public-exporter" ]; then
|
||||
exec bounca-pub-export 300
|
||||
else
|
||||
echo "unknown mode (${BOUNCA_MODE})"
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,37 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export PGHOST=${BOUNCA_DB_HOST}
|
||||
export PGPORT=${BOUNCA_DB_PORT}
|
||||
export PGUSER=${BOUNCA_DB_USER}
|
||||
export PGPASSWORD=${BOUNCA_DB_PASSWORD}
|
||||
export PGDATABASE=${BOUNCA_DB_NAME}
|
||||
|
||||
mkdir -p ${BOUNCA_PUBLIC_DIR}
|
||||
|
||||
extract_pub(){
|
||||
echo "Exporting public keys and CRL"
|
||||
for CERT_ID in $(psql -A -q -t -c "SELECT id FROM x509_pki_certificate WHERE type IN ('R', 'I') AND revoked_at IS NULL;"); do
|
||||
CERT_NAME=$(psql -A -q -t -c "SELECT name FROM x509_pki_certificate WHERE id='${CERT_ID}'")
|
||||
echo "Exporting for certificate ${CERT_ID} (${CERT_NAME})"
|
||||
psql -A -q -t -c "SELECT crt FROM x509_pki_keystore WHERE id='${CERT_ID}';" > ${BOUNCA_PUBLIC_DIR}/${CERT_ID}.crt
|
||||
ln -sf ${BOUNCA_PUBLIC_DIR}/${CERT_ID}.crt "${BOUNCA_PUBLIC_DIR}/${CERT_NAME}.crt"
|
||||
if [ "$(psql -A -q -t -c "SELECT COUNT(crl) from x509_pki_crlstore WHERE id='${CERT_ID}'")" != "0" ]; then
|
||||
psql -A -q -t -c "SELECT crl FROM x509_pki_crlstore WHERE id='${CERT_ID}';" > ${BOUNCA_PUBLIC_DIR}/${CERT_ID}.crl
|
||||
ln -sf ${BOUNCA_PUBLIC_DIR}/${CERT_ID}.crl ${BOUNCA_PUBLIC_DIR}/${CERT_NAME}.crl
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Extract once when we start
|
||||
extract_pub
|
||||
|
||||
# First arg of the script is an optional delay between exports.
|
||||
# If set, the script keeps running and export certs and crl every X seconds
|
||||
if [ ${1:-0} -gt 0 ]; then
|
||||
while true; do
|
||||
sleep ${1}
|
||||
extract_pub;
|
||||
done
|
||||
fi
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
vault write database/roles/bounca \
|
||||
db_name="postgres" \
|
||||
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
|
||||
GRANT \"bounca\" TO \"{{name}}\"; \
|
||||
ALTER ROLE \"{{name}}\" SET role = \"bounca\"" \
|
||||
default_ttl="12h" \
|
||||
max_ttl="720h"
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
|
||||
if [ "bounca" != "bounca" ]; then
|
||||
for DIR in vault consul nomad; do
|
||||
if [ -d output/${DIR} ]; then
|
||||
for FILE in $(find output/${DIR} -name "*bounca*.hcl" -type f); do
|
||||
NEW_FILE=$(echo "${FILE}" | sed -E "s/bounca/bounca/g")
|
||||
mv "${FILE}" "${NEW_FILE}"
|
||||
done
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Initialize random passwords if needed
|
||||
|
||||
if ! vault kv list kv/service 2>/dev/null | grep -q -E '^bounca$'; then
|
||||
vault kv put kv/service/bounca \
|
||||
django_secret=$(pwgen -s -n 50 1)
|
||||
fi
|
||||
|
||||
for PWD in django_secret; do
|
||||
if ! vault kv get -field ${PWD} kv/service/bounca >/dev/null 2>&1; then
|
||||
vault kv patch kv/service/bounca \
|
||||
${PWD}=$(pwgen -s -n 50 1)
|
||||
fi
|
||||
done
|
|
@ -0,0 +1,7 @@
|
|||
path "kv/data/service/bounca" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "database/creds/bounca" {
|
||||
capabilities = ["read"]
|
||||
}
|
|
@ -4,5 +4,5 @@ set -euo pipefail
|
|||
|
||||
[[- template "common/vault.mkpgrole.sh.tpl"
|
||||
dict "ctx" .
|
||||
"config" (dict "role" .bounca.instance "database" "postgres")
|
||||
"config" (dict "role" .instance "database" "postgres")
|
||||
]]
|
||||
|
|
|
@ -1 +1 @@
|
|||
[[ template "common/mv_conf.sh.tpl" dict "ctx" . "services" (dict "bounca" .bounca.instance) ]]
|
||||
[[ template "common/mv_conf.sh" dict "ctx" . "services" (dict "bounca" .instance) ]]
|
||||
|
|
|
@ -4,14 +4,14 @@ set -euo pipefail
|
|||
|
||||
# Initialize random passwords if needed
|
||||
|
||||
if ! vault kv list [[ .vault.prefix ]]kv/service 2>/dev/null | grep -q -E '^[[ .bounca.instance ]]$'; then
|
||||
vault kv put [[ .vault.prefix ]]kv/service/[[ .bounca.instance ]] \
|
||||
if ! vault kv list [[ .vault.prefix ]]kv/service 2>/dev/null | grep -q -E '^[[ .instance ]]$'; then
|
||||
vault kv put [[ .vault.prefix ]]kv/service/[[ .instance ]] \
|
||||
django_secret=$(pwgen -s -n 50 1)
|
||||
fi
|
||||
|
||||
for PWD in django_secret; do
|
||||
if ! vault kv get -field ${PWD} [[ .vault.prefix ]]kv/service/[[ .bounca.instance ]] >/dev/null 2>&1; then
|
||||
vault kv patch [[ .vault.prefix ]]kv/service/[[ .bounca.instance ]] \
|
||||
if ! vault kv get -field ${PWD} [[ .vault.prefix ]]kv/service/[[ .instance ]] >/dev/null 2>&1; then
|
||||
vault kv patch [[ .vault.prefix ]]kv/service/[[ .instance ]] \
|
||||
${PWD}=$(pwgen -s -n 50 1)
|
||||
fi
|
||||
done
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
---
|
||||
|
||||
bounca:
|
||||
# Name of this instance (controls job and service name)
|
||||
instance: bounca
|
||||
|
||||
# Name of this instance (controls job and service name)
|
||||
instance: bounca
|
||||
bounca:
|
||||
|
||||
# The image to use
|
||||
image: danielberteaud/bounca:latest
|
||||
|
||||
# Vault policies to use
|
||||
vault:
|
||||
policies:
|
||||
- '[[ .instance ]][[ .consul.suffix ]]'
|
||||
|
||||
postgres:
|
||||
database: '[[ .instance ]]'
|
||||
user: '{{ with secret "[[ .vault.prefix ]]/database/creds/[[ .instance ]]" }}{{ .Data.username }}{{ end }}'
|
||||
password: '{{ with secret "[[ .vault.prefix ]]/database/creds/[[ .instance ]]" }}{{ .Data.password }}{{ end }}'
|
||||
|
||||
# Env variable to pass to the container
|
||||
env:
|
||||
BOUNCA_DB_USER: '{{ with secret "[[ .vault.prefix ]]/database/creds/[[ .bounca.instance ]]" }}{{ .Data.username }}{{ end }}'
|
||||
BOUNCA_DB_PASSWORD: '{{ with secret "[[ .vault.prefix ]]/database/creds/[[ .bounca.instance ]]" }}{{ .Data.password }}{{ end }}'
|
||||
BOUNCA_DJANGO_SECRET: '{{ with secret "[[ .vault.prefix ]]/kv/service/[[ .bounca.instance ]]" }}{{ .Data.data.django_secret }}{{ end }}'
|
||||
BOUNCA_DB_NAME: '[[ .bounca.postgres.database ]]'
|
||||
BOUNCA_DJANGO_SECRET: '{{ with secret "[[ .vault.prefix ]]/kv/service/[[ .instance ]]" }}{{ .Data.data.django_secret }}{{ end }}'
|
||||
|
||||
# Public URL where user can reach the app
|
||||
public_url: https://pki.example.org
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
path "[[ .vault.prefix ]]kv/data/service/[[ .bounca.instance ]]" {
|
||||
path "[[ .vault.prefix ]]kv/data/service/[[ .instance ]]" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "[[ .vault.prefix ]]database/creds/[[ .bounca.instance ]]" {
|
||||
path "[[ .vault.prefix ]]database/creds/[[ .instance ]]" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue