protected async Task ShowGitHubLoginInformation(RotationContext context, SecretReference gitHubSecret, string helpUrl, string gitHubAccountName) { var passwordReference = new SecretReference { Name = gitHubSecret.Name + GitHubPasswordSuffix, Location = gitHubSecret.Location }; var secretReference = new SecretReference { Name = gitHubSecret.Name + GitHubSecretSuffix, Location = gitHubSecret.Location }; var password = await context.GetSecretValue(passwordReference); var secret = await context.GetSecretValue(secretReference); await ShowGitHubLoginInformation(helpUrl, gitHubAccountName, password, secret); }
public override async Task <List <SecretData> > RotateValues(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { string currentPrimary = await context.GetSecretValue(new SecretReference(context.SecretName + PrimarySuffix)); if (currentPrimary == null) { currentPrimary = ""; } var newExpiration = DateTimeOffset.MaxValue; DateTimeOffset newRotateOn = _clock.UtcNow.AddMonths(1); var newSecondary = new SecretData(currentPrimary, newExpiration, newRotateOn); using var rng = RandomNumberGenerator.Create(); var bytes = new byte[parameters.Bytes]; rng.GetNonZeroBytes(bytes); string newPrimaryValue = Convert.ToBase64String(bytes); var newPrimary = new SecretData(newPrimaryValue, newExpiration, newRotateOn); return(new List <SecretData> { newPrimary, newSecondary, }); }
protected async Task ShowDomainAccountInformation(string helpUrl, RotationContext context, SecretReference domainAccountSecret, string domainAccountName) { var passwordReference = new SecretReference { Name = domainAccountSecret.Name, Location = domainAccountSecret.Location }; var password = await context.GetSecretValue(passwordReference); Console.WriteLine($"Please login to {helpUrl} using account {domainAccountName} and password: {password}"); }
public override async Task <List <SecretData> > RotateValues(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { if (!_console.IsInteractive) { throw new HumanInterventionRequiredException($"User intervention required for creation or rotation of GitHub OAuth secret."); } string clientId = await context.GetSecretValue(new SecretReference(context.SecretName + ClientId)); string clientSecret = await context.GetSecretValue(new SecretReference(context.SecretName + ClientSecret)); if (string.IsNullOrEmpty(clientId)) { _console.WriteLine($"Please login to https://github.com/settings/applications/new using your GitHub account, create a new GitHub OAuth application and generate a new client secret."); clientId = await _console.PromptAndValidateAsync("Client Id", "It should be a hexadecimal number.", l => !string.IsNullOrEmpty(l) && l.All(c => c.IsHexChar())); clientSecret = await _console.PromptAndValidateAsync("Client Secret", "It should be a hexadecimal number with at least 40 digits", l => !string.IsNullOrEmpty(l) && l.Length >= 40 && l.All(c => c.IsHexChar())); } else { _console.WriteLine($"Please login to https://github.com/settings/developers using your GitHub account, open {parameters.AppName} and generate a new client secret."); string clientSecretNewValue = await _console.PromptAndValidateAsync($"Client Secret (empty to keep existing), {parameters.Description}", "It should be a hexadecimal number with at least 40 digits", l => string.IsNullOrEmpty(l) || (l.Length >= 40 && l.All(c => c.IsHexChar()))); if (!string.IsNullOrEmpty(clientSecretNewValue)) { clientSecret = clientSecretNewValue; } } return(new List <SecretData> { new SecretData(clientId, DateTimeOffset.MaxValue, DateTimeOffset.MaxValue), new SecretData(clientSecret, DateTimeOffset.MaxValue, _clock.UtcNow.AddMonths(6)) }); }
protected override async Task <SecretData> RotateValue(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { DateTimeOffset expiresOn = _clock.UtcNow.AddMonths(1); DateTimeOffset nextRotationOn = _clock.UtcNow.AddDays(15); string connectionString = await context.GetSecretValue(parameters.ConnectionString); string sas = StorageUtils.GenerateBlobAccountSas(connectionString, parameters.Permissions, parameters.Service, expiresOn); return(new SecretData(sas, expiresOn, nextRotationOn)); }
protected override async Task <SecretData> RotateValue(AzureStorageContainerSasUri.Parameters parameters, RotationContext context, CancellationToken cancellationToken) { DateTimeOffset expiresOn = _clock.UtcNow.AddMonths(1); DateTimeOffset nextRotationOn = _clock.UtcNow.AddDays(15); string connectionString = await context.GetSecretValue(parameters.ConnectionString); (string containerUri, string sas)containerUriAndSas = StorageUtils.GenerateBlobContainerSas(connectionString, parameters.Container, parameters.Permissions, expiresOn); return(new SecretData(containerUriAndSas.sas, expiresOn, nextRotationOn)); }
protected override async Task <SecretData> RotateValue(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { DateTimeOffset now = _clock.UtcNow; CloudStorageAccount account = CloudStorageAccount.Parse(await context.GetSecretValue(parameters.ConnectionString)); CloudTableClient tableClient = account.CreateCloudTableClient(); CloudTable table = tableClient.GetTableReference(parameters.Table); string sas = table.GetSharedAccessSignature(new SharedAccessTablePolicy { Permissions = SharedAccessTablePolicy.PermissionsFromString(parameters.Permissions), SharedAccessExpiryTime = now.AddMonths(1), }); string result = table.Uri.AbsoluteUri + sas; return(new SecretData(result, now.AddMonths(1), now.AddDays(15))); }
protected override async Task <SecretData> RotateValue(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { var existing = await context.GetSecretValue(new SecretReference(context.SecretName)); if (!_console.IsInteractive) { throw new HumanInterventionRequiredException($"Text secret rotation required. Human intervention required."); } var newValue = await _console.PromptAsync($"Input value for {context.SecretName} (empty to keep existing), {parameters.Description}: "); if (string.IsNullOrEmpty(newValue)) { newValue = existing; } return(new SecretData(newValue, DateTimeOffset.MaxValue, DateTimeOffset.MaxValue)); }
public override async Task <List <SecretData> > RotateValues(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { if (!Console.IsInteractive) { throw new HumanInterventionRequiredException($"User intervention required for creation or rotation of GitHub bot account."); } string password = await context.GetSecretValue(new SecretReference(context.SecretName + GitHubPasswordSuffix)); if (string.IsNullOrEmpty(password)) { return(await NewAccount(parameters)); } else { return(await UpdateAccount(password, parameters, context)); } }
public override async Task <List <SecretData> > RotateValues(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { if (!_console.IsInteractive) { throw new HumanInterventionRequiredException($"User intervention required for creation or rotation of an AD Application."); } string appId = await context.GetSecretValue(new SecretReference(context.SecretName + AppIdSuffix)); if (string.IsNullOrEmpty(appId)) { _console.WriteLine($@"Steps: 1. Open https://ms.portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/CreateApplicationBlade/quickStartType//isMSAApp/ under your account. 2. Register a new application. 3. Create a new secret under the new application."); appId = await _console.PromptAndValidateAsync("Application Client Id", "Expecting GUID", l => Guid.TryParse(l, out _)); } else { _console.WriteLine($@"Steps: 1. Open https://ms.portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/quickStartType//sourceType/Microsoft_AAD_IAM/appId/{appId} under your account. 2. Navigate to Certificates & Secrets. 3. Create a new secret. 4. Delete old secret once it's saved."); } string appSecret = await _console.PromptAndValidateAsync("Application Client Secret", "Expecting at least 30 characters.", l => l != null && l.Length >= 30); DateTime expiresOn = await _console.PromptAndValidateAsync($"Secret expiration (M/d/yyyy)", "Secret expiration format must be M/d/yyyy.", (string value, out DateTime parsedValue) => DateTime.TryParseExact(value, "M/d/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedValue)); return(new List <SecretData> { new SecretData(appId, DateTimeOffset.MaxValue, DateTimeOffset.MaxValue), new SecretData(appSecret, expiresOn, expiresOn.AddDays(-15)) }); }
protected override async Task <SecretData> RotateValue(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { string adAppId = await context.GetSecretValue(new SecretReference { Location = parameters.ADApplication.Location, Name = parameters.ADApplication.Name + ADApplication.AppIdSuffix }); SecretValue adAppSecret = await context.GetSecret(new SecretReference { Location = parameters.ADApplication.Location, Name = parameters.ADApplication.Name + ADApplication.AppSecretSuffix }); var connectionString = new StringBuilder(); connectionString.Append($"Data Source={parameters.DataSource}"); if (!string.IsNullOrEmpty(parameters.InitialCatalog)) { connectionString.Append($";Initial Catalog={parameters.InitialCatalog}"); } connectionString.Append($";AAD Federated Security=True;Application Client Id={adAppId};Application Key={adAppSecret?.Value}"); if (!string.IsNullOrWhiteSpace(parameters.AdditionalParameters)) { connectionString.Append($";{parameters.AdditionalParameters}"); } return(new SecretData(connectionString.ToString(), adAppSecret.ExpiresOn, adAppSecret.NextRotationOn)); }
protected override async Task <SecretData> RotateValue(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { if (!_console.IsInteractive) { throw new HumanInterventionRequiredException($"User intervention required for creation or rotation of a Domain Account."); } string generatedPassword = PasswordGenerator.GenerateRandomPassword(15, false); string password = await context.GetSecretValue(new SecretReference(context.SecretName)); if (!string.IsNullOrEmpty(password)) { _console.WriteLine($"Current password for account {parameters.AccountName}: {password}"); } _console.WriteLine($@"Steps: 1. Ctrl-alt-delete on a domain joined windows computer 2. Put in the name of the domain account {parameters.AccountName} 3. Put the current secret {password} into the ""Old Password"" 4. Put the new password {generatedPassword} or your custom one in the ""New Password"" field 5. Update the account"); if (!string.IsNullOrWhiteSpace(parameters.Description)) { _console.WriteLine($"Additional information: {parameters.Description}"); } string newPassword = await _console.PromptAsync($"Enter a new password or press enter to use a generated password {generatedPassword} : "); if (string.IsNullOrWhiteSpace(newPassword)) { newPassword = generatedPassword; } return(new SecretData(newPassword, DateTimeOffset.MaxValue, _clock.UtcNow.AddMonths(6))); }
public override async Task <List <SecretData> > RotateValues(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { if (!_console.IsInteractive) { throw new HumanInterventionRequiredException($"User intervention required for creation or rotation of GitHub App Secret."); } string appId = await context.GetSecretValue(new SecretReference(context.SecretName + AppId)); string privateKey = await context.GetSecretValue(new SecretReference(context.SecretName + AppPrivateKey)); string oauthId = await context.GetSecretValue(new SecretReference(context.SecretName + OAuthId)); string oauthSecret = await context.GetSecretValue(new SecretReference(context.SecretName + OAuthSecret)); string webhookSecret = await context.GetSecretValue(new SecretReference(context.SecretName + AppHookSecret)); bool isNew = string.IsNullOrEmpty(appId); if (isNew) { _console.WriteLine("Please login to https://github.com/settings/apps/new using your GitHub account, create a new GitHub App and generate a new private key."); string name = await _console.PromptAsync("Application Name (From the Url)"); context.SetValue("app-name", name); appId = await _console.PromptAndValidateAsync("App Id", "Allowed are only digits.", l => !string.IsNullOrEmpty(l) && l.All(c => char.IsDigit(c))); } else { string name = context.GetValue("app-name", "<null>"); _console.WriteLine($"Please login to https://github.com/settings/apps/{name} using your GitHub account."); _console.WriteLine("To roll Private Key and Client Secret: first generate a new one and remove the old once it was successfully saved."); } if (parameters.HasPrivateKey) { privateKey = await _console.PromptAndValidateAsync("Private Key file path", "Allowed are only valid pem files with private key.", (ConsoleExtension.TryParse <string>) TryParsePemFileWithPrivateKey); } if (parameters.HasOAuthSecret) { oauthId = await _console.PromptAndValidateAsync("OAuth Client Id", "Iv1. followed by 16 hex digits", l => !string.IsNullOrEmpty(l) && Regex.IsMatch(l, "^Iv1\\.[a-fA-F0-9]{16}$")); oauthSecret = await _console.PromptAndValidateAsync("OAuth Client Secret", "Hexadecimal number with at least 40 digits", l => !string.IsNullOrEmpty(l) && l.Length >= 40 && l.All(c => c.IsHexChar())); } if (parameters.HasWebhookSecret) { webhookSecret = await _console.PromptAndValidateAsync("Webhook Secret", "is required", l => !string.IsNullOrWhiteSpace(l)); } DateTimeOffset rollOn = _clock.UtcNow.AddMonths(6); return(new List <SecretData> { new SecretData(appId, DateTimeOffset.MaxValue, rollOn), new SecretData(privateKey, DateTimeOffset.MaxValue, rollOn), new SecretData(oauthId, DateTimeOffset.MaxValue, rollOn), new SecretData(oauthSecret, DateTimeOffset.MaxValue, rollOn), new SecretData(webhookSecret, DateTimeOffset.MaxValue, rollOn) }); }
protected override async Task <SecretData> RotateValue(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { string dataSource = parameters.DataSource; string adminConnectionString = await context.GetSecretValue(parameters.AdminConnection); bool haveFullAdmin = false; if (string.IsNullOrEmpty(adminConnectionString)) { if (!_console.IsInteractive) { throw new HumanInterventionRequiredException($"No admin connection for server {dataSource} available, user intervention required."); } adminConnectionString = await _console.PromptAsync($"No admin connection for server {dataSource} is available, please input one: "); haveFullAdmin = true; } var masterDbConnectionString = new SqlConnectionStringBuilder(adminConnectionString) { InitialCatalog = "master", }; if (masterDbConnectionString.DataSource != dataSource) { throw new InvalidOperationException($"Admin connection is for server {masterDbConnectionString.DataSource}, but I was requested to create a connection to {dataSource}"); } await using var masterDbConnection = new SqlConnection(masterDbConnectionString.ToString()); var dbConnectionString = new SqlConnectionStringBuilder(adminConnectionString) { InitialCatalog = parameters.Database, }; await using var dbConnection = new SqlConnection(dbConnectionString.ToString()); string currentUserIndex = context.GetValue("currentUserIndex", "2"); string nextUserId; int nextUserIndex; switch (currentUserIndex) { case "1": nextUserId = context.SecretName + "-2"; // lgtm [cs/hardcoded-credentials] Value decorates name intentionally and checked elsewhere nextUserIndex = 2; break; case "2": nextUserId = context.SecretName + "-1"; // lgtm [cs/hardcoded-credentials] Value decorates name intentionally and checked elsewhere nextUserIndex = 1; break; default: throw new InvalidOperationException($"Unexpected 'currentUserIndex' value '{currentUserIndex}'."); } var newPassword = PasswordGenerator.GenerateRandomPassword(40, false); await masterDbConnection.OpenAsync(cancellationToken); if (haveFullAdmin && parameters.Permissions == "admin") { await UpdateMasterDbWithFullAdmin(context, masterDbConnection); } var updateLoginCommand = masterDbConnection.CreateCommand(); updateLoginCommand.CommandText = $@" IF NOT EXISTS ( select name from sys.sql_logins where name = '{nextUserId}') BEGIN CREATE LOGIN [{nextUserId}] WITH PASSWORD = N'{newPassword}'; END ELSE BEGIN ALTER LOGIN [{nextUserId}] WITH PASSWORD = N'{newPassword}'; END"; await updateLoginCommand.ExecuteNonQueryAsync(cancellationToken); await dbConnection.OpenAsync(cancellationToken); var updateUserCommand = dbConnection.CreateCommand(); updateUserCommand.CommandText = $@" IF NOT EXISTS ( select name from sys.database_principals where name = '{nextUserId}') BEGIN CREATE USER [{nextUserId}] FOR LOGIN [{nextUserId}]; END "; if (parameters.Permissions == "admin") { updateUserCommand.CommandText += $@" ALTER ROLE db_owner ADD MEMBER [{nextUserId}] "; } else { foreach (var c in parameters.Permissions) { switch (c) { case 'r': updateUserCommand.CommandText += $@" ALTER ROLE db_datareader ADD MEMBER [{nextUserId}] "; break; case 'w': updateUserCommand.CommandText += $@" ALTER ROLE db_datawriter ADD MEMBER [{nextUserId}] "; break; default: throw new InvalidOperationException($"Invalid permissions specification '{c}'"); } } } await updateUserCommand.ExecuteNonQueryAsync(cancellationToken); context.SetValue("currentUserIndex", nextUserIndex.ToString()); var connectionString = new SqlConnectionStringBuilder(adminConnectionString) { UserID = nextUserId, Password = newPassword, InitialCatalog = parameters.Database, DataSource = dataSource, Encrypt = true, }; var result = connectionString.ToString(); result = OldifyConnectionString(result); if (!string.IsNullOrEmpty(parameters.ExtraSettings)) { result += parameters.ExtraSettings; } return(new SecretData(result, DateTimeOffset.MaxValue, _clock.UtcNow.AddMonths(1))); }
private async Task <List <SecretData> > UpdateAccount(string password, Parameters parameters, RotationContext context) { var secrets = new List <SecretData>(3); var secret = await context.GetSecretValue(new SecretReference(context.SecretName + GitHubSecretSuffix)); const string helpUrl = "https://github.com/settings/security"; await ShowGitHubLoginInformation(helpUrl, parameters.Name, secret, password); var rollPassword = await Console.ConfirmAsync("Do you want to roll bot's password (yes/no): "); if (rollPassword) { var newPassword = await AskUserForPassword(); secrets.Add(new SecretData(newPassword, DateTimeOffset.MaxValue, DateTimeOffset.MaxValue)); } else { secrets.Add(new SecretData(password, DateTimeOffset.MaxValue, DateTimeOffset.MaxValue)); } bool rollSecret = await Console.ConfirmAsync("Do you want to roll bot's secret (yes/no): ");; bool rollRecoveryCodes = true; if (!rollSecret) { rollRecoveryCodes = await Console.ConfirmAsync("Do you want to roll recovery codes (yes/no): "); } else { Console.WriteLine("Be aware that roll of the secret also rolls recovery codes."); } if (rollRecoveryCodes) { var newRecoveryCodes = await AskUserForRecoveryCodes(); secrets.Add(new SecretData(newRecoveryCodes, DateTimeOffset.MaxValue, DateTimeOffset.MaxValue)); } else { string recoveryCodes = await context.GetSecretValue(new SecretReference(context.SecretName + GitHubRecoveryCodesSuffix)); secrets.Add(new SecretData(recoveryCodes, DateTimeOffset.MaxValue, DateTimeOffset.MaxValue)); } if (rollSecret) { var newSecret = await AskUserForSecretAndShowConfirmationCode(); secrets.Add(new SecretData(newSecret, DateTimeOffset.MaxValue, DateTimeOffset.MaxValue)); } else { secrets.Add(new SecretData(secret, DateTimeOffset.MaxValue, DateTimeOffset.MaxValue)); } return(secrets); }