1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

docker setup

This commit is contained in:
Kyle Spearrin 2017-08-07 16:31:00 -04:00
parent ee8b0a25a8
commit 9bc6ba554a
25 changed files with 456 additions and 171 deletions

View File

@ -28,7 +28,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Billing", "src\Billing\Bill
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity", "src\Identity\Identity.csproj", "{04148736-3C0B-445E-8B74-2020E7A53502}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "Docker", "docker\Docker.dcproj", "{026DDB58-F0DB-4089-8168-83015AF785AE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Setup", "util\Setup\Setup.csproj", "{EF2164EF-1FC0-4518-A2ED-CE02D3630B00}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -62,10 +62,10 @@ Global
{04148736-3C0B-445E-8B74-2020E7A53502}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04148736-3C0B-445E-8B74-2020E7A53502}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04148736-3C0B-445E-8B74-2020E7A53502}.Release|Any CPU.Build.0 = Release|Any CPU
{026DDB58-F0DB-4089-8168-83015AF785AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{026DDB58-F0DB-4089-8168-83015AF785AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{026DDB58-F0DB-4089-8168-83015AF785AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{026DDB58-F0DB-4089-8168-83015AF785AE}.Release|Any CPU.Build.0 = Release|Any CPU
{EF2164EF-1FC0-4518-A2ED-CE02D3630B00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF2164EF-1FC0-4518-A2ED-CE02D3630B00}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF2164EF-1FC0-4518-A2ED-CE02D3630B00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF2164EF-1FC0-4518-A2ED-CE02D3630B00}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -77,6 +77,7 @@ Global
{B78A6C74-1A24-48C6-802A-13BE3E4DAFF1} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
{02BC2982-ED8D-4A6D-A41E-092B3DAEB98A} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
{04148736-3C0B-445E-8B74-2020E7A53502} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
{EF2164EF-1FC0-4518-A2ED-CE02D3630B00} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}

View File

@ -7,3 +7,4 @@ echo "=================="
& $dir\src\Api\build.ps1
& $dir\src\Identity\build.ps1
& $dir\nginx\build.ps1
& $dir\util\Setup\build.ps1

View File

@ -9,3 +9,4 @@ echo -e "=================="
$DIR/src/Api/build.sh
$DIR/src/Identity/build.sh
$DIR/nginx/build.sh
$DIR/util/Setup/build.sh

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.0</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<ProjectGuid>026ddb58-f0db-4089-8168-83015af785ae</ProjectGuid>
<DockerLaunchBrowser>True</DockerLaunchBrowser>
<DockerServiceUrl>http://localhost:{ServicePort}</DockerServiceUrl>
<DockerServiceName>api</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.yml" />
</ItemGroup>
</Project>

View File

@ -4,12 +4,11 @@ services:
mssql:
volumes:
- /etc/bitwarden/mssql_data:/var/opt/mssql/data
api:
volumes:
- /etc/bitwarden/core:/etc/core
identity:
volumes:
- /etc/bitwarden/core:/etc/core
- /etc/bitwarden/identity:/etc/bitwarden/identity
nginx:
volumes:
- /etc/bitwarden/nginx:/etc/bitwarden/nginx
- /etc/bitwarden/letsencrypt:/etc/letsencrypt
- /etc/bitwarden/ssl:/etc/certificates

View File

@ -4,8 +4,13 @@ services:
mssql:
volumes:
- mssql_data:/var/opt/mssql/data
identity:
volumes:
- c:/bitwarden/identity:/etc/bitwarden/identity
nginx:
volumes:
- c:/bitwarden/nginx:/etc/bitwarden/nginx
- c:/bitwarden/letsencrypt:/etc/letsencrypt
- c:/bitwarden/ssl:/etc/certificates
volumes:
mssql_data:

View File

@ -4,14 +4,13 @@ services:
mssql:
volumes:
- mssql_data:/var/opt/mssql/data
api:
volumes:
- c:/bitwarden/core:/etc/core
identity:
volumes:
- c:/bitwarden/core:/etc/core
- c:/bitwarden/identity:/etc/bitwarden/identity
nginx:
volumes:
- c:/bitwarden/nginx:/etc/bitwarden/nginx
- c:/bitwarden/letsencrypt:/etc/letsencrypt
- c:/bitwarden/ssl:/etc/certificates
volumes:
mssql_data:

View File

@ -3,7 +3,6 @@ FROM nginx:stable
RUN rm /etc/nginx/nginx.conf
COPY nginx.conf /etc/nginx/nginx.conf
RUN rm /etc/nginx/conf.d/default.conf
COPY default.conf /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -1,73 +0,0 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name bw.kylespearrin.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name bw.kylespearrin.com;
ssl_certificate /etc/letsencrypt/live/bw.kylespearrin.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bw.kylespearrin.com/privkey.pem;
ssl_session_timeout 30m;
ssl_session_cache shared:SSL:20m;
ssl_session_tickets off;
# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
ssl_dhparam /etc/letsencrypt/live/bw.kylespearrin.com/dhparam.pem;
# SSL protocols TLS v1~TLSv1.2 are allowed. Disabed SSLv3
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# Disabled insecure ciphers suite. For example, MD5, DES, RC4, PSK
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4:@STRENGTH";
# enables server-side protection from BEAST attacks
ssl_prefer_server_ciphers on;
# 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/letsencrypt/live/bw.kylespearrin.com/fullchain.pem;
resolver 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=300s;
# Headers
# X-Frame-Options is to prevent from clickJacking attack
#add_header X-Frame-Options SAMEORIGIN;
# disable content-type sniffing on some browsers.
add_header X-Content-Type-Options nosniff;
# This header enables the Cross-site scripting (XSS) filter
add_header X-XSS-Protection "1; mode=block";
# This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack
#add_header Strict-Transport-Security max-age=15768000;
location /api/ {
proxy_pass http://api/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Url-Scheme $scheme;
proxy_redirect off;
}
location /identity/ {
proxy_pass http://identity/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Url-Scheme $scheme;
proxy_redirect off;
}
}

