Skip to content

Cloudformation support for Secrets Manager Integration #164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions aws-redshift-cluster/aws-redshift-cluster.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"maxLength": 128
},
"MasterUserPassword": {
"description": "The password associated with the master user account for the cluster that is being created. Password must be between 8 and 64 characters in length, should have at least one uppercase letter.Must contain at least one lowercase letter.Must contain one number.Can be any printable ASCII character.",
"description": "The password associated with the master user account for the cluster that is being created. You can't use MasterUserPassword if ManageMasterPassword is true. Password must be between 8 and 64 characters in length, should have at least one uppercase letter.Must contain at least one lowercase letter.Must contain one number.Can be any printable ASCII character.",
"type": "string",
"maxLength": 64
},
Expand Down Expand Up @@ -281,6 +281,18 @@
"NamespaceResourcePolicy": {
"description": "The namespace resource policy document that will be attached to a Redshift cluster.",
"type": "object"
},
"ManageMasterPassword": {
"description": "A boolean indicating if the redshift cluster's admin user credentials is managed by Redshift or not. You can't use MasterUserPassword if ManageMasterPassword is true. If ManageMasterPassword is false or not set, Amazon Redshift uses MasterUserPassword for the admin user account's password.",
"type": "boolean"
},
"MasterPasswordSecretKmsKeyId": {
"description": "The ID of the Key Management Service (KMS) key used to encrypt and store the cluster's admin user credentials secret.",
"type": "string"
},
"MasterPasswordSecretArn": {
"description": "The Amazon Resource Name (ARN) for the cluster's admin user credentials secret.",
"type": "string"
}
},
"additionalProperties": false,
Expand All @@ -297,7 +309,8 @@
"/properties/DeferMaintenanceIdentifier",
"/properties/Endpoint/Port",
"/properties/Endpoint/Address",
"/properties/ClusterNamespaceArn"
"/properties/ClusterNamespaceArn",
"/properties/MasterPasswordSecretArn"
],
"createOnlyProperties": [
"/properties/ClusterIdentifier",
Expand All @@ -313,7 +326,8 @@
"/properties/Classic",
"/properties/SnapshotIdentifier",
"/properties/DeferMaintenance",
"/properties/DeferMaintenanceDuration"
"/properties/DeferMaintenanceDuration",
"/properties/ManageMasterPassword"
],
"tagging": {
"taggable": true
Expand Down
32 changes: 30 additions & 2 deletions aws-redshift-cluster/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ To declare this entity in your AWS CloudFormation template, use the following sy
"<a href="#resourceaction" title="ResourceAction">ResourceAction</a>" : <i>String</i>,
"<a href="#rotateencryptionkey" title="RotateEncryptionKey">RotateEncryptionKey</a>" : <i>Boolean</i>,
"<a href="#multiaz" title="MultiAZ">MultiAZ</a>" : <i>Boolean</i>,
"<a href="#namespaceresourcepolicy" title="NamespaceResourcePolicy">NamespaceResourcePolicy</a>" : <i>Map</i>
"<a href="#namespaceresourcepolicy" title="NamespaceResourcePolicy">NamespaceResourcePolicy</a>" : <i>Map</i>,
"<a href="#managemasterpassword" title="ManageMasterPassword">ManageMasterPassword</a>" : <i>Boolean</i>,
"<a href="#masterpasswordsecretkmskeyid" title="MasterPasswordSecretKmsKeyId">MasterPasswordSecretKmsKeyId</a>" : <i>String</i>,
}
}
</pre>
Expand Down Expand Up @@ -125,6 +127,8 @@ Properties:
<a href="#rotateencryptionkey" title="RotateEncryptionKey">RotateEncryptionKey</a>: <i>Boolean</i>
<a href="#multiaz" title="MultiAZ">MultiAZ</a>: <i>Boolean</i>
<a href="#namespaceresourcepolicy" title="NamespaceResourcePolicy">NamespaceResourcePolicy</a>: <i>Map</i>
<a href="#managemasterpassword" title="ManageMasterPassword">ManageMasterPassword</a>: <i>Boolean</i>
<a href="#masterpasswordsecretkmskeyid" title="MasterPasswordSecretKmsKeyId">MasterPasswordSecretKmsKeyId</a>: <i>String</i>
</pre>

## Properties
Expand Down Expand Up @@ -155,7 +159,7 @@ _Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/l

#### MasterUserPassword

The password associated with the master user account for the cluster that is being created. Password must be between 8 and 64 characters in length, should have at least one uppercase letter.Must contain at least one lowercase letter.Must contain one number.Can be any printable ASCII character.
The password associated with the master user account for the cluster that is being created. You can't use MasterUserPassword if ManageMasterPassword is true. Password must be between 8 and 64 characters in length, should have at least one uppercase letter.Must contain at least one lowercase letter.Must contain one number.Can be any printable ASCII character.

