public override async Task RunAsync(CancellationToken cancellationToken) { var now = _clock.UtcNow; var manifest = SecretManifest.Read(_manifestFile); using var storage = _storageLocationTypeRegistry.Create(manifest.StorageLocation.Type, manifest.StorageLocation.Parameters); var existingSecrets = (await storage.ListSecretsAsync()).ToDictionary(p => p.Name); foreach (var(name, secret) in manifest.Secrets) { var secretType = _secretTypeRegistry.Create(secret.Type, secret.Parameters); var names = secretType.GetCompositeSecretSuffixes().Select(suffix => name + suffix).ToList(); var existing = new List <SecretProperties>(); foreach (var n in names) { existingSecrets.TryGetValue(n, out var e); existing.Add(e); // here we intentionally ignore the result of TryGetValue because we want to add null to the list to represent "this isn't in the store" } bool regenerate = false; if (existing.Any(e => e == null)) { // secret is missing from storage (either completely missing or partially missing) regenerate = true; } else { // If these fields aren't the same for every part of a composite secrets, assume the soonest value is right var nextRotation = existing.Select(e => e.NextRotationOn).Min(); var expires = existing.Select(e => e.ExpiresOn).Min(); if (nextRotation <= now) { // we have hit the rotation time, rotate regenerate = true; } else if (expires <= now) { // the secret has expired, this shouldn't happen in normal operation but we should rotate regenerate = true; } } if (regenerate) { var primary = existing.FirstOrDefault(p => p != null); var currentTags = primary?.Tags ?? ImmutableDictionary.Create <string, string>(); var context = new RotationContext(currentTags); var newValues = await secretType.RotateValues(context, cancellationToken); var newTags = context.GetValues(); foreach (var(n, value) in names.Zip(newValues)) { await storage.SetSecretValueAsync(n, new SecretValue(value.Value, newTags, value.NextRotationOn, value.ExpiresOn)); } } } }
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}"); }
// GET: Rotation public ActionResult Index() { if (Session["Email"] != null) { RotationContext context = new RotationContext(); List <Rotation> rotations = context.Rotations.ToList(); return(View(rotations)); } return(RedirectToAction("Index", "Home")); }
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 ActionResult Create(FormCollection formCollection) { if (Session["Email"] != null) { Rotation rotations = new Rotation(); RotationContext context = new RotationContext(); rotations.Rotations = formCollection["Rotations"]; rotations.Supervisor = formCollection["Supervisor"]; context.Rotations.Add(rotations); context.SaveChanges(); return(RedirectToAction("Index")); } return(RedirectToAction("Index", "Home")); }
public ActionResult Edit(int?id) { if (Session["Email"] != null) { if (id != null) { RotationContext context = new RotationContext(); Rotation rotation = context.Rotations.Single(pro => pro.Id == id); return(View(rotation)); } else { return(RedirectToAction("Index", "Rotation")); } } return(RedirectToAction("Index", "Home")); }
public ActionResult Delete(int?id, FormCollection formCollection) { if (Session["Email"] != null) { if (id != null) { RotationContext context = new RotationContext(); Rotation rotations = context.Rotations.Single(pro => pro.Id == id); context.Rotations.Remove(rotations); context.SaveChanges(); return(RedirectToAction("Index")); } else { return(RedirectToAction("Index", "Rotation")); } } return(RedirectToAction("Index", "Home")); }
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 ActionResult Edit(FormCollection formCollection, int?id) { if (Session["Email"] != null) { if (id != null) { RotationContext context = new RotationContext(); Rotation rotation = context.Rotations.Single(pro => pro.Id == id); rotation.Rotations = formCollection["Rotations"]; rotation.Supervisor = formCollection["Supervisor"]; context.SaveChanges(); return(RedirectToAction("Index")); } else { return(RedirectToAction("Index", "Rotation")); } } return(RedirectToAction("Index", "Home")); }
public override async Task RunAsync(CancellationToken cancellationToken) { try { _console.WriteLine($"Synchronizing secrets contained in {_manifestFile}"); if (_force || _forcedSecrets.Any()) { bool confirmed = await _console.ConfirmAsync( "--force or --force-secret is set, this will rotate one or more secrets ahead of schedule, possibly causing service disruption. Continue? "); if (!confirmed) { return; } } DateTimeOffset now = _clock.UtcNow; SecretManifest manifest = SecretManifest.Read(_manifestFile); using StorageLocationType.Bound storage = _storageLocationTypeRegistry .Get(manifest.StorageLocation.Type).BindParameters(manifest.StorageLocation.Parameters); using var disposables = new DisposableList(); var references = new Dictionary <string, StorageLocationType.Bound>(); foreach (var(name, storageReference) in manifest.References) { var bound = _storageLocationTypeRegistry.Get(storageReference.Type) .BindParameters(storageReference.Parameters); disposables.Add(bound); references.Add(name, bound); } Dictionary <string, SecretProperties> existingSecrets = (await storage.ListSecretsAsync()).ToDictionary(p => p.Name); List <(string name, SecretManifest.Secret secret, SecretType.Bound bound, HashSet <string> references)> orderedSecretTypes = GetTopologicallyOrderedSecrets(manifest.Secrets); var regeneratedSecrets = new HashSet <string>(); foreach (var(name, secret, secretType, secretReferences) in orderedSecretTypes) { _console.WriteLine($"Synchronizing secret {name}, type {secret.Type}"); List <string> names = secretType.GetCompositeSecretSuffixes().Select(suffix => name + suffix).ToList(); var existing = new List <SecretProperties>(); foreach (string n in names) { existingSecrets.Remove(n, out SecretProperties e); existing.Add(e); // here we intentionally ignore the result of Remove because we want to add null to the list to represent "this isn't in the store" } bool regenerate = false; if (_force) { _console.WriteLine("--force is set, will rotate."); regenerate = true; } else if (_forcedSecrets.Contains(name)) { _console.WriteLine($"--force-secret={name} is set, will rotate."); regenerate = true; } else if (existing.Any(e => e == null)) { _console.WriteLine("Secret not found in storage, will create."); // secret is missing from storage (either completely missing or partially missing) regenerate = true; } else if (regeneratedSecrets.Overlaps(secretReferences)) { _console.WriteLine("Referenced secret was rotated, will rotate."); regenerate = true; } else { // If these fields aren't the same for every part of a composite secrets, assume the soonest value is right DateTimeOffset nextRotation = existing.Select(e => e.NextRotationOn).Min(); DateTimeOffset expires = existing.Select(e => e.ExpiresOn).Min(); if (nextRotation <= now) { _console.WriteLine($"Secret scheduled for rotation on {nextRotation}, will rotate."); // we have hit the rotation time, rotate regenerate = true; // since the rotation runs weekly, we need a 1 week grace period // where verification runs will not fail, but rotation will happen. // otherwise a secret scheduled for rotation on tuesday, will cause // a build failure on wednesday, before it gets rotated normally on the following monday // the verification mode is to catch the "the rotation hasn't happened in months" case if (_verifyOnly && nextRotation > now.AddDays(-7)) { _console.WriteLine("Secret is within verification grace period."); regenerate = false; } } if (expires <= now) { _console.WriteLine($"Secret expired on {expires}, will rotate."); // the secret has expired, this shouldn't happen in normal operation but we should rotate regenerate = true; } } if (!regenerate) { _console.WriteLine("Secret is fine."); } if (regenerate && _verifyOnly) { _console.LogError($"Secret {name} requires rotation."); } else if (regenerate) { _console.WriteLine($"Generating new value(s) for secret {name}..."); SecretProperties primary = existing.FirstOrDefault(p => p != null); IImmutableDictionary <string, string> currentTags = primary?.Tags ?? ImmutableDictionary.Create <string, string>(); var context = new RotationContext(name, currentTags, storage, references); List <SecretData> newValues = await secretType.RotateValues(context, cancellationToken); IImmutableDictionary <string, string> newTags = context.GetValues(); regeneratedSecrets.Add(name); _console.WriteLine("Done."); _console.WriteLine($"Storing new value(s) in storage for secret {name}..."); foreach (var(n, value) in names.Zip(newValues)) { await storage.SetSecretValueAsync(n, new SecretValue(value.Value, newTags, value.NextRotationOn, value.ExpiresOn)); } _console.WriteLine("Done."); } } if (!_verifyOnly) { foreach (var(name, key) in manifest.Keys) { await storage.EnsureKeyAsync(name, key); } foreach (var(name, value) in existingSecrets) { _console.LogWarning($"Extra secret '{name}' consider deleting it."); } } } catch (FailWithExitCodeException) { throw; } catch (HumanInterventionRequiredException hire) { _console.LogError(hire.Message); throw new FailWithExitCodeException(42); } catch (Exception ex) { _console.LogError($"Unhandled Exception: {ex.Message}"); throw new FailWithExitCodeException(-1); } }
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))); }
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)); }
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) { SecretValue secret = await context.GetSecret(parameters.Secret); byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes(secret.Value); string secretEncodedBase64 = System.Convert.ToBase64String(plainTextBytes); return(new SecretData(secretEncodedBase64, secret.ExpiresOn, secret.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 an Azure DevOps access token."); } string helpUrl = $"https://dev.azure.com/{parameters.Organization}/_usersSettings/tokens"; if (parameters.GitHubBotAccountSecret != null) { await ShowGitHubLoginInformation(context, parameters.GitHubBotAccountSecret, helpUrl, parameters.GitHubBotAccountName); } if (parameters.DomainAccountSecret != null) { await ShowDomainAccountInformation(helpUrl, context, parameters.DomainAccountSecret, parameters.DomainAccountName); } var expiration = await Console.PromptAndValidateAsync("PAT expiration (M/d/yyyy)", "PAT expiration format must be M/d/yyyy.", (string value, out DateTime parsedValue) => DateTime.TryParseExact(value, "M/d/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedValue)); var pat = await Console.PromptAndValidateAsync("PAT", "PAT must have at least 52 characters.", value => value != null && value.Length >= 52); return(new SecretData(pat, expiration, expiration.Add(_rotateBeforeExpiration))); }
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)); } }
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 GitHub access token."); } const string helpUrl = "https://github.com/settings/tokens"; await ShowGitHubLoginInformation(context, parameters.GitHubBotAccountSecret, helpUrl, parameters.GitHubBotAccountName); var pat = await Console.PromptAndValidateAsync("PAT", "PAT must have at least 40 characters.", value => value != null && value.Length >= 40); return(new SecretData(pat, 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 containerUri, string sas)containerUriAndSas = StorageUtils.GenerateBlobContainerSas(connectionString, parameters.Container, parameters.Permissions, expiresOn); string uriWithSas = containerUriAndSas.containerUri + containerUriAndSas.sas; return(new SecretData(uriWithSas, expiresOn, nextRotationOn)); }
protected override async Task <SecretData> RotateValue(Parameters parameters, RotationContext context, CancellationToken cancellationToken) { string key = await StorageUtils.RotateStorageAccountKey(parameters.Subscription.ToString(), parameters.Account, context, _tokenCredentialProvider, cancellationToken); return(new SecretData(key, DateTimeOffset.MaxValue, _clock.UtcNow.AddMonths(6))); }
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))); }
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) }); }
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(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))); }
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 {TokenName}."); } var mapHelpEnvironmentToHost = new Dictionary <string, string>(EnvironmentToHost, StringComparer.OrdinalIgnoreCase); string helpUrl; if (parameters == null) { helpUrl = ""; } else if (!mapHelpEnvironmentToHost.TryGetValue(parameters.Environment, out helpUrl)) { helpUrl = parameters.Environment; } Console.WriteLine(string.Format(HelpMessage, helpUrl)); var token = await Console.PromptAndValidateAsync(TokenName, $"{TokenName} must be {TokenFormatDescription}.", ValidateToken); DateTimeOffset expiresOn = DateTimeOffset.MaxValue; DateTimeOffset nextRotationOn = Clock.UtcNow.AddMonths(6); if (HasExpiration) { expiresOn = await Console.PromptAndValidateAsync($"{TokenName} expiration", $"{TokenName} expiration must be {ExpirationFormatDescription}.", (ConsoleExtension.TryParse <DateTime>) TryParseExpirationDate); var calculatedNextRotationOn = expiresOn.Add(RotateBeforeExpiration); if (calculatedNextRotationOn < nextRotationOn) { nextRotationOn = calculatedNextRotationOn; } Console.WriteLine($"Next rotation was set to {nextRotationOn:yyyy-MM-dd}"); } return(new SecretData(token, expiresOn, nextRotationOn)); }
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); }
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 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) { string key; DateTimeOffset expiresOn; DateTimeOffset nextRotationOn; if (parameters.StorageKeySecret != null) { SecretValue storageKeySecret = await context.GetSecret(parameters.StorageKeySecret); key = storageKeySecret.Value; expiresOn = storageKeySecret.ExpiresOn; nextRotationOn = storageKeySecret.NextRotationOn; } else { key = await StorageUtils.RotateStorageAccountKey(parameters.Subscription.ToString(), parameters.Account, context, _tokenCredentialProvider, cancellationToken); expiresOn = DateTimeOffset.MaxValue; nextRotationOn = _clock.UtcNow.AddMonths(6); } string connectionString = $"DefaultEndpointsProtocol=https;AccountName={parameters.Account};AccountKey={key}"; return(new SecretData(connectionString, expiresOn, nextRotationOn)); }
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)); }