From 50c62af8082e5206cc253df4586011bb5ce17cda Mon Sep 17 00:00:00 2001 From: Itay Zagron Date: Wed, 1 Dec 2021 18:08:26 +0100 Subject: [PATCH] Added secretsmanager secrets --- client/client.go | 3 + client/mocks/mock_secrets_manager.go | 96 +++++++ client/services.go | 8 + docs/tables/aws_secretsmanager_secrets.md | 26 ++ go.mod | 2 +- go.sum | 2 + .../aws_secretsmanager_secrets_test.go | 28 ++ .../infra/aws_secretsmanager_secrets.tf | 209 +++++++++++++++ resources/provider.go | 1 + resources/secretsmanager_secrets.go | 247 ++++++++++++++++++ resources/secretsmanager_secrets_test.go | 53 ++++ 11 files changed, 674 insertions(+), 1 deletion(-) create mode 100644 client/mocks/mock_secrets_manager.go create mode 100644 docs/tables/aws_secretsmanager_secrets.md create mode 100644 resources/integration_tests/aws_secretsmanager_secrets_test.go create mode 100644 resources/integration_tests/infra/aws_secretsmanager_secrets.tf create mode 100644 resources/secretsmanager_secrets.go create mode 100644 resources/secretsmanager_secrets_test.go diff --git a/client/client.go b/client/client.go index f97cda9d8..46c395bbc 100644 --- a/client/client.go +++ b/client/client.go @@ -51,6 +51,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3" s3control "github.com/aws/aws-sdk-go-v2/service/s3control" "github.com/aws/aws-sdk-go-v2/service/sagemaker" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" "github.com/aws/aws-sdk-go-v2/service/sns" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/aws-sdk-go-v2/service/ssm" @@ -130,6 +131,7 @@ type Services struct { S3Manager S3ManagerClient SSM SSMClient SageMaker SageMakerClient + SecretsManager SecretsManagerClient SQS SQSClient Apigateway ApigatewayClient Apigatewayv2 Apigatewayv2Client @@ -394,6 +396,7 @@ func initServices(region string, c aws.Config) Services { S3Control: s3control.NewFromConfig(awsCfg), S3Manager: newS3ManagerFromConfig(awsCfg), SageMaker: sagemaker.NewFromConfig(awsCfg), + SecretsManager: secretsmanager.NewFromConfig(awsCfg), SNS: sns.NewFromConfig(awsCfg), SSM: ssm.NewFromConfig(awsCfg), SQS: sqs.NewFromConfig(awsCfg), diff --git a/client/mocks/mock_secrets_manager.go b/client/mocks/mock_secrets_manager.go new file mode 100644 index 000000000..ad61b5c1e --- /dev/null +++ b/client/mocks/mock_secrets_manager.go @@ -0,0 +1,96 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/cloudquery/cq-provider-aws/client (interfaces: SecretsManagerClient) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + secretsmanager "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + gomock "github.com/golang/mock/gomock" +) + +// MockSecretsManagerClient is a mock of SecretsManagerClient interface. +type MockSecretsManagerClient struct { + ctrl *gomock.Controller + recorder *MockSecretsManagerClientMockRecorder +} + +// MockSecretsManagerClientMockRecorder is the mock recorder for MockSecretsManagerClient. +type MockSecretsManagerClientMockRecorder struct { + mock *MockSecretsManagerClient +} + +// NewMockSecretsManagerClient creates a new mock instance. +func NewMockSecretsManagerClient(ctrl *gomock.Controller) *MockSecretsManagerClient { + mock := &MockSecretsManagerClient{ctrl: ctrl} + mock.recorder = &MockSecretsManagerClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSecretsManagerClient) EXPECT() *MockSecretsManagerClientMockRecorder { + return m.recorder +} + +// DescribeSecret mocks base method. +func (m *MockSecretsManagerClient) DescribeSecret(arg0 context.Context, arg1 *secretsmanager.DescribeSecretInput, arg2 ...func(*secretsmanager.Options)) (*secretsmanager.DescribeSecretOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DescribeSecret", varargs...) + ret0, _ := ret[0].(*secretsmanager.DescribeSecretOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DescribeSecret indicates an expected call of DescribeSecret. +func (mr *MockSecretsManagerClientMockRecorder) DescribeSecret(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeSecret", reflect.TypeOf((*MockSecretsManagerClient)(nil).DescribeSecret), varargs...) +} + +// GetResourcePolicy mocks base method. +func (m *MockSecretsManagerClient) GetResourcePolicy(arg0 context.Context, arg1 *secretsmanager.GetResourcePolicyInput, arg2 ...func(*secretsmanager.Options)) (*secretsmanager.GetResourcePolicyOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetResourcePolicy", varargs...) + ret0, _ := ret[0].(*secretsmanager.GetResourcePolicyOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetResourcePolicy indicates an expected call of GetResourcePolicy. +func (mr *MockSecretsManagerClientMockRecorder) GetResourcePolicy(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResourcePolicy", reflect.TypeOf((*MockSecretsManagerClient)(nil).GetResourcePolicy), varargs...) +} + +// ListSecrets mocks base method. +func (m *MockSecretsManagerClient) ListSecrets(arg0 context.Context, arg1 *secretsmanager.ListSecretsInput, arg2 ...func(*secretsmanager.Options)) (*secretsmanager.ListSecretsOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ListSecrets", varargs...) + ret0, _ := ret[0].(*secretsmanager.ListSecretsOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListSecrets indicates an expected call of ListSecrets. +func (mr *MockSecretsManagerClientMockRecorder) ListSecrets(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSecrets", reflect.TypeOf((*MockSecretsManagerClient)(nil).ListSecrets), varargs...) +} diff --git a/client/services.go b/client/services.go index e9e6ea01a..cfe961bb7 100644 --- a/client/services.go +++ b/client/services.go @@ -41,6 +41,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3" s3control "github.com/aws/aws-sdk-go-v2/service/s3control" "github.com/aws/aws-sdk-go-v2/service/sagemaker" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" "github.com/aws/aws-sdk-go-v2/service/sns" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/aws-sdk-go-v2/service/ssm" @@ -490,3 +491,10 @@ type CodebuildClient interface { BatchGetProjects(ctx context.Context, params *codebuild.BatchGetProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.BatchGetProjectsOutput, error) ListProjects(ctx context.Context, params *codebuild.ListProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.ListProjectsOutput, error) } + +//go:generate mockgen -package=mocks -destination=./mocks/mock_secrets_manager.go . SecretsManagerClient +type SecretsManagerClient interface { + ListSecrets(ctx context.Context, params *secretsmanager.ListSecretsInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.ListSecretsOutput, error) + DescribeSecret(ctx context.Context, params *secretsmanager.DescribeSecretInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.DescribeSecretOutput, error) + GetResourcePolicy(ctx context.Context, params *secretsmanager.GetResourcePolicyInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetResourcePolicyOutput, error) +} diff --git a/docs/tables/aws_secretsmanager_secrets.md b/docs/tables/aws_secretsmanager_secrets.md new file mode 100644 index 000000000..f0463a49e --- /dev/null +++ b/docs/tables/aws_secretsmanager_secrets.md @@ -0,0 +1,26 @@ + +# Table: aws_secretsmanager_secrets +A structure that contains the details about a secret +## Columns +| Name | Type | Description | +| ------------- | ------------- | ----- | +|account_id|text|The AWS Account ID of the resource.| +|region|text|The AWS Region of the resource.| +|policy|jsonb|A JSON-formatted string that describes the permissions that are associated with the attached secret.| +|replication_status|jsonb|A replication object consisting of a RegionReplicationStatus object and includes a Region, KMSKeyId, status, and status message.| +|arn|text|The Amazon Resource Name (ARN) of the secret| +|created_date|timestamp without time zone|The date and time when a secret was created.| +|deleted_date|timestamp without time zone|The date and time the deletion of the secret occurred| +|description|text|The user-provided description of the secret.| +|kms_key_id|text|The ARN or alias of the Amazon Web Services KMS customer master key (CMK) used to encrypt the SecretString and SecretBinary fields in each version of the secret| +|last_accessed_date|timestamp without time zone|The last date that this secret was accessed| +|last_changed_date|timestamp without time zone|The last date and time that this secret was modified in any way.| +|last_rotated_date|timestamp without time zone|The most recent date and time that the Secrets Manager rotation process was successfully completed| +|name|text|The friendly name of the secret| +|owning_service|text|Returns the name of the service that created the secret.| +|primary_region|text|The Region where Secrets Manager originated the secret.| +|rotation_enabled|boolean|Indicates whether automatic, scheduled rotation is enabled for this secret.| +|rotation_lambda_arn|text|The ARN of an Amazon Web Services Lambda function invoked by Secrets Manager to rotate and expire the secret either automatically per the schedule or manually by a call to RotateSecret.| +|rotation_rules_automatically_after_days|bigint|Specifies the number of days between automatic scheduled rotations of the secret| +|secret_versions_to_stages|jsonb|A list of all of the currently assigned SecretVersionStage staging labels and the SecretVersionId attached to each one| +|tags|jsonb|The list of user-defined tags associated with the secret| diff --git a/go.mod b/go.mod index 15efb9fc3..7ce69ef63 100644 --- a/go.mod +++ b/go.mod @@ -137,7 +137,7 @@ require ( ) require ( - github.com/aws/aws-sdk-go v1.17.7 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.10.1 github.com/aws/aws-sdk-go-v2/service/ssm v1.16.0 ) diff --git a/go.sum b/go.sum index 12370ea55..34aaf3c18 100644 --- a/go.sum +++ b/go.sum @@ -253,6 +253,8 @@ github.com/aws/aws-sdk-go-v2/service/s3control v1.14.1 h1:Nmcb6pxJtjJof+mmF9TJvy github.com/aws/aws-sdk-go-v2/service/s3control v1.14.1/go.mod h1:dTnxIRqR69JUZobQDUh47rlbYe8PzTd0k4o+gDkHeV4= github.com/aws/aws-sdk-go-v2/service/sagemaker v1.19.1 h1:cy6fUlP94vzD/0VUD3SWGUBfYrOr+zP+ChsTxUtZydQ= github.com/aws/aws-sdk-go-v2/service/sagemaker v1.19.1/go.mod h1:G9AcXDbGtZVA8XBdmpbVQv1lvmiuk4I9n2MQlp1FJ9k= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.10.1 h1:e0gg30cCKsNHV+WD17zbzipx5nYRrnb+4Y5wO5pap80= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.10.1/go.mod h1:vUIn46AiFjPEm4/ALXV1weLTEWu37mF6OfLMw5vxG2Q= github.com/aws/aws-sdk-go-v2/service/sns v1.1.2 h1:1U/FujyBEkNwrvANUcZFuVnAQqy0EAUEGToso5Dcijs= github.com/aws/aws-sdk-go-v2/service/sns v1.1.2/go.mod h1:/vvAGyo3/TG5CSrJQarIlwzjE6O/DjBIvJTRkpYkvwA= github.com/aws/aws-sdk-go-v2/service/sqs v1.9.1 h1:8m+6iuSldxMrVQbjHRcWPnUxdpD3RCPtacmFFNkR4Vw= diff --git a/resources/integration_tests/aws_secretsmanager_secrets_test.go b/resources/integration_tests/aws_secretsmanager_secrets_test.go new file mode 100644 index 000000000..d2b4de5a4 --- /dev/null +++ b/resources/integration_tests/aws_secretsmanager_secrets_test.go @@ -0,0 +1,28 @@ +package integration_tests + +import ( + "fmt" + "testing" + + "github.com/cloudquery/cq-provider-aws/resources" + providertest "github.com/cloudquery/cq-provider-sdk/provider/testing" +) + +func TestIntegrationSecretsmanagerSecrets(t *testing.T) { + awsTestIntegrationHelper(t, resources.SecretsmanagerSecrets(), []string{"aws_secretsmanager_secrets.tf"}, func(res *providertest.ResourceIntegrationTestData) providertest.ResourceIntegrationVerification { + return providertest.ResourceIntegrationVerification{ + Name: "aws_secretsmanager_secrets", + ExpectedValues: []providertest.ExpectedValue{{ + Count: 1, + Data: map[string]interface{}{ + "name": fmt.Sprintf("secretsmanager-secret-%s%s", res.Prefix, res.Suffix), + "tags": map[string]interface{}{ + "TestId": res.Suffix, + "Type": "integration_test", + "Name": fmt.Sprintf("secretsmanager-secret-%s%s", res.Prefix, res.Suffix), + }, + }, + }}, + } + }) +} diff --git a/resources/integration_tests/infra/aws_secretsmanager_secrets.tf b/resources/integration_tests/infra/aws_secretsmanager_secrets.tf new file mode 100644 index 000000000..9d58ba81c --- /dev/null +++ b/resources/integration_tests/infra/aws_secretsmanager_secrets.tf @@ -0,0 +1,209 @@ +resource "aws_secretsmanager_secret" "secretsmanager_secret" { + name = "secretsmanager-secret-${var.test_prefix}${var.test_suffix}" + tags = { + Name = "secretsmanager-secret-${var.test_prefix}${var.test_suffix}" + } +} + +data "aws_iam_policy_document" "secretsmanager_secret_iam_policy" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "secretsmanager_secret_iam_role" { + name = "secretsmanager-secret-iam-role-${var.test_prefix}${var.test_suffix}" + assume_role_policy = data.aws_iam_policy_document.secretsmanager_secret_iam_policy.json +} + +resource "aws_lambda_permission" "secretsmanager_secret_lambda_permission" { + function_name = aws_lambda_function.secretsmanager_secret_rotation_function.function_name + statement_id = "AllowExecutionSecretManager" + action = "lambda:InvokeFunction" + principal = "secretsmanager.amazonaws.com" +} + +resource "aws_lambda_function" "secretsmanager_secret_rotation_function" { + filename = data.archive_file.rotation_lambda_func_zip_inline.output_path + source_code_hash = data.archive_file.rotation_lambda_func_zip_inline.output_base64sha256 + function_name = "secretsmanager-secret-rotation-function-${var.test_prefix}${var.test_suffix}" + handler = "lambda_function.lambda_handler" + runtime = "python3.9" + timeout = 30 + role = aws_iam_role.secretsmanager_secret_iam_role.arn + + environment { + variables = { #https://docs.aws.amazon.com/general/latest/gr/rande.html#asm_region + SECRETS_MANAGER_ENDPOINT = "https://secretsmanager.${data.aws_region.current.name}.amazonaws.com" + } + } +} + +resource "aws_secretsmanager_secret_rotation" "secretsmanager_secret_rotation" { + secret_id = aws_secretsmanager_secret.secretsmanager_secret.id + rotation_lambda_arn = aws_lambda_function.secretsmanager_secret_rotation_function.arn + + rotation_rules { + automatically_after_days = 30 + } +} + +data "archive_file" "rotation_lambda_func_zip_inline" { + type = "zip" + output_path = "./tmp/rotation_lambda_zip_inline.zip" + source { + content = <