diff --git a/API.md b/API.md
index 26b391b5..db8456e2 100644
--- a/API.md
+++ b/API.md
@@ -4,6 +4,8 @@
### SopsSecret
+- *Implements:* @aws-cdk/aws-secretsmanager.ISecret
+
A drop in replacement for the normal Secret, that is populated with the encrypted content of the given sops file.
#### Initializers
@@ -45,12 +47,11 @@ new SopsSecret(scope: Construct, id: string, props: SopsSecretProps)
| **Name** | **Description** |
| --- | --- |
| toString
| Returns a string representation of this construct. |
-| applyRemovalPolicy
| Apply the given removal policy to this resource. |
-| addReplicaRegion
| Adds a replica region for the secret. |
| addRotationSchedule
| Adds a rotation schedule to the secret. |
-| addTargetAttachment
| Adds a target attachment to the secret. |
| addToResourcePolicy
| Adds a statement to the IAM resource policy associated with this secret. |
+| applyRemovalPolicy
| Apply the given removal policy to this resource. |
| attach
| Attach a target to this secret. |
+| currentVersionId
| Returns the current versionId that was created via the SopsSync. |
| denyAccountRootDelete
| Denies the `DeleteSecret` action to all principals within the current account. |
| grantRead
| Grants reading the secret value to some role. |
| grantWrite
| Grants writing and updating the secret value to some role. |
@@ -66,52 +67,6 @@ public toString(): string
Returns a string representation of this construct.
-##### `applyRemovalPolicy`
-
-```typescript
-public applyRemovalPolicy(policy: RemovalPolicy): void
-```
-
-Apply the given removal policy to this resource.
-
-The Removal Policy controls what happens to this resource when it stops
-being managed by CloudFormation, either because you've removed it from the
-CDK application or because you've made a change that requires the resource
-to be replaced.
-
-The resource can be deleted (`RemovalPolicy.DESTROY`), or left in your AWS
-account for data recovery and cleanup later (`RemovalPolicy.RETAIN`).
-
-###### `policy`Required
-
-- *Type:* @aws-cdk/core.RemovalPolicy
-
----
-
-##### `addReplicaRegion`
-
-```typescript
-public addReplicaRegion(region: string, encryptionKey?: IKey): void
-```
-
-Adds a replica region for the secret.
-
-###### `region`Required
-
-- *Type:* string
-
-The name of the region.
-
----
-
-###### `encryptionKey`Optional
-
-- *Type:* @aws-cdk/aws-kms.IKey
-
-The customer-managed encryption key to use for encrypting the secret value.
-
----
-
##### `addRotationSchedule`
```typescript
@@ -132,41 +87,43 @@ Adds a rotation schedule to the secret.
---
-##### ~~`addTargetAttachment`~~
+##### `addToResourcePolicy`
```typescript
-public addTargetAttachment(id: string, options: AttachedSecretOptions): SecretTargetAttachment
+public addToResourcePolicy(statement: PolicyStatement): AddToResourcePolicyResult
```
-Adds a target attachment to the secret.
-
-###### `id`Required
-
-- *Type:* string
+Adds a statement to the IAM resource policy associated with this secret.
----
+If this secret was created in this stack, a resource policy will be
+automatically created upon the first call to `addToResourcePolicy`. If
+the secret is imported, then this is a no-op.
-###### `options`Required
+###### `statement`Required
-- *Type:* @aws-cdk/aws-secretsmanager.AttachedSecretOptions
+- *Type:* @aws-cdk/aws-iam.PolicyStatement
---
-##### `addToResourcePolicy`
+##### `applyRemovalPolicy`
```typescript
-public addToResourcePolicy(statement: PolicyStatement): AddToResourcePolicyResult
+public applyRemovalPolicy(policy: RemovalPolicy): void
```
-Adds a statement to the IAM resource policy associated with this secret.
+Apply the given removal policy to this resource.
-If this secret was created in this stack, a resource policy will be
-automatically created upon the first call to `addToResourcePolicy`. If
-the secret is imported, then this is a no-op.
+The Removal Policy controls what happens to this resource when it stops
+being managed by CloudFormation, either because you've removed it from the
+CDK application or because you've made a change that requires the resource
+to be replaced.
-###### `statement`Required
+The resource can be deleted (`RemovalPolicy.DESTROY`), or left in your AWS
+account for data recovery and cleanup later (`RemovalPolicy.RETAIN`).
-- *Type:* @aws-cdk/aws-iam.PolicyStatement
+###### `policy`Required
+
+- *Type:* @aws-cdk/core.RemovalPolicy
---
@@ -182,10 +139,16 @@ Attach a target to this secret.
- *Type:* @aws-cdk/aws-secretsmanager.ISecretAttachmentTarget
-The target to attach.
-
---
+##### `currentVersionId`
+
+```typescript
+public currentVersionId(): string
+```
+
+Returns the current versionId that was created via the SopsSync.
+
##### `denyAccountRootDelete`
```typescript
@@ -247,13 +210,6 @@ Interpret the secret as a JSON object and return a field's value from it as a `S
| **Name** | **Description** |
| --- | --- |
| isConstruct
| Return whether the given object is a Construct. |
-| isResource
| Check whether the given construct is a Resource. |
-| fromSecretArn
| *No description.* |
-| fromSecretAttributes
| Import an existing secret into the Stack. |
-| fromSecretCompleteArn
| Imports a secret by complete ARN. |
-| fromSecretName
| Imports a secret by secret name; |
-| fromSecretNameV2
| Imports a secret by secret name. |
-| fromSecretPartialArn
| Imports a secret by partial ARN. |
---
@@ -273,217 +229,19 @@ Return whether the given object is a Construct.
---
-##### `isResource`
-
-```typescript
-import { SopsSecret } from 'cdk-sops-secrets'
-
-SopsSecret.isResource(construct: IConstruct)
-```
-
-Check whether the given construct is a Resource.
-
-###### `construct`Required
-
-- *Type:* @aws-cdk/core.IConstruct
-
----
-
-##### ~~`fromSecretArn`~~
-
-```typescript
-import { SopsSecret } from 'cdk-sops-secrets'
-
-SopsSecret.fromSecretArn(scope: Construct, id: string, secretArn: string)
-```
-
-###### `scope`Required
-
-- *Type:* constructs.Construct
-
----
-
-###### `id`Required
-
-- *Type:* string
-
----
-
-###### `secretArn`Required
-
-- *Type:* string
-
----
-
-##### `fromSecretAttributes`
-
-```typescript
-import { SopsSecret } from 'cdk-sops-secrets'
-
-SopsSecret.fromSecretAttributes(scope: Construct, id: string, attrs: SecretAttributes)
-```
-
-Import an existing secret into the Stack.
-
-###### `scope`Required
-
-- *Type:* constructs.Construct
-
-the scope of the import.
-
----
-
-###### `id`Required
-
-- *Type:* string
-
-the ID of the imported Secret in the construct tree.
-
----
-
-###### `attrs`Required
-
-- *Type:* @aws-cdk/aws-secretsmanager.SecretAttributes
-
-the attributes of the imported secret.
-
----
-
-##### `fromSecretCompleteArn`
-
-```typescript
-import { SopsSecret } from 'cdk-sops-secrets'
-
-SopsSecret.fromSecretCompleteArn(scope: Construct, id: string, secretCompleteArn: string)
-```
-
-Imports a secret by complete ARN.
-
-The complete ARN is the ARN with the Secrets Manager-supplied suffix.
-
-###### `scope`Required
-
-- *Type:* constructs.Construct
-
----
-
-###### `id`Required
-
-- *Type:* string
-
----
-
-###### `secretCompleteArn`Required
-
-- *Type:* string
-
----
-
-##### ~~`fromSecretName`~~
-
-```typescript
-import { SopsSecret } from 'cdk-sops-secrets'
-
-SopsSecret.fromSecretName(scope: Construct, id: string, secretName: string)
-```
-
-Imports a secret by secret name;
-
-the ARN of the Secret will be set to the secret name.
-A secret with this name must exist in the same account & region.
-
-###### `scope`Required
-
-- *Type:* constructs.Construct
-
----
-
-###### `id`Required
-
-- *Type:* string
-
----
-
-###### `secretName`Required
-
-- *Type:* string
-
----
-
-##### `fromSecretNameV2`
-
-```typescript
-import { SopsSecret } from 'cdk-sops-secrets'
-
-SopsSecret.fromSecretNameV2(scope: Construct, id: string, secretName: string)
-```
-
-Imports a secret by secret name.
-
-A secret with this name must exist in the same account & region.
-Replaces the deprecated `fromSecretName`.
-
-###### `scope`Required
-
-- *Type:* constructs.Construct
-
----
-
-###### `id`Required
-
-- *Type:* string
-
----
-
-###### `secretName`Required
-
-- *Type:* string
-
----
-
-##### `fromSecretPartialArn`
-
-```typescript
-import { SopsSecret } from 'cdk-sops-secrets'
-
-SopsSecret.fromSecretPartialArn(scope: Construct, id: string, secretPartialArn: string)
-```
-
-Imports a secret by partial ARN.
-
-The partial ARN is the ARN without the Secrets Manager-supplied suffix.
-
-###### `scope`Required
-
-- *Type:* constructs.Construct
-
----
-
-###### `id`Required
-
-- *Type:* string
-
----
-
-###### `secretPartialArn`Required
-
-- *Type:* string
-
----
-
#### Properties
| **Name** | **Type** | **Description** |
| --- | --- | --- |
| node
| @aws-cdk/core.ConstructNode
| The construct tree node associated with this construct. |
| env
| @aws-cdk/core.ResourceEnvironment
| The environment this resource belongs to. |
-| stack
| @aws-cdk/core.Stack
| The stack in which this resource is defined. |
| secretArn
| string
| The ARN of the secret in AWS Secrets Manager. |
| secretName
| string
| The name of the secret. |
| secretValue
| @aws-cdk/core.SecretValue
| Retrieve the value of the stored secret as a `SecretValue`. |
+| stack
| @aws-cdk/core.Stack
| The stack in which this resource is defined. |
+| sync
| SopsSync
| *No description.* |
| encryptionKey
| @aws-cdk/aws-kms.IKey
| The customer-managed encryption key that is used to encrypt this secret, if any. |
| secretFullArn
| string
| The full ARN of the secret in AWS Secrets Manager, which is the ARN including the Secrets Manager-supplied 6-character suffix. |
-| sync
| SopsSync
| *No description.* |
---
@@ -518,18 +276,6 @@ that might be different than the stack they were imported into.
---
-##### `stack`Required
-
-```typescript
-public readonly stack: Stack;
-```
-
-- *Type:* @aws-cdk/core.Stack
-
-The stack in which this resource is defined.
-
----
-
##### `secretArn`Required
```typescript
@@ -572,6 +318,28 @@ Retrieve the value of the stored secret as a `SecretValue`.
---
+##### `stack`Required
+
+```typescript
+public readonly stack: Stack;
+```
+
+- *Type:* @aws-cdk/core.Stack
+
+The stack in which this resource is defined.
+
+---
+
+##### `sync`Required
+
+```typescript
+public readonly sync: SopsSync;
+```
+
+- *Type:* SopsSync
+
+---
+
##### `encryptionKey`Optional
```typescript
@@ -601,16 +369,6 @@ This is equal to `secretArn` in most cases, but is undefined when a full ARN is
---
-##### `sync`Required
-
-```typescript
-public readonly sync: SopsSync;
-```
-
-- *Type:* SopsSync
-
----
-
### SopsSync
diff --git a/README.md b/README.md
index 21da114c..4a1f7e0c 100644
--- a/README.md
+++ b/README.md
@@ -43,4 +43,7 @@ Other than that, or perhaps more importantly, my goal was to learn new things:
# Other Tools like this
-* [sops-secretsmanager-cdk](https://github.com/isotoma/sops-secretsmanager-cdk): Does nearly the same (really, found it after this was already done). Less options than this construct.
\ No newline at end of file
+The problem this Construct addresses is so good, already two other implementations exist:
+
+* [isotoma/sops-secretsmanager-cdk](https://github.com/isotoma/sops-secretsmanager-cdk): Does nearly the same. Uses CustomResource, wraps the sops cli, does not support flatten. Found it after I published my solution to npm :-/
+* [taimos/secretsmanager-versioning](https://github.com/taimos/secretsmanager-versioning): Different approach on the same problem. This is a cli tool with very nice integration into cdk and also handles git versioning information.
\ No newline at end of file
diff --git a/go.mod b/go.mod
deleted file mode 100644
index f311056e..00000000
--- a/go.mod
+++ /dev/null
@@ -1,9 +0,0 @@
-module github.com/markussiebert/cdk-sops-secrets
-
-go 1.18
-
-require (
- cdk-sops-secrets/lambda v0.0.0
-)
-
-replace cdk-sops-secrets/lambda => ./lambda
\ No newline at end of file
diff --git a/lambda/__snapshots__/handler_json_test.snap b/lambda/__snapshots__/handler_json_test.snap
index e603c816..d5bd822c 100755
--- a/lambda/__snapshots__/handler_json_test.snap
+++ b/lambda/__snapshots__/handler_json_test.snap
@@ -10,6 +10,7 @@
[Test_FullWorkflow_Create_S3_JSON_Simple - 2]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
+ ClientRequestToken: "",
SecretId: "arn:aws:secretsmanager:eu-central-1:123456789012:secret:testsecret",
SecretString: "{\n \"key1\": \"value1\",\n \"key2\": 12345,\n \"key3\": false\n}"
}
@@ -40,6 +41,7 @@ nil
[Test_FullWorkflow_Create_S3_JSON_Complex - 2]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
+ ClientRequestToken: "",
SecretId: "arn:aws:secretsmanager:eu-central-1:123456789012:secret:testsecret",
SecretString: "{\n \"and now\": {\n \"some\": [\n {\n \"basic\": false\n },\n {\n \"nested\": 12345\n },\n {\n \"type\": 1.2345\n },\n {\n \"tests\": \"Finish!\"\n }\n ]\n },\n \"some\": {\n \"deep\": {\n \"nested\": {\n \"arrays\": [\n \"with\",\n \"several\",\n {\n \"values\": {\n \"and\": \"objects\"\n }\n }\n ],\n \"object\": \"structure\"\n }\n },\n \"notsodeep\": \"struct\"\n }\n}"
}
@@ -70,6 +72,7 @@ nil
[Test_FullWorkflow_Create_S3_JSON_Complex_StringifyValues - 2]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
+ ClientRequestToken: "",
SecretId: "arn:aws:secretsmanager:eu-central-1:123456789012:secret:testsecret",
SecretString: "{\n \"and now\": {\n \"some\": [\n {\n \"basic\": \"false\"\n },\n {\n \"nested\": \"12345\"\n },\n {\n \"type\": \"1.2345\"\n },\n {\n \"tests\": \"Finish!\"\n }\n ]\n },\n \"some\": {\n \"deep\": {\n \"nested\": {\n \"arrays\": [\n \"with\",\n \"several\",\n {\n \"values\": {\n \"and\": \"objects\"\n }\n }\n ],\n \"object\": \"structure\"\n }\n },\n \"notsodeep\": \"struct\"\n }\n}"
}
@@ -100,6 +103,7 @@ nil
[Test_FullWorkflow_Create_S3_JSON_Complex_Flat - 2]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
+ ClientRequestToken: "",
SecretId: "arn:aws:secretsmanager:eu-central-1:123456789012:secret:testsecret",
SecretString: "{\n \"and now.some[0].basic\": false,\n \"and now.some[1].nested\": 12345,\n \"and now.some[2].type\": 1.2345,\n \"and now.some[3].tests\": \"Finish!\",\n \"some.deep.nested.arrays[0]\": \"with\",\n \"some.deep.nested.arrays[1]\": \"several\",\n \"some.deep.nested.arrays[2].values.and\": \"objects\",\n \"some.deep.nested.object\": \"structure\",\n \"some.notsodeep\": \"struct\"\n}"
}
diff --git a/lambda/__snapshots__/handler_yaml_test.snap b/lambda/__snapshots__/handler_yaml_test.snap
index 97d3f15c..5f46a6ca 100755
--- a/lambda/__snapshots__/handler_yaml_test.snap
+++ b/lambda/__snapshots__/handler_yaml_test.snap
@@ -10,6 +10,7 @@
[Test_FullWorkflow_Create_S3_YAML_Simple - 2]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
+ ClientRequestToken: "",
SecretId: "arn:aws:secretsmanager:eu-central-1:123456789012:secret:testsecret",
SecretString: "key1: value1\nkey2: 12345\nkey3: false\n"
}
@@ -40,6 +41,7 @@ nil
[Test_FullWorkflow_Create_S3_YAML_as_JSON_Simple - 2]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
+ ClientRequestToken: "",
SecretId: "arn:aws:secretsmanager:eu-central-1:123456789012:secret:testsecret",
SecretString: "{\n \"key1\": \"value1\",\n \"key2\": 12345,\n \"key3\": false\n}"
}
@@ -70,6 +72,7 @@ nil
[Test_FullWorkflow_Create_S3_YAML_Complex - 2]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
+ ClientRequestToken: "",
SecretId: "arn:aws:secretsmanager:eu-central-1:123456789012:secret:testsecret",
SecretString: "and now:\n some:\n - basic: false\n - nested: 12345\n - type: 1.2345\n - tests: Finish!\nsome:\n deep:\n nested:\n arrays:\n - with\n - several\n - values:\n and: objects\n object: structure\n notsodeep: struct\n"
}
@@ -100,6 +103,7 @@ nil
[Test_FullWorkflow_Create_S3_YAML_as_JSON_Complex - 2]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
+ ClientRequestToken: "",
SecretId: "arn:aws:secretsmanager:eu-central-1:123456789012:secret:testsecret",
SecretString: "{\n \"and now\": {\n \"some\": [\n {\n \"basic\": false\n },\n {\n \"nested\": 12345\n },\n {\n \"type\": 1.2345\n },\n {\n \"tests\": \"Finish!\"\n }\n ]\n },\n \"some\": {\n \"deep\": {\n \"nested\": {\n \"arrays\": [\n \"with\",\n \"several\",\n {\n \"values\": {\n \"and\": \"objects\"\n }\n }\n ],\n \"object\": \"structure\"\n }\n },\n \"notsodeep\": \"struct\"\n }\n}"
}
@@ -130,6 +134,7 @@ nil
[Test_FullWorkflow_Create_S3_YAML_Complex_Flat - 2]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
+ ClientRequestToken: "",
SecretId: "arn:aws:secretsmanager:eu-central-1:123456789012:secret:testsecret",
SecretString: "and now.some[0].basic: false\nand now.some[1].nested: 12345\nand now.some[2].type: 1.2345\nand now.some[3].tests: Finish!\nsome.deep.nested.arrays[0]: with\nsome.deep.nested.arrays[1]: several\nsome.deep.nested.arrays[2].values.and: objects\nsome.deep.nested.object: structure\nsome.notsodeep: struct\n"
}
@@ -160,6 +165,7 @@ nil
[Test_FullWorkflow_Create_S3_YAML_as_JSON_Complex_Flat - 2]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
+ ClientRequestToken: "",
SecretId: "arn:aws:secretsmanager:eu-central-1:123456789012:secret:testsecret",
SecretString: "{\n \"and now.some[0].basic\": false,\n \"and now.some[1].nested\": 12345,\n \"and now.some[2].type\": 1.2345,\n \"and now.some[3].tests\": \"Finish!\",\n \"some.deep.nested.arrays[0]\": \"with\",\n \"some.deep.nested.arrays[1]\": \"several\",\n \"some.deep.nested.arrays[2].values.and\": \"objects\",\n \"some.deep.nested.object\": \"structure\",\n \"some.notsodeep\": \"struct\"\n}"
}
diff --git a/lambda/__snapshots__/main_test.snap b/lambda/__snapshots__/main_test.snap
index 2512641d..c13b4296 100755
--- a/lambda/__snapshots__/main_test.snap
+++ b/lambda/__snapshots__/main_test.snap
@@ -35,7 +35,8 @@
[Test_UpdateSecret - 1]
>>>SecretsManagerMockClient.PutSecretValue.Input
{
- SecretId: "arn:${Partition}:secretsmanager:${Region}:${Account}:secret:${SecretId}",
+ ClientRequestToken: "4547532a137611d83958d17095c6c2d38ae0036a760c3b79c9dd5957d1c20cf2",
+ SecretId: "arn::secretsmanager:::secret:",
SecretString: "some-secret-data"
}
---
diff --git a/lambda/main.go b/lambda/main.go
index d77cdc02..856d7664 100644
--- a/lambda/main.go
+++ b/lambda/main.go
@@ -8,6 +8,7 @@ import (
"log"
"regexp"
"strconv"
+ "strings"
runtime "github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
@@ -66,23 +67,29 @@ func decryptSopsFileContent(content []byte, format string) (data []byte, err err
return resp, nil
}
-func (a AWS) updateSecret(secretArn string, secretContent []byte) (data *secretsmanager.PutSecretValueOutput, err error) {
+func (a AWS) updateSecret(sopsFileName string, secretArn string, secretContent []byte) (data *secretsmanager.PutSecretValueOutput, err error) {
secretContentString := string(secretContent)
+ clientRequestToken := strings.Split(sopsFileName, ".")[0]
input := &secretsmanager.PutSecretValueInput{
- SecretId: &secretArn,
- SecretString: &secretContentString,
+ SecretId: &secretArn,
+ SecretString: &secretContentString,
+ ClientRequestToken: &clientRequestToken,
}
secretResp, secretErr := a.secretsmanager.PutSecretValue(input)
if secretErr != nil {
- return nil, errors.New(fmt.Sprintf("Failed to store secret value:\n%v\n", secretErr))
+ return nil, errors.New(fmt.Sprintf("Failed to store secret value:\nsecretArn: %s\nClientRequestToken: %s\n%v\n", secretArn, clientRequestToken, err))
}
- re := regexp.MustCompile(`(^arn:.*:secretsmanager:)(.*)`)
- arn := re.ReplaceAllString(*secretResp.ARN, `arn:custom:sopssync:$2`)
+ arn := generatePhysicalResourceId(*secretResp.ARN)
secretResp.ARN = &arn
log.Printf("Succesfully stored secret:\n%v\n", secretResp)
return secretResp, nil
}
+func generatePhysicalResourceId(input string) string {
+ re := regexp.MustCompile(`(^arn:.*:secretsmanager:)(.*)`)
+ return re.ReplaceAllString(input, `arn:custom:sopssync:$2`)
+}
+
func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (physicalResourceID string, data map[string]interface{}, err error) {
// event
// eventJson, _ := json.MarshalIndent(event, "", " ")
@@ -94,7 +101,7 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy
jsonResourceProps, err := json.Marshal(event.ResourceProperties)
if err != nil {
- return "", nil, err
+ return "error", nil, err
}
resourceProperties := SopsSyncResourcePropertys{}
@@ -102,16 +109,18 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy
return "", nil, err
}
+ tempArn := generatePhysicalResourceId(resourceProperties.SecretARN)
+
sopsFile := resourceProperties.SopsS3File
// This is where the magic happens
ecnryptedContent, err := a.getS3FileContent(sopsFile)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
decryptedContent, err := decryptSopsFileContent(ecnryptedContent, resourceProperties.Format)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
//log.Println(string(decryptedContent))
var decryptedInterface interface{}
@@ -120,14 +129,14 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy
{
err := json.Unmarshal(decryptedContent, &decryptedInterface)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
}
case "yaml":
{
err := yaml.Unmarshal(decryptedContent, &decryptedInterface)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
}
default:
@@ -139,7 +148,7 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy
}
resourcePropertiesFlatten, err := strconv.ParseBool(resourceProperties.Flatten)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
var finalInterface interface{}
@@ -148,7 +157,7 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy
flattenedInterface := make(map[string]interface{})
err := flatten("", decryptedInterface, flattenedInterface)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
finalInterface = flattenedInterface
} else {
@@ -160,12 +169,12 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy
}
resourcePropertiesStringifyValues, err := strconv.ParseBool(resourceProperties.StringifyValues)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
if resourcePropertiesStringifyValues {
finalInterface, _, err = stringifyValues(finalInterface)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
}
@@ -174,23 +183,23 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy
}
resourcePropertieConvertToJSON, err := strconv.ParseBool(resourceProperties.ConvertToJSON)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
if resourcePropertieConvertToJSON || resourceProperties.Format == "json" {
decryptedContent, err = toJSON(finalInterface)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
} else if resourceProperties.Format == "yaml" {
decryptedContent, err = toYAML(finalInterface)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
}
// Write the secret
- updateSecretResp, err := a.updateSecret(resourceProperties.SecretARN, decryptedContent)
+ updateSecretResp, err := a.updateSecret(sopsFile.Key, resourceProperties.SecretARN, decryptedContent)
if err != nil {
- return "", nil, err
+ return tempArn, nil, err
}
returnData := make(map[string]interface{})
@@ -204,6 +213,7 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy
} else if event.RequestType == cfn.RequestDelete {
return "", nil, nil
} else {
+ // Should never happen ...
return "", nil, errors.New(fmt.Sprintf("RequestType '%s' not supported", event.RequestType))
}
}
diff --git a/lambda/main_test.go b/lambda/main_test.go
index 4ab6bc15..d1f96b44 100644
--- a/lambda/main_test.go
+++ b/lambda/main_test.go
@@ -48,10 +48,11 @@ func Test_UpdateSecret(t *testing.T) {
t: t,
},
}
+ fileName := "4547532a137611d83958d17095c6c2d38ae0036a760c3b79c9dd5957d1c20cf2.yaml"
inputArn := "arn:${Partition}:secretsmanager:${Region}:${Account}:secret:${SecretId}"
secretValue := []byte("some-secret-data")
- response, err := mocks.updateSecret(inputArn, secretValue)
+ response, err := mocks.updateSecret(fileName, inputArn, secretValue)
check(err)
snaps.MatchSnapshot(t, response)
diff --git a/src/index.ts b/src/index.ts
index b73b1b7a..85eead42 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,15 +1,31 @@
import * as fs from 'fs';
import * as path from 'path';
+import {
+ IGrantable,
+ Grant,
+ PolicyStatement,
+ AddToResourcePolicyResult,
+} from '@aws-cdk/aws-iam';
import { IKey, Key } from '@aws-cdk/aws-kms';
import { Code, Runtime, SingletonFunction } from '@aws-cdk/aws-lambda';
import { Asset } from '@aws-cdk/aws-s3-assets';
-import { ISecret, Secret, SecretProps } from '@aws-cdk/aws-secretsmanager';
+import {
+ ISecret,
+ ISecretAttachmentTarget,
+ RotationSchedule,
+ RotationScheduleOptions,
+ Secret,
+ SecretProps,
+} from '@aws-cdk/aws-secretsmanager';
import {
Annotations,
Construct,
CustomResource,
Lazy,
+ RemovalPolicy,
+ ResourceEnvironment,
SecretValue,
+ Stack,
} from '@aws-cdk/core';
/**
@@ -230,20 +246,84 @@ export interface SopsSecretProps extends SecretProps, SopsSyncOptions {}
* A drop in replacement for the normal Secret, that is populated with the encrypted
* content of the given sops file.
*/
-export class SopsSecret extends Secret {
+export class SopsSecret extends Construct implements ISecret {
+ private readonly secret: Secret;
+ readonly encryptionKey?: IKey | undefined;
+ readonly secretArn: string;
+ readonly secretFullArn?: string | undefined;
+ readonly secretName: string;
+ readonly stack: Stack;
+ readonly env: ResourceEnvironment;
+
readonly sync: SopsSync;
public constructor(scope: Construct, id: string, props: SopsSecretProps) {
- super(scope, id, props as SecretProps);
+ super(scope, id);
+ this.secret = new Secret(this, 'Resource', props as SecretProps);
+
+ // Fullfill secret Interface
+ this.encryptionKey = this.secret.encryptionKey;
+ this.secretArn = this.secret.secretArn;
+ this.secretName = this.secret.secretName;
+ this.stack = Stack.of(scope);
+ this.env = {
+ account: this.stack.account,
+ region: this.stack.region,
+ };
+
this.sync = new SopsSync(this, 'SopsSync', {
- secret: this,
+ secret: this.secret,
...(props as SopsSyncOptions),
});
}
+ /**
+ * Returns the current versionId that was created via the SopsSync
+ */
+ public currentVersionId(): string {
+ return this.sync.versionId;
+ }
+
+ public grantRead(grantee: IGrantable, versionStages?: string[]): Grant {
+ return this.secret.grantRead(grantee, versionStages);
+ }
+ public grantWrite(grantee: IGrantable): Grant {
+ return this.secret.grantWrite(grantee);
+ }
+ public addRotationSchedule(
+ id: string,
+ options: RotationScheduleOptions,
+ ): RotationSchedule {
+ throw new Error(
+ `Method not allowed as this secret is managed by SopsSync!\nid: ${id}\noptions: ${JSON.stringify(
+ options,
+ null,
+ 2,
+ )}`,
+ );
+ }
+ public addToResourcePolicy(
+ statement: PolicyStatement,
+ ): AddToResourcePolicyResult {
+ return this.secret.addToResourcePolicy(statement);
+ }
+ public denyAccountRootDelete(): void {
+ return this.secret.denyAccountRootDelete();
+ }
+ public attach(target: ISecretAttachmentTarget): ISecret {
+ return this.secret.attach(target);
+ }
+ public applyRemovalPolicy(policy: RemovalPolicy): void {
+ return this.secret.applyRemovalPolicy(policy);
+ }
+
public secretValueFromJson(jsonField: string) {
return SecretValue.secretsManager(this.secretArn, {
jsonField,
versionId: this.sync.versionId,
});
}
+
+ public get secretValue(): SecretValue {
+ return this.secretValueFromJson('');
+ }
}
diff --git a/test/secret.integ.snapshot/SecretIntegration.assets.json b/test/secret.integ.snapshot/SecretIntegration.assets.json
index b6394111..8a6c67db 100644
--- a/test/secret.integ.snapshot/SecretIntegration.assets.json
+++ b/test/secret.integ.snapshot/SecretIntegration.assets.json
@@ -1,15 +1,15 @@
{
"version": "16.0.0",
"files": {
- "934edcf6115b2213914d0b909d6a57615b77497bc80e31d20eff033244b10a83": {
+ "8bcfb9be629c18f572586234ab20394a37fd422dc1fb7657730fadbbdf69bc36": {
"source": {
- "path": "asset.934edcf6115b2213914d0b909d6a57615b77497bc80e31d20eff033244b10a83.zip",
+ "path": "asset.8bcfb9be629c18f572586234ab20394a37fd422dc1fb7657730fadbbdf69bc36.zip",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
- "objectKey": "934edcf6115b2213914d0b909d6a57615b77497bc80e31d20eff033244b10a83.zip",
+ "objectKey": "8bcfb9be629c18f572586234ab20394a37fd422dc1fb7657730fadbbdf69bc36.zip",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
@@ -66,7 +66,7 @@
}
}
},
- "e2df3958ec05e41000a474e751c6beafa915cb92a2b6d699f4ceefdde98d611d": {
+ "06aa81c8f1b4bd04a11046c7287961ffe7a7a9372052cf5d65223b1e90e04266": {
"source": {
"path": "SecretIntegration.template.json",
"packaging": "file"
@@ -74,7 +74,7 @@
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
- "objectKey": "e2df3958ec05e41000a474e751c6beafa915cb92a2b6d699f4ceefdde98d611d.json",
+ "objectKey": "06aa81c8f1b4bd04a11046c7287961ffe7a7a9372052cf5d65223b1e90e04266.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
diff --git a/test/secret.integ.snapshot/SecretIntegration.template.json b/test/secret.integ.snapshot/SecretIntegration.template.json
index 8b5f20c2..47d9f683 100644
--- a/test/secret.integ.snapshot/SecretIntegration.template.json
+++ b/test/secret.integ.snapshot/SecretIntegration.template.json
@@ -1,6 +1,6 @@
{
"Resources": {
- "SopsSecretJSON9B18C923": {
+ "SopsSecretJSON72040543": {
"Type": "AWS::SecretsManager::Secret",
"Properties": {
"GenerateSecretString": {}
@@ -18,7 +18,7 @@
]
},
"SecretARN": {
- "Ref": "SopsSecretJSON9B18C923"
+ "Ref": "SopsSecretJSON72040543"
},
"SopsS3File": {
"Bucket": {
@@ -77,7 +77,7 @@
],
"Effect": "Allow",
"Resource": {
- "Ref": "SopsSecretJSON9B18C923"
+ "Ref": "SopsSecretJSON72040543"
}
},
{
@@ -128,7 +128,7 @@
],
"Effect": "Allow",
"Resource": {
- "Ref": "SopsSecretYAML311046EA"
+ "Ref": "SopsSecretYAMLC392F558"
}
},
{
@@ -138,7 +138,7 @@
],
"Effect": "Allow",
"Resource": {
- "Ref": "SopsSecretYAMLasJSONE90622C5"
+ "Ref": "SopsSecretYAMLasJSON64419C04"
}
},
{
@@ -148,7 +148,7 @@
],
"Effect": "Allow",
"Resource": {
- "Ref": "SopsComplexSecretJSONFA37D522"
+ "Ref": "SopsComplexSecretJSONAD4C2662"
}
},
{
@@ -158,7 +158,7 @@
],
"Effect": "Allow",
"Resource": {
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
}
},
{
@@ -168,7 +168,7 @@
],
"Effect": "Allow",
"Resource": {
- "Ref": "SopComplexSecretYAMLBAE4AFBC"
+ "Ref": "SopComplexSecretYAMLF52D88F2"
}
},
{
@@ -178,7 +178,7 @@
],
"Effect": "Allow",
"Resource": {
- "Ref": "SopComplexSecretYAMLFlatC969B78F"
+ "Ref": "SopComplexSecretYAMLFlatD9CE8782"
}
},
{
@@ -188,7 +188,7 @@
],
"Effect": "Allow",
"Resource": {
- "Ref": "SopsComplexSecretYAMLasJSON9DFE8B13"
+ "Ref": "SopsComplexSecretYAMLasJSONEAE81DB0"
}
},
{
@@ -198,7 +198,7 @@
],
"Effect": "Allow",
"Resource": {
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
}
}
],
@@ -219,7 +219,7 @@
"S3Bucket": {
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
},
- "S3Key": "934edcf6115b2213914d0b909d6a57615b77497bc80e31d20eff033244b10a83.zip"
+ "S3Key": "8bcfb9be629c18f572586234ab20394a37fd422dc1fb7657730fadbbdf69bc36.zip"
},
"Role": {
"Fn::GetAtt": [
@@ -240,7 +240,7 @@
"SingletonLambdaSopsSyncProviderServiceRoleC45BBD25"
]
},
- "SopsSecretYAML311046EA": {
+ "SopsSecretYAMLC392F558": {
"Type": "AWS::SecretsManager::Secret",
"Properties": {
"GenerateSecretString": {}
@@ -258,7 +258,7 @@
]
},
"SecretARN": {
- "Ref": "SopsSecretYAML311046EA"
+ "Ref": "SopsSecretYAMLC392F558"
},
"SopsS3File": {
"Bucket": {
@@ -274,7 +274,7 @@
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
- "SopsSecretYAMLasJSONE90622C5": {
+ "SopsSecretYAMLasJSON64419C04": {
"Type": "AWS::SecretsManager::Secret",
"Properties": {
"GenerateSecretString": {}
@@ -292,7 +292,7 @@
]
},
"SecretARN": {
- "Ref": "SopsSecretYAMLasJSONE90622C5"
+ "Ref": "SopsSecretYAMLasJSON64419C04"
},
"SopsS3File": {
"Bucket": {
@@ -308,7 +308,7 @@
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
- "SopsComplexSecretJSONFA37D522": {
+ "SopsComplexSecretJSONAD4C2662": {
"Type": "AWS::SecretsManager::Secret",
"Properties": {
"GenerateSecretString": {}
@@ -326,7 +326,7 @@
]
},
"SecretARN": {
- "Ref": "SopsComplexSecretJSONFA37D522"
+ "Ref": "SopsComplexSecretJSONAD4C2662"
},
"SopsS3File": {
"Bucket": {
@@ -342,7 +342,7 @@
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
- "SopsComplexSecretJSONFlat34CFF1D0": {
+ "SopsComplexSecretJSONFlatF5FC1D69": {
"Type": "AWS::SecretsManager::Secret",
"Properties": {
"GenerateSecretString": {}
@@ -360,7 +360,7 @@
]
},
"SecretARN": {
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
},
"SopsS3File": {
"Bucket": {
@@ -376,7 +376,7 @@
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
- "SopComplexSecretYAMLBAE4AFBC": {
+ "SopComplexSecretYAMLF52D88F2": {
"Type": "AWS::SecretsManager::Secret",
"Properties": {
"GenerateSecretString": {}
@@ -394,7 +394,7 @@
]
},
"SecretARN": {
- "Ref": "SopComplexSecretYAMLBAE4AFBC"
+ "Ref": "SopComplexSecretYAMLF52D88F2"
},
"SopsS3File": {
"Bucket": {
@@ -410,7 +410,7 @@
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
- "SopComplexSecretYAMLFlatC969B78F": {
+ "SopComplexSecretYAMLFlatD9CE8782": {
"Type": "AWS::SecretsManager::Secret",
"Properties": {
"GenerateSecretString": {}
@@ -428,7 +428,7 @@
]
},
"SecretARN": {
- "Ref": "SopComplexSecretYAMLFlatC969B78F"
+ "Ref": "SopComplexSecretYAMLFlatD9CE8782"
},
"SopsS3File": {
"Bucket": {
@@ -444,7 +444,7 @@
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
- "SopsComplexSecretYAMLasJSON9DFE8B13": {
+ "SopsComplexSecretYAMLasJSONEAE81DB0": {
"Type": "AWS::SecretsManager::Secret",
"Properties": {
"GenerateSecretString": {}
@@ -462,7 +462,7 @@
]
},
"SecretARN": {
- "Ref": "SopsComplexSecretYAMLasJSON9DFE8B13"
+ "Ref": "SopsComplexSecretYAMLasJSONEAE81DB0"
},
"SopsS3File": {
"Bucket": {
@@ -478,7 +478,7 @@
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
- "SopsComplexSecretYAMLasJSONFlatAE095DDA": {
+ "SopsComplexSecretYAMLasJSONFlat9FD04B78": {
"Type": "AWS::SecretsManager::Secret",
"Properties": {
"GenerateSecretString": {}
@@ -496,7 +496,7 @@
]
},
"SecretARN": {
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
},
"SopsS3File": {
"Bucket": {
@@ -563,7 +563,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
},
":SecretString:and now.some[0].basic::",
{
@@ -582,7 +582,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
},
":SecretString:and now.some[1].nested::",
{
@@ -601,7 +601,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
},
":SecretString:and now.some[2].type::",
{
@@ -620,7 +620,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
},
":SecretString:and now.some[3].tests::",
{
@@ -639,7 +639,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
},
":SecretString:some.deep.nested.arrays[0]::",
{
@@ -658,7 +658,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
},
":SecretString:some.deep.nested.arrays[1]::",
{
@@ -677,7 +677,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
},
":SecretString:some.deep.nested.arrays[2].values.and::",
{
@@ -696,7 +696,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
},
":SecretString:some.deep.nested.object::",
{
@@ -715,7 +715,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretJSONFlat34CFF1D0"
+ "Ref": "SopsComplexSecretJSONFlatF5FC1D69"
},
":SecretString:some.notsodeep::",
{
@@ -734,7 +734,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
},
":SecretString:and now.some[0].basic::",
{
@@ -753,7 +753,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
},
":SecretString:and now.some[1].nested::",
{
@@ -772,7 +772,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
},
":SecretString:and now.some[2].type::",
{
@@ -791,7 +791,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
},
":SecretString:and now.some[3].tests::",
{
@@ -810,7 +810,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
},
":SecretString:some.deep.nested.arrays[0]::",
{
@@ -829,7 +829,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
},
":SecretString:some.deep.nested.arrays[1]::",
{
@@ -848,7 +848,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
},
":SecretString:some.deep.nested.arrays[2].values.and::",
{
@@ -867,7 +867,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
},
":SecretString:some.deep.nested.object::",
{
@@ -886,7 +886,7 @@
[
"{{resolve:secretsmanager:",
{
- "Ref": "SopsComplexSecretYAMLasJSONFlatAE095DDA"
+ "Ref": "SopsComplexSecretYAMLasJSONFlat9FD04B78"
},
":SecretString:some.notsodeep::",
{
diff --git a/test/secret.test.ts b/test/secret.test.ts
index 3eeff96d..8278ab73 100644
--- a/test/secret.test.ts
+++ b/test/secret.test.ts
@@ -190,7 +190,7 @@ test('secretValueFromJson(...)', () => {
[
'{{resolve:secretsmanager:',
{
- Ref: 'SopsSecretBBFD4AF3',
+ Ref: 'SopsSecretF929FB43',
},
':SecretString:test::',
{