diff --git a/src/Admin/Program.cs b/src/Admin/Program.cs index 1c650b4e53..4885edc1ae 100644 --- a/src/Admin/Program.cs +++ b/src/Admin/Program.cs @@ -11,6 +11,7 @@ namespace Bit.Admin { Host .CreateDefaultBuilder(args) + .ConfigureCustomAppConfiguration(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.ConfigureKestrel(o => diff --git a/src/Admin/appsettings.SelfHosted.json b/src/Admin/appsettings.SelfHosted.json new file mode 100644 index 0000000000..c7ee4b2b35 --- /dev/null +++ b/src/Admin/appsettings.SelfHosted.json @@ -0,0 +1,20 @@ +{ + "globalSettings": { + "baseServiceUri": { + "vault": null, + "api": null, + "identity": null, + "admin": null, + "notifications": null, + "sso": null, + "portal": null, + "internalNotifications": null, + "internalAdmin": null, + "internalIdentity": null, + "internalApi": null, + "internalVault": null, + "internalSso": null, + "internalPortal": null + } + } +} diff --git a/src/Api/Program.cs b/src/Api/Program.cs index 4cb8ffbbb7..839013a5c5 100644 --- a/src/Api/Program.cs +++ b/src/Api/Program.cs @@ -13,6 +13,7 @@ namespace Bit.Api { Host .CreateDefaultBuilder(args) + .ConfigureCustomAppConfiguration(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); diff --git a/src/Api/appsettings.SelfHosted.json b/src/Api/appsettings.SelfHosted.json new file mode 100644 index 0000000000..c7ee4b2b35 --- /dev/null +++ b/src/Api/appsettings.SelfHosted.json @@ -0,0 +1,20 @@ +{ + "globalSettings": { + "baseServiceUri": { + "vault": null, + "api": null, + "identity": null, + "admin": null, + "notifications": null, + "sso": null, + "portal": null, + "internalNotifications": null, + "internalAdmin": null, + "internalIdentity": null, + "internalApi": null, + "internalVault": null, + "internalSso": null, + "internalPortal": null + } + } +} diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 2f1eab29cc..0509e46764 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -1,18 +1,36 @@ using System; -using Bit.Core.Enums; namespace Bit.Core.Settings { public class GlobalSettings : IGlobalSettings { + private string _logDirectory; + private string _licenseDirectory; + + public GlobalSettings() + { + BaseServiceUri = new BaseServiceUriSettings(this); + Attachment = new FileStorageSettings(this, "attchments", "attchments"); + Send = new FileStorageSettings(this, "attchments/send", "attchments/send"); + DataProtection = new DataProtectionSettings(this); + } + public bool SelfHosted { get; set; } public virtual string KnownProxies { get; set; } public virtual string SiteName { get; set; } public virtual string StripeApiKey { get; set; } public virtual string ProjectName { get; set; } - public virtual string LogDirectory { get; set; } + public virtual string LogDirectory + { + get => BuildDirectory(_logDirectory, "/logs"); + set => _logDirectory = value; + } public virtual long? LogRollBySizeLimit { get; set; } - public virtual string LicenseDirectory { get; set; } + public virtual string LicenseDirectory + { + get => BuildDirectory(_licenseDirectory, "/core/licenses"); + set => _licenseDirectory = value; + } public string LicenseCertificatePassword { get; set; } public virtual string PushRelayBaseUri { get; set; } public virtual string InternalIdentityKey { get; set; } @@ -23,17 +41,17 @@ namespace Bit.Core.Settings public virtual int OrganizationInviteExpirationHours { get; set; } = 120; // 5 days public virtual string EventGridKey { get; set; } public virtual InstallationSettings Installation { get; set; } = new InstallationSettings(); - public virtual BaseServiceUriSettings BaseServiceUri { get; set; } = new BaseServiceUriSettings(); + public virtual BaseServiceUriSettings BaseServiceUri { get; set; } public virtual SqlSettings SqlServer { get; set; } = new SqlSettings(); public virtual SqlSettings PostgreSql { get; set; } = new SqlSettings(); public virtual MailSettings Mail { get; set; } = new MailSettings(); public virtual ConnectionStringSettings Storage { get; set; } = new ConnectionStringSettings(); public virtual ConnectionStringSettings Events { get; set; } = new ConnectionStringSettings(); public virtual NotificationsSettings Notifications { get; set; } = new NotificationsSettings(); - public virtual IFileStorageSettings Attachment { get; set; } = new FileStorageSettings(); - public virtual FileStorageSettings Send { get; set; } = new FileStorageSettings(); + public virtual IFileStorageSettings Attachment { get; set; } + public virtual FileStorageSettings Send { get; set; } public virtual IdentityServerSettings IdentityServer { get; set; } = new IdentityServerSettings(); - public virtual DataProtectionSettings DataProtection { get; set; } = new DataProtectionSettings(); + public virtual DataProtectionSettings DataProtection { get; set; } public virtual DocumentDbSettings DocumentDb { get; set; } = new DocumentDbSettings(); public virtual SentrySettings Sentry { get; set; } = new SentrySettings(); public virtual SyslogSettings Syslog { get; set; } = new SyslogSettings(); @@ -47,21 +65,137 @@ namespace Bit.Core.Settings public virtual AppleIapSettings AppleIap { get; set; } = new AppleIapSettings(); public virtual SsoSettings Sso { get; set; } = new SsoSettings(); + public string BuildExternalUri(string explicitValue, string name) + { + if (!string.IsNullOrWhiteSpace(explicitValue)) + { + return explicitValue; + } + if (!SelfHosted) + { + return null; + } + return string.Format("{0}/{1}", BaseServiceUri.Vault, name); + } + + public string BuildInternalUri(string explicitValue, string name) + { + if (!string.IsNullOrWhiteSpace(explicitValue)) + { + return explicitValue; + } + if (!SelfHosted) + { + return null; + } + return string.Format("http://{0}:5000", name); + } + + public string BuildDirectory(string explicitValue, string appendedPath) + { + if (!string.IsNullOrWhiteSpace(explicitValue)) + { + return explicitValue; + } + if (!SelfHosted) + { + return null; + } + return string.Concat("/etc/bitwarden", appendedPath); + } + public class BaseServiceUriSettings { + private readonly GlobalSettings _globalSettings; + + private string _api; + private string _identity; + private string _admin; + private string _notifications; + private string _sso; + private string _portal; + private string _internalApi; + private string _internalIdentity; + private string _internalAdmin; + private string _internalNotifications; + private string _internalSso; + private string _internalVault; + private string _internalPortal; + + public BaseServiceUriSettings(GlobalSettings globalSettings) + { + _globalSettings = globalSettings; + } + public string Vault { get; set; } public string VaultWithHash => $"{Vault}/#"; - public string Api { get; set; } - public string Identity { get; set; } - public string Admin { get; set; } - public string Notifications { get; set; } - public string Sso { get; set; } - public string InternalNotifications { get; set; } - public string InternalAdmin { get; set; } - public string InternalIdentity { get; set; } - public string InternalApi { get; set; } - public string InternalVault { get; set; } - public string InternalSso { get; set; } + + public string Api + { + get => _globalSettings.BuildExternalUri(_api, "api"); + set => _api = value; + } + public string Identity + { + get => _globalSettings.BuildExternalUri(_identity, "identity"); + set => _identity = value; + } + public string Admin + { + get => _globalSettings.BuildExternalUri(_admin, "admin"); + set => _admin = value; + } + public string Notifications + { + get => _globalSettings.BuildExternalUri(_notifications, "notifications"); + set => _notifications = value; + } + public string Sso + { + get => _globalSettings.BuildExternalUri(_sso, "sso"); + set => _sso = value; + } + public string Portal + { + get => _globalSettings.BuildExternalUri(_portal, "portal"); + set => _portal = value; + } + + public string InternalNotifications + { + get => _globalSettings.BuildInternalUri(_internalNotifications, "notifications"); + set => _internalNotifications = value; + } + public string InternalAdmin + { + get => _globalSettings.BuildInternalUri(_internalAdmin, "admin"); + set => _internalAdmin = value; + } + public string InternalIdentity + { + get => _globalSettings.BuildInternalUri(_internalIdentity, "identity"); + set => _internalIdentity = value; + } + public string InternalApi + { + get => _globalSettings.BuildInternalUri(_internalApi, "api"); + set => _internalApi = value; + } + public string InternalVault + { + get => _globalSettings.BuildInternalUri(_internalVault, "web"); + set => _internalVault = value; + } + public string InternalSso + { + get => _globalSettings.BuildInternalUri(_internalSso, "sso"); + set => _internalSso = value; + } + public string InternalPortal + { + get => _globalSettings.BuildInternalUri(_internalPortal, "portal"); + set => _internalPortal = value; + } } public class SqlSettings @@ -73,29 +207,20 @@ namespace Bit.Core.Settings public string ConnectionString { get => _connectionString; - set - { - _connectionString = value.Trim('"'); - } + set => _connectionString = value.Trim('"'); } public string ReadOnlyConnectionString { get => string.IsNullOrWhiteSpace(_readOnlyConnectionString) ? _connectionString : _readOnlyConnectionString; - set - { - _readOnlyConnectionString = value.Trim('"'); - } + set => _readOnlyConnectionString = value.Trim('"'); } - + public string JobSchedulerConnectionString { get => _jobSchedulerConnectionString; - set - { - _jobSchedulerConnectionString = value.Trim('"'); - } + set => _jobSchedulerConnectionString = value.Trim('"'); } } @@ -106,27 +231,43 @@ namespace Bit.Core.Settings public string ConnectionString { get => _connectionString; - set - { - _connectionString = value.Trim('"'); - } + set => _connectionString = value.Trim('"'); } } public class FileStorageSettings : IFileStorageSettings { + private readonly GlobalSettings _globalSettings; + private readonly string _urlName; + private readonly string _directoryName; private string _connectionString; + private string _baseDirectory; + private string _baseUrl; + + public FileStorageSettings(GlobalSettings globalSettings, string urlName, string directoryName) + { + _globalSettings = globalSettings; + _urlName = urlName; + _directoryName = directoryName; + } public string ConnectionString { get => _connectionString; - set - { - _connectionString = value.Trim('"'); - } + set => _connectionString = value.Trim('"'); + } + + public string BaseDirectory + { + get => _globalSettings.BuildDirectory(_baseDirectory, string.Concat("/core/", _directoryName)); + set => _baseDirectory = value; + } + + public string BaseUrl + { + get => _globalSettings.BuildExternalUri(_baseUrl, _urlName); + set => _baseUrl = value; } - public string BaseDirectory { get; set; } - public string BaseUrl { get; set; } } public class MailSettings @@ -157,9 +298,22 @@ namespace Bit.Core.Settings public class DataProtectionSettings { + private readonly GlobalSettings _globalSettings; + + private string _directory; + + public DataProtectionSettings(GlobalSettings globalSettings) + { + _globalSettings = globalSettings; + } + public string CertificateThumbprint { get; set; } public string CertificatePassword { get; set; } - public string Directory { get; set; } + public string Directory + { + get => _globalSettings.BuildDirectory(_directory, "/core/aspnet-dataprotection"); + set => _directory = value; + } } public class DocumentDbSettings @@ -228,10 +382,7 @@ namespace Bit.Core.Settings public string ConnectionString { get => _connectionString; - set - { - _connectionString = value.Trim('"'); - } + set => _connectionString = value.Trim('"'); } public string HubName { get; set; } } @@ -265,9 +416,15 @@ namespace Bit.Core.Settings public class InstallationSettings { + private string _identityUri; + public Guid Id { get; set; } public string Key { get; set; } - public string IdentityUri { get; set; } + public string IdentityUri + { + get => string.IsNullOrWhiteSpace(_identityUri) ? "https://identity.bitwarden.com" : _identityUri; + set => _identityUri = value; + } } public class AmazonSettings diff --git a/src/Core/Utilities/HostBuilderExtensions.cs b/src/Core/Utilities/HostBuilderExtensions.cs new file mode 100644 index 0000000000..c8c227af3c --- /dev/null +++ b/src/Core/Utilities/HostBuilderExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Reflection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Bit.Core.Utilities +{ + public static class HostBuilderExtensions + { + public static IHostBuilder ConfigureCustomAppConfiguration(this IHostBuilder hostBuilder, string[] args) + { + // Reload app configuration with SelfHosted overrides. + return hostBuilder.ConfigureAppConfiguration((hostingContext, config) => + { + if (Environment.GetEnvironmentVariable("globalSettings__selfHosted")?.ToLower() != "true") + { + return; + } + + var env = hostingContext.HostingEnvironment; + + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true) + .AddJsonFile("appsettings.SelfHosted.json", optional: true, reloadOnChange: true); + + if (env.IsDevelopment()) + { + var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); + if (appAssembly != null) + { + config.AddUserSecrets(appAssembly, optional: true); + } + } + + config.AddEnvironmentVariables(); + + if (args != null) + { + config.AddCommandLine(args); + } + }); + } + } +} diff --git a/src/Events/Program.cs b/src/Events/Program.cs index cdacd16bf1..ddc7fb0178 100644 --- a/src/Events/Program.cs +++ b/src/Events/Program.cs @@ -12,6 +12,7 @@ namespace Bit.Events { Host .CreateDefaultBuilder(args) + .ConfigureCustomAppConfiguration(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); diff --git a/src/Events/appsettings.SelfHosted.json b/src/Events/appsettings.SelfHosted.json new file mode 100644 index 0000000000..c7ee4b2b35 --- /dev/null +++ b/src/Events/appsettings.SelfHosted.json @@ -0,0 +1,20 @@ +{ + "globalSettings": { + "baseServiceUri": { + "vault": null, + "api": null, + "identity": null, + "admin": null, + "notifications": null, + "sso": null, + "portal": null, + "internalNotifications": null, + "internalAdmin": null, + "internalIdentity": null, + "internalApi": null, + "internalVault": null, + "internalSso": null, + "internalPortal": null + } + } +} diff --git a/src/Identity/Program.cs b/src/Identity/Program.cs index 82e51e858b..cbaf19cdd0 100644 --- a/src/Identity/Program.cs +++ b/src/Identity/Program.cs @@ -12,6 +12,7 @@ namespace Bit.Identity { Host .CreateDefaultBuilder(args) + .ConfigureCustomAppConfiguration(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); diff --git a/src/Identity/appsettings.SelfHosted.json b/src/Identity/appsettings.SelfHosted.json new file mode 100644 index 0000000000..c7ee4b2b35 --- /dev/null +++ b/src/Identity/appsettings.SelfHosted.json @@ -0,0 +1,20 @@ +{ + "globalSettings": { + "baseServiceUri": { + "vault": null, + "api": null, + "identity": null, + "admin": null, + "notifications": null, + "sso": null, + "portal": null, + "internalNotifications": null, + "internalAdmin": null, + "internalIdentity": null, + "internalApi": null, + "internalVault": null, + "internalSso": null, + "internalPortal": null + } + } +} diff --git a/src/Notifications/Program.cs b/src/Notifications/Program.cs index 38b6ec1231..098ace19b6 100644 --- a/src/Notifications/Program.cs +++ b/src/Notifications/Program.cs @@ -11,6 +11,7 @@ namespace Bit.Notifications { Host .CreateDefaultBuilder(args) + .ConfigureCustomAppConfiguration(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); diff --git a/src/Notifications/appsettings.SelfHosted.json b/src/Notifications/appsettings.SelfHosted.json new file mode 100644 index 0000000000..c7ee4b2b35 --- /dev/null +++ b/src/Notifications/appsettings.SelfHosted.json @@ -0,0 +1,20 @@ +{ + "globalSettings": { + "baseServiceUri": { + "vault": null, + "api": null, + "identity": null, + "admin": null, + "notifications": null, + "sso": null, + "portal": null, + "internalNotifications": null, + "internalAdmin": null, + "internalIdentity": null, + "internalApi": null, + "internalVault": null, + "internalSso": null, + "internalPortal": null + } + } +} diff --git a/test/Core.Test/AutoFixture/GlobalSettingsFixtures.cs b/test/Core.Test/AutoFixture/GlobalSettingsFixtures.cs new file mode 100644 index 0000000000..55f482126a --- /dev/null +++ b/test/Core.Test/AutoFixture/GlobalSettingsFixtures.cs @@ -0,0 +1,16 @@ +using AutoFixture; + +namespace Bit.Core.Test.AutoFixture +{ + internal class GlobalSettings : ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customize(composer => composer + .Without(s => s.BaseServiceUri) + .Without(s => s.Attachment) + .Without(s => s.Send) + .Without(s => s.DataProtection)); + } + } +} diff --git a/test/Core.Test/AutoFixture/SutProvider.cs b/test/Core.Test/AutoFixture/SutProvider.cs index fdc4fa7965..a3cfc210d7 100644 --- a/test/Core.Test/AutoFixture/SutProvider.cs +++ b/test/Core.Test/AutoFixture/SutProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using AutoFixture; using AutoFixture.Kernel; @@ -21,7 +21,7 @@ namespace Bit.Core.Test.AutoFixture public SutProvider(IFixture fixture) { _dependencies = new Dictionary>(); - _fixture = (fixture ?? new Fixture()).WithAutoNSubstitutions(); + _fixture = (fixture ?? new Fixture()).WithAutoNSubstitutions().Customize(new GlobalSettings()); _constructorParameterRelay = new ConstructorParameterRelay(this, _fixture); _fixture.Customizations.Add(_constructorParameterRelay); } diff --git a/test/Core.Test/Services/OrganizationServiceTests.cs b/test/Core.Test/Services/OrganizationServiceTests.cs index bac45b970f..016af4e6c0 100644 --- a/test/Core.Test/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/Services/OrganizationServiceTests.cs @@ -43,7 +43,7 @@ namespace Bit.Core.Test.Services var ssoConfigRepo = Substitute.For(); var ssoUserRepo = Substitute.For(); var referenceEventService = Substitute.For(); - var globalSettings = Substitute.For(); + var globalSettings = Substitute.For(); var taxRateRepository = Substitute.For(); var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo, @@ -105,7 +105,7 @@ namespace Bit.Core.Test.Services var ssoConfigRepo = Substitute.For(); var ssoUserRepo = Substitute.For(); var referenceEventService = Substitute.For(); - var globalSettings = Substitute.For(); + var globalSettings = Substitute.For(); var taxRateRepo = Substitute.For(); var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo, diff --git a/util/Setup/EnvironmentFileBuilder.cs b/util/Setup/EnvironmentFileBuilder.cs index 6a09fe4266..8711fdfe6d 100644 --- a/util/Setup/EnvironmentFileBuilder.cs +++ b/util/Setup/EnvironmentFileBuilder.cs @@ -23,21 +23,7 @@ namespace Bit.Setup ["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 { @@ -89,23 +75,8 @@ namespace Bit.Setup _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__send__baseDirectory"] = $"{_context.OutputDir}/core/attachments/send", - ["globalSettings__send__baseUrl"] = $"{_context.Config.Url}/attachments/send", - ["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" : @@ -134,8 +105,6 @@ namespace Bit.Setup _mssqlOverrideValues = new Dictionary { - ["ACCEPT_EULA"] = "Y", - ["MSSQL_PID"] = "Express", ["SA_PASSWORD"] = dbPassword, }; }