private static void MigrateDatabase(int attempt = 1) { try { Helpers.WriteLine(_context, "Migrating database."); var vaultConnectionString = Helpers.GetValueFromEnvFile("global", "globalSettings__sqlServer__connectionString"); var migrator = new DbMigrator(vaultConnectionString, null); var success = migrator.MigrateMsSqlDatabase(false); if (success) { Helpers.WriteLine(_context, "Migration successful."); } else { Helpers.WriteLine(_context, "Migration failed."); } } catch (SqlException e) { if (e.Message.Contains("Server is in script upgrade mode") && attempt < 10) { var nextAttempt = attempt + 1; Helpers.WriteLine(_context, "Database is in script upgrade mode. " + "Trying again (attempt #{0})...", nextAttempt); System.Threading.Thread.Sleep(20000); MigrateDatabase(nextAttempt); return; } throw e; } }
public static void Main(string[] args) { CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US"); _context = new Context { Args = args }; ParseParameters(); if (_context.Parameters.ContainsKey("q")) { _context.Quiet = _context.Parameters["q"] == "true" || _context.Parameters["q"] == "1"; } if (_context.Parameters.ContainsKey("os")) { _context.HostOS = _context.Parameters["os"]; } if (_context.Parameters.ContainsKey("corev")) { _context.CoreVersion = _context.Parameters["corev"]; } if (_context.Parameters.ContainsKey("webv")) { _context.WebVersion = _context.Parameters["webv"]; } if (_context.Parameters.ContainsKey("keyconnectorv")) { _context.KeyConnectorVersion = _context.Parameters["keyconnectorv"]; } if (_context.Parameters.ContainsKey("stub")) { _context.Stub = _context.Parameters["stub"] == "true" || _context.Parameters["stub"] == "1"; } Helpers.WriteLine(_context); if (_context.Parameters.ContainsKey("install")) { Install(); } else if (_context.Parameters.ContainsKey("update")) { Update(); } else if (_context.Parameters.ContainsKey("printenv")) { PrintEnvironment(); } else { Helpers.WriteLine(_context, "No top-level command detected. Exiting..."); } }
public void GenerateIdentityCertificate() { Helpers.WriteLine(_context, "Generating key for IdentityServer."); _context.Install.IdentityCertPassword = CoreHelpers.SecureRandomString(32); Directory.CreateDirectory($"{_context.DestDir}/identity"); Helpers.Exec("openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout /tmp/bytegarden-identity.key " + "-out /tmp/bytegarden-identity.crt -subj \"/CN=ByteGarden IdentityServer\" -days 10950"); Helpers.Exec($"openssl pkcs12 -export -out {_context.DestDir}/identity/identity.pfx -inkey /tmp/bytegarden-identity.key " + $"-in /tmp/bytegarden-identity.crt -certfile /tmp/bytegarden-identity.crt -passout pass:{_context.Install.IdentityCertPassword}"); File.Delete("/tmp/bytegarden-identity.key"); File.Delete("/tmp/bytegarden-identity.crt"); }
private void Build() { var template = Helpers.ReadTemplate("EnvironmentFile"); Helpers.WriteLine(_context, "Building docker environment files."); Directory.CreateDirectory("/bitwarden/docker/"); using (var sw = File.CreateText("/bitwarden/docker/global.env")) { 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(template(new TemplateModel(_mssqlValues))); } Helpers.Exec("chmod 600 /bitwarden/docker/mssql.env"); Helpers.WriteLine(_context, "Building docker environment override files."); Directory.CreateDirectory("/bitwarden/env/"); using (var sw = File.CreateText("/bitwarden/env/global.override.env")) { 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")) { sw.Write(template(new TemplateModel(_mssqlOverrideValues))); } Helpers.Exec("chmod 600 /bitwarden/env/mssql.override.env"); if (_context.Config.EnableKeyConnector) { using (var sw = File.CreateText("/bitwarden/env/key-connector.override.env")) { sw.Write(template(new TemplateModel(_keyConnectorOverrideValues))); } Helpers.Exec("chmod 600 /bitwarden/env/key-connector.override.env"); } // Empty uid env file. Only used on Linux hosts. if (!File.Exists("/bitwarden/env/uid.env")) { using (var sw = File.CreateText("/bitwarden/env/uid.env")) { } } }
public void Build() { var model = new TemplateModel { Url = _context.Config.Url }; Helpers.WriteLine(_context, "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(template(model)); } }
public void Build() { var model = new TemplateModel { Url = _context.Config.Url }; Helpers.WriteLine(_context, "Building Asset Links For Fido2."); Directory.CreateDirectory("/bitwarden/web/"); var template = Helpers.ReadTemplate("AssetLinks"); using (var sw = File.CreateText("/bitwarden/web/assetlinks.json")) { sw.Write(template(model)); } }
private void Build(TemplateModel model) { Directory.CreateDirectory("/bitwarden/nginx/"); Helpers.WriteLine(_context, "Building nginx config."); if (!_context.Config.GenerateNginxConfig) { Helpers.WriteLine(_context, "...skipped"); return; } var template = Helpers.ReadTemplate("NginxConfig"); using (var sw = File.CreateText(ConfFile)) { sw.WriteLine(template(model)); } }
public void Build() { var model = new TemplateModel { Url = _context.Config.Url }; // Needed for backwards compatability with migrated U2F tokens. Helpers.WriteLine(_context, "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(template(model)); } }
private void Build(TemplateModel model) { Directory.CreateDirectory($"{_context.DestDir}/nginx"); Helpers.WriteLine(_context, "Building nginx config."); if (!_context.Config.Nginx.Enable) { Helpers.WriteLine(_context, "...skipped"); return; } var template = Helpers.ReadTemplate("NginxConfig"); using (var sw = File.CreateText(ConfFile)) { sw.WriteLine(template(model)); } }
private void Build() { Directory.CreateDirectory("/bitwarden/docker/"); Helpers.WriteLine(_context, "Building docker-compose.yml."); if (!_context.Config.GenerateComposeConfig) { Helpers.WriteLine(_context, "...skipped"); return; } var template = Helpers.ReadTemplate("DockerCompose"); var model = new TemplateModel(_context); using (var sw = File.CreateText("/bitwarden/docker/docker-compose.yml")) { sw.Write(template(model)); } }
private void Build() { var template = Helpers.ReadTemplate("EnvironmentFile"); Helpers.WriteLine(_context, "Building docker environment files."); Directory.CreateDirectory($"{_context.DestDir}/docker/"); using (var sw = File.CreateText($"{_context.DestDir}/docker/global.env")) { sw.Write(template(new TemplateModel(_globalValues))); } Helpers.Exec($"chmod 600 {_context.DestDir}/docker/global.env"); using (var sw = File.CreateText($"{_context.DestDir}/docker/mssql.env")) { sw.Write(template(new TemplateModel(_mssqlValues))); } Helpers.Exec($"chmod 600 {_context.DestDir}/docker/mssql.env"); Helpers.WriteLine(_context, "Building docker environment override files."); Directory.CreateDirectory($"{_context.DestDir}/env/"); using (var sw = File.CreateText($"{_context.DestDir}/env/global.override.env")) { sw.Write(template(new TemplateModel(_globalOverrideValues))); } Helpers.Exec($"chmod 600 {_context.DestDir}/env/global.override.env"); using (var sw = File.CreateText($"{_context.DestDir}/env/mssql.override.env")) { sw.Write(template(new TemplateModel(_mssqlOverrideValues))); } Helpers.Exec($"chmod 600 {_context.DestDir}/env/mssql.override.env"); // Empty uid env file. Only used on Linux hosts. if (!File.Exists($"{_context.DestDir}/env/uid.env")) { using (var sw = File.CreateText($"{_context.DestDir}/env/uid.env")) { } } }
public void LoadConfiguration() { if (!File.Exists(ConfigPath)) { Helpers.WriteLine(this, "No existing `config.yml` detected. Let's generate one."); // Looks like updating from older version. Try to create config file. var url = Helpers.GetValueFromEnvFile("global", "globalSettings__baseServiceUri__vault"); if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) { Helpers.WriteLine(this, "Unable to determine existing installation url."); return; } Config.Url = url; var push = Helpers.GetValueFromEnvFile("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 confContent = File.ReadAllText(nginxFile); var selfSigned = confContent.Contains("/etc/ssl/self/"); Config.Ssl = confContent.Contains("ssl http2;"); Config.SslManagedLetsEncrypt = !selfSigned && confContent.Contains("/etc/letsencrypt/live/"); var diffieHellman = confContent.Contains("/dhparam.pem;"); var trusted = confContent.Contains("ssl_trusted_certificate "); 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(UnderscoredNamingConvention.Instance) .Build(); Config = deserializer.Deserialize <Configuration>(configText); }
public void BuildForInstall() { if (_context.Stub) { _context.Config.Ssl = true; _context.Install.Trusted = true; _context.Install.SelfSignedCert = false; _context.Install.DiffieHellman = false; _context.Install.IdentityCertPassword = "******"; return; } _context.Config.Ssl = _context.Config.SslManagedLetsEncrypt; if (!_context.Config.Ssl) { var skipSSL = _context.Parameters.ContainsKey("skip-ssl") && (_context.Parameters["skip-ssl"] == "true" || _context.Parameters["skip-ssl"] == "1"); if (!skipSSL) { _context.Config.Ssl = Helpers.ReadQuestion("Do you have a SSL certificate to use?"); if (_context.Config.Ssl) { 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(_context, "NOTE", message); } else if (Helpers.ReadQuestion("Do you want to generate a self-signed SSL certificate?")) { Directory.CreateDirectory($"/bitwarden/ssl/self/{_context.Install.Domain}/"); Helpers.WriteLine(_context, "Generating self signed SSL certificate."); _context.Config.Ssl = true; _context.Install.Trusted = false; _context.Install.SelfSignedCert = true; Helpers.Exec("openssl req -x509 -newkey rsa:4096 -sha256 -nodes -days 36500 " + $"-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:{_context.Install.Domain}\nbasicConstraints=CA:true')) " + $"-subj \"/C=US/ST=California/L=Santa Barbara/O=Bitwarden Inc./OU=Bitwarden/CN={_context.Install.Domain}\""); } } } if (_context.Config.SslManagedLetsEncrypt) { _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)?"); } Helpers.WriteLine(_context, "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 36500"); Helpers.Exec("openssl pkcs12 -export -out /bitwarden/identity/identity.pfx -inkey identity.key " + $"-in identity.crt -passout pass:{_context.Install.IdentityCertPassword}"); Helpers.WriteLine(_context); 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(_context, "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(_context, "WARNING", message, ConsoleColor.Yellow); } }
public void BuildForInstall() { if (_context.Stub) { _context.Config.Ssl.Enable = true; _context.Install.Trusted = true; _context.Install.SelfSignedCert = false; _context.Install.DiffieHellman = false; _context.Install.IdentityCertPassword = "******"; return; } _context.Config.Ssl.Enable = _context.Config.Ssl.ManagedLetsEncrypt; if (!_context.Config.Ssl.Enable && _context.Install.Ssl == null) { _context.Config.Ssl.Enable = Helpers.ReadQuestion("Do you have a SSL certificate to use?"); if (_context.Config.Ssl.Enable) { Directory.CreateDirectory($"{_context.DestDir}/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(_context, "NOTE", message); } else if (Helpers.ReadQuestion("Do you want to generate a self-signed SSL certificate?")) { Directory.CreateDirectory($"{_context.DestDir}/ssl/self/{_context.Install.Domain}/"); Helpers.WriteLine(_context, "Generating self signed SSL certificate."); _context.Config.Ssl.Enable = true; _context.Install.Trusted = false; _context.Install.SelfSignedCert = true; Helpers.Exec("openssl req -x509 -newkey rsa:4096 -sha256 -nodes -days 365 " + $"-keyout {_context.DestDir}/ssl/self/{_context.Install.Domain}/private.key " + $"-out {_context.DestDir}/ssl/self/{_context.Install.Domain}/certificate.crt " + $"-reqexts SAN -extensions SAN " + $"-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=ByteGarden/CN={_context.Install.Domain}\""); } } if (_context.Config.Ssl?.ManagedLetsEncrypt ?? false) { _context.Install.Trusted = true; _context.Install.DiffieHellman = true; Directory.CreateDirectory($"{_context.DestDir}/letsencrypt/live/{_context.Install.Domain}/"); Helpers.Exec($"openssl dhparam -out " + $"{_context.DestDir}/letsencrypt/live/{_context.Install.Domain}/dhparam.pem 2048"); } else if (_context.Config.Ssl.Enable && !_context.Install.SelfSignedCert) { _context.Install.Trusted = Helpers.ReadQuestion("Is this a trusted SSL certificate " + "(requires ca.crt, see docs)?"); } GenerateIdentityCertificate(); Helpers.WriteLine(_context); if (!_context.Config.Ssl.Enable) { var message = "You are not using a SSL certificate. ByteGarden requires HTTPS to operate. \n" + "You must front your installation with a HTTPS proxy or the web vault (and \n" + "other ByteGarden apps) will not work properly."; Helpers.ShowBanner(_context, "WARNING", message, ConsoleColor.Yellow); } else if (_context.Config.Ssl.Enable && !_context.Install.Trusted) { var message = "You are using an untrusted SSL certificate. This certificate will not be \n" + "trusted by ByteGarden 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(_context, "WARNING", message, ConsoleColor.Yellow); } }
private static void MigrateDatabase(int attempt = 1) { try { Helpers.WriteLine(_context, "Migrating database."); var vaultConnectionString = Helpers.GetValueFromEnvFile("global", "globalSettings__sqlServer__connectionString"); var masterConnectionString = new SqlConnectionStringBuilder(vaultConnectionString) { InitialCatalog = "master" }.ConnectionString; using (var connection = new SqlConnection(masterConnectionString)) { var command = new SqlCommand( "IF ((SELECT COUNT(1) FROM sys.databases WHERE [name] = 'vault') = 0) " + "CREATE DATABASE [vault];", connection); command.Connection.Open(); command.ExecuteNonQuery(); command.CommandText = "IF ((SELECT DATABASEPROPERTYEX([name], 'IsAutoClose') " + "FROM sys.databases WHERE [name] = 'vault') = 1) " + "ALTER DATABASE [vault] SET AUTO_CLOSE OFF;"; command.ExecuteNonQuery(); } var upgrader = DeployChanges.To .SqlDatabase(vaultConnectionString) .JournalToSqlTable("dbo", "Migration") .WithScriptsAndCodeEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains($".DbScripts.") && !s.Contains(".Archive.")) .WithTransaction() .WithExecutionTimeout(new TimeSpan(0, 5, 0)) .LogToConsole() .Build(); var result = upgrader.PerformUpgrade(); if (result.Successful) { Helpers.WriteLine(_context, "Migration successful."); } else { Helpers.WriteLine(_context, "Migration failed."); } } catch (SqlException e) { if (e.Message.Contains("Server is in script upgrade mode") && attempt < 10) { var nextAttempt = attempt + 1; Helpers.WriteLine(_context, "Database is in script upgrade mode. " + "Trying again (attempt #{0})...", nextAttempt); System.Threading.Thread.Sleep(20000); MigrateDatabase(nextAttempt); return; } throw e; } }