_Required_: No

Expand Down Expand Up @@ -646,6 +650,26 @@ _Type_: Map

_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)

#### ManageMasterPassword

A boolean indicating if the redshift cluster's admin user credentials is managed by Redshift or not. You can't use MasterUserPassword if ManageMasterPassword is true. If ManageMasterPassword is false or not set, Amazon Redshift uses MasterUserPassword for the admin user account's password.

_Required_: No

_Type_: Boolean

_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)

#### MasterPasswordSecretKmsKeyId

The ID of the Key Management Service (KMS) key used to encrypt and store the cluster's admin user credentials secret.

_Required_: No

_Type_: String

_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)

## Return Values

### Ref
Expand Down Expand Up @@ -673,3 +697,7 @@ Returns the <code>Address</code> value.
#### ClusterNamespaceArn

The Amazon Resource Name (ARN) of the cluster namespace.

#### MasterPasswordSecretArn

The Amazon Resource Name (ARN) for the cluster's admin user credentials secret.
2 changes: 1 addition & 1 deletion aws-redshift-cluster/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>redshift</artifactId>
<version>2.21.37</version>
<version>2.21.44</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.cloudformation/aws-cloudformation-rpdk-java-plugin -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.amazonaws.util.CollectionUtils;
import com.amazonaws.util.StringUtils;
import com.google.common.collect.Sets;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import software.amazon.awssdk.services.redshift.RedshiftClient;
import software.amazon.awssdk.services.redshift.model.AquaConfiguration;
Expand Down Expand Up @@ -204,10 +206,16 @@ protected boolean issueModifyClusterMaintenanceRequest(ResourceModel model) {
ObjectUtils.allNotNull(model.getDeferMaintenanceIdentifier());
}

// check for required parameters to not have null values
protected boolean invalidCreateClusterRequest(ResourceModel model) {
return model.getClusterIdentifier() == null || model.getNodeType() == null
|| model.getMasterUsername() == null || model.getMasterUserPassword() == null;
// check for required parameters to not have null values
boolean isInvalid = model.getClusterIdentifier() == null || model.getNodeType() == null
|| model.getMasterUsername() == null;

// check if either MasterUserPassword is provided or ManageMasterPassword is true
if (model.getMasterUserPassword() == null) {
return !BooleanUtils.isTrue(model.getManageMasterPassword()) || isInvalid;
}
return isInvalid;
}

protected boolean issueModifyClusterParameterGroupRequest(ResourceModel prevModel, ResourceModel model) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ static CreateClusterRequest translateToCreateRequest(final ResourceModel model,
.enhancedVpcRouting(model.getEnhancedVpcRouting())
.maintenanceTrackName(model.getMaintenanceTrackName())
.multiAZ(model.getMultiAZ())
.manageMasterPassword(model.getManageMasterPassword())
.masterPasswordSecretKmsKeyId(model.getMasterPasswordSecretKmsKeyId())
.build();
}