4
nginx/entrypoint.sh Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
cp /etc/bitwarden/nginx/default.conf /etc/nginx/conf.d/default.conf
nginx -g 'daemon off;'

View File

@ -1,7 +1,8 @@
$dockerDir="../docker"
$dir = Split-Path -Parent $MyInvocation.MyCommand.Path
$dockerDir="${dir}\..\docker"
docker --version
docker-compose --version
docker-compose -f $dockerDir/docker-compose.yml -f $dockerDir/docker-compose.windows.yml down
docker-compose -f $dockerDir/docker-compose.yml -f $dockerDir/docker-compose.windows.yml up -d
docker-compose -f ${dockerDir}\docker-compose.yml -f ${dockerDir}\docker-compose.windows.yml down
docker-compose -f ${dockerDir}\docker-compose.yml -f ${dockerDir}\docker-compose.windows.yml up -d

View File

@ -1,13 +1,12 @@
param (
[string]$outputDir = "c:/bitwarden",
[string]$domain = $( Read-Host "Please enter your domain name (i.e. bitwarden.company.com)" ),
[string]$email = $( Read-Host "Please enter your email address (used to generate an HTTPS certificate with LetsEncrypt)" )
[string]$email = $( Read-Host "Please enter your email address: " ),
[string]$letsencrypt = $( Read-Host "Generate Let's Encrypt Cert (y/n)" )
)
$dockerDir="../docker"
$certPassword=-join ((48..57) + (97..122) | Get-Random -Count 32 | % {[char]$_})
$databasePassword=-join ((48..57) + (97..122) | Get-Random -Count 32 | % {[char]$_})
$duoKey=-join ((48..57) + (97..122) | Get-Random -Count 32 | % {[char]$_})
docker --version
@ -15,23 +14,6 @@ docker --version
#docker run -it --rm -p 80:80 -v $outputDir/letsencrypt:/etc/letsencrypt/ certbot/certbot certonly --standalone --noninteractive --preferred-challenges http --email $email --agree-tos -d $domain
#docker run -it --rm -v $outputDir/letsencrypt/live:/certificates/ bitwarden/openssl openssl dhparam -out /certificates/$domain/dhparam.pem 2048
mkdir -p $outputDir/core
docker run -it --rm -v $outputDir/core:/certificates bitwarden/openssl openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout /certificates/identity.key -out /certificates/identity.crt -subj "/CN=bitwarden IdentityServer" -days 10950
docker run -it --rm -v $outputDir/core:/certificates bitwarden/openssl openssl pkcs12 -export -out /certificates/identity.pfx -inkey /certificates/identity.key -in /certificates/identity.crt -certfile /certificates/identity.crt -passout pass:$certPassword
rm $outputDir/core/identity.key
rm $outputDir/core/identity.crt
docker run -it --rm -v ${outputDir}:/bitwarden bitwarden/setup dotnet Setup.dll -domain ${domain} -letsencrypt ${letsencrypt} -db_pass ${databasePassword}
Add-Content $dockerDir/global.override.env "
globalSettings:baseServiceUri:vault=https://$domain
globalSettings:baseServiceUri:api=https://$domain/api
globalSettings:baseServiceUri:identity=https://$domain/identity
globalSettings:sqlServer:connectionString=Server=tcp:mssql,1433;Initial Catalog=vault;Persist Security Info=False;User ID=sa;Password=$databasePassword;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30;
globalSettings:identityServer:certificatePassword=$certPassword
globalSettings:duo:aKey=$duoKey
globalSettings:yubico:clientId=REPLACE
globalSettings:yubico:REPLACE"
Add-Content $dockerDir/mssql.override.env "
ACCEPT_EULA=Y
MSSQL_PID=Express
SA_PASSWORD=$databasePassword"
echo "Setup complete"

