Package: cdk-sops-secrets
IntroductionThis construct library provides a replacement for CDK SecretsManager secrets, with extended functionality for Mozilla/sops.Using this library it is possible to populate Secrets with values from a Mozilla/sops file without additional scripts and steps in the CI stage. Thereby transformations like JSON conversion of YAML files and transformation into a flat, JSONPath like structure will be performed, but can be disabled.Secrets filled in this way can be used immediately within the CloudFormation stack and dynamic references. This construct should handle all dependencies, if you use thesecretValueFromJson()orsecretValue()call to access secret values.This way, secrets can be securely stored in git repositories and easily synchronized into AWS SecretsManager secrets.StabilityYou can consider this package as stable. Updates will followSemantic Versioning.Nevertheless, I would recommend pinning the exact version of this library in yourpackage.json.PrerequisitesAWS: I think you already knew it, but this construct will only work with an AWS account.KMS Key: It makes most sense to encrypt your secrets with AWS KMS if you want to sync and use the secret content afterwards in your AWS account.mozilla/sops: This construct assumes that you store your secrets encrypted via sops in your git repository.CDK: As this is a CDK construct, it's only useful if you use the CloudDevelopmentToolkit.Getting startedCreate a Mozilla/sops secrets file (encrypted with an already existing KMS key) and place it somewhere in your git repositoryCreate a secret with the SopsSecret construct inside your appconstsecret=newSopsSecret(stack,'SopsComplexSecretJSON',{sopsFilePath:'secets/sopsfile-encrypted.json',});Optional: Access the secret via dynamic referencessecret.secretValueFromJson('json.path.dotted.notation.accessor[0]').toString(),Advanced configuration examplesEven if using the main functionality should be done in 3 lines of code, there are more options to configure the constructs of this library. If you want to get an Overview of all available configuration options take a look at thedocumentation at the CDK ConstructHub.The most useful settings will be explained in the further chapters:Getting a specific (older version)While creating the secret or updating the entries of a secret, the native CDK functioncdk.FileSystem.fingerprint(...)is used to generate the version information of the AWS SecretsManager secret.
Therefore, it is possible to reference the entries from a specific AWS SecretsManager version.constversionId=cdk.FileSystem.fingerprint(`./sops/SomeSecrets.json`)constpassphrase=ecs.Secret.fromSecretsManagerVersion(secretMgmt,{versionId:versionId},'MY_PRIVATE_PASSPHRASE')constcontainer=TaskDef.addContainer('Container',{secrets:{MY_PRIVATE_PASSPHRASE:passphrase,},});Default conversions and how to disable them?As default behavior, the SopsSecret (via the SopsSync) will convert all content to JSON and flatten its structure. This is useful, because the AWS SecretsManager has some limitations if it comes to YAML and/or complex objects and decimal values. Even if you can store YAML, complex objects and even binaries in AWS SecretsManager secrets, you can't access their values via the SecretsManager API — you can only return them as is. So accessing (nested) values or values from YAML files won't be possible viadynamic referencesin CloudFormation (and CDK). That's why I decided that conversion to JSON, flatten the structure and stringify all values should be the default behavior. But you can turn off all of these conversion steps:constsecret=newSopsSecret(this,'SopsComplexSecretJSON',{convertToJSON:false,//disableconvertingtheencryptedcontenttoJSONstringify:false,//disablestringifyingallvaluesflatten:false,//disableflatteningoftheobjectstructuresopsFilePath:'secrets/sopsfile-encrypted.json',});Resource provider is missing permissionsSometimes it can be necessary to access the IAM role of the SopsSync provider. If this is the case, you should create the provider before creating the SopsSecret, and pass the provider to it like this://Createtheproviderconstprovider=newSopsSyncProvider(this,'CustomSopsSyncProvider');//GrantwhateveryouneedtotheproviderconstmyExtraKmsKey=Key.fromKeyArn(this,'MyExtraKmsKey','YourKeyArn');myExtraKmsKey.grantDecrypt(provider);//createthesecretandpassthetheprovidertoitconstsecret=newSopsSecret(this,'SopsComplexSecretJSON',{sopsProvider:provider,secretName:'myCoolSecret',sopsFilePath:'secrets/sopsfile-encrypted.json',});UploadType: INLINE / ASSETI decided, that the default behavior should be "INLINE" because of the following consideration:Fewer permissions: If we use inline content instead of a S3 asset, the SopsSyncProvider does not need permissions to access the asset bucket and its KMS key.Faster: If we don't have to upload and download things from and to S3, it should be a little faster.Interchangeable: As we use the same information to generate the version of the secret, no new version of the secret should be created, if you change from INLINE to ASSET or vice versa, even if the CloudFormation resource updates.I personally think sops files are not that big, that we should run into limits, but if so — we can change to assetuploadType.You can change the uplaodType via the properties:constsecret=newSopsSecret(this,'SopsWithAssetUpload',{sopsFilePath:'secrets/sopsfile-encrypted.json',uploadType:UploadType.ASSET,//insteadofthedefaultUploadType.INLINE});FAQIt does not work, what can I do?Even if this construct has some unit and integration tests performed, there can be bugs and issues. As everything is performed by a cloudformation custom resource provider, a good starting point is the log of the corresponding lambda function. It should be located in your AWS Account under Cloudwatch -> Log groups:/aws/lambda/<YOUR-STACK-NAME>-SingletonLambdaSopsSyncProvider<SOMETHINGsomething1234>Error getting data key: 0 successful groups required, got 0This error message (and failed sync) is related to the  mozilla/sops issues#948and#634. You must not create your secret with the--aws-profileflag. This profile will be written to your sops filed and is required in every runtime environment. You have to define the profile to use via the environment variableAWS_PROFILEinstead, to avoid this.Asset of sync lambda not foundThe lambda asset code is generated relative to the path of the index.ts in this package. With tools like nx this can lead to wrong results, so that the asset could not be found.You can override the asset path via thecdk.jsonor via the flag-cof the cdk cli.The context used for this override issops_sync_provider_asset_path.So for example you can usecdkdeploy-c"sops_sync_provider_asset_path=some/path/asset.zip"or in your cdk.json{"context":{"sops_sync_provider_asset_path":"some/path/asset.zip"}}MotivationI have created this project to solve a recurring problem of syncing Mozilla/sops secrets into AWS SecretsManager in a convenient, secure way.Other than that, or perhaps more importantly, my goal was to learn new things:Write a Golang lambdaWriting unit tests incl. mocks in GolangReproducible builds of Golang binaries (byte-by-byte identical)Build reproducible zips (byte-by-byte identical)Release a NPM packageSetting up projects with projenCI/CD with GitHub actionsCDK unit and integration testsOther Tools like thisThe problem this Construct addresses is so good, already two other implementations exist: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: Different approach on the same problem. This is a CLI tool with very nice integration into CDK and also handles git versioning information.LicenseThe Apache-2.0 license. Please have a look at theLICENSEandLICENSE-3RD-PARTY.