Expand Down Expand Up @@ -593,6 +595,17 @@ static ResourceModel translateFromReadResponse(final DescribeClustersResponse aw
.findAny()
.orElse(null);

final String masterPasswordSecretArn = streamOfOrEmpty(awsResponse.clusters())
.map(software.amazon.awssdk.services.redshift.model.Cluster::masterPasswordSecretArn)
.filter(Objects::nonNull)
.findAny()
.orElse(null);

final String masterPasswordSecretKmsKeyId = streamOfOrEmpty(awsResponse.clusters())
.map(software.amazon.awssdk.services.redshift.model.Cluster::masterPasswordSecretKmsKeyId)
.filter(Objects::nonNull)
.findAny()
.orElse(null);

return ResourceModel.builder()
.clusterIdentifier(clusterIdentifier)
Expand Down Expand Up @@ -634,6 +647,8 @@ static ResourceModel translateFromReadResponse(final DescribeClustersResponse aw
.deferMaintenanceIdentifier(translateDeferMaintenanceIdentifierFromSdk(deferMaintenanceWindows))
.deferMaintenanceStartTime(translateDeferMaintenanceStartTimeFromSdk(deferMaintenanceWindows))
.deferMaintenanceEndTime(translateDeferMaintenanceEndTimeFromSdk(deferMaintenanceWindows))
.masterPasswordSecretArn(masterPasswordSecretArn)
.masterPasswordSecretKmsKeyId(masterPasswordSecretKmsKeyId)
.build();
}

Expand Down Expand Up @@ -683,7 +698,7 @@ static String finalClusterSnapshotIdentifierBuilder(String clusterIdentifier, bo
static ModifyClusterRequest translateToUpdateRequest(final ResourceModel model, final ResourceModel prevModel) {
ModifyClusterRequest modifyClusterRequest = ModifyClusterRequest.builder()
.clusterIdentifier(model.getClusterIdentifier())
.masterUserPassword(model.getMasterUserPassword().equals(prevModel.getMasterUserPassword()) ? null : model.getMasterUserPassword())
.masterUserPassword(model.getMasterUserPassword() == null || model.getMasterUserPassword().equals(prevModel.getMasterUserPassword()) ? null : model.getMasterUserPassword())
.allowVersionUpgrade(model.getAllowVersionUpgrade() == null || model.getAllowVersionUpgrade().equals(prevModel.getAllowVersionUpgrade()) ? null : model.getAllowVersionUpgrade())
.automatedSnapshotRetentionPeriod(model.getAutomatedSnapshotRetentionPeriod() == null || model.getAutomatedSnapshotRetentionPeriod().equals(prevModel.getAutomatedSnapshotRetentionPeriod()) ? null : model.getAutomatedSnapshotRetentionPeriod())
//.clusterParameterGroupName(model.getClusterParameterGroupName() == null || model.getClusterParameterGroupName().equals(prevModel.getClusterParameterGroupName()) ? null : model.getClusterParameterGroupName())
Expand All @@ -706,6 +721,8 @@ static ModifyClusterRequest translateToUpdateRequest(final ResourceModel model,
.maintenanceTrackName(model.getMaintenanceTrackName() == null || model.getMaintenanceTrackName().equals(prevModel.getMaintenanceTrackName()) ? null : model.getMaintenanceTrackName())
.enhancedVpcRouting(model.getEnhancedVpcRouting() == null || model.getEnhancedVpcRouting().equals(prevModel.getEnhancedVpcRouting()) ? null : model.getEnhancedVpcRouting())
.multiAZ(model.getMultiAZ() == null || model.getMultiAZ().equals(prevModel.getMultiAZ()) ? null : model.getMultiAZ())
.manageMasterPassword(model.getManageMasterPassword())
.masterPasswordSecretKmsKeyId(model.getMasterPasswordSecretKmsKeyId() == null || model.getMasterPasswordSecretKmsKeyId().equals(prevModel.getMasterPasswordSecretKmsKeyId()) ? null : model.getMasterPasswordSecretKmsKeyId())
.build();

return modifyClusterRequest;
Expand Down Expand Up @@ -877,6 +894,8 @@ static RestoreFromClusterSnapshotRequest translateToRestoreFromClusterSnapshotRe
.numberOfNodes(model.getNumberOfNodes())
.encrypted(model.getEncrypted())
.multiAZ(model.getMultiAZ())
.manageMasterPassword(model.getManageMasterPassword())
.masterPasswordSecretKmsKeyId(model.getMasterPasswordSecretKmsKeyId())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ public class UpdateHandler extends BaseHandlerStd {
"PreferredMaintenanceWindow",
"PubliclyAccessible",
"VpcSecurityGroupIds",
"MultiAZ"
"MultiAZ",
"ManageMasterPassword",
"MasterPasswordSecretKmsKeyId"
};
public static final String[] DETECTABLE_MODIFY_CLUSTER_ATTRIBUTES_SENSITIVE = new String[] {
"MasterUserPassword"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class AbstractTestBase {
protected static final String AWS_PARTITION;
protected static final String AWS_ACCOUNT_ID;
protected static final String CLUSTER_IDENTIFIER;
protected static final String SNAPSHOT_IDENTIFIER;
protected static final String CLUSTER_NAMESPACE_UUID;
protected static final String MASTER_USERNAME;
protected static final String MASTER_USERPASSWORD;
Expand Down Expand Up @@ -65,6 +66,7 @@ public class AbstractTestBase {
DEFER_MAINTENANCE_IDENTIFIER = "cfn-defer-maintenance-identifier";
DEFER_MAINTENANCE_START_TIME = "2023-12-10T00:00:00Z";
DEFER_MAINTENANCE_END_TIME = "2024-01-19T00:00:00Z";
SNAPSHOT_IDENTIFIER = "redshift-cluster-1-snapshot";

RESOURCE_POLICY = ResourcePolicy.builder()
.resourceArn(CLUSTER_NAMESPACE_ARN)
Expand Down Expand Up @@ -181,6 +183,12 @@ public static ResourceModel createClusterRequestModel() {
.build();
}

public static ResourceModel restoreClusterRequestModel() {
return createClusterRequestModel().toBuilder()
.snapshotIdentifier(SNAPSHOT_IDENTIFIER)
.build();
}

public static ResourceModel createClusterResponseModel() {
return ResourceModel.builder()
.clusterIdentifier(CLUSTER_IDENTIFIER)
Expand Down
Loading