1
0
mirror of https://github.com/bitwarden/server.git synced 2024-12-01 13:43:23 +01:00
bitwarden-server/util/Setup/Program.cs

355 lines
12 KiB
C#
Raw Normal View History

using System.Globalization;
using System.Net.Http.Json;
2021-12-16 15:35:09 +01:00
using Bit.Migrator;
using Bit.Setup.Enums;
2017-08-07 22:31:00 +02:00
2022-08-29 22:06:55 +02:00
namespace Bit.Setup;
public class Program
2017-08-07 22:31:00 +02:00
{
2022-08-29 22:06:55 +02:00
private static Context _context;
public static void Main(string[] args)
2017-08-07 22:31:00 +02:00
{
2022-08-29 22:06:55 +02:00
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
2017-08-07 22:31:00 +02:00
2022-08-29 22:06:55 +02:00
_context = new Context
2017-08-07 22:31:00 +02:00
{
2022-08-29 22:06:55 +02:00
Args = args
};
2022-08-29 22:06:55 +02:00
ParseParameters();
2019-07-11 21:03:17 +02:00
2022-08-29 22:06:55 +02:00
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";
}
2018-03-30 15:23:33 +02:00
2022-08-29 22:06:55 +02:00
Helpers.WriteLine(_context);
2022-08-29 22:06:55 +02:00
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...");
}
}
2019-03-12 15:26:14 +01:00
2022-08-29 22:06:55 +02:00
private static void Install()
{
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 (_context.Parameters.ContainsKey("dbname"))
{
_context.Install.Database = _context.Parameters["dbname"];
}
2017-08-07 22:31:00 +02:00
2022-08-29 22:06:55 +02:00
if (_context.Stub)
2022-08-29 20:53:16 +02:00
{
2022-08-29 22:06:55 +02:00
_context.Install.InstallationId = Guid.Empty;
_context.Install.InstallationKey = "SECRET_INSTALLATION_KEY";
}
else if (!ValidateInstallation())
{
return;
}
2022-08-29 22:06:55 +02:00
var certBuilder = new CertBuilder(_context);
certBuilder.BuildForInstall();
2018-08-30 22:09:18 +02:00
2022-08-29 22:06:55 +02:00
// Set the URL
_context.Config.Url = string.Format("http{0}://{1}",
_context.Config.Ssl ? "s" : string.Empty, _context.Install.Domain);
2017-08-11 14:57:31 +02:00
2022-08-29 22:06:55 +02:00
var nginxBuilder = new NginxConfigBuilder(_context);
nginxBuilder.BuildForInstaller();
2022-08-29 22:06:55 +02:00
var environmentFileBuilder = new EnvironmentFileBuilder(_context);
environmentFileBuilder.BuildForInstaller();
2017-10-24 04:45:59 +02:00
2022-08-29 22:06:55 +02:00
var appIdBuilder = new AppIdBuilder(_context);
appIdBuilder.Build();
2022-08-29 22:06:55 +02:00
var dockerComposeBuilder = new DockerComposeBuilder(_context);
dockerComposeBuilder.BuildForInstaller();
2018-08-30 17:35:44 +02:00
2022-08-29 22:06:55 +02:00
_context.SaveConfiguration();
2018-08-31 15:16:01 +02:00
2022-08-29 22:06:55 +02:00
Console.WriteLine("\nInstallation complete");
2018-08-31 15:16:01 +02:00
2022-08-29 22:06:55 +02:00
Console.WriteLine("\nIf you need to make additional configuration changes, you can modify\n" +
"the settings in `{0}` and then run:\n{1}",
_context.HostOS == "win" ? ".\\bwdata\\config.yml" : "./bwdata/config.yml",
_context.HostOS == "win" ? "`.\\bitwarden.ps1 -rebuild` or `.\\bitwarden.ps1 -update`" :
"`./bitwarden.sh rebuild` or `./bitwarden.sh update`");
2018-08-31 15:16:01 +02:00
2022-08-29 22:06:55 +02:00
Console.WriteLine("\nNext steps, run:");
if (_context.HostOS == "win")
{
Console.WriteLine("`.\\bitwarden.ps1 -start`");
}
else
{
Console.WriteLine("`./bitwarden.sh start`");
}
Console.WriteLine(string.Empty);
}
2017-08-07 22:31:00 +02:00
2022-08-29 22:06:55 +02:00
private static void Update()
{
// This portion of code checks for multiple certs in the Identity.pfx PKCS12 bag. If found, it generates
// a new cert and bag to replace the old Identity.pfx. This fixes an issue that came up as a result of
// moving the project to .NET 5.
_context.Install.IdentityCertPassword = Helpers.GetValueFromEnvFile("global", "globalSettings__identityServer__certificatePassword");
var certCountString = Helpers.Exec("openssl pkcs12 -nokeys -info -in /bitwarden/identity/identity.pfx " +
$"-passin pass:{_context.Install.IdentityCertPassword} 2> /dev/null | grep -c \"\\-----BEGIN CERTIFICATE----\"", true);
if (int.TryParse(certCountString, out var certCount) && certCount > 1)
{
// Extract key from identity.pfx
Helpers.Exec("openssl pkcs12 -in /bitwarden/identity/identity.pfx -nocerts -nodes -out identity.key " +
$"-passin pass:{_context.Install.IdentityCertPassword} > /dev/null 2>&1");
// Extract certificate from identity.pfx
Helpers.Exec("openssl pkcs12 -in /bitwarden/identity/identity.pfx -clcerts -nokeys -out identity.crt " +
$"-passin pass:{_context.Install.IdentityCertPassword} > /dev/null 2>&1");
// Create new PKCS12 bag with certificate and key
Helpers.Exec("openssl pkcs12 -export -out /bitwarden/identity/identity.pfx -inkey identity.key " +
$"-in identity.crt -passout pass:{_context.Install.IdentityCertPassword} > /dev/null 2>&1");
2022-08-29 20:53:16 +02:00
}
2022-08-29 22:06:55 +02:00
if (_context.Parameters.ContainsKey("db"))
2022-08-29 20:53:16 +02:00
{
PrepareAndMigrateDatabase();
2022-08-29 22:06:55 +02:00
}
else
{
RebuildConfigs();
}
}
2022-08-29 22:06:55 +02:00
private static void PrintEnvironment()
{
_context.LoadConfiguration();
if (!_context.PrintToScreen())
{
return;
}
Console.WriteLine("\nBitwarden is up and running!");
Console.WriteLine("===================================================");
Console.WriteLine("\nvisit {0}", _context.Config.Url);
Console.Write("to update, run ");
if (_context.HostOS == "win")
{
Console.Write("`.\\bitwarden.ps1 -updateself` and then `.\\bitwarden.ps1 -update`");
}
else
{
Console.Write("`./bitwarden.sh updateself` and then `./bitwarden.sh update`");
2017-08-24 17:16:01 +02:00
}
2022-08-29 22:06:55 +02:00
Console.WriteLine("\n");
}
private static void PrepareAndMigrateDatabase()
2022-08-29 22:06:55 +02:00
{
var vaultConnectionString = Helpers.GetValueFromEnvFile("global",
"globalSettings__sqlServer__connectionString");
var migrator = new DbMigrator(vaultConnectionString);
[DEVOPS-1519] Add transition mode to mssql migrator utility (#3259) * Add RerunableSqlTableJournal * Add extension to use rerunable sql table journal * Use rerunable sql journal * format * Enable logging * FIx * Disable logging * Rename to SqlTableJournalExtensions * Move RerunableSqlTableJournal to Extension class * Fix usings * Add rerunable schema * Format * Fix typo * Enable logging in db migrator * add rerunable column in dbo migrations table migration * Trying * Fix journal table name * Trying to migrate first * After migration * Testing * Add update from rerunable to not rerunable script * Change name * Add rerunable option and script folder name * Add rerunable options and folder * Fix * Add transition (aka rerunable) migrations to Setup * Parse parameters on migrator utility * Fix sql scripts * Remove CreateSchemaTableSql as it'll be migrated using migration * Embed dbScripts_data_migration folder * Remove testing sql script * Add optins parsing nuget for msSqlMigratorUtility * Fix sql journal * Ran dotnet format * Comment out index * ▫️Revert "Comment out index" This reverts commit df15fa91e05d5b195e130c36e5ae51fc508b2506. * Disable logging * Add newline * Rename rerunable to repeatable * remove repeatable journal * Remove migration adding the repeatable column in dbo.Migrations table * Add using * Enable log for testing * Disable logging in the setup * Remove unused method * Add migrator constants * Use constants in yet another place * Fix * Add constant * Fix * Fix
2023-09-28 16:29:52 +02:00
var enableLogging = false;
[DEVOPS-1519] Add transition mode to mssql migrator utility (#3259) * Add RerunableSqlTableJournal * Add extension to use rerunable sql table journal * Use rerunable sql journal * format * Enable logging * FIx * Disable logging * Rename to SqlTableJournalExtensions * Move RerunableSqlTableJournal to Extension class * Fix usings * Add rerunable schema * Format * Fix typo * Enable logging in db migrator * add rerunable column in dbo migrations table migration * Trying * Fix journal table name * Trying to migrate first * After migration * Testing * Add update from rerunable to not rerunable script * Change name * Add rerunable option and script folder name * Add rerunable options and folder * Fix * Add transition (aka rerunable) migrations to Setup * Parse parameters on migrator utility * Fix sql scripts * Remove CreateSchemaTableSql as it'll be migrated using migration * Embed dbScripts_data_migration folder * Remove testing sql script * Add optins parsing nuget for msSqlMigratorUtility * Fix sql journal * Ran dotnet format * Comment out index * ▫️Revert "Comment out index" This reverts commit df15fa91e05d5b195e130c36e5ae51fc508b2506. * Disable logging * Add newline * Rename rerunable to repeatable * remove repeatable journal * Remove migration adding the repeatable column in dbo.Migrations table * Add using * Enable log for testing * Disable logging in the setup * Remove unused method * Add migrator constants * Use constants in yet another place * Fix * Add constant * Fix * Fix
2023-09-28 16:29:52 +02:00
// execute all general migration scripts (will detect those not yet applied)
migrator.MigrateMsSqlDatabaseWithRetries(enableLogging);
[DEVOPS-1519] Add transition mode to mssql migrator utility (#3259) * Add RerunableSqlTableJournal * Add extension to use rerunable sql table journal * Use rerunable sql journal * format * Enable logging * FIx * Disable logging * Rename to SqlTableJournalExtensions * Move RerunableSqlTableJournal to Extension class * Fix usings * Add rerunable schema * Format * Fix typo * Enable logging in db migrator * add rerunable column in dbo migrations table migration * Trying * Fix journal table name * Trying to migrate first * After migration * Testing * Add update from rerunable to not rerunable script * Change name * Add rerunable option and script folder name * Add rerunable options and folder * Fix * Add transition (aka rerunable) migrations to Setup * Parse parameters on migrator utility * Fix sql scripts * Remove CreateSchemaTableSql as it'll be migrated using migration * Embed dbScripts_data_migration folder * Remove testing sql script * Add optins parsing nuget for msSqlMigratorUtility * Fix sql journal * Ran dotnet format * Comment out index * ▫️Revert "Comment out index" This reverts commit df15fa91e05d5b195e130c36e5ae51fc508b2506. * Disable logging * Add newline * Rename rerunable to repeatable * remove repeatable journal * Remove migration adding the repeatable column in dbo.Migrations table * Add using * Enable log for testing * Disable logging in the setup * Remove unused method * Add migrator constants * Use constants in yet another place * Fix * Add constant * Fix * Fix
2023-09-28 16:29:52 +02:00
// execute explicit transition migration scripts, per EDD
migrator.MigrateMsSqlDatabaseWithRetries(enableLogging, true, MigratorConstants.TransitionMigrationsFolderName);
2022-08-29 22:06:55 +02:00
}
2022-08-29 22:06:55 +02:00
private static bool ValidateInstallation()
{
var installationId = string.Empty;
var installationKey = string.Empty;
CloudRegion cloudRegion;
2022-08-29 22:06:55 +02:00
if (_context.Parameters.ContainsKey("install-id"))
{
installationId = _context.Parameters["install-id"].ToLowerInvariant();
}
else
2022-08-29 20:53:16 +02:00
{
var prompt = "Enter your installation id (get at https://bitwarden.com/host)";
installationId = Helpers.ReadInput(prompt);
while (string.IsNullOrEmpty(installationId))
{
Helpers.WriteError("Invalid input for installation id. Please try again.");
installationId = Helpers.ReadInput(prompt);
}
2022-08-29 22:06:55 +02:00
}
2022-08-29 22:06:55 +02:00
if (!Guid.TryParse(installationId.Trim(), out var installationidGuid))
{
Console.WriteLine("Invalid installation id.");
return false;
}
2022-08-29 22:06:55 +02:00
if (_context.Parameters.ContainsKey("install-key"))
{
installationKey = _context.Parameters["install-key"];
}
else
{
var prompt = "Enter your installation key";
installationKey = Helpers.ReadInput(prompt);
while (string.IsNullOrEmpty(installationKey))
{
Helpers.WriteError("Invalid input for installation key. Please try again.");
installationKey = Helpers.ReadInput(prompt);
}
}
if (_context.Parameters.ContainsKey("cloud-region"))
{
Enum.TryParse(_context.Parameters["cloud-region"], out cloudRegion);
}
else
{
var prompt = "Enter your region (US/EU) [US]";
var region = Helpers.ReadInput(prompt);
if (string.IsNullOrEmpty(region)) region = "US";
while (!Enum.TryParse(region, out cloudRegion))
{
Helpers.WriteError("Invalid input for region. Please try again.");
region = Helpers.ReadInput(prompt);
if (string.IsNullOrEmpty(region)) region = "US";
}
2022-08-29 22:06:55 +02:00
}
2017-08-19 15:33:14 +02:00
2022-08-29 22:06:55 +02:00
_context.Install.InstallationId = installationidGuid;
_context.Install.InstallationKey = installationKey;
_context.Install.CloudRegion = cloudRegion;
2022-08-29 22:06:55 +02:00
try
{
string url;
switch (cloudRegion)
{
case CloudRegion.EU:
url = "https://api.bitwarden.eu/installations/";
break;
case CloudRegion.US:
default:
url = "https://api.bitwarden.com/installations/";
break;
}
var response = new HttpClient().GetAsync(url + _context.Install.InstallationId).GetAwaiter().GetResult();
2017-08-19 15:33:14 +02:00
2022-08-29 22:06:55 +02:00
if (!response.IsSuccessStatusCode)
2017-08-19 15:33:14 +02:00
{
2022-08-29 22:06:55 +02:00
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
2017-08-19 15:33:14 +02:00
{
Console.WriteLine($"Invalid installation id for {cloudRegion.ToString()} region.");
2017-08-19 15:33:14 +02:00
}
2022-08-29 22:06:55 +02:00
else
2017-08-19 15:33:14 +02:00
{
Console.WriteLine($"Unable to validate installation id for {cloudRegion.ToString()} region.");
2017-08-19 15:33:14 +02:00
}
2022-08-29 22:06:55 +02:00
return false;
2017-08-19 15:33:14 +02:00
}
2022-08-29 22:06:55 +02:00
var result = response.Content.ReadFromJsonAsync<InstallationValidationResponseModel>().GetAwaiter().GetResult();
if (!result.Enabled)
2017-08-19 15:33:14 +02:00
{
Console.WriteLine($"Installation id has been disabled in the {cloudRegion.ToString()} region.");
2017-08-19 15:33:14 +02:00
return false;
}
2017-11-07 04:55:15 +01:00
2022-08-29 22:06:55 +02:00
return true;
}
catch
{
Console.WriteLine($"Unable to validate installation id. Problem contacting Bitwarden {cloudRegion.ToString()} server.");
2022-08-29 22:06:55 +02:00
return false;
}
}
2021-12-16 15:35:09 +01:00
2022-08-29 22:06:55 +02:00
private static void RebuildConfigs()
{
_context.LoadConfiguration();
2017-08-07 22:31:00 +02:00
2022-08-29 22:06:55 +02:00
var environmentFileBuilder = new EnvironmentFileBuilder(_context);
environmentFileBuilder.BuildForUpdater();
2017-08-07 22:31:00 +02:00
2022-08-29 22:06:55 +02:00
var certBuilder = new CertBuilder(_context);
certBuilder.BuildForUpdater();
2022-08-29 22:06:55 +02:00
var nginxBuilder = new NginxConfigBuilder(_context);
nginxBuilder.BuildForUpdater();
2018-08-30 17:35:44 +02:00
2022-08-29 22:06:55 +02:00
var appIdBuilder = new AppIdBuilder(_context);
appIdBuilder.Build();
2022-08-29 20:53:16 +02:00
2022-08-29 22:06:55 +02:00
var dockerComposeBuilder = new DockerComposeBuilder(_context);
dockerComposeBuilder.BuildForUpdater();
_context.SaveConfiguration();
Console.WriteLine(string.Empty);
}
2017-08-08 18:29:59 +02:00
2022-08-29 22:06:55 +02:00
private static void ParseParameters()
{
_context.Parameters = new Dictionary<string, string>();
for (var i = 0; i < _context.Args.Length; i = i + 2)
2017-08-07 22:31:00 +02:00
{
2022-08-29 22:06:55 +02:00
if (!_context.Args[i].StartsWith("-"))
2017-08-07 22:31:00 +02:00
{
2022-08-29 22:06:55 +02:00
continue;
}
2022-08-29 20:53:16 +02:00
2022-08-29 22:06:55 +02:00
_context.Parameters.Add(_context.Args[i].Substring(1), _context.Args[i + 1]);
}
2022-08-29 20:53:16 +02:00
}
2022-08-29 22:06:55 +02:00
class InstallationValidationResponseModel
{
public bool Enabled { get; init; }
}
2017-08-07 22:31:00 +02:00
}