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));
                    }
                }
            }
        }
Esempio n. 2
0
        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}");
        }
Esempio n. 3
0
 // 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"));
 }
Esempio n. 4
0
        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);
        }
Esempio n. 5
0
        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"));
        }
Esempio n. 6
0
 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"));
 }
Esempio n. 7
0
        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"));
        }
Esempio n. 8
0
        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)));
        }
Esempio n. 9
0
        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"));
        }
Esempio n. 10
0
        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);
            }
        }
Esempio n. 11
0
        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)));
        }
Esempio n. 12
0
        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));
        }
Esempio n. 13
0
        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))
            });
        }
Esempio n. 14
0
        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));
        }
Esempio n. 15
0
        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)));
        }
Esempio n. 16
0
        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));
            }
        }
Esempio n. 17
0
        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));
        }
Esempio n. 19
0
        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)));
        }
Esempio n. 20
0
        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)));
        }
Esempio n. 21
0
        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)
            });
        }
Esempio n. 22
0
        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)));
        }
Esempio n. 25
0
        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));
        }
Esempio n. 26
0
        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);
        }
Esempio n. 27
0
        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,
            });
        }
Esempio n. 28
0
        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)));
        }
Esempio n. 29
0
        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));
        }
Esempio n. 30
0
        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));
        }