View File

@ -7,10 +7,7 @@ echo -e "\nPlease enter your email address (used to generate an HTTPS certificat
read EMAIL
OUTPUT_DIR=./bitwarden
DOCKER_DIR=../docker
CERT_PASSWORD=$(LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c 32)
DATABASE_PASSWORD=$(LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c 32)
DUO_KEY=$(LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c 64)
docker --version
@ -18,25 +15,6 @@ docker --version
#docker run -it --rm -p 80:80 -v $OUTPUT_DIR/letsencrypt:/etc/letsencrypt/ certbot/certbot certonly --standalone --noninteractive --preferred-challenges http --email $EMAIL --agree-tos -d $DOMAIN
#docker run -it --rm -v $OUTPUT_DIR/letsencrypt/live:/certificates/ bitwarden/openssl openssl dhparam -out /certificates/$DOMAIN/dhparam.pem 2048
mkdir -p $OUTPUT_DIR/core
docker run -it --rm -v $OUTPUT_DIR/core:/certificates bitwarden/openssl openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout /certificates/identity.key -out /certificates/identity.crt -subj "/CN=bitwarden IdentityServer" -days 10950
docker run -it --rm -v $OUTPUT_DIR/core:/certificates bitwarden/openssl openssl pkcs12 -export -out /certificates/identity.pfx -inkey /certificates/identity.key -in /certificates/identity.crt -certfile /certificates/identity.crt -passout pass:$CERT_PASSWORD
rm $OUTPUT_DIR/core/identity.key
rm $OUTPUT_DIR/core/identity.crt
docker run -it --rm -v $OUTPUT_DIR:/bitwarden bitwarden/setup dotnet Setup.dll -domain $DOMAIN -letsencrypt y -db_pass $DATABASE_PASSWORD
cat >> $DOCKER_DIR/global.override.env << EOF
globalSettings:baseServiceUri:vault=https://$DOMAIN
globalSettings:baseServiceUri:api=https://$DOMAIN/api
globalSettings:baseServiceUri:identity=https://$DOMAIN/identity
globalSettings:sqlServer:connectionString=Server=tcp:mssql,1433;Initial Catalog=vault;Persist Security Info=False;User ID=sa;Password=$DATABASE_PASSWORD;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30;
globalSettings:identityServer:certificatePassword=$CERT_PASSWORD
globalSettings:duo:aKey=$DUO_KEY
globalSettings:yubico:clientId=REPLACE
globalSettings:yubico:REPLACE
EOF
cat >> $DOCKER_DIR/mssql.override.env << EOF
ACCEPT_EULA=Y
MSSQL_PID=Express
SA_PASSWORD=$DATABASE_PASSWORD
EOF
echo -e "\nSetup complete"

