From 3b04c88ff8e5d9c1e8ab6901b5e39797630cd4ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Fri, 23 Jun 2023 09:23:42 +0200 Subject: [PATCH] [DEVOPS-1204] Migrate unified & it's build pipeline to self-host repo (#109) * Move docker unified folder from server repo * Add build self host workflow * Change checkout to master * Fix name * Change workflow triggers * Use clients repoin build * Revert dockerfile * Fix build * USer server branch input * Add flag * FIx * Add check for branch * Fix * Fix dockerfile * Change name of the workflow --- .github/workflows/build-self-host.yml | 16 - .github/workflows/build-unified.yml | 190 ++++++++++++ docker-unified/.env.example | 3 + docker-unified/Dockerfile | 283 ++++++++++++++++++ docker-unified/docker-compose.yml | 56 ++++ docker-unified/entrypoint.sh | 108 +++++++ docker-unified/hbs/app-id.hbs | 15 + docker-unified/hbs/config.yaml | 7 + docker-unified/hbs/nginx-config.hbs | 189 ++++++++++++ docker-unified/nginx/logrotate.sh | 15 + docker-unified/nginx/mime.types | 138 +++++++++ docker-unified/nginx/nginx.conf | 147 +++++++++ docker-unified/nginx/proxy.conf | 27 ++ .../nginx/security-headers-ssl.conf | 2 + docker-unified/nginx/security-headers.conf | 3 + docker-unified/settings.env | 69 +++++ docker-unified/supervisord/admin.ini | 9 + docker-unified/supervisord/api.ini | 9 + docker-unified/supervisord/events.ini | 9 + docker-unified/supervisord/icons.ini | 9 + docker-unified/supervisord/identity.ini | 10 + docker-unified/supervisord/nginx.ini | 7 + docker-unified/supervisord/notifications.ini | 9 + docker-unified/supervisord/scim.ini | 9 + docker-unified/supervisord/sso.ini | 9 + docker-unified/supervisord/supervisord.conf | 15 + 26 files changed, 1347 insertions(+), 16 deletions(-) delete mode 100644 .github/workflows/build-self-host.yml create mode 100644 .github/workflows/build-unified.yml create mode 100644 docker-unified/.env.example create mode 100644 docker-unified/Dockerfile create mode 100644 docker-unified/docker-compose.yml create mode 100755 docker-unified/entrypoint.sh create mode 100644 docker-unified/hbs/app-id.hbs create mode 100644 docker-unified/hbs/config.yaml create mode 100644 docker-unified/hbs/nginx-config.hbs create mode 100644 docker-unified/nginx/logrotate.sh create mode 100644 docker-unified/nginx/mime.types create mode 100644 docker-unified/nginx/nginx.conf create mode 100644 docker-unified/nginx/proxy.conf create mode 100644 docker-unified/nginx/security-headers-ssl.conf create mode 100644 docker-unified/nginx/security-headers.conf create mode 100644 docker-unified/settings.env create mode 100644 docker-unified/supervisord/admin.ini create mode 100644 docker-unified/supervisord/api.ini create mode 100644 docker-unified/supervisord/events.ini create mode 100644 docker-unified/supervisord/icons.ini create mode 100644 docker-unified/supervisord/identity.ini create mode 100644 docker-unified/supervisord/nginx.ini create mode 100644 docker-unified/supervisord/notifications.ini create mode 100644 docker-unified/supervisord/scim.ini create mode 100644 docker-unified/supervisord/sso.ini create mode 100644 docker-unified/supervisord/supervisord.conf diff --git a/.github/workflows/build-self-host.yml b/.github/workflows/build-self-host.yml deleted file mode 100644 index f644b15..0000000 --- a/.github/workflows/build-self-host.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Build Self-Host - -on: - workflow_dispatch: - -jobs: - stub: - name: Stub - runs-on: ubuntu-22.04 - steps: - - name: Checkout repo - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Stub - run: echo "Stub" diff --git a/.github/workflows/build-unified.yml b/.github/workflows/build-unified.yml new file mode 100644 index 0000000..f1683ba --- /dev/null +++ b/.github/workflows/build-unified.yml @@ -0,0 +1,190 @@ +--- +name: Build Self-Host Unified + +on: + push: + paths: + - "docker-unified/**" + - ".github/workflows/build-self-host.yml" + workflow_dispatch: + inputs: + server_branch: + description: "Server branch name to deploy (examples: 'master', 'rc', 'feature/sm')" + required: true + type: string + default: master + pull_request: + paths: + - ".github/workflows/build-self-host.yml" + - "docker-unified/**" + +jobs: + build-docker: + name: Build Docker image + runs-on: ubuntu-22.04 + steps: + - name: Checkout repo + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + + - name: Get server branch to checkout + id: server-branch-name + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + SERVER_BRANCH: ${{ github.event.inputs.server_branch }} + run: | + if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then + echo "server_branch=$SERVER_BRANCH" >> $GITHUB_OUTPUT + echo "Branch: $SERVER_BRANCH" + else + echo "server_branch=master" >> $GITHUB_OUTPUT + echo "Branch: master" + fi + + - name: Check Branch to Publish + env: + PUBLISH_BRANCHES: "master,rc,hotfix-rc" + SERVER_BRANCH: ${{ steps.server-branch-name.outputs.server_branch }} + id: publish-branch-check + run: | + IFS="," read -a publish_branches <<< $PUBLISH_BRANCHES + + if [[ " ${publish_branches[*]} " =~ " ${GITHUB_REF:11} " && " ${publish_branches[*]} " =~ " ${SERVER_BRANCH} " ]]; then + echo "is_publish_branch=true" >> $GITHUB_ENV + else + echo "is_publish_branch=false" >> $GITHUB_ENV + fi + + ########## Set up Docker ########## + - name: Set up QEMU emulators + uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0 + + ########## Login to Docker registries ########## + - name: Login to Azure - QA Subscription + uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 + with: + creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} + + - name: Login to Azure ACR + run: az acr login -n bitwardenqa + + - name: Login to Azure - Prod Subscription + uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 + with: + creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} + + - name: Login to Azure ACR + run: az acr login -n bitwardenprod + + - name: Login to Azure - CI Subscription + uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve github PAT secrets + id: retrieve-secret-pat + uses: bitwarden/gh-actions/get-keyvault-secrets@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b + with: + keyvault: "bitwarden-ci" + secrets: "github-pat-bitwarden-devops-bot-repo-scope" + + - name: Setup Docker Trust + if: ${{ env.is_publish_branch == 'true' }} + uses: bitwarden/gh-actions/setup-docker-trust@f955298c7a982b3fb5dbb73afd582c584fd5beec + with: + azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + azure-keyvault-name: "bitwarden-ci" + + ########## Generate image tag and build Docker image ########## + - name: Generate Docker image tag + id: tag + run: | + IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name + if [[ "$IMAGE_TAG" == "master" ]]; then + IMAGE_TAG=dev + elif [[ "$IMAGE_TAG" == "rc" ]] || [[ "$IMAGE_TAG" == "hotfix-rc" ]]; then + IMAGE_TAG=beta + fi + + echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + + - name: Generate tag list + id: tag-list + env: + IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} + run: | + if [ "$IMAGE_TAG" = "dev" ] || [ "$IMAGE_TAG" = "beta" ]; then + echo "tags=bitwardenqa.azurecr.io/self-host:${IMAGE_TAG},bitwardenprod.azurecr.io/self-host:${IMAGE_TAG},bitwarden/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT + else + echo "tags=bitwardenqa.azurecr.io/self-host:${IMAGE_TAG},bitwardenprod.azurecr.io/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT + fi + + - name: Checkout server repo + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + with: + repository: bitwarden/server + token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + ref: ${{ steps.server-branch-name.outputs.server_branch }} + path: 'server' + + - name: Build and push Docker image + uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # v3.2.0 + with: + context: . + file: docker-unified/Dockerfile + platforms: | + linux/amd64, + linux/arm/v7, + linux/arm64/v8 + push: true + tags: ${{ steps.tag-list.outputs.tags }} + secrets: | + "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" + + - name: Log out of Docker and disable Docker Notary + if: ${{ env.is_publish_branch == 'true' }} + run: | + docker logout + echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV + + check-failures: + name: Check for failures + if: always() + runs-on: ubuntu-22.04 + needs: build-docker + steps: + - name: Check if any job failed + if: | + github.ref == 'refs/heads/master' + || github.ref == 'refs/heads/rc' + || github.ref == 'refs/heads/hotfix-rc' + env: + BUILD_DOCKER_STATUS: ${{ needs.build-docker.result }} + run: | + if [ "$BUILD_DOCKER_STATUS" = "failure" ]; then + exit 1 + fi + + - name: Login to Azure - CI subscription + uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 + if: failure() + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b + if: failure() + with: + keyvault: "bitwarden-ci" + secrets: "devops-alerts-slack-webhook-url" + + - name: Notify Slack on failure + uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0 + if: failure() + env: + SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} + with: + status: ${{ job.status }} diff --git a/docker-unified/.env.example b/docker-unified/.env.example new file mode 100644 index 0000000..06a987d --- /dev/null +++ b/docker-unified/.env.example @@ -0,0 +1,3 @@ +COMPOSE_PROJECT_NAME=bitwarden +REGISTRY=bitwarden +TAG=dev diff --git a/docker-unified/Dockerfile b/docker-unified/Dockerfile new file mode 100644 index 0000000..7ae228b --- /dev/null +++ b/docker-unified/Dockerfile @@ -0,0 +1,283 @@ +# syntax = docker/dockerfile:1.2 +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM debian AS web-setup + +# Add packages +RUN apt-get update && apt-get install -y \ + curl \ + jq \ + unzip \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /tmp + +# Download tags from 'clients' repository +RUN --mount=type=secret,id=GH_PAT,target=/etc/secrets/GH_PAT if [ -e "/etc/secrets/GH_PAT" ]; then \ +curl --header "Authorization: token $(cat /etc/secrets/GH_PAT)" \ + https://api.github.com/repos/bitwarden/clients/git/refs/tags --output tags.json ; else \ + curl https://api.github.com/repos/bitwarden/clients/git/refs/tags --output tags.json ; fi + +RUN cat tags.json + +# Grab last tag/release of the 'web' client +RUN cat tags.json | jq -r 'last(.[] | select(.ref|test("refs/tags/web-v[0-9]{4}.[0-9]{1,2}.[0-9]+"))) | .ref | split("/")[2]' > tag.txt + +# Extract the version of the 'web' client +RUN cat tag.txt | grep -o -E "[0-9]{4}\.[0-9]{1,2}\.[0-9]+" > version.txt + +# Download the built release artifact for the 'web' client +RUN TAG=$(cat tag.txt) \ + && VERSION=$(cat version.txt) \ + && curl -L https://github.com/bitwarden/clients/releases/download/$TAG/web-$VERSION-selfhosted-COMMERCIAL.zip -O + +# Unzip the 'web' client to /tmp/build +RUN VERSION=$(cat version.txt) \ + && unzip web-$VERSION-selfhosted-COMMERCIAL.zip + +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:6.0 AS dotnet-build + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +# Determine proper runtime value for .NET +# We put the value in a file to be read by later layers. +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ + RID=linux-arm ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Add packages +RUN apt-get update && apt-get install -y \ + npm \ + && rm -rf /var/lib/apt/lists/* + +RUN npm install -g gulp + +# Copy csproj files as distinct layers +WORKDIR /source +COPY server/src/Admin/*.csproj ./src/Admin/ +COPY server/src/Api/*.csproj ./src/Api/ +COPY server/src/Events/*.csproj ./src/Events/ +COPY server/src/Icons/*.csproj ./src/Icons/ +COPY server/src/Identity/*.csproj ./src/Identity/ +COPY server/src/Notifications/*.csproj ./src/Notifications/ +COPY server/bitwarden_license/src/Sso/*.csproj ./bitwarden_license/src/Sso/ +COPY server/bitwarden_license/src/Scim/*.csproj ./bitwarden_license/src/Scim/ +COPY server/src/Core/*.csproj ./src/Core/ +COPY server/src/Infrastructure.Dapper/*.csproj ./src/Infrastructure.Dapper/ +COPY server/src/Infrastructure.EntityFramework/*.csproj ./src/Infrastructure.EntityFramework/ +COPY server/src/SharedWeb/*.csproj ./src/SharedWeb/ +COPY server/util/Migrator/*.csproj ./util/Migrator/ +COPY server/util/MySqlMigrations/*.csproj ./util/MySqlMigrations/ +COPY server/util/PostgresMigrations/*.csproj ./util/PostgresMigrations/ +COPY server/util/SqliteMigrations/*.csproj ./util/SqliteMigrations/ +COPY server/bitwarden_license/src/Commercial.Core/*.csproj ./bitwarden_license/src/Commercial.Core/ +COPY server/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/*.csproj ./bitwarden_license/src/Commercial.Infrastructure.EntityFramework/ +COPY server/Directory.Build.props . + +# Restore Admin project dependencies and tools +WORKDIR /source/src/Admin +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Restore Api project dependencies and tools +WORKDIR /source/src/Api +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Restore Events project dependencies and tools +WORKDIR /source/src/Events +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Restore Icons project dependencies and tools +WORKDIR /source/src/Icons +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Restore Identity project dependencies and tools +WORKDIR /source/src/Identity +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Restore Notifications project dependencies and tools +WORKDIR /source/src/Notifications +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Restore Sso project dependencies and tools +WORKDIR /source/bitwarden_license/src/Sso +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Restore Scim project dependencies and tools +WORKDIR /source/bitwarden_license/src/Scim +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Copy required project files +WORKDIR /source +COPY server/src/Admin/. ./src/Admin/ +COPY server/src/Api/. ./src/Api/ +COPY server/src/Events/. ./src/Events/ +COPY server/src/Icons/. ./src/Icons/ +COPY server/src/Identity/. ./src/Identity/ +COPY server/src/Notifications/. ./src/Notifications/ +COPY server/bitwarden_license/src/Sso/. ./bitwarden_license/src/Sso/ +COPY server/bitwarden_license/src/Scim/. ./bitwarden_license/src/Scim/ +COPY server/src/Core/. ./src/Core/ +COPY server/src/Infrastructure.Dapper/. ./src/Infrastructure.Dapper/ +COPY server/src/Infrastructure.EntityFramework/. ./src/Infrastructure.EntityFramework/ +COPY server/src/SharedWeb/. ./src/SharedWeb/ +COPY server/util/Migrator/. ./util/Migrator/ +COPY server/util/MySqlMigrations/. ./util/MySqlMigrations/ +COPY server/util/PostgresMigrations/. ./util/PostgresMigrations/ +COPY server/util/SqliteMigrations/. ./util/SqliteMigrations/ +COPY server/util/EfShared/. ./util/EfShared/ +COPY server/bitwarden_license/src/Commercial.Core/. ./bitwarden_license/src/Commercial.Core/ +COPY server/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/. ./bitwarden_license/src/Commercial.Infrastructure.EntityFramework/ +COPY server/.git/. ./.git/ + +# Build Admin app +WORKDIR /source/src/Admin +RUN npm install +RUN gulp --gulpfile "gulpfile.js" build +RUN . /tmp/rid.txt && dotnet publish -c release -o /app/Admin --no-restore --no-self-contained -r $RID + +# Build Api app +WORKDIR /source/src/Api +RUN . /tmp/rid.txt && dotnet publish -c release -o /app/Api --no-restore --no-self-contained -r $RID + +# Build Events app +WORKDIR /source/src/Events +RUN . /tmp/rid.txt && dotnet publish -c release -o /app/Events --no-restore --no-self-contained -r $RID + +# Build Icons app +WORKDIR /source/src/Icons +RUN . /tmp/rid.txt && dotnet publish -c release -o /app/Icons --no-restore --no-self-contained -r $RID + +# Build Identity app +WORKDIR /source/src/Identity +RUN . /tmp/rid.txt && dotnet publish -c release -o /app/Identity --no-restore --no-self-contained -r $RID + +# Build Notifications app +WORKDIR /source/src/Notifications +RUN . /tmp/rid.txt && dotnet publish -c release -o /app/Notifications --no-restore --no-self-contained -r $RID + +# Build Sso app +WORKDIR /source/bitwarden_license/src/Sso +RUN npm install +RUN gulp --gulpfile "gulpfile.js" build +RUN . /tmp/rid.txt && dotnet publish -c release -o /app/Sso --no-restore --no-self-contained -r $RID + +# Build Scim app +WORKDIR /source/bitwarden_license/src/Scim +RUN . /tmp/rid.txt && dotnet publish -c release -o /app/Scim --no-restore --no-self-contained -r $RID + +############################################### +# App stage # +############################################### +FROM mcr.microsoft.com/dotnet/aspnet:6.0 +ARG TARGETPLATFORM +LABEL com.bitwarden.product="bitwarden" +LABEL com.bitwarden.project="unified" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV BW_ENABLE_ADMIN=true +ENV BW_ENABLE_API=true +ENV BW_ENABLE_EVENTS=false +ENV BW_ENABLE_ICONS=true +ENV BW_ENABLE_IDENTITY=true +ENV BW_ENABLE_NOTIFICATIONS=true +ENV BW_ENABLE_SCIM=false +ENV BW_ENABLE_SSO=false +ENV BW_DB_FILE="/etc/bitwarden/vault.db" +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__selfHosted="true" +ENV globalSettings__unifiedDeployment="true" +ENV globalSettings__pushRelayBaseUri="https://push.bitwarden.com" +ENV globalSettings__baseServiceUri__internalAdmin="http://localhost:5000" +ENV globalSettings__baseServiceUri__internalApi="http://localhost:5001" +ENV globalSettings__baseServiceUri__internalEvents="http://localhost:5003" +ENV globalSettings__baseServiceUri__internalIcons="http://localhost:5004" +ENV globalSettings__baseServiceUri__internalIdentity="http://localhost:5005" +ENV globalSettings__baseServiceUri__internalNotifications="http://localhost:5006" +ENV globalSettings__baseServiceUri__internalSso="http://localhost:5007" +ENV globalSettings__baseServiceUri__internalScim="http://localhost:5002" +ENV globalSettings__baseServiceUri__internalVault="http://localhost:8080" +ENV globalSettings__identityServer__certificatePassword="default_cert_password" +ENV globalSettings__dataProtection__directory="/etc/bitwarden/data-protection" +ENV globalSettings__attachment__baseDirectory="/etc/bitwarden/attachments" +ENV globalSettings__send__baseDirectory="/etc/bitwarden/attachments/send" +ENV globalSettings__licenseDirectory="/etc/bitwarden/licenses" +ENV globalSettings__logDirectoryByProject="false" +ENV globalSettings__logRollBySizeLimit="1073741824" + +# Add packages +RUN apt-get update && apt-get install -y \ + curl \ + nginx \ + openssl \ + supervisor \ + tzdata \ + unzip \ + && rm -rf /var/lib/apt/lists/* + +# Create required directories +RUN mkdir -p /etc/bitwarden/attachments/send +RUN mkdir -p /etc/bitwarden/data-protection +RUN mkdir -p /etc/bitwarden/licenses +RUN mkdir -p /etc/bitwarden/logs +RUN mkdir -p /etc/supervisor +RUN mkdir -p /etc/supervisor.d +RUN mkdir -p /var/log/bitwarden +RUN mkdir -p /var/log/nginx/logs +RUN mkdir -p /etc/nginx/http.d +RUN mkdir -p /var/run/nginx +RUN mkdir -p /var/lib/nginx/tmp +RUN touch /var/run/nginx/nginx.pid +RUN mkdir -p /app + +# Copy all apps from dotnet-build stage +WORKDIR /app +COPY --from=dotnet-build /app ./ + +# Copy Web files from web-setup stage +COPY --from=web-setup /tmp/build /app/Web + +# Set up supervisord +COPY docker-unified/supervisord/*.ini /etc/supervisor.d/ +COPY docker-unified/supervisord/supervisord.conf /etc/supervisor/supervisord.conf +RUN rm -f /etc/supervisord.conf + +# Set up nginx +COPY docker-unified/nginx/nginx.conf /etc/nginx +COPY docker-unified/nginx/proxy.conf /etc/nginx +COPY docker-unified/nginx/mime.types /etc/nginx +COPY docker-unified/nginx/security-headers.conf /etc/nginx +COPY docker-unified/nginx/security-headers-ssl.conf /etc/nginx +COPY docker-unified/nginx/logrotate.sh / +RUN chmod +x /logrotate.sh + +# Copy configuration templates +COPY docker-unified/hbs/nginx-config.hbs /etc/hbs/ +COPY docker-unified/hbs/app-id.hbs /etc/hbs/ +COPY docker-unified/hbs/config.yaml /etc/hbs/ + +# Download hbs tool for generating final configurations +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ] ; then curl -L --output hbs.zip https://github.com/bitwarden/Handlebars.conf/releases/download/v1.3.0/hbs_linux-x64_dotnet.zip; fi +RUN if [ "$TARGETPLATFORM" = "linux/arm/v7" ] ; then curl -L --output hbs.zip https://github.com/bitwarden/Handlebars.conf/releases/download/v1.3.0/hbs_linux-armv7_dotnet.zip; fi +RUN if [ "$TARGETPLATFORM" = "linux/arm64" ] ; then curl -L --output hbs.zip https://github.com/bitwarden/Handlebars.conf/releases/download/v1.3.0/hbs_linux-arm64_dotnet.zip; fi + +# Extract hbs +RUN unzip hbs.zip -d /usr/local/bin && rm hbs.zip +RUN chmod +x /usr/local/bin/hbs + +# Copy entrypoint script and make it executable +COPY docker-unified/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +VOLUME ["/etc/bitwarden"] + +WORKDIR /app +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker-unified/docker-compose.yml b/docker-unified/docker-compose.yml new file mode 100644 index 0000000..25e3bcd --- /dev/null +++ b/docker-unified/docker-compose.yml @@ -0,0 +1,56 @@ +--- +version: "3.8" + +services: + bitwarden: + depends_on: + - db + env_file: + - settings.env + image: ${REGISTRY:-bitwarden}/self-host:${TAG:-beta} + restart: always + ports: + - "80:8080" + - "443:8443" + volumes: + - bitwarden:/etc/bitwarden + - logs:/var/log/bitwarden + + # MariaDB Example + db: + environment: + MARIADB_USER: "bitwarden" + MARIADB_PASSWORD: "super_strong_password" + MARIADB_DATABASE: "bitwarden_vault" + MARIADB_RANDOM_ROOT_PASSWORD: "true" + image: mariadb:10 + restart: always + volumes: + - data:/var/lib/mysql + + # PostgreSQL Example + # db: + # environment: + # POSTGRES_USER: "bitwarden" + # POSTGRES_PASSWORD: "super_strong_password" + # POSTGRES_DB: "bitwarden_vault" + # image: postgres:14 + # restart: always + # volumes: + # - data:/var/lib/postgresql/data + + # MS SQL Server Example + # Docs: https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-docker-container-deployment + # db: + # environment: + # MSSQL_SA_PASSWORD: "super_strong_password" + # ACCEPT_EULA: Y + # image: mcr.microsoft.com/mssql/server:2019-latest + # restart: always + # volumes: + # - data:/var/opt/mssql + +volumes: + bitwarden: + logs: + data: diff --git a/docker-unified/entrypoint.sh b/docker-unified/entrypoint.sh new file mode 100755 index 0000000..a04337c --- /dev/null +++ b/docker-unified/entrypoint.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +# Set up user group +PGID="${PGID:-1000}" +addgroup --gid $PGID bitwarden + +# Set up user +PUID="${PUID:-1000}" +adduser --no-create-home --shell /bin/bash --disabled-password --uid $PUID --gid $PGID --gecos "" bitwarden + +# Translate environment variables for application settings +VAULT_SERVICE_URI=https://$BW_DOMAIN +MYSQL_CONNECTION_STRING="server=$BW_DB_SERVER;port=${BW_DB_PORT:-3306};database=$BW_DB_DATABASE;user=$BW_DB_USERNAME;password=$BW_DB_PASSWORD" +POSTGRESQL_CONNECTION_STRING="Host=$BW_DB_SERVER;Port=${BW_DB_PORT:-5432};Database=$BW_DB_DATABASE;Username=$BW_DB_USERNAME;Password=$BW_DB_PASSWORD" +SQLSERVER_CONNECTION_STRING="Server=$BW_DB_SERVER,${BW_DB_PORT:-1433};Database=$BW_DB_DATABASE;User Id=$BW_DB_USERNAME;Password=$BW_DB_PASSWORD;Encrypt=True;TrustServerCertificate=True" +SQLITE_CONNECTION_STRING="Data Source=$BW_DB_FILE;" +INTERNAL_IDENTITY_KEY=$(openssl rand -hex 30) +OIDC_IDENTITY_CLIENT_KEY=$(openssl rand -hex 30) +DUO_AKEY=$(openssl rand -hex 30) + +export globalSettings__baseServiceUri__vault=${globalSettings__baseServiceUri__vault:-$VAULT_SERVICE_URI} +export globalSettings__installation__id=$BW_INSTALLATION_ID +export globalSettings__installation__key=$BW_INSTALLATION_KEY +export globalSettings__internalIdentityKey=${globalSettings__internalIdentityKey:-$INTERNAL_IDENTITY_KEY} +export globalSettings__oidcIdentityClientKey=${globalSettings__oidcIdentityClientKey:-$OIDC_IDENTITY_CLIENT_KEY} +export globalSettings__duo__aKey=${globalSettings__duo__aKey:-$DUO_AKEY} + +export globalSettings__databaseProvider=$BW_DB_PROVIDER +export globalSettings__mysql__connectionString=${globalSettings__mysql__connectionString:-$MYSQL_CONNECTION_STRING} +export globalSettings__postgreSql__connectionString=${globalSettings__postgreSql__connectionString:-$POSTGRESQL_CONNECTION_STRING} +export globalSettings__sqlServer__connectionString=${globalSettings__sqlServer__connectionString:-$SQLSERVER_CONNECTION_STRING} +export globalSettings__sqlite__connectionString=${globalSettings__sqlite__connectionString:-$SQLITE_CONNECTION_STRING} + +if [ "$BW_ENABLE_SSL" = "true" ]; then + export globalSettings__baseServiceUri__internalVault=https://localhost:${BW_PORT_HTTPS:-8443} +else + export globalSettings__baseServiceUri__internalVault=http://localhost:${BW_PORT_HTTP:-8080} +fi + +# Generate Identity certificate +if [ ! -f /etc/bitwarden/identity.pfx ]; then + openssl req \ + -x509 \ + -newkey rsa:4096 \ + -sha256 \ + -nodes \ + -keyout /etc/bitwarden/identity.key \ + -out /etc/bitwarden/identity.crt \ + -subj "/CN=Bitwarden IdentityServer" \ + -days 36500 + + openssl pkcs12 \ + -export \ + -out /etc/bitwarden/identity.pfx \ + -inkey /etc/bitwarden/identity.key \ + -in /etc/bitwarden/identity.crt \ + -passout pass:$globalSettings__identityServer__certificatePassword + + rm /etc/bitwarden/identity.crt + rm /etc/bitwarden/identity.key +fi + +cp /etc/bitwarden/identity.pfx /app/Identity/identity.pfx +cp /etc/bitwarden/identity.pfx /app/Sso/identity.pfx + +# Generate SSL certificates +if [ "$BW_ENABLE_SSL" = "true" -a ! -f /etc/bitwarden/${BW_SSL_KEY:-ssl.key} ]; then + openssl req \ + -x509 \ + -newkey rsa:4096 \ + -sha256 \ + -nodes \ + -days 36500 \ + -keyout /etc/bitwarden/${BW_SSL_KEY:-ssl.key} \ + -out /etc/bitwarden/${BW_SSL_CERT:-ssl.crt} \ + -reqexts SAN \ + -extensions SAN \ + -config <(cat /usr/lib/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:${BW_DOMAIN:-localhost}\nbasicConstraints=CA:true")) \ + -subj "/C=US/ST=California/L=Santa Barbara/O=Bitwarden Inc./OU=Bitwarden/CN=${BW_DOMAIN:-localhost}" +fi + +# Launch a loop to rotate nginx logs on a daily basis +/bin/sh -c "/logrotate.sh loop >/dev/null 2>&1 &" + +/usr/local/bin/hbs + +# Enable/Disable services +sed -i "s/autostart=true/autostart=${BW_ENABLE_ADMIN}/" /etc/supervisor.d/admin.ini +sed -i "s/autostart=true/autostart=${BW_ENABLE_API}/" /etc/supervisor.d/api.ini +sed -i "s/autostart=true/autostart=${BW_ENABLE_EVENTS}/" /etc/supervisor.d/events.ini +sed -i "s/autostart=true/autostart=${BW_ENABLE_ICONS}/" /etc/supervisor.d/icons.ini +sed -i "s/autostart=true/autostart=${BW_ENABLE_IDENTITY}/" /etc/supervisor.d/identity.ini +sed -i "s/autostart=true/autostart=${BW_ENABLE_NOTIFICATIONS}/" /etc/supervisor.d/notifications.ini +sed -i "s/autostart=true/autostart=${BW_ENABLE_SCIM}/" /etc/supervisor.d/scim.ini +sed -i "s/autostart=true/autostart=${BW_ENABLE_SSO}/" /etc/supervisor.d/sso.ini + +chown -R $PUID:$PGID \ + /app \ + /etc/bitwarden \ + /etc/nginx/http.d \ + /etc/supervisor \ + /etc/supervisor.d \ + /var/lib/nginx \ + /var/log \ + /var/run/nginx \ + /run + +exec setpriv --reuid=$PUID --regid=$PGID --init-groups /usr/bin/supervisord \ No newline at end of file diff --git a/docker-unified/hbs/app-id.hbs b/docker-unified/hbs/app-id.hbs new file mode 100644 index 0000000..eed8768 --- /dev/null +++ b/docker-unified/hbs/app-id.hbs @@ -0,0 +1,15 @@ +{ + "trustedFacets": [ + { + "version": { + "major": 1, + "minor": 0 + }, + "ids": [ + "{{{String.Coalesce env.globalSettings__baseServiceUri__vault "https://localhost"}}}", + "ios:bundle-id:com.8bit.bitwarden", + "android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" + ] + } + ] +} diff --git a/docker-unified/hbs/config.yaml b/docker-unified/hbs/config.yaml new file mode 100644 index 0000000..2dc04a6 --- /dev/null +++ b/docker-unified/hbs/config.yaml @@ -0,0 +1,7 @@ +helper_categories: + - String +templates: + - src: /etc/hbs/app-id.hbs + dest: /app/Web/app-id.json + - src: /etc/hbs/nginx-config.hbs + dest: /etc/nginx/http.d/bitwarden.conf \ No newline at end of file diff --git a/docker-unified/hbs/nginx-config.hbs b/docker-unified/hbs/nginx-config.hbs new file mode 100644 index 0000000..5b57673 --- /dev/null +++ b/docker-unified/hbs/nginx-config.hbs @@ -0,0 +1,189 @@ +server { + listen {{{String.Coalesce env.BW_PORT_HTTP "8080"}}} default_server; + #listen [::]:{{{String.Coalesce env.BW_PORT_HTTP "8080"}}} default_server; + server_name {{{String.Coalesce env.BW_DOMAIN "localhost"}}}; +{{#if (String.Equal env.BW_ENABLE_SSL "true")}} + + return 301 https://{{{String.Coalesce env.BW_DOMAIN "localhost"}}}:{{{String.Coalesce env.BW_PORT_HTTPS "8443"}}}$request_uri; +} + +server { + listen {{{String.Coalesce env.BW_PORT_HTTPS "8443"}}} ssl http2; + #listen [::]:{{{String.Coalesce env.BW_PORT_HTTPS "8443"}}} ssl http2; + server_name {{{String.Coalesce env.BW_DOMAIN "localhost"}}}; + + ssl_certificate /etc/bitwarden/{{{String.Coalesce env.BW_SSL_CERT "ssl.crt"}}}; + ssl_certificate_key /etc/bitwarden/{{{String.Coalesce env.BW_SSL_KEY "ssl.key"}}}; + ssl_session_timeout 30m; + ssl_session_cache shared:SSL:20m; + ssl_session_tickets off; +{{#if (String.Equal env.BW_ENABLE_SSL_DH "true")}} + + # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits + ssl_dhparam /etc/bitwarden/{{{String.Coalesce env.BW_SSL_DH_CERT "dh.pem"}}}; +{{/if}} + + ssl_protocols {{{String.Coalesce env.BW_SSL_PROTOCOLS "TLSv1.2"}}}; + ssl_ciphers "{{{String.Coalesce env.BW_SSL_CIPHERS "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"}}}"; + # Enables server-side protection from BEAST attacks + ssl_prefer_server_ciphers on; +{{#if (String.Equal env.BW_ENABLE_SSL_CA "true")}} + + # OCSP Stapling --- + # Fetch OCSP records from URL in ssl_certificate and cache them + ssl_stapling on; + ssl_stapling_verify on; + + # Verify chain of trust of OCSP response using Root CA and Intermediate certs + ssl_trusted_certificate /etc/bitwarden/{{{String.Coalesce env.BW_SSL_CA_CERT "ca.crt"}}}; + resolver 1.1.1.1 1.0.0.1 9.9.9.9 149.112.112.112 valid=300s; +{{/if}} + + include /etc/nginx/security-headers-ssl.conf; +{{/if}} + include /etc/nginx/security-headers.conf; +{{#if (String.IsNotNullOrWhitespace env.BW_REAL_IPS)}} + +{{#each (String.Split env.BW_REAL_IPS ",")}} + set_real_ip_from {{{String.Trim .}}}; +{{/each}} + real_ip_header X-Forwarded-For; + real_ip_recursive on; +{{/if}} + + location / { + root /app/Web; +{{#if (String.Equal env.BW_ENABLE_SSL "true")}} + include /etc/nginx/security-headers-ssl.conf; +{{/if}} + include /etc/nginx/security-headers.conf; + add_header Content-Security-Policy "{{{String.Coalesce env.BW_CSP "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com; child-src 'self' https://*.duosecurity.com https://*.duofederal.com; frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; connect-src 'self' https://api.pwnedpasswords.com https://api.2fa.directory; object-src 'self' blob:;"}}}"; + add_header X-Frame-Options SAMEORIGIN; + add_header X-Robots-Tag "noindex, nofollow"; + } + + location /alive { + default_type text/plain; + return 200 $date_gmt; + } + + location = /app-id.json { + root /app/Web; +{{#if (String.Equal env.BW_ENABLE_SSL "true")}} + include /etc/nginx/security-headers-ssl.conf; +{{/if}} + include /etc/nginx/security-headers.conf; + proxy_hide_header Content-Type; + add_header Content-Type $fido_content_type; + } + + location = /duo-connector.html { + root /app/Web; + } + + location = /webauthn-connector.html { + root /app/Web; + } + + location = /webauthn-fallback-connector.html { + root /app/Web; + } + + location = /sso-connector.html { + root /app/Web; + } + + location = /captcha-connector.html { + root /app/Web; + } + + location = /captcha-mobile-connector.html { + root /app/Web; + } + + location /attachments/ { + alias /etc/bitwarden/attachments/; + } +{{#if (String.Equal env.BW_ENABLE_API "true")}} + + location /api/ { + proxy_pass http://localhost:5001/; + } +{{/if}} +{{#if (String.Equal env.BW_ENABLE_ICONS "true")}} + + location /icons/ { +{{#if (String.Equal env.BW_ICONS_PROXY_TO_CLOUD "true")}} + proxy_pass https://icons.bitwarden.net/; + proxy_set_header Host icons.bitwarden.net; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_ssl_server_name on; +{{else}} + proxy_pass http://localhost:5004/; +{{/if}} + } +{{/if}} +{{#if (String.Equal env.BW_ENABLE_NOTIFICATIONS "true")}} + + location /notifications/ { + proxy_pass http://localhost:5006/; + } + + location /notifications/hub { + proxy_pass http://localhost:5006/hub; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + } +{{/if}} +{{#if (String.Equal env.BW_ENABLE_EVENTS "true")}} + + location /events/ { + proxy_pass http://localhost:5003/; + } +{{/if}} +{{#if (String.Equal env.BW_ENABLE_SSO "true")}} + + location /sso { + proxy_pass http://localhost:5007; +{{#if (String.Equal env.BW_ENABLE_SSL "true")}} + include /etc/nginx/security-headers-ssl.conf; +{{/if}} + include /etc/nginx/security-headers.conf; + add_header X-Frame-Options SAMEORIGIN; + } +{{/if}} +{{#if (String.Equal env.BW_ENABLE_IDENTITY "true")}} + + location /identity { + proxy_pass http://localhost:5005; +{{#if (String.Equal env.BW_ENABLE_SSL "true")}} + include /etc/nginx/security-headers-ssl.conf; +{{/if}} + include /etc/nginx/security-headers.conf; + add_header X-Frame-Options SAMEORIGIN; + } +{{/if}} +{{#if (String.Equal env.BW_ENABLE_ADMIN "true")}} + + location /admin { + proxy_pass http://localhost:5000; +{{#if (String.Equal env.BW_ENABLE_SSL "true")}} + include /etc/nginx/security-headers-ssl.conf; +{{/if}} + include /etc/nginx/security-headers.conf; + add_header X-Frame-Options SAMEORIGIN; + } +{{/if}} +{{#if (String.Equal env.BW_ENABLE_SCIM "true")}} + + location /scim/ { + proxy_pass http://localhost:5002/; + } +{{/if}} +{{#if (String.Equal env.BW_ENABLE_KEY_CONNECTOR "true")}} + + location /key-connector/ { + proxy_pass {{{env.BW_KEY_CONNECTOR_INTERNAL_URL}}}/; + } +{{/if}} +} diff --git a/docker-unified/nginx/logrotate.sh b/docker-unified/nginx/logrotate.sh new file mode 100644 index 0000000..d86c79c --- /dev/null +++ b/docker-unified/nginx/logrotate.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +while true +do + [ "$1" = "loop" ] && sleep $((24 * 3600 - (`date +%_H` * 3600 + `date +%_M` * 60 + `date +%_S`))) + ts=$(date +%Y%m%d_%H%M%S) + mv /var/log/nginx/access.log /var/log/nginx/access.$ts.log + mv /var/log/nginx/error.log /var/log/nginx/error.$ts.log + kill -USR1 `cat /var/run/nginx/nginx.pid` + sleep 1 + gzip /var/log/nginx/access.$ts.log + gzip /var/log/nginx/error.$ts.log + find /var/log/nginx/ -name "*.gz" -mtime +32 -delete + [ "$1" != "loop" ] && break +done diff --git a/docker-unified/nginx/mime.types b/docker-unified/nginx/mime.types new file mode 100644 index 0000000..7c3b1e7 --- /dev/null +++ b/docker-unified/nginx/mime.types @@ -0,0 +1,138 @@ +types { + + # Data interchange + + application/atom+xml atom; + application/json json map topojson; + application/ld+json jsonld; + application/rss+xml rss; + application/vnd.geo+json geojson; + application/xml rdf xml; + + + # JavaScript + + # Normalize to standard type. + # https://tools.ietf.org/html/rfc4329#section-7.2 + application/javascript js; + + + # Manifest files + + application/manifest+json webmanifest; + application/x-web-app-manifest+json webapp; + text/cache-manifest appcache; + + + # Media files + + audio/midi mid midi kar; + audio/mp4 aac f4a f4b m4a; + audio/mpeg mp3; + audio/ogg oga ogg opus; + audio/x-realaudio ra; + audio/x-wav wav; + image/bmp bmp; + image/gif gif; + image/jpeg jpeg jpg; + image/jxr jxr hdp wdp; + image/png png; + image/svg+xml svg svgz; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/webp webp; + image/x-jng jng; + video/3gpp 3gp 3gpp; + video/mp4 f4p f4v m4v mp4; + video/mpeg mpeg mpg; + video/ogg ogv; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-mng mng; + video/x-ms-asf asf asx; + video/x-ms-wmv wmv; + video/x-msvideo avi; + + # Serving `.ico` image files with a different media type + # prevents Internet Explorer from displaying then as images: + # https://github.com/h5bp/html5-boilerplate/commit/37b5fec090d00f38de64b591bcddcb205aadf8ee + + image/x-icon cur ico; + + + # Microsoft Office + + application/msword doc; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; + application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; + + + # Web fonts + + application/font-woff woff; + application/font-woff2 woff2; + application/vnd.ms-fontobject eot; + + # Browsers usually ignore the font media types and simply sniff + # the bytes to figure out the font type. + # https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern + # + # However, Blink and WebKit based browsers will show a warning + # in the console if the following font types are served with any + # other media types. + + application/x-font-ttf ttc ttf; + font/opentype otf; + + + # Other + + application/java-archive ear jar war; + application/mac-binhex40 hqx; + application/octet-stream bin deb dll dmg exe img iso msi msm msp safariextz; + application/pdf pdf; + application/postscript ai eps ps; + application/rtf rtf; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/vnd.wap.wmlc wmlc; + application/x-7z-compressed 7z; + application/x-bb-appworld bbaw; + application/x-bittorrent torrent; + application/x-chrome-extension crx; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-opera-extension oex; + application/x-perl pl pm; + application/x-pilot pdb prc; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert crt der pem; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xslt+xml xsl; + application/zip zip; + text/css css; + text/csv csv; + text/html htm html shtml; + text/markdown md; + text/mathml mml; + text/plain txt; + text/vcard vcard vcf; + text/vnd.rim.location.xloc xloc; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/vtt vtt; + text/x-component htc; + +} diff --git a/docker-unified/nginx/nginx.conf b/docker-unified/nginx/nginx.conf new file mode 100644 index 0000000..93445e8 --- /dev/null +++ b/docker-unified/nginx/nginx.conf @@ -0,0 +1,147 @@ +# nginx Configuration File +# http://wiki.nginx.org/Configuration + +daemon off; + +# Run as a less privileged user for security reasons. +# user www www; + +# How many worker threads to run; +# "auto" sets it to the number of CPU cores available in the system, and +# offers the best performance. Don't set it higher than the number of CPU +# cores if changing this parameter. + +# The maximum number of connections for Nginx is calculated by: +# max_clients = worker_processes * worker_connections +worker_processes auto; + +# Maximum open file descriptors per process; +# should be > worker_connections. +worker_rlimit_nofile 8192; + +events { + # When you need > 8000 * cpu_cores connections, you start optimizing your OS, + # and this is probably the point at which you hire people who are smarter than + # you, as this is *a lot* of requests. + worker_connections 8000; +} + +# Default error log file +# (this is only used when you don't override error_log on a server{} level) +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx/nginx.pid; + +http { + # Include proxy and server configuration. + include /etc/nginx/proxy.conf; + include /etc/nginx/http.d/bitwarden.conf; + + # Hide nginx version information. + server_tokens off; + + # Define the MIME types for files. + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Update charset_types to match updated mime.types. + # text/html is always included by charset module. + # Default: text/html text/xml text/plain text/vnd.wap.wml application/javascript application/rss+xml + charset_types + text/css + text/plain + text/vnd.wap.wml + application/javascript + application/json + application/rss+xml + application/xml; + + # Format to use in log files + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + # Default log file + # (this is only used when you don't override access_log on a server{} level) + access_log /var/log/nginx/access.log main; + + # How long to allow each connection to stay idle; longer values are better + # for each individual client, particularly for SSL, but means that worker + # connections are tied up longer. (Default: 65) + keepalive_timeout 20; + + # Speed up file transfers by using sendfile() to copy directly + # between descriptors rather than using read()/write(). + # For performance reasons, on FreeBSD systems w/ ZFS + # this option should be disabled as ZFS's ARC caches + # frequently used files in RAM by default. + sendfile on; + + # Tell Nginx not to send out partial frames; this increases throughput + # since TCP frames are filled up before being sent out. (adds TCP_CORK) + tcp_nopush on; + + + # Compression + + # Enable Gzip compressed. + gzip on; + + # Compression level (1-9). + # 5 is a perfect compromise between size and cpu usage, offering about + # 75% reduction for most ascii files (almost identical to level 9). + gzip_comp_level 5; + + # Don't compress anything that's already small and unlikely to shrink much + # if at all (the default is 20 bytes, which is bad as that usually leads to + # larger files after gzipping). + gzip_min_length 256; + + # Compress data even for clients that are connecting to us via proxies, + # identified by the "Via" header (required for CloudFront). + gzip_proxied any; + + # Tell proxies to cache both the gzipped and regular version of a resource + # whenever the client's Accept-Encoding capabilities header varies; + # Avoids the issue where a non-gzip capable client (which is extremely rare + # today) would display gibberish if their proxy gave them the gzipped version. + gzip_vary on; + + # Compress all output labeled with one of the following MIME-types. + gzip_types + application/atom+xml + application/javascript + application/json + application/ld+json + application/manifest+json + application/rss+xml + application/vnd.geo+json + application/vnd.ms-fontobject + application/x-font-ttf + application/x-web-app-manifest+json + application/xhtml+xml + application/xml + font/opentype + image/bmp + image/svg+xml + image/x-icon + text/cache-manifest + text/css + text/plain + text/vcard + text/vnd.rim.location.xloc + text/vtt + text/x-component + text/x-cross-domain-policy; + # text/html is always compressed by HttpGzipModule + + # This should be turned on if you are going to have pre-compressed copies (.gz) of + # static files available. If not it should be left off as it will cause extra I/O + # for the check. It is best if you enable this in a location{} block for + # a specific directory, or on an individual server{} level. + # gzip_static on; + + # Content type for FIDO U2F facets + map $uri $fido_content_type { + default "application/fido.trusted-apps+json"; + } +} diff --git a/docker-unified/nginx/proxy.conf b/docker-unified/nginx/proxy.conf new file mode 100644 index 0000000..a56be66 --- /dev/null +++ b/docker-unified/nginx/proxy.conf @@ -0,0 +1,27 @@ +proxy_redirect off; + +map $http_host $upstream_host { + default "$host"; + ~. "$http_host"; +} +proxy_set_header Host $upstream_host; + +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + +map $http_x_forwarded_proto $upstream_scheme { + default "$scheme"; + ~. "$http_x_forwarded_proto"; +} +proxy_set_header X-Url-Scheme $upstream_scheme; +proxy_set_header X-Forwarded-Proto $upstream_scheme; + +client_max_body_size 505m; +client_body_buffer_size 128k; +proxy_connect_timeout 90; +proxy_send_timeout 90; +proxy_read_timeout 90; +proxy_buffer_size 128k; +proxy_buffers 4 256k; +proxy_busy_buffers_size 256k; +large_client_header_buffers 4 32k; diff --git a/docker-unified/nginx/security-headers-ssl.conf b/docker-unified/nginx/security-headers-ssl.conf new file mode 100644 index 0000000..d94e835 --- /dev/null +++ b/docker-unified/nginx/security-headers-ssl.conf @@ -0,0 +1,2 @@ +# This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack. 6 months age +add_header Strict-Transport-Security max-age=15768000; \ No newline at end of file diff --git a/docker-unified/nginx/security-headers.conf b/docker-unified/nginx/security-headers.conf new file mode 100644 index 0000000..c23d1b4 --- /dev/null +++ b/docker-unified/nginx/security-headers.conf @@ -0,0 +1,3 @@ +add_header Referrer-Policy same-origin; +add_header X-Content-Type-Options nosniff; +add_header X-XSS-Protection "1; mode=block"; \ No newline at end of file diff --git a/docker-unified/settings.env b/docker-unified/settings.env new file mode 100644 index 0000000..d86baa6 --- /dev/null +++ b/docker-unified/settings.env @@ -0,0 +1,69 @@ +##################### +# Required Settings # +##################### + +# Server hostname +BW_DOMAIN=bitwarden.yourdomain.com + +# Database +# Available providers are sqlserver, postgresql, mysql/mariadb, or sqlite +BW_DB_PROVIDER=mysql +BW_DB_SERVER=db +BW_DB_DATABASE=bitwarden_vault +BW_DB_USERNAME=bitwarden +BW_DB_PASSWORD=super_strong_password + +# Installation information +# Get your ID and key from https://bitwarden.com/host/ +BW_INSTALLATION_ID=00000000-0000-0000-0000-000000000000 +BW_INSTALLATION_KEY=xxxxxxxxxxxx + +##################### +# Optional Settings # +##################### +# Learn more here: https://bitwarden.com/help/environment-variables/ + +# Container user ID/group ID +#PUID=1000 +#PGID=1000 + +# Webserver ports +#BW_PORT_HTTP=8080 +#BW_PORT_HTTPS=8443 + +# SSL +#BW_ENABLE_SSL=true +#BW_ENABLE_SSL_CA=true +#BW_SSL_CERT=ssl.crt +#BW_SSL_KEY=ssl.key +#BW_SSL_CA_CERT=ca.crt + +# Services +# Some services, namely for enterprise use cases, are disabled by default. Defaults shown below. +#BW_ENABLE_ADMIN=true +#BW_ENABLE_API=true +#BW_ENABLE_EVENTS=false +#BW_ENABLE_ICONS=true +#BW_ENABLE_IDENTITY=true +#BW_ENABLE_NOTIFICATIONS=true +#BW_ENABLE_SCIM=false +#BW_ENABLE_SSO=false + +#BW_ICONS_PROXY_TO_CLOUD=false + +# Mail +#globalSettings__mail__replyToEmail=noreply@$BW_DOMAIN +#globalSettings__mail__smtp__host=smtphost.example.com +#globalSettings__mail__smtp__port=587 +#globalSettings__mail__smtp__ssl=false +#globalSettings__mail__smtp__username=smtpusername +#globalSettings__mail__smtp__password=smtppassword + +# Yubikey +#globalSettings__yubico__clientId=REPLACE +#globalSettings__yubico__key=REPLACE + +# Other +#globalSettings__disableUserRegistration=false +#globalSettings__hibpApiKey=REPLACE +#adminSettings__admins=admin1@email.com,admin2@email.com diff --git a/docker-unified/supervisord/admin.ini b/docker-unified/supervisord/admin.ini new file mode 100644 index 0000000..113da5e --- /dev/null +++ b/docker-unified/supervisord/admin.ini @@ -0,0 +1,9 @@ +[program:admin] +autostart=true +autorestart=true +command=/usr/bin/dotnet "Admin.dll" +directory=/app/Admin +environment=ASPNETCORE_URLS="http://+:5000" +redirect_stderr=true +startsecs=15 +stdout_logfile=/var/log/bitwarden/admin.log diff --git a/docker-unified/supervisord/api.ini b/docker-unified/supervisord/api.ini new file mode 100644 index 0000000..410e1d8 --- /dev/null +++ b/docker-unified/supervisord/api.ini @@ -0,0 +1,9 @@ +[program:api] +autostart=true +autorestart=true +command=/usr/bin/dotnet "Api.dll" +directory=/app/Api +environment=ASPNETCORE_URLS="http://+:5001" +redirect_stderr=true +startsecs=15 +stdout_logfile=/var/log/bitwarden/api.log diff --git a/docker-unified/supervisord/events.ini b/docker-unified/supervisord/events.ini new file mode 100644 index 0000000..32093d2 --- /dev/null +++ b/docker-unified/supervisord/events.ini @@ -0,0 +1,9 @@ +[program:events] +autostart=true +autorestart=true +command=/usr/bin/dotnet "Events.dll" +directory=/app/Events +environment=ASPNETCORE_URLS="http://+:5003" +redirect_stderr=true +startsecs=15 +stdout_logfile=/var/log/bitwarden/events.log diff --git a/docker-unified/supervisord/icons.ini b/docker-unified/supervisord/icons.ini new file mode 100644 index 0000000..36489e8 --- /dev/null +++ b/docker-unified/supervisord/icons.ini @@ -0,0 +1,9 @@ +[program:icons] +autostart=true +autorestart=true +command=/usr/bin/dotnet "Icons.dll" +directory=/app/Icons +environment=ASPNETCORE_URLS="http://+:5004" +redirect_stderr=true +startsecs=15 +stdout_logfile=/var/log/bitwarden/icons.log diff --git a/docker-unified/supervisord/identity.ini b/docker-unified/supervisord/identity.ini new file mode 100644 index 0000000..4b1600c --- /dev/null +++ b/docker-unified/supervisord/identity.ini @@ -0,0 +1,10 @@ +[program:identity] +autostart=true +autorestart=true +command=/usr/bin/dotnet "Identity.dll" +directory=/app/Identity +environment=ASPNETCORE_URLS="http://+:5005" +priority=1 +redirect_stderr=true +startsecs=15 +stdout_logfile=/var/log/bitwarden/identity.log diff --git a/docker-unified/supervisord/nginx.ini b/docker-unified/supervisord/nginx.ini new file mode 100644 index 0000000..bc52f3f --- /dev/null +++ b/docker-unified/supervisord/nginx.ini @@ -0,0 +1,7 @@ +[program:nginx] +autostart=true +autorestart=true +command=nginx +redirect_stderr=true +startsecs=15 +stdout_logfile=/var/log/bitwarden/nginx.log diff --git a/docker-unified/supervisord/notifications.ini b/docker-unified/supervisord/notifications.ini new file mode 100644 index 0000000..2744ff7 --- /dev/null +++ b/docker-unified/supervisord/notifications.ini @@ -0,0 +1,9 @@ +[program:notifications] +autostart=true +autorestart=true +command=/usr/bin/dotnet "Notifications.dll" +directory=/app/Notifications +environment=ASPNETCORE_URLS="http://+:5006" +redirect_stderr=true +startsecs=15 +stdout_logfile=/var/log/bitwarden/notifications.log diff --git a/docker-unified/supervisord/scim.ini b/docker-unified/supervisord/scim.ini new file mode 100644 index 0000000..11d00d4 --- /dev/null +++ b/docker-unified/supervisord/scim.ini @@ -0,0 +1,9 @@ +[program:scim] +autostart=true +autorestart=true +command=/usr/bin/dotnet "Scim.dll" +directory=/app/Scim +environment=ASPNETCORE_URLS="http://+:5002" +redirect_stderr=true +startsecs=15 +stdout_logfile=/var/log/bitwarden/scim.log diff --git a/docker-unified/supervisord/sso.ini b/docker-unified/supervisord/sso.ini new file mode 100644 index 0000000..cb29c73 --- /dev/null +++ b/docker-unified/supervisord/sso.ini @@ -0,0 +1,9 @@ +[program:sso] +autostart=true +autorestart=true +command=/usr/bin/dotnet "Sso.dll" +directory=/app/Sso +environment=ASPNETCORE_URLS="http://+:5007" +redirect_stderr=true +startsecs=15 +stdout_logfile=/var/log/bitwarden/sso.log diff --git a/docker-unified/supervisord/supervisord.conf b/docker-unified/supervisord/supervisord.conf new file mode 100644 index 0000000..4cd6a8c --- /dev/null +++ b/docker-unified/supervisord/supervisord.conf @@ -0,0 +1,15 @@ +[unix_http_server] +file=/run/supervisord.sock ; the path to the socket file + +[supervisord] +logfile=/var/log/supervisord.log ; main log file; default $CWD/supervisord.log +nodaemon=true ; start in foreground if true; default false + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=unix:///run/supervisord.sock ; use a unix:// URL for a unix socket + +[include] +files = /etc/supervisor.d/*.ini