protected override async Task <SecretData> RotateValue(RotationContext context, CancellationToken cancellationToken) { var client = await CreateManagementClient(cancellationToken); var account = await FindAccount(client, cancellationToken); if (account == null) { throw new ArgumentException($"Storage account '{_accountName}' in subscription '{_subscription}' not found."); } var currentKey = context.GetValue("currentKey", "key1"); var id = ResourceId.FromString(account.Id); StorageAccountListKeysResult keys; string keyToReturn; switch (currentKey) { case "key1": keys = await client.StorageAccounts.RegenerateKeyAsync(id.ResourceGroupName, id.Name, "key2", cancellationToken : cancellationToken); keyToReturn = "key2"; break; case "key2": keys = await client.StorageAccounts.RegenerateKeyAsync(id.ResourceGroupName, id.Name, "key1", cancellationToken : cancellationToken); keyToReturn = "key1"; break; default: throw new InvalidOperationException($"Unexpected 'currentKey' value '{currentKey}'."); } var key = keys.Keys.FirstOrDefault(k => k.KeyName == keyToReturn) ?? throw new InvalidOperationException($"Key {keyToReturn} not found."); var connectionString = $"DefaultEndpointsProtocol=https;AccountName={id.Name};AccountKey={key.Value}"; context.SetValue("currentKey", keyToReturn); return(new SecretData(connectionString, DateTimeOffset.MaxValue, DateTimeOffset.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))); }
protected override async Task <SecretData> RotateValue(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { var client = await CreateManagementClient(parameters, cancellationToken); var accessPolicyName = context.SecretName + "-access-policy"; var rule = new SBAuthorizationRule(new List <AccessRights?>(), name: accessPolicyName); bool updateRule = false; foreach (var c in parameters.Permissions) { switch (c) { case 's': rule.Rights.Add(AccessRights.Send); break; case 'l': rule.Rights.Add(AccessRights.Listen); break; case 'm': rule.Rights.Add(AccessRights.Manage); break; default: throw new ArgumentException($"Invalid permission specification '{c}'"); } } try { var existingRule = await client.Namespaces.GetAuthorizationRuleAsync(parameters.ResourceGroup, parameters.Namespace, accessPolicyName, cancellationToken); if (existingRule.Rights.Count != rule.Rights.Count || existingRule.Rights.Zip(rule.Rights).Any((p) => p.First != p.Second)) { updateRule = true; } } catch (ErrorResponseException e) when(e.Response.StatusCode == HttpStatusCode.NotFound) { updateRule = true; } if (updateRule) { await client.Namespaces.CreateOrUpdateAuthorizationRuleAsync(parameters.ResourceGroup, parameters.Namespace, accessPolicyName, rule, cancellationToken); } var currentKey = context.GetValue("currentKey", "primary"); AccessKeys keys; string result; switch (currentKey) { case "primary": keys = await client.Namespaces.RegenerateKeysAsync(parameters.ResourceGroup, parameters.Namespace, accessPolicyName, new RegenerateAccessKeyParameters(KeyType.SecondaryKey), cancellationToken); result = keys.SecondaryConnectionString; context.SetValue("currentKey", "secondary"); break; case "secondary": keys = await client.Namespaces.RegenerateKeysAsync(parameters.ResourceGroup, parameters.Namespace, accessPolicyName, new RegenerateAccessKeyParameters(KeyType.PrimaryKey), cancellationToken); result = keys.PrimaryConnectionString; context.SetValue("currentKey", "primary"); break; default: throw new InvalidOperationException($"Unexpected 'currentKey' value '{currentKey}'."); } return(new SecretData(result, DateTimeOffset.MaxValue, _clock.UtcNow.AddMonths(6))); }