예제 #1
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)));
        }
예제 #2
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)
            });
        }
예제 #3
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)));
        }
        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)));
        }