diff --git a/bundles.yml b/bundles.yml new file mode 100644 index 0000000..5b9120e --- /dev/null +++ b/bundles.yml @@ -0,0 +1,4 @@ +--- + +dependencies: + - url: ../common.git diff --git a/consul/config/service-defaults/vaultwarden.hcl b/consul/config/service-defaults/vaultwarden.hcl new file mode 100644 index 0000000..1cefa64 --- /dev/null +++ b/consul/config/service-defaults/vaultwarden.hcl @@ -0,0 +1,3 @@ +Kind = "service-defaults" +Name = "[[ .vaultwarden.instance ]][[ .consul.suffix ]]" +Protocol = "http" diff --git a/consul/config/service-intentions/vaultwarden.hcl b/consul/config/service-intentions/vaultwarden.hcl new file mode 100644 index 0000000..ced542a --- /dev/null +++ b/consul/config/service-intentions/vaultwarden.hcl @@ -0,0 +1,16 @@ +[[ $c := merge .vaultwarden . -]] +Kind = "service-intentions" +Name = "[[ $c.instance ]][[ $c.consul.suffix ]]" +Sources = [ + { + Name = "[[ $c.traefik.instance ]]" + Permissions = [ + { + Action = "allow" + HTTP { + Methods = ["GET", "HEAD", "POST", "PUT", "DELETE"] + } + } + ] + } +] diff --git a/images/vaultwarden/Dockerfile b/images/vaultwarden/Dockerfile new file mode 100644 index 0000000..114f307 --- /dev/null +++ b/images/vaultwarden/Dockerfile @@ -0,0 +1,72 @@ +FROM rust:alpine AS build + +ARG VAULTWARDEN_FEATURES=[[ join .vaultwarden.server.features "," ]] \ + VAULTWARDEN_SERVER_VERSION=1.29.2 \ + VAULTWARDEN_WEB_VERSION=2023.9.1 + +RUN set -euxo pipefail &&\ + apk --no-cache upgrade &&\ + apk --no-cache add \ + curl \ + ca-certificates \ + tar \ + musl-dev \ +[[- if has .vaultwarden.server.features "postgresql" ]] + postgresql15-dev \ +[[- end ]] +[[- if has .vaultwarden.server.features "mysql" ]] + mariadb-dev \ +[[- end ]] +[[- if has .vaultwarden.server.features "sqlite" ]] + sqlite-dev \ +[[- end ]] + &&\ + cd /tmp &&\ + curl -sSLO https://github.com/dani-garcia/vaultwarden/archive/refs/tags/${VAULTWARDEN_SERVER_VERSION}.tar.gz &&\ + tar xvzf ${VAULTWARDEN_SERVER_VERSION}.tar.gz &&\ + cd vaultwarden-${VAULTWARDEN_SERVER_VERSION} &&\ + rustup target add x86_64-unknown-linux-musl &&\ + cargo build --features=${VAULTWARDEN_FEATURES} --profile "release" --target "x86_64-unknown-linux-musl" &&\ + find ./target -type f -name vaultwarden &&\ + # Move vaultwarden bin to copy it easily in the runtime stage \ + mv ./target/x86_64-unknown-linux-musl/release/vaultwarden / &&\ + chown root:root /vaultwarden &&\ + chmod 755 /vaultwarden &&\ + cd ../ &&\ + curl -sSLO https://github.com/dani-garcia/bw_web_builds/releases/download/v${VAULTWARDEN_WEB_VERSION}/bw_web_v${VAULTWARDEN_WEB_VERSION}.tar.gz &&\ + tar xvzf bw_web_v${VAULTWARDEN_WEB_VERSION}.tar.gz &&\ + mv web-vault / &&\ + chown -R root:root /web-vault + +FROM [[ .docker.repo ]][[ .docker.base_images.alpine.image ]] +MAINTAINER [[ .docker.maintainer ]] + +ENV ROCKET_PROFILE=release \ + ROCKET_ADDRESS=0.0.0.0 \ + ROCKET_PORT=8234 \ + DATA_FOLDER=/data \ + DATABASE_URL=/data/db.sqlite3 + +COPY --from=build /vaultwarden /usr/local/bin/ +COPY --from=build /web-vault /opt/vaultwarden/web-vault + +RUN set -euxo pipefail &&\ + apk --no-cache upgrade &&\ + apk --no-cache add \ + ca-certificates \ + curl \ + openssl \ + tzdata \ + &&\ + addgroup -g 8234 vaultwarden &&\ + adduser --system --ingroup vaultwarden --disabled-password --uid 8234 --home /opt/vaultwarden --shell /sbin/nologin vaultwarden &&\ + mkdir /data &&\ + chown vaultwarden:vaultwarden /data + +WORKDIR /opt/vaultwarden + +USER vaultwarden + +EXPOSE 8234 + +CMD ["vaultwarden"] diff --git a/init/vault-vaultwarden b/init/vault-vaultwarden new file mode 100755 index 0000000..b0f9f3e --- /dev/null +++ b/init/vault-vaultwarden @@ -0,0 +1,8 @@ +#!/bin/sh + +set -euo pipefail + +[[- template "common/vault.mkpgrole.sh.tpl" + dict "ctx" . + "config" (dict "role" .vaultwarden.instance "database" "postgres") +]] diff --git a/prep.d/10-mv_conf.sh b/prep.d/10-mv_conf.sh new file mode 100755 index 0000000..a3e5867 --- /dev/null +++ b/prep.d/10-mv_conf.sh @@ -0,0 +1 @@ +[[ template "common/mv_conf.sh.tpl" dict "ctx" . "services" (dict "vaultwarden" .vaultwarden.instance) ]] diff --git a/variables.yml b/variables.yml new file mode 100644 index 0000000..4b3487e --- /dev/null +++ b/variables.yml @@ -0,0 +1,42 @@ +--- + +vaultwarden: + instance: vaultwarden + server: + count: 1 + image: vaultwarden/server:1.29.2 + #image: danielberteaud/vaultwarden:latest + features: + - postgresql + resources: + cpu: 80 + memory: 128 + + consul: + connect: + upstreams: + - service_name: '[[ .mail.smtp_service_name ]]' + local_bind_port: 25 + + env: + ORG_EVENTS_ENABLED: 'true' + EVENTS_DAYS_RETAIN: 720 + SIGNUPS_VERIFY: 'true' + SMTP_HOST: localhost + SMTP_PORT: 25 + SMTP_FROM: vaultwarden-no-reply@[[ .consul.domain ]] + SMTP_SECURITY: off + TRASH_AUTO_DELETE_DAYS: 7 + INCOMPLETE_2FA_TIME_LIMIT: 5 + USER_ATTACHMENT_LIMIT: 204800 + + public_url: https://vaultwarden.example.org/ + traefik: + middlewares: [] + admin: + traefik: + middlewares: [] + volumes: + data: + type: csi + source: vaultwarden-data diff --git a/vault/policies/vaultwarden.hcl b/vault/policies/vaultwarden.hcl new file mode 100644 index 0000000..dfb68fb --- /dev/null +++ b/vault/policies/vaultwarden.hcl @@ -0,0 +1,4 @@ +[[ $c := merge .vaultwarden . ]] +path "[[ $c.vault.prefix ]]database/creds/[[ $c.instance ]]" { + capabilities = ["read"] +} diff --git a/vaultwarden.nomad.hcl b/vaultwarden.nomad.hcl new file mode 100644 index 0000000..b02aa47 --- /dev/null +++ b/vaultwarden.nomad.hcl @@ -0,0 +1,103 @@ +[[ $c := merge .vaultwarden.server . -]] +job "[[ .vaultwarden.instance ]]" { + +[[ template "common/job_start.tpl" $c ]] + + group "vaultwarden" { + count = [[ $c.count ]] + + network { + mode = "bridge" + } + + volume "data" { + type = [[ .vaultwarden.volumes.data.type | toJSON ]] + source = [[ .vaultwarden.volumes.data.source | toJSON ]] + attachment_mode = "file-system" + access_mode = "multi-node-multi-writer" + } + + service { + name = "[[ .vaultwarden.instance ]][[ $c.consul.suffix ]]" + port = 8234 + +[[ template "common/connect.tpl" $c ]] + + check { + type = "http" + path = "/alive" + expose = true + interval = "5s" + timeout = "3s" + + check_restart { + limit = 20 + grace = "20s" + } + } + + tags = [ +[[- $a := merge .vaultwarden.admin . ]] + "[[ $c.traefik.instance ]].enable=true", +[[- if ne $c.traefik.instance $a.traefik.instance ]] + "[[ $a.traefik.instance ]].enable=true", +[[- end ]] + # Admin interface + "[[ $a.traefik.instance ]].http.routers.[[ .vaultwarden.instance ]][[ $a.consul.suffix ]]-admin.rule=Host(`[[ (urlParse $c.public_url).Hostname ]]`) && PathPrefix(`[[ (urlParse $c.public_url).Path | regexp.Replace "/$" "" ]]/admin`)", + "[[ $a.traefik.instance ]].http.routers.[[ .vaultwarden.instance ]][[ $a.consul.suffix ]]-admin.entrypoints=[[ join $a.traefik.entrypoints "," ]]", + "[[ $a.traefik.instance ]].http.routers.[[ .vaultwarden.instance ]][[ $a.consul.suffix ]]-admin.priority=200", + "[[ $a.traefik.instance ]].http.routers.[[ .vaultwarden.instance ]][[ $a.consul.suffix ]].middlewares=[[ template "common/traefik_middlewares.tpl" $a.traefik ]]", + + # Main interface + "[[ $c.traefik.instance ]].http.routers.[[ .vaultwarden.instance ]][[ $c.consul.suffix ]].rule=Host(`[[ (urlParse $c.public_url).Hostname ]]`) + [[- if not ((urlParse $c.public_url).Path | regexp.Match "^/?$") ]] && PathPrefix(`[[ (urlParse $c.public_url).Path ]]`)[[ end ]]", + "[[ $c.traefik.instance ]].http.routers.[[ .vaultwarden.instance ]][[ $c.consul.suffix ]].entrypoints=[[ join $c.traefik.entrypoints "," ]]", + "[[ $c.traefik.instance ]].http.routers.[[ .vaultwarden.instance ]][[ $c.consul.suffix ]].priority=100", + "[[ $c.traefik.instance ]].http.middlewares.[[ .vaultwarden.instance ]]-csp[[ $c.consul.suffix ]].headers.contentSecurityPolicy=default-src 'self'; img-src 'self' data: https://www.gravatar.com; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; connect-src 'self' https://api.pwnedpasswords.com https://api.2fa.directory", +[[- if not ((urlParse $c.public_url).Path | regexp.Match "^/?$") ]] + "[[ $c.traefik.instance ]].http.middlewares.[[ .vaultwarden.instance ]][[ $c.consul.suffix ]]-prefix.stripprefix.prefixes=[[ (urlParse $c.public_url).Path ]]", + "[[ $c.traefik.instance ]].http.routers.[[ .vaultwarden.instance ]][[ $c.consul.suffix ]].middlewares=[[ .vaultwarden.instance ]]-csp[[ $c.consul.suffix ]],[[ template "common/traefik_middlewares.tpl" $c.traefik ]],[[ .vaultwarden.instance ]][[ $c.consul.suffix ]]-prefix", +[[- else ]] + "[[ $c.traefik.instance ]].http.routers.[[ .vaultwarden.instance ]][[ $c.consul.suffix ]].middlewares=[[ .vaultwarden.instance ]]-csp[[ $c.consul.suffix ]],[[ template "common/traefik_middlewares.tpl" $c.traefik ]]", +[[- end ]] + ] + } + +[[ template "common/task.wait_for.tpl" $c ]] + + task "vaultwarden" { + driver = [[ $c.nomad.driver | toJSON ]] + user = 8234 + + config { + image = [[ $c.image | toJSON ]] + pids_limit = 100 + readonly_rootfs = true + } + + vault { + policies = ["[[ .vaultwarden.instance ]][[ $c.consul.suffix ]]"] + env = false + disable_file = true + } + + env { + ROCKET_ADDRESS = "127.0.0.1" + ROCKET_PORT = 8234 + IP_HEADER = "X-Forwarded-for" + DOMAIN = [[ $c.public_url | toJSON ]] + DB_CONNECTION_RETRIES = 0 +[[ template "common/proxy_env.tpl" $c ]] + } + +[[ template "common/file_env.tpl" $c.env ]] + + volume_mount { + volume = "data" + destination = "/data" + } + +[[ template "common/resources.tpl" $c.resources ]] + } + } +}