mirror of
https://github.com/bitwarden/server.git
synced 2024-11-22 12:15:36 +01:00
convert setup to use config.yml
This commit is contained in:
parent
a1f0f04660
commit
310e6bcf61
@ -5,35 +5,32 @@ namespace Bit.Setup
|
||||
{
|
||||
public class AppIdBuilder
|
||||
{
|
||||
public AppIdBuilder(string url)
|
||||
{
|
||||
Url = url;
|
||||
}
|
||||
private readonly Context _context;
|
||||
|
||||
public string Url { get; private set; }
|
||||
public AppIdBuilder(Context context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public void Build()
|
||||
{
|
||||
var model = new TemplateModel
|
||||
{
|
||||
Url = _context.Config.Url
|
||||
};
|
||||
|
||||
Console.WriteLine("Building FIDO U2F app id.");
|
||||
Directory.CreateDirectory("/bitwarden/web/");
|
||||
var template = Helpers.ReadTemplate("AppId");
|
||||
using(var sw = File.CreateText("/bitwarden/web/app-id.json"))
|
||||
{
|
||||
sw.Write($@"{{
|
||||
""trustedFacets"": [
|
||||
{{
|
||||
""version"": {{
|
||||
""major"": 1,
|
||||
""minor"": 0
|
||||
}},
|
||||
""ids"": [
|
||||
""{Url}"",
|
||||
""ios:bundle-id:com.8bit.bitwarden"",
|
||||
""android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI""
|
||||
]
|
||||
}}
|
||||
]
|
||||
}}");
|
||||
sw.Write(template(model));
|
||||
}
|
||||
}
|
||||
|
||||
public class TemplateModel
|
||||
{
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,53 +5,81 @@ namespace Bit.Setup
|
||||
{
|
||||
public class CertBuilder
|
||||
{
|
||||
public CertBuilder(string domain, string identityCertPassword, bool letsEncrypt, bool ssl)
|
||||
private readonly Context _context;
|
||||
|
||||
public CertBuilder(Context context)
|
||||
{
|
||||
Domain = domain;
|
||||
IdentityCertPassword = identityCertPassword;
|
||||
LetsEncrypt = letsEncrypt;
|
||||
Ssl = ssl;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public string Domain { get; private set; }
|
||||
public bool LetsEncrypt { get; private set; }
|
||||
public bool Ssl { get; private set; }
|
||||
public string IdentityCertPassword { get; private set; }
|
||||
|
||||
public bool BuildForInstall()
|
||||
public void BuildForInstall()
|
||||
{
|
||||
var selfSignedSsl = false;
|
||||
if(!Ssl)
|
||||
_context.Config.Ssl = _context.Config.SslManagedLetsEncrypt;
|
||||
|
||||
if(!_context.Config.Ssl)
|
||||
{
|
||||
if(Helpers.ReadQuestion("Do you want to generate a self-signed SSL certificate?"))
|
||||
_context.Config.Ssl = Helpers.ReadQuestion("Do you have a SSL certificate to use?");
|
||||
if(_context.Config.Ssl)
|
||||
{
|
||||
Directory.CreateDirectory($"/bitwarden/ssl/self/{Domain}/");
|
||||
Directory.CreateDirectory($"/bitwarden/ssl/{_context.Install.Domain}/");
|
||||
var message = "Make sure 'certificate.crt' and 'private.key' are provided in the \n" +
|
||||
"appropriate directory before running 'start' (see docs for info).";
|
||||
Helpers.ShowBanner("NOTE", message);
|
||||
}
|
||||
else if(Helpers.ReadQuestion("Do you want to generate a self-signed SSL certificate?"))
|
||||
{
|
||||
Directory.CreateDirectory($"/bitwarden/ssl/self/{_context.Install.Domain}/");
|
||||
Console.WriteLine("Generating self signed SSL certificate.");
|
||||
Ssl = selfSignedSsl = true;
|
||||
_context.Config.Ssl = true;
|
||||
_context.Install.Trusted = false;
|
||||
_context.Install.SelfSignedCert = true;
|
||||
Helpers.Exec("openssl req -x509 -newkey rsa:4096 -sha256 -nodes -days 365 " +
|
||||
$"-keyout /bitwarden/ssl/self/{Domain}/private.key " +
|
||||
$"-out /bitwarden/ssl/self/{Domain}/certificate.crt " +
|
||||
$"-keyout /bitwarden/ssl/self/{_context.Install.Domain}/private.key " +
|
||||
$"-out /bitwarden/ssl/self/{_context.Install.Domain}/certificate.crt " +
|
||||
$"-reqexts SAN -extensions SAN " +
|
||||
$"-config <(cat /usr/lib/ssl/openssl.cnf <(printf '[SAN]\nsubjectAltName=DNS:{Domain}\nbasicConstraints=CA:true')) " +
|
||||
$"-subj \"/C=US/ST=Florida/L=Jacksonville/O=8bit Solutions LLC/OU=Bitwarden/CN={Domain}\"");
|
||||
$"-config <(cat /usr/lib/ssl/openssl.cnf <(printf '[SAN]\nsubjectAltName=DNS:{_context.Install.Domain}\nbasicConstraints=CA:true')) " +
|
||||
$"-subj \"/C=US/ST=Florida/L=Jacksonville/O=8bit Solutions LLC/OU=Bitwarden/CN={_context.Install.Domain}\"");
|
||||
}
|
||||
}
|
||||
|
||||
if(LetsEncrypt)
|
||||
if(_context.Config.SslManagedLetsEncrypt)
|
||||
{
|
||||
Directory.CreateDirectory($"/bitwarden/letsencrypt/live/{Domain}/");
|
||||
Helpers.Exec($"openssl dhparam -out /bitwarden/letsencrypt/live/{Domain}/dhparam.pem 2048");
|
||||
_context.Install.Trusted = true;
|
||||
_context.Install.DiffieHellman = true;
|
||||
Directory.CreateDirectory($"/bitwarden/letsencrypt/live/{_context.Install.Domain}/");
|
||||
Helpers.Exec($"openssl dhparam -out /bitwarden/letsencrypt/live/{_context.Install.Domain}/dhparam.pem 2048");
|
||||
}
|
||||
else if(_context.Config.Ssl && !_context.Install.SelfSignedCert)
|
||||
{
|
||||
_context.Install.Trusted = Helpers.ReadQuestion("Is this a trusted SSL certificate " +
|
||||
"(requires ca.crt, see docs)?");
|
||||
}
|
||||
|
||||
Console.WriteLine("Generating key for IdentityServer.");
|
||||
_context.Install.IdentityCertPassword = Helpers.SecureRandomString(32, alpha: true, numeric: true);
|
||||
Directory.CreateDirectory("/bitwarden/identity/");
|
||||
Helpers.Exec("openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout identity.key " +
|
||||
"-out identity.crt -subj \"/CN=Bitwarden IdentityServer\" -days 10950");
|
||||
Helpers.Exec("openssl pkcs12 -export -out /bitwarden/identity/identity.pfx -inkey identity.key " +
|
||||
$"-in identity.crt -certfile identity.crt -passout pass:{IdentityCertPassword}");
|
||||
$"-in identity.crt -certfile identity.crt -passout pass:{_context.Install.IdentityCertPassword}");
|
||||
|
||||
Console.WriteLine();
|
||||
return selfSignedSsl;
|
||||
|
||||
if(!_context.Config.Ssl)
|
||||
{
|
||||
var message = "You are not using a SSL certificate. Bitwarden requires HTTPS to operate. \n" +
|
||||
"You must front your installation with a HTTPS proxy or the web vault (and \n" +
|
||||
"other Bitwarden apps) will not work properly.";
|
||||
Helpers.ShowBanner("WARNING", message, ConsoleColor.Yellow);
|
||||
}
|
||||
else if(_context.Config.Ssl && !_context.Install.Trusted)
|
||||
{
|
||||
var message = "You are using an untrusted SSL certificate. This certificate will not be \n" +
|
||||
"trusted by Bitwarden client applications. You must add this certificate to \n" +
|
||||
"the trusted store on each device or else you will receive errors when trying \n" +
|
||||
"to connect to your installation.";
|
||||
Helpers.ShowBanner("WARNING", message, ConsoleColor.Yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
254
util/Setup/Context.cs
Normal file
254
util/Setup/Context.cs
Normal file
@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace Bit.Setup
|
||||
{
|
||||
public class Context
|
||||
{
|
||||
private const string ConfigPath = "/bitwarden/config.yml";
|
||||
|
||||
public string[] Args { get; set; }
|
||||
public IDictionary<string, string> Parameters { get; set; }
|
||||
public string OutputDir { get; set; } = "/etc/bitwarden";
|
||||
public string HostOS { get; set; } = "win";
|
||||
public string CoreVersion { get; set; } = "latest";
|
||||
public string WebVersion { get; set; } = "latest";
|
||||
public Installation Install { get; set; } = new Installation();
|
||||
public Configuration Config { get; set; } = new Configuration();
|
||||
|
||||
public void LoadConfiguration()
|
||||
{
|
||||
if(!File.Exists(ConfigPath))
|
||||
{
|
||||
// Looks like updating from older version. Try to create config file.
|
||||
var url = Helpers.GetValueFronEnvFile("global", "globalSettings__baseServiceUri__vault");
|
||||
if(!Uri.TryCreate(url, UriKind.Absolute, out var uri))
|
||||
{
|
||||
Console.WriteLine("Unable to determine existing installation url.");
|
||||
return;
|
||||
}
|
||||
Config.Url = url;
|
||||
|
||||
var push = Helpers.GetValueFronEnvFile("global", "globalSettings__pushRelayBaseUri");
|
||||
Config.PushNotifications = push != "REPLACE";
|
||||
|
||||
var composeFile = "/bitwarden/docker/docker-compose.yml";
|
||||
if(File.Exists(composeFile))
|
||||
{
|
||||
var fileLines = File.ReadAllLines(composeFile);
|
||||
foreach(var line in fileLines)
|
||||
{
|
||||
if(!line.StartsWith("# Parameter:"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var paramParts = line.Split("=");
|
||||
if(paramParts.Length < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:MssqlDataDockerVolume" &&
|
||||
bool.TryParse(paramParts[1], out var mssqlDataDockerVolume))
|
||||
{
|
||||
Config.DatabaseDockerVolume = mssqlDataDockerVolume;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:HttpPort" && int.TryParse(paramParts[1], out var httpPort))
|
||||
{
|
||||
Config.HttpPort = httpPort == 0 ? null : httpPort.ToString();
|
||||
continue;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:HttpsPort" && int.TryParse(paramParts[1], out var httpsPort))
|
||||
{
|
||||
Config.HttpsPort = httpsPort == 0 ? null : httpsPort.ToString();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var nginxFile = "/bitwarden/nginx/default.conf";
|
||||
if(File.Exists(nginxFile))
|
||||
{
|
||||
var selfSigned = false;
|
||||
var diffieHellman = false;
|
||||
var trusted = false;
|
||||
var fileLines = File.ReadAllLines(nginxFile);
|
||||
foreach(var line in fileLines)
|
||||
{
|
||||
if(!line.StartsWith("# Parameter:"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var paramParts = line.Split("=");
|
||||
if(paramParts.Length < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:Ssl" && bool.TryParse(paramParts[1], out var ssl))
|
||||
{
|
||||
Config.Ssl = ssl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:LetsEncrypt" && bool.TryParse(paramParts[1], out var le))
|
||||
{
|
||||
Config.SslManagedLetsEncrypt = le;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:SelfSignedSsl" && bool.TryParse(paramParts[1], out var self))
|
||||
{
|
||||
selfSigned = self;
|
||||
return;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:DiffieHellman" && bool.TryParse(paramParts[1], out var dh))
|
||||
{
|
||||
diffieHellman = dh;
|
||||
return;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:Trusted" && bool.TryParse(paramParts[1], out var trust))
|
||||
{
|
||||
trusted = trust;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(Config.SslManagedLetsEncrypt)
|
||||
{
|
||||
Config.Ssl = true;
|
||||
}
|
||||
else if(Config.Ssl)
|
||||
{
|
||||
var sslPath = selfSigned ? $"/etc/ssl/self/{Config.Domain}" : $"/etc/ssl/{Config.Domain}";
|
||||
Config.SslCertificatePath = string.Concat(sslPath, "/", "certificate.crt");
|
||||
Config.SslKeyPath = string.Concat(sslPath, "/", "private.key");
|
||||
if(trusted)
|
||||
{
|
||||
Config.SslCaPath = string.Concat(sslPath, "/", "ca.crt");
|
||||
}
|
||||
if(diffieHellman)
|
||||
{
|
||||
Config.SslDiffieHellmanPath = string.Concat(sslPath, "/", "dhparam.pem");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SaveConfiguration();
|
||||
}
|
||||
|
||||
var configText = File.ReadAllText(ConfigPath);
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(new UnderscoredNamingConvention())
|
||||
.Build();
|
||||
Config = deserializer.Deserialize<Configuration>(configText);
|
||||
}
|
||||
|
||||
public void SaveConfiguration()
|
||||
{
|
||||
if(Config == null)
|
||||
{
|
||||
throw new Exception("Config is null.");
|
||||
}
|
||||
var serializer = new SerializerBuilder()
|
||||
.EmitDefaults()
|
||||
.WithNamingConvention(new UnderscoredNamingConvention())
|
||||
.WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
|
||||
.WithEmissionPhaseObjectGraphVisitor(args => new CommentsObjectGraphVisitor(args.InnerVisitor))
|
||||
.Build();
|
||||
var yaml = serializer.Serialize(Config);
|
||||
Directory.CreateDirectory("/bitwarden/");
|
||||
using(var sw = File.CreateText(ConfigPath))
|
||||
{
|
||||
sw.Write(yaml);
|
||||
}
|
||||
}
|
||||
|
||||
public class Installation
|
||||
{
|
||||
public Guid InstallationId { get; set; }
|
||||
public string InstallationKey { get; set; }
|
||||
public bool DiffieHellman { get; set; }
|
||||
public bool Trusted { get; set; }
|
||||
public bool SelfSignedCert { get; set; }
|
||||
public string IdentityCertPassword { get; set; }
|
||||
public string Domain { get; set; }
|
||||
}
|
||||
|
||||
public class Configuration
|
||||
{
|
||||
[Description("Full URL for accessing the installation from a browser. (Required)")]
|
||||
public string Url { get; set; } = "https://localhost";
|
||||
|
||||
[Description("Auto-generate the `./docker/docker-compose.yml` config file.\n" +
|
||||
"WARNING: Disabling generated config files can break future updates. You will be responsible\n" +
|
||||
"for maintaining this config file.")]
|
||||
public bool GenerateComposeConfig { get; set; } = true;
|
||||
|
||||
[Description("Auto-generate the `./nginx/default.conf` file.\n" +
|
||||
"WARNING: Disabling generated config files can break future updates. You will be responsible\n" +
|
||||
"for maintaining this config file.")]
|
||||
public bool GenerateNginxConfig { get; set; } = true;
|
||||
|
||||
[Description("Compose file port mapping for HTTP. Leave empty for remove the port mapping.")]
|
||||
public string HttpPort { get; set; } = "80";
|
||||
|
||||
[Description("Compose file port mapping for HTTPS. Leave empty for remove the port mapping.")]
|
||||
public string HttpsPort { get; set; } = "443";
|
||||
|
||||
[Description("Set up the Nginx config file for SSL.")]
|
||||
public bool Ssl { get; set; } = true;
|
||||
|
||||
[Description("Installation uses a managed Let's Encrypt certificate.")]
|
||||
public bool SslManagedLetsEncrypt { get; set; }
|
||||
|
||||
[Description("The actual certificate. (Required if using SSL without managed Let's Encrypt)\n" +
|
||||
"Note: The `./ssl` directory is mapped to `/etc/ssl` within the container.")]
|
||||
public string SslCertificatePath { get; set; }
|
||||
|
||||
[Description("The certificate's private key. (Required if using SSL without managed Let's Encrypt)\n" +
|
||||
"Note: The `./ssl` directory is mapped to `/etc/ssl` within the container.")]
|
||||
public string SslKeyPath { get; set; }
|
||||
|
||||
[Description("If the certificate is trusted by a CA, you should provide the CA's certificate.\n" +
|
||||
"Note: The `./ssl` directory is mapped to `/etc/ssl` within the container.")]
|
||||
public string SslCaPath { get; set; }
|
||||
|
||||
[Description("Diffie Hellman ephemeral parameters\n" +
|
||||
"Learn more: https://security.stackexchange.com/q/94390/79072\n" +
|
||||
"Note: The `./ssl` directory is mapped to `/etc/ssl` within the container.")]
|
||||
public string SslDiffieHellmanPath { get; set; }
|
||||
|
||||
[Description("Communicate with the Bitwarden push relay service (push.bitwarden.com) for mobile app live sync.")]
|
||||
public bool PushNotifications { get; set; } = true;
|
||||
|
||||
[Description("Use a docker volume instead of a host-mapped volume for the persisted database.\n" +
|
||||
"WARNING: Changing this value will cause you to lose access to the existing persisted database.")]
|
||||
public bool DatabaseDockerVolume { get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public string Domain
|
||||
{
|
||||
get
|
||||
{
|
||||
if(Uri.TryCreate(Url, UriKind.Absolute, out var uri))
|
||||
{
|
||||
return uri.Host;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,245 +5,58 @@ namespace Bit.Setup
|
||||
{
|
||||
public class DockerComposeBuilder
|
||||
{
|
||||
public DockerComposeBuilder(string os, string webVersion, string coreVersion)
|
||||
{
|
||||
MssqlDataDockerVolume = os == "mac";
|
||||
private readonly Context _context;
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(webVersion))
|
||||
{
|
||||
WebVersion = webVersion;
|
||||
}
|
||||
if(!string.IsNullOrWhiteSpace(coreVersion))
|
||||
{
|
||||
CoreVersion = coreVersion;
|
||||
}
|
||||
public DockerComposeBuilder(Context context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public bool MssqlDataDockerVolume { get; private set; }
|
||||
public int HttpPort { get; private set; }
|
||||
public int HttpsPort { get; private set; }
|
||||
public string CoreVersion { get; private set; } = "latest";
|
||||
public string WebVersion { get; private set; } = "latest";
|
||||
|
||||
public void BuildForInstaller(int httpPort, int httpsPort)
|
||||
public void BuildForInstaller()
|
||||
{
|
||||
if(httpPort != default(int))
|
||||
{
|
||||
HttpPort = httpPort;
|
||||
}
|
||||
|
||||
if(httpsPort != default(int))
|
||||
{
|
||||
HttpsPort = httpsPort;
|
||||
}
|
||||
|
||||
_context.Config.DatabaseDockerVolume = _context.HostOS == "mac";
|
||||
Build();
|
||||
}
|
||||
|
||||
public void BuildForUpdater()
|
||||
{
|
||||
var composeFile = "/bitwarden/docker/docker-compose.yml";
|
||||
if(File.Exists(composeFile))
|
||||
{
|
||||
var fileLines = File.ReadAllLines(composeFile);
|
||||
foreach(var line in fileLines)
|
||||
{
|
||||
if(!line.StartsWith("# Parameter:"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var paramParts = line.Split("=");
|
||||
if(paramParts.Length < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:MssqlDataDockerVolume" &&
|
||||
bool.TryParse(paramParts[1], out var mssqlDataDockerVolume))
|
||||
{
|
||||
MssqlDataDockerVolume = mssqlDataDockerVolume;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:HttpPort" && int.TryParse(paramParts[1], out var httpPort))
|
||||
{
|
||||
HttpPort = httpPort;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(paramParts[0] == "# Parameter:HttpsPort" && int.TryParse(paramParts[1], out var httpsPort))
|
||||
{
|
||||
HttpsPort = httpsPort;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Build();
|
||||
}
|
||||
|
||||
private void Build()
|
||||
{
|
||||
Console.WriteLine("Building docker-compose.yml.");
|
||||
Directory.CreateDirectory("/bitwarden/docker/");
|
||||
Console.WriteLine("Building docker-compose.yml.");
|
||||
if(!_context.Config.GenerateComposeConfig)
|
||||
{
|
||||
Console.WriteLine("...skipped");
|
||||
return;
|
||||
}
|
||||
|
||||
var template = Helpers.ReadTemplate("DockerCompose");
|
||||
var model = new TemplateModel(_context);
|
||||
using(var sw = File.CreateText("/bitwarden/docker/docker-compose.yml"))
|
||||
{
|
||||
sw.Write($@"# https://docs.docker.com/compose/compose-file/
|
||||
# Parameter:MssqlDataDockerVolume={MssqlDataDockerVolume}
|
||||
# Parameter:HttpPort={HttpPort}
|
||||
# Parameter:HttpsPort={HttpsPort}
|
||||
# Parameter:CoreVersion={CoreVersion}
|
||||
# Parameter:WebVersion={WebVersion}
|
||||
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
mssql:
|
||||
image: bitwarden/mssql:{CoreVersion}
|
||||
container_name: bitwarden-mssql
|
||||
restart: always
|
||||
volumes:");
|
||||
|
||||
if(MssqlDataDockerVolume)
|
||||
{
|
||||
sw.Write(@"
|
||||
- mssql_data:/var/opt/mssql/data");
|
||||
}
|
||||
else
|
||||
{
|
||||
sw.Write(@"
|
||||
- ../mssql/data:/var/opt/mssql/data");
|
||||
}
|
||||
|
||||
sw.Write($@"
|
||||
- ../logs/mssql:/var/opt/mssql/log
|
||||
- ../mssql/backups:/etc/bitwarden/mssql/backups
|
||||
env_file:
|
||||
- mssql.env
|
||||
- ../env/uid.env
|
||||
- ../env/mssql.override.env
|
||||
|
||||
web:
|
||||
image: bitwarden/web:{WebVersion}
|
||||
container_name: bitwarden-web
|
||||
restart: always
|
||||
volumes:
|
||||
- ../web:/etc/bitwarden/web
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
|
||||
attachments:
|
||||
image: bitwarden/attachments:{CoreVersion}
|
||||
container_name: bitwarden-attachments
|
||||
restart: always
|
||||
volumes:
|
||||
- ../core/attachments:/etc/bitwarden/core/attachments
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
|
||||
api:
|
||||
image: bitwarden/api:{CoreVersion}
|
||||
container_name: bitwarden-api
|
||||
restart: always
|
||||
volumes:
|
||||
- ../core:/etc/bitwarden/core
|
||||
- ../ca-certificates:/etc/bitwarden/ca-certificates
|
||||
- ../logs/api:/etc/bitwarden/logs
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
- ../env/global.override.env
|
||||
|
||||
identity:
|
||||
image: bitwarden/identity:{CoreVersion}
|
||||
container_name: bitwarden-identity
|
||||
restart: always
|
||||
volumes:
|
||||
- ../identity:/etc/bitwarden/identity
|
||||
- ../core:/etc/bitwarden/core
|
||||
- ../ca-certificates:/etc/bitwarden/ca-certificates
|
||||
- ../logs/identity:/etc/bitwarden/logs
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
- ../env/global.override.env
|
||||
|
||||
admin:
|
||||
image: bitwarden/admin:{CoreVersion}
|
||||
container_name: bitwarden-admin
|
||||
restart: always
|
||||
volumes:
|
||||
- ../core:/etc/bitwarden/core
|
||||
- ../ca-certificates:/etc/bitwarden/ca-certificates
|
||||
- ../logs/admin:/etc/bitwarden/logs
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
- ../env/global.override.env
|
||||
|
||||
icons:
|
||||
image: bitwarden/icons:{CoreVersion}
|
||||
container_name: bitwarden-icons
|
||||
restart: always
|
||||
volumes:
|
||||
- ../ca-certificates:/etc/bitwarden/ca-certificates
|
||||
- ../logs/icons:/etc/bitwarden/logs
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
|
||||
notifications:
|
||||
image: bitwarden/notifications:{CoreVersion}
|
||||
container_name: bitwarden-notifications
|
||||
restart: always
|
||||
volumes:
|
||||
- ../ca-certificates:/etc/bitwarden/ca-certificates
|
||||
- ../logs/notifications:/etc/bitwarden/logs
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
- ../env/global.override.env
|
||||
|
||||
nginx:
|
||||
image: bitwarden/nginx:{CoreVersion}
|
||||
container_name: bitwarden-nginx
|
||||
restart: always
|
||||
ports:");
|
||||
|
||||
if(HttpPort != default(int))
|
||||
{
|
||||
sw.Write($@"
|
||||
- '{HttpPort}:8080'");
|
||||
}
|
||||
|
||||
if(HttpsPort != default(int))
|
||||
{
|
||||
sw.Write($@"
|
||||
- '{HttpsPort}:8443'");
|
||||
}
|
||||
|
||||
sw.Write($@"
|
||||
volumes:
|
||||
- ../nginx:/etc/bitwarden/nginx
|
||||
- ../letsencrypt:/etc/letsencrypt
|
||||
- ../ssl:/etc/ssl
|
||||
- ../logs/nginx:/var/log/nginx
|
||||
env_file:
|
||||
- ../env/uid.env");
|
||||
|
||||
if(MssqlDataDockerVolume)
|
||||
{
|
||||
sw.Write(@"
|
||||
volumes:
|
||||
mssql_data:");
|
||||
}
|
||||
|
||||
// New line at end of file.
|
||||
sw.Write("\n");
|
||||
sw.Write(template(model));
|
||||
}
|
||||
}
|
||||
|
||||
public class TemplateModel
|
||||
{
|
||||
public TemplateModel(Context context)
|
||||
{
|
||||
MssqlDataDockerVolume = context.Config.DatabaseDockerVolume;
|
||||
HttpPort = context.Config.HttpPort;
|
||||
HttpsPort = context.Config.HttpsPort;
|
||||
CoreVersion = context.CoreVersion;
|
||||
WebVersion = context.WebVersion;
|
||||
}
|
||||
|
||||
public bool MssqlDataDockerVolume { get; set; }
|
||||
public string HttpPort { get; set; }
|
||||
public string HttpsPort { get; set; }
|
||||
public string CoreVersion { get; set; } = "latest";
|
||||
public string WebVersion { get; set; } = "latest";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Setup
|
||||
{
|
||||
public class EnvironmentFileBuilder
|
||||
{
|
||||
private readonly Context _context;
|
||||
|
||||
private IDictionary<string, string> _globalValues;
|
||||
private IDictionary<string, string> _mssqlValues;
|
||||
private IDictionary<string, string> _globalOverrideValues;
|
||||
private IDictionary<string, string> _mssqlOverrideValues;
|
||||
|
||||
public string Url { get; set; } = "https://localhost";
|
||||
public string Domain { get; set; } = "localhost";
|
||||
public string IdentityCertPassword { get; set; } = "REPLACE";
|
||||
public Guid? InstallationId { get; set; }
|
||||
public string InstallationKey { get; set; }
|
||||
public bool Push { get; set; }
|
||||
public string DatabasePassword { get; set; } = "REPLACE";
|
||||
public string OutputDirectory { get; set; } = ".";
|
||||
public EnvironmentFileBuilder(Context context)
|
||||
{
|
||||
_context = context;
|
||||
_globalValues = new Dictionary<string, string>
|
||||
{
|
||||
["ASPNETCORE_ENVIRONMENT"] = "Production",
|
||||
["globalSettings__selfHosted"] = "true",
|
||||
["globalSettings__baseServiceUri__vault"] = "http://localhost",
|
||||
["globalSettings__baseServiceUri__api"] = "http://localhost/api",
|
||||
["globalSettings__baseServiceUri__identity"] = "http://localhost/identity",
|
||||
["globalSettings__baseServiceUri__admin"] = "http://localhost/admin",
|
||||
["globalSettings__baseServiceUri__notifications"] = "http://localhost/notifications",
|
||||
["globalSettings__baseServiceUri__internalNotifications"] = "http://notifications:5000",
|
||||
["globalSettings__baseServiceUri__internalAdmin"] = "http://admin:5000",
|
||||
["globalSettings__baseServiceUri__internalIdentity"] = "http://identity:5000",
|
||||
["globalSettings__baseServiceUri__internalApi"] = "http://api:5000",
|
||||
["globalSettings__baseServiceUri__internalVault"] = "http://web:5000",
|
||||
["globalSettings__pushRelayBaseUri"] = "https://push.bitwarden.com",
|
||||
["globalSettings__installation__identityUri"] = "https://identity.bitwarden.com",
|
||||
};
|
||||
_mssqlValues = new Dictionary<string, string>
|
||||
{
|
||||
["ACCEPT_EULA"] = "Y",
|
||||
["MSSQL_PID"] = "Express",
|
||||
["SA_PASSWORD"] = "SECRET",
|
||||
};
|
||||
}
|
||||
|
||||
public void BuildForInstaller()
|
||||
{
|
||||
Directory.CreateDirectory("/bitwarden/env/");
|
||||
Init(true);
|
||||
Init();
|
||||
Build();
|
||||
}
|
||||
|
||||
public void BuildForUpdater()
|
||||
{
|
||||
Init(false);
|
||||
LoadExistingValues(_globalValues, "/bitwarden/env/global.override.env");
|
||||
LoadExistingValues(_mssqlValues, "/bitwarden/env/mssql.override.env");
|
||||
Init();
|
||||
LoadExistingValues(_globalOverrideValues, "/bitwarden/env/global.override.env");
|
||||
LoadExistingValues(_mssqlOverrideValues, "/bitwarden/env/mssql.override.env");
|
||||
|
||||
if(_context.Config.PushNotifications &&
|
||||
_globalOverrideValues.ContainsKey("globalSettings__pushRelayBaseUri") &&
|
||||
_globalOverrideValues["globalSettings__pushRelayBaseUri"] == "REPLACE")
|
||||
{
|
||||
_globalOverrideValues.Remove("globalSettings__pushRelayBaseUri");
|
||||
}
|
||||
|
||||
Build();
|
||||
}
|
||||
|
||||
private void Init(bool forInstall)
|
||||
private void Init()
|
||||
{
|
||||
var dbConnectionString = Helpers.MakeSqlConnectionString("mssql", "vault", "sa", DatabasePassword);
|
||||
_globalValues = new Dictionary<string, string>
|
||||
var dbPassword = Helpers.SecureRandomString(32);
|
||||
var dbConnectionString = Helpers.MakeSqlConnectionString("mssql", "vault", "sa", dbPassword);
|
||||
_globalOverrideValues = new Dictionary<string, string>
|
||||
{
|
||||
["globalSettings__baseServiceUri__vault"] = Url,
|
||||
["globalSettings__baseServiceUri__api"] = $"{Url}/api",
|
||||
["globalSettings__baseServiceUri__identity"] = $"{Url}/identity",
|
||||
["globalSettings__baseServiceUri__admin"] = $"{Url}/admin",
|
||||
["globalSettings__baseServiceUri__notifications"] = $"{Url}/notifications",
|
||||
["globalSettings__sqlServer__connectionString"] = $"\"{ dbConnectionString }\"",
|
||||
["globalSettings__identityServer__certificatePassword"] = IdentityCertPassword,
|
||||
["globalSettings__attachment__baseDirectory"] = $"{OutputDirectory}/core/attachments",
|
||||
["globalSettings__attachment__baseUrl"] = $"{Url}/attachments",
|
||||
["globalSettings__dataProtection__directory"] = $"{OutputDirectory}/core/aspnet-dataprotection",
|
||||
["globalSettings__logDirectory"] = $"{OutputDirectory}/logs",
|
||||
["globalSettings__licenseDirectory"] = $"{OutputDirectory}/core/licenses",
|
||||
["globalSettings__baseServiceUri__vault"] = _context.Config.Url,
|
||||
["globalSettings__baseServiceUri__api"] = $"{_context.Config.Url}/api",
|
||||
["globalSettings__baseServiceUri__identity"] = $"{_context.Config.Url}/identity",
|
||||
["globalSettings__baseServiceUri__admin"] = $"{_context.Config.Url}/admin",
|
||||
["globalSettings__baseServiceUri__notifications"] = $"{_context.Config.Url}/notifications",
|
||||
["globalSettings__sqlServer__connectionString"] = $"\"{dbConnectionString}\"",
|
||||
["globalSettings__identityServer__certificatePassword"] = _context.Install?.IdentityCertPassword,
|
||||
["globalSettings__attachment__baseDirectory"] = $"{_context.OutputDir}/core/attachments",
|
||||
["globalSettings__attachment__baseUrl"] = $"{_context.Config.Url}/attachments",
|
||||
["globalSettings__dataProtection__directory"] = $"{_context.OutputDir}/core/aspnet-dataprotection",
|
||||
["globalSettings__logDirectory"] = $"{_context.OutputDir}/logs",
|
||||
["globalSettings__licenseDirectory"] = $"{_context.OutputDir}/core/licenses",
|
||||
["globalSettings__internalIdentityKey"] = Helpers.SecureRandomString(64, alpha: true, numeric: true),
|
||||
["globalSettings__duo__aKey"] = Helpers.SecureRandomString(64, alpha: true, numeric: true),
|
||||
["globalSettings__installation__id"] = InstallationId?.ToString(),
|
||||
["globalSettings__installation__key"] = InstallationKey,
|
||||
["globalSettings__installation__id"] = _context.Install?.InstallationId.ToString(),
|
||||
["globalSettings__installation__key"] = _context.Install?.InstallationKey,
|
||||
["globalSettings__yubico__clientId"] = "REPLACE",
|
||||
["globalSettings__yubico__key"] = "REPLACE",
|
||||
["globalSettings__mail__replyToEmail"] = $"no-reply@{Domain}",
|
||||
["globalSettings__mail__replyToEmail"] = $"no-reply@{_context.Config.Domain}",
|
||||
["globalSettings__mail__smtp__host"] = "REPLACE",
|
||||
["globalSettings__mail__smtp__username"] = "REPLACE",
|
||||
["globalSettings__mail__smtp__password"] = "REPLACE",
|
||||
@ -67,16 +100,16 @@ namespace Bit.Setup
|
||||
["adminSettings__admins"] = string.Empty,
|
||||
};
|
||||
|
||||
if(forInstall && !Push)
|
||||
if(!_context.Config.PushNotifications)
|
||||
{
|
||||
_globalValues.Add("globalSettings__pushRelayBaseUri", "REPLACE");
|
||||
_globalOverrideValues.Add("globalSettings__pushRelayBaseUri", "REPLACE");
|
||||
}
|
||||
|
||||
_mssqlValues = new Dictionary<string, string>
|
||||
_mssqlOverrideValues = new Dictionary<string, string>
|
||||
{
|
||||
["ACCEPT_EULA"] = "Y",
|
||||
["MSSQL_PID"] = "Express",
|
||||
["SA_PASSWORD"] = DatabasePassword,
|
||||
["SA_PASSWORD"] = dbPassword,
|
||||
};
|
||||
}
|
||||
|
||||
@ -120,59 +153,34 @@ namespace Bit.Setup
|
||||
|
||||
private void Build()
|
||||
{
|
||||
var template = Helpers.ReadTemplate("EnvironmentFile");
|
||||
|
||||
Console.WriteLine("Building docker environment files.");
|
||||
Directory.CreateDirectory("/bitwarden/docker/");
|
||||
using(var sw = File.CreateText("/bitwarden/docker/global.env"))
|
||||
{
|
||||
sw.Write($@"ASPNETCORE_ENVIRONMENT=Production
|
||||
globalSettings__selfHosted=true
|
||||
globalSettings__baseServiceUri__vault=http://localhost
|
||||
globalSettings__baseServiceUri__api=http://localhost/api
|
||||
globalSettings__baseServiceUri__identity=http://localhost/identity
|
||||
globalSettings__baseServiceUri__admin=http://localhost/admin
|
||||
globalSettings__baseServiceUri__notifications=http://localhost/notifications
|
||||
globalSettings__baseServiceUri__internalNotifications=http://notifications:5000
|
||||
globalSettings__baseServiceUri__internalAdmin=http://admin:5000
|
||||
globalSettings__baseServiceUri__internalIdentity=http://identity:5000
|
||||
globalSettings__baseServiceUri__internalApi=http://api:5000
|
||||
globalSettings__baseServiceUri__internalVault=http://web:5000
|
||||
globalSettings__pushRelayBaseUri=https://push.bitwarden.com
|
||||
globalSettings__installation__identityUri=https://identity.bitwarden.com
|
||||
");
|
||||
sw.Write(template(new TemplateModel(_globalValues)));
|
||||
}
|
||||
|
||||
Helpers.Exec("chmod 600 /bitwarden/docker/global.env");
|
||||
|
||||
using(var sw = File.CreateText("/bitwarden/docker/mssql.env"))
|
||||
{
|
||||
sw.Write($@"ACCEPT_EULA=Y
|
||||
MSSQL_PID=Express
|
||||
SA_PASSWORD=SECRET
|
||||
");
|
||||
sw.Write(template(new TemplateModel(_mssqlValues)));
|
||||
}
|
||||
|
||||
Helpers.Exec("chmod 600 /bitwarden/docker/mssql.env");
|
||||
|
||||
Console.WriteLine("Building docker environment override files.");
|
||||
Directory.CreateDirectory(" /bitwarden/env/");
|
||||
Directory.CreateDirectory("/bitwarden/env/");
|
||||
using(var sw = File.CreateText("/bitwarden/env/global.override.env"))
|
||||
{
|
||||
foreach(var item in _globalValues)
|
||||
{
|
||||
sw.WriteLine($"{item.Key}={item.Value}");
|
||||
}
|
||||
sw.Write(template(new TemplateModel(_globalOverrideValues)));
|
||||
}
|
||||
|
||||
Helpers.Exec("chmod 600 /bitwarden/env/global.override.env");
|
||||
|
||||
using(var sw = File.CreateText("/bitwarden/env/mssql.override.env"))
|
||||
{
|
||||
foreach(var item in _mssqlValues)
|
||||
{
|
||||
sw.WriteLine($"{item.Key}={item.Value}");
|
||||
}
|
||||
sw.Write(template(new TemplateModel(_mssqlOverrideValues)));
|
||||
}
|
||||
|
||||
Helpers.Exec("chmod 600 /bitwarden/env/mssql.override.env");
|
||||
|
||||
// Empty uid env file. Only used on Linux hosts.
|
||||
@ -181,5 +189,21 @@ SA_PASSWORD=SECRET
|
||||
using(var sw = File.CreateText("/bitwarden/env/uid.env")) { }
|
||||
}
|
||||
}
|
||||
|
||||
public class TemplateModel
|
||||
{
|
||||
public TemplateModel(IEnumerable<KeyValuePair<string, string>> variables)
|
||||
{
|
||||
Variables = variables.Select(v => new Kvp { Key = v.Key, Value = v.Value });
|
||||
}
|
||||
|
||||
public IEnumerable<Kvp> Variables { get; set; }
|
||||
|
||||
public class Kvp
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@ -192,5 +194,21 @@ namespace Bit.Setup
|
||||
Console.WriteLine();
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
public static Func<object, string> ReadTemplate(string templateName)
|
||||
{
|
||||
var assembly = typeof(Helpers).GetTypeInfo().Assembly;
|
||||
var fullTemplateName = $"Bit.Setup.Templates.{templateName}.hbs";
|
||||
if(!assembly.GetManifestResourceNames().Any(f => f == fullTemplateName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
using(var s = assembly.GetManifestResourceStream(fullTemplateName))
|
||||
using(var sr = new StreamReader(s))
|
||||
{
|
||||
var templateText = sr.ReadToEnd();
|
||||
return HandlebarsDotNet.Handlebars.Compile(templateText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,177 +16,99 @@ namespace Bit.Setup
|
||||
"child-src 'self' https://*.duosecurity.com; frame-src 'self' https://*.duosecurity.com; " +
|
||||
"connect-src 'self' wss://{0} https://haveibeenpwned.com https://api.pwnedpasswords.com;";
|
||||
|
||||
public NginxConfigBuilder(string domain, string url, bool ssl, bool selfSignedSsl, bool letsEncrypt,
|
||||
bool trusted, bool diffieHellman)
|
||||
{
|
||||
Domain = domain;
|
||||
Url = url;
|
||||
Ssl = ssl;
|
||||
SelfSignedSsl = selfSignedSsl;
|
||||
LetsEncrypt = letsEncrypt;
|
||||
Trusted = trusted;
|
||||
DiffieHellman = diffieHellman;
|
||||
}
|
||||
private readonly Context _context;
|
||||
|
||||
public NginxConfigBuilder(string domain, string url)
|
||||
public NginxConfigBuilder(Context context)
|
||||
{
|
||||
Domain = domain;
|
||||
Url = url;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public bool Ssl { get; private set; }
|
||||
public bool SelfSignedSsl { get; private set; }
|
||||
public bool LetsEncrypt { get; private set; }
|
||||
public string Domain { get; private set; }
|
||||
public string Url { get; private set; }
|
||||
public bool DiffieHellman { get; private set; }
|
||||
public bool Trusted { get; private set; }
|
||||
|
||||
public void BuildForInstaller()
|
||||
{
|
||||
Build();
|
||||
var model = new TemplateModel(_context);
|
||||
if(model.Ssl && !_context.Config.SslManagedLetsEncrypt)
|
||||
{
|
||||
var sslPath = _context.Install.SelfSignedCert ?
|
||||
$"/etc/ssl/self/{model.Domain}" : $"/etc/ssl/{model.Domain}";
|
||||
_context.Config.SslCertificatePath = model.CertificatePath =
|
||||
string.Concat(sslPath, "/", "certificate.crt");
|
||||
_context.Config.SslKeyPath = model.KeyPath =
|
||||
string.Concat(sslPath, "/", "private.key");
|
||||
if(_context.Install.Trusted)
|
||||
{
|
||||
_context.Config.SslCaPath = model.CaPath =
|
||||
string.Concat(sslPath, "/", "ca.crt");
|
||||
}
|
||||
if(_context.Install.DiffieHellman)
|
||||
{
|
||||
_context.Config.SslDiffieHellmanPath = model.DiffieHellmanPath =
|
||||
string.Concat(sslPath, "/", "dhparam.pem");
|
||||
}
|
||||
}
|
||||
Build(model);
|
||||
}
|
||||
|
||||
public void BuildForUpdater()
|
||||
{
|
||||
if(File.Exists(ConfFile))
|
||||
{
|
||||
var confContent = File.ReadAllText(ConfFile);
|
||||
Ssl = confContent.Contains("ssl http2;");
|
||||
SelfSignedSsl = confContent.Contains("/etc/ssl/self/");
|
||||
LetsEncrypt = !SelfSignedSsl && confContent.Contains("/etc/letsencrypt/live/");
|
||||
DiffieHellman = confContent.Contains("/dhparam.pem;");
|
||||
Trusted = confContent.Contains("ssl_trusted_certificate ");
|
||||
}
|
||||
|
||||
Build();
|
||||
var model = new TemplateModel(_context);
|
||||
Build(model);
|
||||
}
|
||||
|
||||
private void Build()
|
||||
private void Build(TemplateModel model)
|
||||
{
|
||||
Directory.CreateDirectory("/bitwarden/nginx/");
|
||||
|
||||
var sslPath = LetsEncrypt ? $"/etc/letsencrypt/live/{Domain}" :
|
||||
SelfSignedSsl ? $"/etc/ssl/self/{Domain}" : $"/etc/ssl/{Domain}";
|
||||
var certFile = LetsEncrypt ? "fullchain.pem" : "certificate.crt";
|
||||
var keyFile = LetsEncrypt ? "privkey.pem" : "private.key";
|
||||
var caFile = LetsEncrypt ? "fullchain.pem" : "ca.crt";
|
||||
|
||||
Console.WriteLine("Building nginx config.");
|
||||
if(!_context.Config.GenerateNginxConfig)
|
||||
{
|
||||
Console.WriteLine("...skipped");
|
||||
return;
|
||||
}
|
||||
|
||||
var template = Helpers.ReadTemplate("NginxConfig");
|
||||
using(var sw = File.CreateText(ConfFile))
|
||||
{
|
||||
sw.WriteLine($@"# Config Parameters
|
||||
# Parameter:Ssl={Ssl}
|
||||
# Parameter:SelfSignedSsl={SelfSignedSsl}
|
||||
# Parameter:LetsEncrypt={LetsEncrypt}
|
||||
# Parameter:Domain={Domain}
|
||||
# Parameter:Url={Url}
|
||||
# Parameter:DiffieHellman={DiffieHellman}
|
||||
# Parameter:Trusted={Trusted}
|
||||
sw.WriteLine(template(model));
|
||||
}
|
||||
}
|
||||
|
||||
server {{
|
||||
listen 8080 default_server;
|
||||
listen [::]:8080 default_server;
|
||||
server_name {Domain};");
|
||||
public class TemplateModel
|
||||
{
|
||||
public TemplateModel() { }
|
||||
|
||||
public TemplateModel(Context context)
|
||||
{
|
||||
Ssl = context.Config.Ssl;
|
||||
Domain = context.Config.Domain;
|
||||
Url = context.Config.Url;
|
||||
|
||||
if(Ssl)
|
||||
{
|
||||
sw.WriteLine($@" return 301 {Url}$request_uri;
|
||||
}}
|
||||
|
||||
server {{
|
||||
listen 8443 ssl http2;
|
||||
listen [::]:8443 ssl http2;
|
||||
server_name {Domain};
|
||||
|
||||
ssl_certificate {sslPath}/{certFile};
|
||||
ssl_certificate_key {sslPath}/{keyFile};
|
||||
|
||||
ssl_session_timeout 30m;
|
||||
ssl_session_cache shared:SSL:20m;
|
||||
ssl_session_tickets off;");
|
||||
|
||||
if(DiffieHellman)
|
||||
if(context.Config.SslManagedLetsEncrypt)
|
||||
{
|
||||
sw.WriteLine($@"
|
||||
# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
|
||||
ssl_dhparam {sslPath}/dhparam.pem;");
|
||||
var sslPath = $"/etc/letsencrypt/live/{Domain}";
|
||||
CertificatePath = CaPath = string.Concat(sslPath, "/", "fullchain.pem");
|
||||
KeyPath = string.Concat(sslPath, "/", "privkey.pem");
|
||||
DiffieHellmanPath = string.Concat(sslPath, "/", "dhparam.pem");
|
||||
}
|
||||
|
||||
sw.WriteLine($@"
|
||||
# SSL protocol TLSv1.2 is allowed. Disabled SSLv3, TLSv1, and TLSv1.1
|
||||
ssl_protocols TLSv1.2;
|
||||
# Enable most secure cipher suites only.
|
||||
ssl_ciphers ""{SslCiphers}"";
|
||||
# Enables server-side protection from BEAST attacks
|
||||
ssl_prefer_server_ciphers on;");
|
||||
|
||||
if(Trusted)
|
||||
else
|
||||
{
|
||||
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 {sslPath}/{caFile};
|
||||
|
||||
resolver 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=300s;
|
||||
|
||||
# This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack. 6 months age
|
||||
add_header Strict-Transport-Security max-age=15768000;");
|
||||
CertificatePath = context.Config.SslCertificatePath;
|
||||
KeyPath = context.Config.SslKeyPath;
|
||||
CaPath = context.Config.SslCaPath;
|
||||
DiffieHellmanPath = context.Config.SslDiffieHellmanPath;
|
||||
}
|
||||
}
|
||||
|
||||
sw.WriteLine($@"
|
||||
location / {{
|
||||
proxy_pass http://web:5000/;
|
||||
# Security headers
|
||||
#add_header X-Frame-Options SAMEORIGIN;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection ""1; mode=block"";
|
||||
add_header Referrer-Policy same-origin;
|
||||
add_header Content-Security-Policy ""{string.Format(ContentSecurityPolicy, Domain)}"";
|
||||
}}
|
||||
|
||||
location = /app-id.json {{
|
||||
proxy_pass http://web:5000/app-id.json;
|
||||
proxy_hide_header Content-Type;
|
||||
add_header Content-Type $fido_content_type;
|
||||
}}
|
||||
|
||||
location /attachments/ {{
|
||||
proxy_pass http://attachments:5000/;
|
||||
}}
|
||||
|
||||
location /api/ {{
|
||||
proxy_pass http://api:5000/;
|
||||
}}
|
||||
|
||||
location /identity/ {{
|
||||
proxy_pass http://identity:5000/;
|
||||
}}
|
||||
|
||||
location /icons/ {{
|
||||
proxy_pass http://icons:5000/;
|
||||
}}
|
||||
|
||||
location /notifications/ {{
|
||||
proxy_pass http://notifications:5000/;
|
||||
}}
|
||||
|
||||
location /notifications/hub {{
|
||||
proxy_pass http://notifications:5000/hub;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $http_connection;
|
||||
}}
|
||||
|
||||
location /admin {{
|
||||
proxy_pass http://admin:5000;
|
||||
}}
|
||||
}}");
|
||||
}
|
||||
|
||||
public bool Ssl { get; set; }
|
||||
public string Domain { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string CertificatePath { get; set; }
|
||||
public string KeyPath { get; set; }
|
||||
public string CaPath { get; set; }
|
||||
public string DiffieHellmanPath { get; set; }
|
||||
public string ContentSecurityPolicy => string.Format(NginxConfigBuilder.ContentSecurityPolicy, Domain);
|
||||
public string SslCiphers => NginxConfigBuilder.SslCiphers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,48 +5,44 @@ using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
|
||||
namespace Bit.Setup
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
private static string[] _args = null;
|
||||
private static IDictionary<string, string> _parameters = null;
|
||||
private static Guid? _installationId = null;
|
||||
private static string _installationKey = null;
|
||||
private static string _hostOs = "win";
|
||||
private static string _coreVersion = "latest";
|
||||
private static string _webVersion = "latest";
|
||||
private static Context _context;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine();
|
||||
_context = new Context
|
||||
{
|
||||
Args = args
|
||||
};
|
||||
ParseParameters();
|
||||
|
||||
_args = args;
|
||||
_parameters = ParseParameters();
|
||||
if(_parameters.ContainsKey("os"))
|
||||
if(_context.Parameters.ContainsKey("os"))
|
||||
{
|
||||
_hostOs = _parameters["os"];
|
||||
_context.HostOS = _context.Parameters["os"];
|
||||
}
|
||||
if(_parameters.ContainsKey("corev"))
|
||||
if(_context.Parameters.ContainsKey("corev"))
|
||||
{
|
||||
_coreVersion = _parameters["corev"];
|
||||
_context.CoreVersion = _context.Parameters["corev"];
|
||||
}
|
||||
if(_parameters.ContainsKey("webv"))
|
||||
if(_context.Parameters.ContainsKey("webv"))
|
||||
{
|
||||
_webVersion = _parameters["webv"];
|
||||
_context.WebVersion = _context.Parameters["webv"];
|
||||
}
|
||||
|
||||
if(_parameters.ContainsKey("install"))
|
||||
if(_context.Parameters.ContainsKey("install"))
|
||||
{
|
||||
Install();
|
||||
}
|
||||
else if(_parameters.ContainsKey("update"))
|
||||
else if(_context.Parameters.ContainsKey("update"))
|
||||
{
|
||||
Update();
|
||||
}
|
||||
else if(_parameters.ContainsKey("printenv"))
|
||||
else if(_context.Parameters.ContainsKey("printenv"))
|
||||
{
|
||||
PrintEnvironment();
|
||||
}
|
||||
@ -58,147 +54,46 @@ namespace Bit.Setup
|
||||
|
||||
private static void Install()
|
||||
{
|
||||
var outputDir = _parameters.ContainsKey("out") ?
|
||||
_parameters["out"].ToLowerInvariant() : "/etc/bitwarden";
|
||||
var domain = _parameters.ContainsKey("domain") ?
|
||||
_parameters["domain"].ToLowerInvariant() : "localhost";
|
||||
var letsEncrypt = _parameters.ContainsKey("letsencrypt") ?
|
||||
_parameters["letsencrypt"].ToLowerInvariant() == "y" : false;
|
||||
if(_context.Parameters.ContainsKey("letsencrypt"))
|
||||
{
|
||||
_context.Config.SslManagedLetsEncrypt =
|
||||
_context.Parameters["letsencrypt"].ToLowerInvariant() == "y";
|
||||
}
|
||||
if(_context.Parameters.ContainsKey("domain"))
|
||||
{
|
||||
_context.Install.Domain = _context.Parameters["domain"].ToLowerInvariant();
|
||||
}
|
||||
|
||||
if(!ValidateInstallation())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ssl = letsEncrypt;
|
||||
if(!letsEncrypt)
|
||||
{
|
||||
ssl = Helpers.ReadQuestion("Do you have a SSL certificate to use?");
|
||||
if(ssl)
|
||||
{
|
||||
Directory.CreateDirectory($"/bitwarden/ssl/{domain}/");
|
||||
var message = "Make sure 'certificate.crt' and 'private.key' are provided in the \n" +
|
||||
"appropriate directory before running 'start' (see docs for info).";
|
||||
Helpers.ShowBanner("NOTE", message);
|
||||
}
|
||||
}
|
||||
var certBuilder = new CertBuilder(_context);
|
||||
certBuilder.BuildForInstall();
|
||||
|
||||
// Set the URL
|
||||
_context.Config.Url = string.Format("http{0}://{1}",
|
||||
_context.Config.Ssl ? "s" : string.Empty, _context.Install.Domain);
|
||||
|
||||
var identityCertPassword = Helpers.SecureRandomString(32, alpha: true, numeric: true);
|
||||
var certBuilder = new CertBuilder(domain, identityCertPassword, letsEncrypt, ssl);
|
||||
var selfSignedSsl = certBuilder.BuildForInstall();
|
||||
ssl = certBuilder.Ssl; // Ssl prop can get flipped during the build
|
||||
|
||||
var sslTrusted = letsEncrypt;
|
||||
var sslDiffieHellman = letsEncrypt;
|
||||
if(ssl && !selfSignedSsl && !letsEncrypt)
|
||||
{
|
||||
sslDiffieHellman = Helpers.ReadQuestion("Use Diffie Hellman ephemeral parameters for SSL " +
|
||||
"(requires dhparam.pem, see docs)?");
|
||||
sslTrusted = Helpers.ReadQuestion("Is this a trusted SSL certificate (requires ca.crt, see docs)?");
|
||||
}
|
||||
|
||||
if(!ssl)
|
||||
{
|
||||
var message = "You are not using a SSL certificate. Bitwarden requires HTTPS to operate. \n" +
|
||||
"You must front your installation with a HTTPS proxy. The web vault (and \n" +
|
||||
"other Bitwarden apps) will not work properly without HTTPS.";
|
||||
Helpers.ShowBanner("WARNING", message, ConsoleColor.Yellow);
|
||||
}
|
||||
else if(ssl && !sslTrusted)
|
||||
{
|
||||
var message = "You are using an untrusted SSL certificate. This certificate will not be \n" +
|
||||
"trusted by Bitwarden client applications. You must add this certificate to \n" +
|
||||
"the trusted store on each device or else you will receive errors when trying \n" +
|
||||
"to connect to your installation.";
|
||||
Helpers.ShowBanner("WARNING", message, ConsoleColor.Yellow);
|
||||
}
|
||||
|
||||
var url = $"https://{domain}";
|
||||
int httpPort = default(int), httpsPort = default(int);
|
||||
if(Helpers.ReadQuestion("Do you want to use the default ports for HTTP (80) and HTTPS (443)?"))
|
||||
{
|
||||
httpPort = 80;
|
||||
if(ssl)
|
||||
{
|
||||
httpsPort = 443;
|
||||
}
|
||||
}
|
||||
else if(ssl)
|
||||
{
|
||||
httpsPort = 443;
|
||||
if(int.TryParse(Helpers.ReadInput("HTTPS port").Trim(), out httpsPort) && httpsPort != 443)
|
||||
{
|
||||
url += (":" + httpsPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Using default port.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
httpPort = 80;
|
||||
if(!int.TryParse(Helpers.ReadInput("HTTP port").Trim(), out httpPort) && httpPort != 80)
|
||||
{
|
||||
Console.WriteLine("Using default port.");
|
||||
}
|
||||
}
|
||||
|
||||
if(Helpers.ReadQuestion("Is your installation behind a reverse proxy?"))
|
||||
{
|
||||
if(Helpers.ReadQuestion("Do you use the default HTTPS port (443) on your reverse proxy?"))
|
||||
{
|
||||
url = $"https://{domain}";
|
||||
}
|
||||
else
|
||||
{
|
||||
if(int.TryParse(Helpers.ReadInput("Proxy HTTPS port").Trim(), out var httpsReversePort)
|
||||
&& httpsReversePort != 443)
|
||||
{
|
||||
url += (":" + httpsReversePort);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Using default port.");
|
||||
url = $"https://{domain}";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!ssl)
|
||||
{
|
||||
Console.WriteLine("ERROR: You must use a reverse proxy if not using SSL.");
|
||||
return;
|
||||
}
|
||||
|
||||
var push = Helpers.ReadQuestion("Do you want to use push notifications?");
|
||||
|
||||
var nginxBuilder = new NginxConfigBuilder(domain, url, ssl, selfSignedSsl, letsEncrypt,
|
||||
sslTrusted, sslDiffieHellman);
|
||||
var nginxBuilder = new NginxConfigBuilder(_context);
|
||||
nginxBuilder.BuildForInstaller();
|
||||
|
||||
var environmentFileBuilder = new EnvironmentFileBuilder
|
||||
{
|
||||
DatabasePassword = Helpers.SecureRandomString(32),
|
||||
Domain = domain,
|
||||
IdentityCertPassword = identityCertPassword,
|
||||
InstallationId = _installationId,
|
||||
InstallationKey = _installationKey,
|
||||
OutputDirectory = outputDir,
|
||||
Push = push,
|
||||
Url = url
|
||||
};
|
||||
var environmentFileBuilder = new EnvironmentFileBuilder(_context);
|
||||
environmentFileBuilder.BuildForInstaller();
|
||||
|
||||
var appIdBuilder = new AppIdBuilder(url);
|
||||
var appIdBuilder = new AppIdBuilder(_context);
|
||||
appIdBuilder.Build();
|
||||
|
||||
var dockerComposeBuilder = new DockerComposeBuilder(_hostOs, _webVersion, _coreVersion);
|
||||
dockerComposeBuilder.BuildForInstaller(httpPort, httpsPort);
|
||||
var dockerComposeBuilder = new DockerComposeBuilder(_context);
|
||||
dockerComposeBuilder.BuildForInstaller();
|
||||
|
||||
_context.SaveConfiguration();
|
||||
}
|
||||
|
||||
private static void Update()
|
||||
{
|
||||
if(_parameters.ContainsKey("db"))
|
||||
if(_context.Parameters.ContainsKey("db"))
|
||||
{
|
||||
MigrateDatabase();
|
||||
}
|
||||
@ -210,12 +105,12 @@ namespace Bit.Setup
|
||||
|
||||
private static void PrintEnvironment()
|
||||
{
|
||||
var vaultUrl = Helpers.GetValueFronEnvFile("global", "globalSettings__baseServiceUri__vault");
|
||||
_context.LoadConfiguration();
|
||||
Console.WriteLine("\nBitwarden is up and running!");
|
||||
Console.WriteLine("===================================================");
|
||||
Console.WriteLine("\nvisit {0}", vaultUrl);
|
||||
Console.WriteLine("\nvisit {0}", _context.Config.Url);
|
||||
Console.Write("to update, run ");
|
||||
if(_hostOs == "win")
|
||||
if(_context.HostOS == "win")
|
||||
{
|
||||
Console.Write("'.\\bitwarden.ps1 -updateself' and then '.\\bitwarden.ps1 -update'");
|
||||
}
|
||||
@ -296,13 +191,13 @@ namespace Bit.Setup
|
||||
return false;
|
||||
}
|
||||
|
||||
_installationId = installationidGuid;
|
||||
_installationKey = Helpers.ReadInput("Enter your installation key");
|
||||
_context.Install.InstallationId = installationidGuid;
|
||||
_context.Install.InstallationKey = Helpers.ReadInput("Enter your installation key");
|
||||
|
||||
try
|
||||
{
|
||||
var response = new HttpClient().GetAsync("https://api.bitwarden.com/installations/" + _installationId)
|
||||
.GetAwaiter().GetResult();
|
||||
var response = new HttpClient().GetAsync("https://api.bitwarden.com/installations/" +
|
||||
_context.Install.InstallationId).GetAwaiter().GetResult();
|
||||
|
||||
if(!response.IsSuccessStatusCode)
|
||||
{
|
||||
@ -337,42 +232,35 @@ namespace Bit.Setup
|
||||
|
||||
private static void RebuildConfigs()
|
||||
{
|
||||
var environmentFileBuilder = new EnvironmentFileBuilder();
|
||||
_context.LoadConfiguration();
|
||||
|
||||
var environmentFileBuilder = new EnvironmentFileBuilder(_context);
|
||||
environmentFileBuilder.BuildForUpdater();
|
||||
|
||||
var url = Helpers.GetValueFronEnvFile("global", "globalSettings__baseServiceUri__vault");
|
||||
if(!Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
|
||||
{
|
||||
Console.WriteLine("Unable to determine existing installation url.");
|
||||
return;
|
||||
}
|
||||
|
||||
var domain = uri.Host;
|
||||
|
||||
var nginxBuilder = new NginxConfigBuilder(domain, url);
|
||||
var nginxBuilder = new NginxConfigBuilder(_context);
|
||||
nginxBuilder.BuildForUpdater();
|
||||
|
||||
var appIdBuilder = new AppIdBuilder(url);
|
||||
var appIdBuilder = new AppIdBuilder(_context);
|
||||
appIdBuilder.Build();
|
||||
|
||||
var dockerComposeBuilder = new DockerComposeBuilder(_hostOs, _webVersion, _coreVersion);
|
||||
var dockerComposeBuilder = new DockerComposeBuilder(_context);
|
||||
dockerComposeBuilder.BuildForUpdater();
|
||||
|
||||
_context.SaveConfiguration();
|
||||
}
|
||||
|
||||
private static IDictionary<string, string> ParseParameters()
|
||||
private static void ParseParameters()
|
||||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
for(var i = 0; i < _args.Length; i = i + 2)
|
||||
_context.Parameters = new Dictionary<string, string>();
|
||||
for(var i = 0; i < _context.Args.Length; i = i + 2)
|
||||
{
|
||||
if(!_args[i].StartsWith("-"))
|
||||
if(!_context.Args[i].StartsWith("-"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dict.Add(_args[i].Substring(1), _args[i + 1]);
|
||||
_context.Parameters.Add(_context.Args[i].Substring(1), _context.Args[i + 1]);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,12 +9,15 @@
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="DbScripts\**\*.sql" />
|
||||
<EmbeddedResource Include="Templates\**\*.hbs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Handlebars.Net" Version="1.9.5" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.5.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="dbup" Version="3.3.5" />
|
||||
<PackageReference Include="YamlDotNet" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
15
util/Setup/Templates/AppId.hbs
Normal file
15
util/Setup/Templates/AppId.hbs
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"trustedFacets": [
|
||||
{
|
||||
"version": {
|
||||
"major": 1,
|
||||
"minor": 0
|
||||
},
|
||||
"ids": [
|
||||
"{{{Url}}}",
|
||||
"ios:bundle-id:com.8bit.bitwarden",
|
||||
"android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
133
util/Setup/Templates/DockerCompose.hbs
Normal file
133
util/Setup/Templates/DockerCompose.hbs
Normal file
@ -0,0 +1,133 @@
|
||||
# https://docs.docker.com/compose/compose-file/
|
||||
#
|
||||
# WARNING: This file is generated. Do not make changes to this file.
|
||||
# They will be overwritten on update. If you want to make additions to
|
||||
# this file, you can create a `docker-compose.override.yml` file in the
|
||||
# same directory and it will be merged into this file at runtime.
|
||||
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
mssql:
|
||||
image: bitwarden/mssql:{{{CoreVersion}}}
|
||||
container_name: bitwarden-mssql
|
||||
restart: always
|
||||
volumes:
|
||||
{{#if MssqlDataDockerVolume}}
|
||||
- mssql_data:/var/opt/mssql/data
|
||||
{{else}}
|
||||
- ../mssql/data:/var/opt/mssql/data
|
||||
{{/if}}
|
||||
- ../logs/mssql:/var/opt/mssql/log
|
||||
- ../mssql/backups:/etc/bitwarden/mssql/backups
|
||||
env_file:
|
||||
- mssql.env
|
||||
- ../env/uid.env
|
||||
- ../env/mssql.override.env
|
||||
|
||||
web:
|
||||
image: bitwarden/web:{{{WebVersion}}}
|
||||
container_name: bitwarden-web
|
||||
restart: always
|
||||
volumes:
|
||||
- ../web:/etc/bitwarden/web
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
|
||||
attachments:
|
||||
image: bitwarden/attachments:{{{CoreVersion}}}
|
||||
container_name: bitwarden-attachments
|
||||
restart: always
|
||||
volumes:
|
||||
- ../core/attachments:/etc/bitwarden/core/attachments
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
|
||||
api:
|
||||
image: bitwarden/api:{{{CoreVersion}}}
|
||||
container_name: bitwarden-api
|
||||
restart: always
|
||||
volumes:
|
||||
- ../core:/etc/bitwarden/core
|
||||
- ../ca-certificates:/etc/bitwarden/ca-certificates
|
||||
- ../logs/api:/etc/bitwarden/logs
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
- ../env/global.override.env
|
||||
|
||||
identity:
|
||||
image: bitwarden/identity:{{{CoreVersion}}}
|
||||
container_name: bitwarden-identity
|
||||
restart: always
|
||||
volumes:
|
||||
- ../identity:/etc/bitwarden/identity
|
||||
- ../core:/etc/bitwarden/core
|
||||
- ../ca-certificates:/etc/bitwarden/ca-certificates
|
||||
- ../logs/identity:/etc/bitwarden/logs
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
- ../env/global.override.env
|
||||
|
||||
admin:
|
||||
image: bitwarden/admin:{{{CoreVersion}}}
|
||||
container_name: bitwarden-admin
|
||||
restart: always
|
||||
volumes:
|
||||
- ../core:/etc/bitwarden/core
|
||||
- ../ca-certificates:/etc/bitwarden/ca-certificates
|
||||
- ../logs/admin:/etc/bitwarden/logs
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
- ../env/global.override.env
|
||||
|
||||
icons:
|
||||
image: bitwarden/icons:{{{CoreVersion}}}
|
||||
container_name: bitwarden-icons
|
||||
restart: always
|
||||
volumes:
|
||||
- ../ca-certificates:/etc/bitwarden/ca-certificates
|
||||
- ../logs/icons:/etc/bitwarden/logs
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
|
||||
notifications:
|
||||
image: bitwarden/notifications:{{{CoreVersion}}}
|
||||
container_name: bitwarden-notifications
|
||||
restart: always
|
||||
volumes:
|
||||
- ../ca-certificates:/etc/bitwarden/ca-certificates
|
||||
- ../logs/notifications:/etc/bitwarden/logs
|
||||
env_file:
|
||||
- global.env
|
||||
- ../env/uid.env
|
||||
- ../env/global.override.env
|
||||
|
||||
nginx:
|
||||
image: bitwarden/nginx:{{{CoreVersion}}}
|
||||
container_name: bitwarden-nginx
|
||||
restart: always
|
||||
ports:
|
||||
{{#if HttpPort}}
|
||||
- '{{{HttpPort}}}:8080'
|
||||
{{/if}}
|
||||
{{#if HttpsPort}}
|
||||
- '{{{HttpsPort}}}:8443'
|
||||
{{/if}}
|
||||
volumes:
|
||||
- ../nginx:/etc/bitwarden/nginx
|
||||
- ../letsencrypt:/etc/letsencrypt
|
||||
- ../ssl:/etc/ssl
|
||||
- ../logs/nginx:/var/log/nginx
|
||||
env_file:
|
||||
- ../env/uid.env
|
||||
{{#if MssqlDataDockerVolume}}
|
||||
|
||||
volumes:
|
||||
mssql_data:
|
||||
{{/if}}
|
3
util/Setup/Templates/EnvironmentFile.hbs
Normal file
3
util/Setup/Templates/EnvironmentFile.hbs
Normal file
@ -0,0 +1,3 @@
|
||||
{{#each Variables}}
|
||||
{{{Key}}}={{{Value}}}
|
||||
{{/each}}
|
99
util/Setup/Templates/NginxConfig.hbs
Normal file
99
util/Setup/Templates/NginxConfig.hbs
Normal file
@ -0,0 +1,99 @@
|
||||
# WARNING: This file is generated. Do not make changes to this file.
|
||||
# They will be overwritten on update.
|
||||
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
listen [::]:8080 default_server;
|
||||
server_name {{{Domain}}};
|
||||
{{#if Ssl}}
|
||||
|
||||
return 301 {{{Url}}}$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 8443 ssl http2;
|
||||
listen [::]:8443 ssl http2;
|
||||
server_name {{{Domain}}};
|
||||
|
||||
ssl_certificate {{{CertificatePath}}};
|
||||
ssl_certificate_key {{{KeyPath}}};
|
||||
ssl_session_timeout 30m;
|
||||
ssl_session_cache shared:SSL:20m;
|
||||
ssl_session_tickets off;
|
||||
{{#if DiffieHellmanPath}}
|
||||
|
||||
# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
|
||||
ssl_dhparam {{{DiffieHellmanPath}}};
|
||||
{{/if}}
|
||||
|
||||
# SSL protocol TLSv1.2 is allowed. Disabled SSLv3, TLSv1, and TLSv1.1
|
||||
ssl_protocols TLSv1.2;
|
||||
# Enable most secure cipher suites only.
|
||||
ssl_ciphers "{{{SslCiphers}}}";
|
||||
# Enables server-side protection from BEAST attacks
|
||||
ssl_prefer_server_ciphers on;
|
||||
{{#if CaPath}}
|
||||
|
||||
# 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 {{{CaPath}}};
|
||||
resolver 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=300s;
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
# Security headers
|
||||
add_header Referrer-Policy same-origin;
|
||||
#add_header X-Frame-Options SAMEORIGIN;
|
||||
{{#if Ssl}}
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
# This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack. 6 months age
|
||||
add_header Strict-Transport-Security max-age=15768000;
|
||||
{{/if}}
|
||||
|
||||
location / {
|
||||
proxy_pass http://web:5000/;
|
||||
# Security headers
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header Content-Security-Policy "{{{ContentSecurityPolicy}}}";
|
||||
}
|
||||
|
||||
location = /app-id.json {
|
||||
proxy_pass http://web:5000/app-id.json;
|
||||
proxy_hide_header Content-Type;
|
||||
add_header Content-Type $fido_content_type;
|
||||
}
|
||||
|
||||
location /attachments/ {
|
||||
proxy_pass http://attachments:5000/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://api:5000/;
|
||||
}
|
||||
|
||||
location /identity/ {
|
||||
proxy_pass http://identity:5000/;
|
||||
}
|
||||
|
||||
location /icons/ {
|
||||
proxy_pass http://icons:5000/;
|
||||
}
|
||||
|
||||
location /notifications/ {
|
||||
proxy_pass http://notifications:5000/;
|
||||
}
|
||||
|
||||
location /notifications/hub {
|
||||
proxy_pass http://notifications:5000/hub;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $http_connection;
|
||||
}
|
||||
|
||||
location /admin {
|
||||
proxy_pass http://admin:5000;
|
||||
}
|
||||
}
|
111
util/Setup/YamlComments.cs
Normal file
111
util/Setup/YamlComments.cs
Normal file
@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.Core.Events;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.ObjectGraphVisitors;
|
||||
using YamlDotNet.Serialization.TypeInspectors;
|
||||
|
||||
// ref: https://github.com/aaubry/YamlDotNet/issues/152#issuecomment-349034754
|
||||
|
||||
namespace Bit.Setup
|
||||
{
|
||||
public class CommentGatheringTypeInspector : TypeInspectorSkeleton
|
||||
{
|
||||
private readonly ITypeInspector _innerTypeDescriptor;
|
||||
|
||||
public CommentGatheringTypeInspector(ITypeInspector innerTypeDescriptor)
|
||||
{
|
||||
_innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException(nameof(innerTypeDescriptor));
|
||||
}
|
||||
|
||||
public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object container)
|
||||
{
|
||||
return _innerTypeDescriptor.GetProperties(type, container).Select(d => new CommentsPropertyDescriptor(d));
|
||||
}
|
||||
|
||||
private sealed class CommentsPropertyDescriptor : IPropertyDescriptor
|
||||
{
|
||||
private readonly IPropertyDescriptor _baseDescriptor;
|
||||
|
||||
public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor)
|
||||
{
|
||||
_baseDescriptor = baseDescriptor;
|
||||
Name = baseDescriptor.Name;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public int Order { get; set; }
|
||||
public Type Type => _baseDescriptor.Type;
|
||||
public bool CanWrite => _baseDescriptor.CanWrite;
|
||||
|
||||
public Type TypeOverride
|
||||
{
|
||||
get { return _baseDescriptor.TypeOverride; }
|
||||
set { _baseDescriptor.TypeOverride = value; }
|
||||
}
|
||||
|
||||
public ScalarStyle ScalarStyle
|
||||
{
|
||||
get { return _baseDescriptor.ScalarStyle; }
|
||||
set { _baseDescriptor.ScalarStyle = value; }
|
||||
}
|
||||
|
||||
public void Write(object target, object value)
|
||||
{
|
||||
_baseDescriptor.Write(target, value);
|
||||
}
|
||||
|
||||
public T GetCustomAttribute<T>() where T : Attribute
|
||||
{
|
||||
return _baseDescriptor.GetCustomAttribute<T>();
|
||||
}
|
||||
|
||||
public IObjectDescriptor Read(object target)
|
||||
{
|
||||
var description = _baseDescriptor.GetCustomAttribute<DescriptionAttribute>();
|
||||
return description != null ?
|
||||
new CommentsObjectDescriptor(_baseDescriptor.Read(target), description.Description) :
|
||||
_baseDescriptor.Read(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CommentsObjectDescriptor : IObjectDescriptor
|
||||
{
|
||||
private readonly IObjectDescriptor _innerDescriptor;
|
||||
|
||||
public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment)
|
||||
{
|
||||
_innerDescriptor = innerDescriptor;
|
||||
Comment = comment;
|
||||
}
|
||||
|
||||
public string Comment { get; private set; }
|
||||
public object Value => _innerDescriptor.Value;
|
||||
public Type Type => _innerDescriptor.Type;
|
||||
public Type StaticType => _innerDescriptor.StaticType;
|
||||
public ScalarStyle ScalarStyle => _innerDescriptor.ScalarStyle;
|
||||
}
|
||||
|
||||
public class CommentsObjectGraphVisitor : ChainedObjectGraphVisitor
|
||||
{
|
||||
public CommentsObjectGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor)
|
||||
: base(nextVisitor) { }
|
||||
|
||||
public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
|
||||
{
|
||||
if(value is CommentsObjectDescriptor commentsDescriptor && commentsDescriptor.Comment != null)
|
||||
{
|
||||
context.Emit(new Comment(string.Empty, false));
|
||||
foreach(var comment in commentsDescriptor.Comment.Split(Environment.NewLine))
|
||||
{
|
||||
context.Emit(new Comment(comment, false));
|
||||
}
|
||||
}
|
||||
return base.EnterMapping(key, value, context);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user