View File

@ -92,7 +92,7 @@ namespace Bit.Api
// Services
services.AddBaseServices();
services.AddDefaultServices();
services.AddDefaultServices(globalSettings);
// Cors
services.AddCors(config =>

View File

@ -45,7 +45,7 @@ namespace Bit.Billing
// Services
services.AddBaseServices();
services.AddDefaultServices();
services.AddDefaultServices(globalSettings);
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

View File

@ -22,13 +22,22 @@ namespace Bit.Core.Utilities
filter = (e) => true;
}
var serilog = new LoggerConfiguration()
var config = new LoggerConfiguration()
.Enrich.FromLogContext()
.Filter.ByIncludingOnly(filter)
.WriteTo.AzureDocumentDB(new Uri(globalSettings.DocumentDb.Uri), globalSettings.DocumentDb.Key,
timeToLive: TimeSpan.FromDays(7))
.CreateLogger();
.Filter.ByIncludingOnly(filter);
if(globalSettings.DocumentDb != null && !string.IsNullOrWhiteSpace(globalSettings.DocumentDb.Uri) &&
!string.IsNullOrWhiteSpace(globalSettings.DocumentDb.Key))
{
config.WriteTo.AzureDocumentDB(new Uri(globalSettings.DocumentDb.Uri), globalSettings.DocumentDb.Key,
timeToLive: TimeSpan.FromDays(7));
}
else
{
// local file sink
}
var serilog = config.CreateLogger();
factory.AddSerilog(serilog);
appLifetime.ApplicationStopped.Register(Log.CloseAndFlush);
}

View File

