예제 #1
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);
        }
예제 #2
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,
            });
        }
예제 #3
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}");
        }
예제 #4
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));
        }
예제 #6
0
        protected override async Task <SecretData> RotateValue(AzureStorageContainerSasUri.Parameters parameters, RotationContext context, CancellationToken cancellationToken)
        {
            DateTimeOffset expiresOn      = _clock.UtcNow.AddMonths(1);
            DateTimeOffset nextRotationOn = _clock.UtcNow.AddDays(15);

            string connectionString = await context.GetSecretValue(parameters.ConnectionString);

            (string containerUri, string sas)containerUriAndSas = StorageUtils.GenerateBlobContainerSas(connectionString, parameters.Container, parameters.Permissions, expiresOn);

            return(new SecretData(containerUriAndSas.sas, expiresOn, nextRotationOn));
        }
예제 #7
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)));
        }
예제 #8
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));
        }
예제 #9
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));
            }
        }
예제 #10
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))
            });
        }
예제 #11
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));
        }
예제 #12
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)));
        }
예제 #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 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)
            });
        }
예제 #14
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)));
        }
예제 #15
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);
        }