using System; using System.Collections.Generic; using System.Data.SqlClient; using System.IO; using System.Linq; namespace Bit.Setup { public class EnvironmentFileBuilder { private readonly Context _context; private IDictionary _globalValues; private IDictionary _mssqlValues; private IDictionary _globalOverrideValues; private IDictionary _mssqlOverrideValues; public EnvironmentFileBuilder(Context context) { _context = context; _globalValues = new Dictionary { ["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__sso"] = "http://localhost/sso", ["globalSettings__baseServiceUri__portal"] = "http://localhost/portal", ["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__baseServiceUri__internalSso"] = "http://sso:5000", ["globalSettings__baseServiceUri__internalPortal"] = "http://portal:5000", ["globalSettings__pushRelayBaseUri"] = "https://push.bitwarden.com", ["globalSettings__installation__identityUri"] = "https://identity.bitwarden.com", }; _mssqlValues = new Dictionary { ["ACCEPT_EULA"] = "Y", ["MSSQL_PID"] = "Express", ["SA_PASSWORD"] = "SECRET", }; } public void BuildForInstaller() { Directory.CreateDirectory("/bitwarden/env/"); Init(); Build(); } public void BuildForUpdater() { 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() { var dbPassword = _context.Stub ? "RANDOM_DATABASE_PASSWORD" : Helpers.SecureRandomString(32); var dbConnectionString = new SqlConnectionStringBuilder { DataSource = "tcp:mssql,1433", InitialCatalog = "vault", UserID = "sa", Password = dbPassword, MultipleActiveResultSets = false, Encrypt = true, ConnectTimeout = 30, TrustServerCertificate = true, PersistSecurityInfo = false }.ConnectionString; _globalOverrideValues = new Dictionary { ["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__baseServiceUri__sso"] = $"{_context.Config.Url}/sso", ["globalSettings__baseServiceUri__portal"] = $"{_context.Config.Url}/portal", ["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__logRollBySizeLimit"] = string.Empty, ["globalSettings__syslog__destination"] = string.Empty, ["globalSettings__licenseDirectory"] = $"{_context.OutputDir}/core/licenses", ["globalSettings__internalIdentityKey"] = _context.Stub ? "RANDOM_IDENTITY_KEY" : Helpers.SecureRandomString(64, alpha: true, numeric: true), ["globalSettings__oidcIdentityClientKey"] = _context.Stub ? "RANDOM_IDENTITY_KEY" : Helpers.SecureRandomString(64, alpha: true, numeric: true), ["globalSettings__duo__aKey"] = _context.Stub ? "RANDOM_DUO_AKEY" : Helpers.SecureRandomString(64, alpha: true, numeric: true), ["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@{_context.Config.Domain}", ["globalSettings__mail__smtp__host"] = "REPLACE", ["globalSettings__mail__smtp__port"] = "587", ["globalSettings__mail__smtp__ssl"] = "false", ["globalSettings__mail__smtp__username"] = "REPLACE", ["globalSettings__mail__smtp__password"] = "REPLACE", ["globalSettings__disableUserRegistration"] = "false", ["globalSettings__hibpApiKey"] = "REPLACE", ["adminSettings__admins"] = string.Empty, }; if (!_context.Config.PushNotifications) { _globalOverrideValues.Add("globalSettings__pushRelayBaseUri", "REPLACE"); } _mssqlOverrideValues = new Dictionary { ["ACCEPT_EULA"] = "Y", ["MSSQL_PID"] = "Express", ["SA_PASSWORD"] = dbPassword, }; } private void LoadExistingValues(IDictionary _values, string file) { if (!File.Exists(file)) { return; } var fileLines = File.ReadAllLines(file); foreach (var line in fileLines) { if (!line.Contains("=")) { continue; } var value = string.Empty; var lineParts = line.Split("=", 2); if (lineParts.Length < 1) { continue; } if (lineParts.Length > 1) { value = lineParts[1]; } if (_values.ContainsKey(lineParts[0])) { _values[lineParts[0]] = value; } else { _values.Add(lineParts[0], value); } } } 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"); // 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 class TemplateModel { public TemplateModel(IEnumerable> variables) { Variables = variables.Select(v => new Kvp { Key = v.Key, Value = v.Value }); } public IEnumerable Variables { get; set; } public class Kvp { public string Key { get; set; } public string Value { get; set; } } } } }