@ -48,10 +48,19 @@ namespace Bit.Core.Utilities
services.AddSingleton<IGroupService, GroupService>();
}
public static void AddDefaultServices(this IServiceCollection services)
public static void AddDefaultServices(this IServiceCollection services, GlobalSettings globalSettings)
{
services.AddSingleton<IMailService, RazorViewMailService>();
services.AddSingleton<IMailDeliveryService, SendGridMailDeliveryService>();
if(!string.IsNullOrWhiteSpace(globalSettings.Mail.SendGridApiKey))
{
services.AddSingleton<IMailDeliveryService, SendGridMailDeliveryService>();
}
else
{
services.AddSingleton<IMailDeliveryService, NoopMailDeliveryService>();
}
#if NET461
services.AddSingleton<IPushNotificationService, NotificationHubPushNotificationService>();
services.AddSingleton<IPushRegistrationService, NotificationHubPushRegistrationService>();
@ -59,8 +68,23 @@ namespace Bit.Core.Utilities
services.AddSingleton<IPushNotificationService, NoopPushNotificationService>();
services.AddSingleton<IPushRegistrationService, NoopPushRegistrationService>();
#endif
services.AddSingleton<IBlockIpService, AzureQueueBlockIpService>();
services.AddSingleton<IAttachmentStorageService, AzureAttachmentStorageService>();
if(!string.IsNullOrWhiteSpace(globalSettings.Storage.ConnectionString))
{
services.AddSingleton<IBlockIpService, AzureQueueBlockIpService>();
}
else
{
services.AddSingleton<IBlockIpService, NoopBlockIpService>();
}
if(!string.IsNullOrWhiteSpace(globalSettings.Attachment.ConnectionString))
{
services.AddSingleton<IAttachmentStorageService, AzureAttachmentStorageService>();
}
else
{
services.AddSingleton<IAttachmentStorageService, NoopAttachmentStorageService>();
}
}
public static void AddNoopServices(this IServiceCollection services)
@ -147,14 +171,18 @@ namespace Bit.Core.Utilities
else if(!string.IsNullOrWhiteSpace(globalSettings.IdentityServer.CertificatePassword) &&
System.IO.File.Exists("identity.pfx"))
{
var identityServerCert = CoreHelpers.GetCertificate("identity.pfx",
var identityServerCert = CoreHelpers.GetCertificate("identity.pfx",
globalSettings.IdentityServer.CertificatePassword);
identityServerBuilder.AddSigningCredential(identityServerCert);
}
else if(!string.IsNullOrWhiteSpace(globalSettings.IdentityServer.CertificateThumbprint))
{
var identityServerCert = CoreHelpers.GetCertificate(globalSettings.IdentityServer.CertificateThumbprint);
identityServerBuilder.AddSigningCredential(identityServerCert);
}
else
{
var identityServerCert = CoreHelpers.GetCertificate(globalSettings.IdentityServer.CertificateThumbprint);
identityServerBuilder.AddSigningCredential(identityServerCert);
throw new Exception("No identity certificate to use.");
}
services.AddScoped<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
@ -168,7 +196,9 @@ namespace Bit.Core.Utilities
this IServiceCollection services, IHostingEnvironment env, GlobalSettings globalSettings)
{
#if NET461
if(!env.IsDevelopment() && !globalSettings.SelfHosted)
if(!env.IsDevelopment() && !globalSettings.SelfHosted &&
!string.IsNullOrWhiteSpace(globalSettings.Storage.ConnectionString) &&
!string.IsNullOrWhiteSpace(globalSettings.DataProtection.CertificateThumbprint))
{
var dataProtectionCert = CoreHelpers.GetCertificate(globalSettings.DataProtection.CertificateThumbprint);
var storageAccount = CloudStorageAccount.Parse(globalSettings.Storage.ConnectionString);

View File

@ -49,7 +49,7 @@ namespace Bit.Identity
// Services
services.AddBaseServices();
services.AddDefaultServices();
services.AddDefaultServices(globalSettings);
}
public void Configure(

View File

@ -1,5 +1,4 @@
#!/bin/sh
cp /etc/core/identity.pfx /app/identity.pfx
cp /etc/bitwarden/identity/identity.pfx /app/identity.pfx
dotnet /app/Identity.dll

10
util/Setup/Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM microsoft/dotnet:2.0.0-preview2-runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
# Dependencies
openssl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY obj/Docker/publish .

91
util/Setup/Helpers.cs Normal file
View File

@ -0,0 +1,91 @@
using System;
using System.Security.Cryptography;
using System.Text;
namespace Setup
{
public static class Helpers
{
public static string SecureRandomString(int length, bool alpha = true, bool upper = true, bool lower = true,
bool numeric = true, bool special = false)
{
return SecureRandomString(length, RandomStringCharacters(alpha, upper, lower, numeric, special));
}
// ref https://stackoverflow.com/a/8996788/1090359 with modifications
public static string SecureRandomString(int length, string characters)
{
if(length < 0)
{
throw new ArgumentOutOfRangeException(nameof(length), "length cannot be less than zero.");
}
if((characters?.Length ?? 0) == 0)
{
throw new ArgumentOutOfRangeException(nameof(characters), "characters invalid.");
}
const int byteSize = 0x100;
if(byteSize < characters.Length)
{
throw new ArgumentException(
string.Format("{0} may contain no more than {1} characters.", nameof(characters), byteSize),
nameof(characters));
}
var outOfRangeStart = byteSize - (byteSize % characters.Length);
using(var rng = RandomNumberGenerator.Create())
{
var sb = new StringBuilder();
var buffer = new byte[128];
while(sb.Length < length)
{
rng.GetBytes(buffer);
for(var i = 0; i < buffer.Length && sb.Length < length; ++i)
{
// Divide the byte into charSet-sized groups. If the random value falls into the last group and the
// last group is too small to choose from the entire allowedCharSet, ignore the value in order to
// avoid biasing the result.
if(outOfRangeStart <= buffer[i])
{
continue;
}
sb.Append(characters[buffer[i] % characters.Length]);
}
}
return sb.ToString();
}
}
private static string RandomStringCharacters(bool alpha, bool upper, bool lower, bool numeric, bool special)
{
var characters = string.Empty;
if(alpha)
{
if(upper)
{
characters += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
}
if(lower)
{
characters += "abcdefghijklmnopqrstuvwxyz";
}
}
if(numeric)
{
characters += "0123456789";
}
if(special)
{
characters += "!@#$%^*&";
}
return characters;
}
}
}

225
util/Setup/Program.cs Normal file
View File

@ -0,0 +1,225 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace Setup
{
public class Program
{
private static string[] _args = null;
private static IDictionary<string, string> _parameters = null;
private static string _domain = null;
private static string _certPassword = null;
private static bool _ssl = false;
private static bool _letsEncrypt = false;
public static void Main(string[] args)
{
_args = args;
_parameters = ParseParameters();
_domain = _parameters.ContainsKey("domain") ? _parameters["domain"].ToLowerInvariant() : "localhost";
_letsEncrypt = _parameters.ContainsKey("letsencrypt") ? _parameters["letsencrypt"].ToLowerInvariant() == "y" : false;
_ssl = _letsEncrypt || (_parameters.ContainsKey("ssl") ? _parameters["ssl"].ToLowerInvariant() == "y" : false);
_certPassword = Helpers.SecureRandomString(32, alpha: true, numeric: true);
MakeIdentityCert();
BuildNginxConfig();
BuildEnvironmentFiles();
}
private static void MakeIdentityCert()
{
Directory.CreateDirectory("/bitwarden/identity/");
var identityCertResult = Exec("openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout identity.key " +
"-out identity.crt -subj \"/CN=bitwarden IdentityServer\" -days 10950");
var identityPfxResult = Exec("openssl pkcs12 -export -out /bitwarden/identity/identity.pfx -inkey identity.key " +
$"-in identity.crt -certfile identity.crt -passout pass:{_certPassword}");
}
private static void BuildNginxConfig()
{
Directory.CreateDirectory("/bitwarden/nginx/");
var sslCiphers = "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:" +
"DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:" +
"ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:" +
"ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:" +
"AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4:@STRENGTH";
var dh = _letsEncrypt ||
(_parameters.ContainsKey("ssl_dh") ? _parameters["ssl_dh"].ToLowerInvariant() == "y" : false);
var trusted = _letsEncrypt ||
(_parameters.ContainsKey("ssl_trusted") ? _parameters["ssl_trusted"].ToLowerInvariant() == "y" : false);
var certPath = _letsEncrypt ? $"/etc/letsencrypt/live/{_domain}" : $"/etc/certificates/{_domain}";
using(var sw = File.CreateText("/bitwarden/nginx/default.conf"))
{
sw.WriteLine($@"server {{
listen 80 default_server;
listen [::]:80 default_server;
server_name {_domain};");
if(_ssl)
{
sw.WriteLine($@" return 301 https://$server_name$request_uri;
}}
server {{
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name {_domain};
ssl_certificate {certPath}/fullchain.pem;
ssl_certificate_key {certPath}/privkey.pem;
ssl_session_timeout 30m;
ssl_session_cache shared:SSL:20m;
ssl_session_tickets off;");
if(dh)
{
sw.WriteLine($@"
# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
ssl_dhparam {certPath}/dhparam.pem;");
}
sw.WriteLine($@"
# SSL protocols TLS v1~TLSv1.2 are allowed. Disabed SSLv3
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# Disabled insecure ciphers suite. For example, MD5, DES, RC4, PSK
ssl_ciphers ""{sslCiphers}"";
# enables server-side protection from BEAST attacks
ssl_prefer_server_ciphers on;");
if(trusted)
{
sw.WriteLine($@"
# 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 {certPath}/fullchain.pem;
resolver 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=300s;");
}
sw.WriteLine($@"
# Headers
# X-Frame-Options is to prevent from clickJacking attack
#add_header X-Frame-Options SAMEORIGIN;
# disable content-type sniffing on some browsers.
add_header X-Content-Type-Options nosniff;
# This header enables the Cross-site scripting (XSS) filter
add_header X-XSS-Protection ""1; mode=block"";
# This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack
#add_header Strict-Transport-Security max-age=15768000;");
}
sw.WriteLine($@"
location /api/ {{
proxy_pass http://api/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Url-Scheme $scheme;
proxy_redirect off;
}}
location /identity/ {{
proxy_pass http://identity/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Url-Scheme $scheme;
proxy_redirect off;
}}
}}");
}
}
private static void BuildEnvironmentFiles()
{
var url = _ssl ? $"https://{_domain}" : $"http://{_domain}";
var dbPass = _parameters.ContainsKey("db_pass") ? _parameters["db_pass"].ToLowerInvariant() : "REPLACE";
var dbConnectionString = "Server=tcp:mssql,1433;Initial Catalog=vault;Persist Security Info=False;User ID=sa;" +
$"Password={dbPass};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=True;" +
"Connection Timeout=30;";
using(var sw = File.CreateText("/bitwarden/global.override.env"))
{
sw.Write($@"globalSettings:baseServiceUri:vault={url}
globalSettings:baseServiceUri:api={url}/api
globalSettings:baseServiceUri:identity={url}/identity
globalSettings:sqlServer:connectionString={dbConnectionString}
globalSettings:identityServer:certificatePassword={_certPassword}
globalSettings:duo:aKey={Helpers.SecureRandomString(32, alpha: true, numeric: true)}
globalSettings:yubico:clientId=REPLACE
globalSettings:yubico:REPLACE");
}
using(var sw = File.CreateText("/bitwarden/mssql.override.env"))
{
sw.Write($@"ACCEPT_EULA=Y
MSSQL_PID=Express
SA_PASSWORD={dbPass}");
}
}
private static IDictionary<string, string> ParseParameters()
{
var dict = new Dictionary<string, string>();
for(var i = 0; i < _args.Length; i = i + 2)
{
if(!_args[i].StartsWith("-"))
{
continue;
}
dict.Add(_args[i].Substring(1), _args[i + 1]);
}
return dict;
}
private static string Exec(string cmd)
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
};
if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var escapedArgs = cmd.Replace("\"", "\\\"");
process.StartInfo.FileName = "/bin/bash";
process.StartInfo.Arguments = $"-c \"{escapedArgs}\"";
}
else
{
process.StartInfo.FileName = "powershell";
process.StartInfo.Arguments = cmd;
}
process.Start();
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return result;
}
}
}

13
util/Setup/Setup.csproj Normal file
View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
</Project>

11
util/Setup/build.ps1 Normal file
View File

@ -0,0 +1,11 @@
$dir = Split-Path -Parent $MyInvocation.MyCommand.Path
echo "`n# Building Setup"
echo "`nBuilding app"
echo ".NET Core version $(dotnet --version)"
dotnet publish $dir\Setup.csproj -f netcoreapp2.0 -c "Release" -o $dir\obj\Docker\publish
echo "`nBuilding docker image"
docker --version
docker build -t bitwarden/setup $dir\.

14
util/Setup/build.sh Normal file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -e
DIR="$(dirname $(readlink -f $0))"
echo -e "\n# Building Setup"
echo -e "\nBuilding app"
echo -e ".NET Core version $(dotnet --version)"
dotnet publish $DIR/Setup.csproj -f netcoreapp2.0 -c "Release" -o $DIR/obj/Docker/publish
echo -e "\nBuilding docker image"
docker --version
docker build -t bitwarden/setup $DIR/.