Skip to content
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

Making it compatible with RDS MSSQL Custom #12

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
68f2da2
making it compatible with AWS MSSQL Custom
dupontz Jun 27, 2024
1e110dd
adding tests
dupontz Jun 27, 2024
86b64d0
custom AWS RDS IAM profile
dupontz Jul 16, 2024
1934d2f
typo
dupontz Jul 16, 2024
d978b6a
typo
dupontz Jul 16, 2024
0138fb0
typo
dupontz Jul 16, 2024
f1c1efc
update resource name
dupontz Jul 16, 2024
bad8d80
typo
dupontz Jul 16, 2024
eabb57c
typo
dupontz Jul 16, 2024
ab05081
missing depency
dupontz Jul 16, 2024
73be170
ts
dupontz Jul 16, 2024
6a161db
ts
dupontz Jul 16, 2024
fc6a754
ts
dupontz Jul 16, 2024
4a7fb8c
ts
dupontz Jul 16, 2024
8bf6607
ts
dupontz Jul 16, 2024
5d77707
AWS required name convention
dupontz Jul 16, 2024
1bbd3b5
output instanceId
dupontz Oct 18, 2024
45a6217
retrieve ec2InstanceId when custom RDS
dupontz Oct 22, 2024
dffef23
custom resource default config
dupontz Oct 22, 2024
58c2b29
syntax
dupontz Oct 22, 2024
121f359
unecessary condition
dupontz Oct 22, 2024
e0595f4
ts
dupontz Oct 22, 2024
381e5e6
ts
dupontz Oct 23, 2024
eb2ca6e
using aws standard function (vendored)
dupontz Oct 23, 2024
11edd90
using standard request library
dupontz Oct 23, 2024
f68a3da
ts
dupontz Oct 24, 2024
6c25a28
install required packages
dupontz Oct 24, 2024
75a5bf8
ts
dupontz Oct 24, 2024
7db5c19
ts
dupontz Oct 24, 2024
ec40eb5
ts
dupontz Oct 24, 2024
42ec17d
ts
dupontz Oct 24, 2024
0829791
ts
dupontz Oct 24, 2024
c894ac5
ts
dupontz Oct 24, 2024
4757295
ts
dupontz Oct 24, 2024
3ac2a34
update ec2 atribute name
dupontz Oct 24, 2024
c385204
update ec2 atribute name
dupontz Oct 24, 2024
37705b0
ts
dupontz Oct 24, 2024
7016795
ts
dupontz Oct 24, 2024
5883fe3
ts
dupontz Oct 25, 2024
3452889
ts
dupontz Oct 25, 2024
2ffccff
making restoring snapshot compatible
dupontz Oct 29, 2024
a112fa9
typo
dupontz Oct 29, 2024
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
44 changes: 44 additions & 0 deletions lambdas/cfnresponse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright 2016 Amazon Web Services, Inc. or its affiliates. All Rights Reserved.
# This file is licensed to you under the AWS Customer Agreement (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at http://aws.amazon.com/agreement/ .
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied.
# See the License for the specific language governing permissions and limitations under the License.

import requests
import json

SUCCESS = "SUCCESS"
FAILED = "FAILED"

def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False):
responseUrl = event['ResponseURL']

print(responseUrl)

responseBody = {}
responseBody['Status'] = responseStatus
responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name
responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name
responseBody['StackId'] = event['StackId']
responseBody['RequestId'] = event['RequestId']
responseBody['LogicalResourceId'] = event['LogicalResourceId']
responseBody['NoEcho'] = noEcho
responseBody['Data'] = responseData

json_responseBody = json.dumps(responseBody)

print("Response body:\n" + json_responseBody)

headers = {
'content-type' : '',
'content-length' : str(len(json_responseBody))
}

try:
response = requests.put(responseUrl,
data=json_responseBody,
headers=headers)
print("Status code: " + response.reason)
except Exception as e:
print("send(..) failed executing requests.put(..): " + str(e))
11 changes: 11 additions & 0 deletions lambdas/package.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

if [ "$(which docker)" == "" ]; then
if [ "$(which pip)" == "" ]; then
echo "You need either PIP or DOCKER to package Sns Slack notifier"
exit -1
fi
pip install -r requirements.txt -t .
else
docker run --rm -v $PWD:/src -w /src python:3.11-alpine pip install -r requirements.txt -t /src
fi
1 change: 1 addition & 0 deletions lambdas/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requests==2.32.3
73 changes: 73 additions & 0 deletions lambdas/target_group_from_rds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import os
import sys
import json
import logging
import boto3

sys.path.append(f"{os.environ['LAMBDA_TASK_ROOT']}/lib")
sys.path.append(os.path.dirname(os.path.realpath(__file__)))

import cfnresponse

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def get_instance_id_by_name(instance_name):
# Create an EC2 client
print(f"instance_name: {instance_name}")
ec2 = boto3.client('ec2')

# Describe instances with filters for the 'Name' tag
response = ec2.describe_instances(
Filters=[
{
'Name': 'tag:Name',
'Values': [instance_name]
}
]
)

# Extract instance ID(s)
instances = response['Reservations']
instance_ids = []

for reservation in instances:
for instance in reservation['Instances']:
instance_ids.append(instance['InstanceId'])

if not instance_ids:
print(f"No instances found with name {instance_name}")
return None

if len(instance_ids) > 1:
print(f"More than one instance found with name {instance_name}")
return None

return instance_ids[0]

def lambda_handler(event, context):
logger.info('got event {}'.format(event))
try:
responseData = {}
if event['RequestType'] == 'Delete':
logger.info('Incoming RequestType: Delete operation')
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
if event['RequestType'] in ["Create", "Update"]:
ResourceRef=event['ResourceProperties']['RDSInstanceId']
ec2_name = 'do-not-delete-rds-custom-' + ResourceRef
response = get_instance_id_by_name(ec2_name)

logger.info(f'found instance:{response}')

responseData = {}
responseData['Ec2InstanceId'] = response
logger.info('Retrieved Ec2InstanceId! ')
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
else:
logger.info('Unexpected RequestType!')
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
except Exception as err:
logger.error(err)
responseData = {"Data": str(err)}
cfnresponse.send(event,context,cfnresponse.FAILED,responseData)
return
5 changes: 4 additions & 1 deletion rds-mssql.cfhighlander.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Description "#{component_name} - #{component_version}"

ComponentVersion component_version

DependsOn 'vpc'
Parameters do
ComponentParam 'VPCId', type: 'AWS::EC2::VPC::Id'
ComponentParam 'StackOctet', isGlobal: true
Expand All @@ -18,5 +18,8 @@
ComponentParam 'SubnetIds', type: 'CommaDelimitedList'
ComponentParam 'DatabaseBucket' if defined?(native_backup_restore) and native_backup_restore
end

LambdaFunctions 'tg_custom_resources' unless disable_custom_resources

end

75 changes: 61 additions & 14 deletions rds-mssql.cfndsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

Description "#{component_name} - #{component_version}"

Condition("UseUsernameAndPassword", FnEquals(Ref(:RDSSnapshotID), ''))

tags = []
tags << { Key: 'Environment', Value: Ref(:EnvironmentName) }
tags << { Key: 'EnvironmentType', Value: Ref(:EnvironmentType) }

extra_tags.each { |key,value| tags << { Key: key, Value: value } } if defined? extra_tags



ingress = []
security_group_rules.each do |rule|
sg_rule = {
Expand Down Expand Up @@ -51,11 +55,33 @@
Tags tags + [{ Key: 'Name', Value: FnJoin('-', [ Ref(:EnvironmentName), component_name, 'subnet-group' ])}]
end

RDS_DBParameterGroup 'ParametersRDS' do
Description FnJoin(' ', [ Ref(:EnvironmentName), component_name, 'parameter group' ])
Family family
Parameters parameters if defined? parameters
Tags tags + [{ Key: 'Name', Value: FnJoin('-', [ Ref(:EnvironmentName), component_name, 'parameter-group' ])}]
if !engine.include? "custom"
RDS_DBParameterGroup 'ParametersRDS' do
Description FnJoin(' ', [ Ref(:EnvironmentName), component_name, 'parameter group' ])
Family family
Parameters parameters if defined? parameters
Tags tags + [{ Key: 'Name', Value: FnJoin('-', [ Ref(:EnvironmentName), component_name, 'parameter-group' ])}]
end
else
policies = []
iam_policies.each do |name,policy|
policies << iam_policy_allow(name,policy['action'],policy['resource'] || '*')
end if defined? iam_policies

managed_iam_policies = external_parameters.fetch(:managed_iam_policies, [])

Role('Role') do
RoleName FnSub("AWSRDSCustom-${EnvironmentName}-#{external_parameters[:component_name]}")
AssumeRolePolicyDocument service_role_assume_policy(iam_services)
Path '/'
ManagedPolicyArns managed_iam_policies if managed_iam_policies.any?
end

IAM_InstanceProfile('InstanceProfile') do
InstanceProfileName FnSub("AWSRDSCustom-${EnvironmentName}-#{external_parameters[:component_name]}")
Path '/'
Roles [Ref('Role')]
end
end

if defined?(native_backup_restore) and native_backup_restore
Expand Down Expand Up @@ -124,14 +150,15 @@
RDS_DBInstance 'RDS' do
AllowMajorVersionUpgrade allow_major_version_upgrade unless allow_major_version_upgrade.nil?
DeletionPolicy deletion_policy if defined? deletion_policy
CustomIAMInstanceProfile Ref('InstanceProfile') if engine.include?("custom")
DBInstanceClass Ref('RDSInstanceType')
AllocatedStorage Ref('RDSAllocatedStorage')
StorageType 'gp2'
Engine engine
EngineVersion engine_version
DBParameterGroupName Ref('ParametersRDS')
MasterUsername instance_username
MasterUserPassword instance_password
DBParameterGroupName Ref('ParametersRDS') if !engine.include?("custom")
MasterUsername FnIf('UseUsernameAndPassword', instance_username, Ref('AWS::NoValue'))
MasterUserPassword FnIf('UseUsernameAndPassword', instance_password, Ref('AWS::NoValue'))
DBSnapshotIdentifier Ref('RDSSnapshotID')
DBSubnetGroupName Ref('SubnetGroupRDS')
VPCSecurityGroups [Ref('SecurityGroupRDS')]
Expand Down Expand Up @@ -160,14 +187,34 @@
KmsKeyId kms_key_id if (defined? kms_key_id) && (storage_encrypted == true)
end

Output(:InstanceId) {
Value(Ref(:RDS))
Export FnSub("${EnvironmentName}-#{external_parameters[:component_name]}-InstanceId")
}

record = defined?(dns_record) ? dns_record : 'mssql'

Route53_RecordSet('DatabaseIntHostRecord') do
HostedZoneName FnJoin('', [ Ref('EnvironmentName'), '.', Ref('DnsDomain'), '.'])
Name FnJoin('', [ record, '.', Ref('EnvironmentName'), '.', Ref('DnsDomain'), '.' ])
Type 'CNAME'
TTL 60
ResourceRecords [ FnGetAtt('RDS','Endpoint.Address') ]
if engine.include?("custom")

Resource("CustomEc2InstanceId") do
Type 'Custom::RegisterTG'
Property 'ServiceToken',FnGetAtt('RdsTargetGroupCR','Arn')
Property 'AwsRegion', Ref('AWS::Region')
Property 'RDSInstanceId',Ref(:RDS)
end unless disable_custom_resources

Route53_RecordSet('DatabaseIntHostRecord') do
HostedZoneName FnJoin('', [ Ref('EnvironmentName'), '.', Ref('DnsDomain'), '.'])
Name FnJoin('', [ record, '.', Ref('EnvironmentName'), '.', Ref('DnsDomain'), '.' ])
Type 'CNAME'
TTL 60
ResourceRecords [ FnGetAtt('RDS','Endpoint.Address') ]
end

Output(:Ec2InstanceId) {
Value(FnGetAtt( 'CustomEc2InstanceId','Ec2InstanceId') )
Export FnSub("${EnvironmentName}-#{external_parameters[:component_name]}-Ec2InstanceId")
}
end

end
36 changes: 35 additions & 1 deletion rds-mssql.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,38 @@ storage_encrypted: false
#backup_retention_period: 10

#parameter to allow major version upgrades
#allow_major_version_upgrade: true
#allow_major_version_upgrade: true

disable_custom_resources: false

tg_custom_resources:
custom_policies:
ec2-tg:
action:
- ec2:*
resource: '*'
opsdns:
action:
- sts:AssumeRole
resource:
- Fn::If:
- RemoteNSRecords
- Ref: ParentIAMRole
- arn:aws:iam::123456789012:user/noaccess
roles:
TGResource:
policies_inline:
- cloudwatch-logs
- ec2-tg

functions:
RdsTargetGroupCR:
code: target_group_from_rds.py
handler: target_group_from_rds.lambda_handler
runtime: python3.11
timeout: 600
role: TGResource
environment:
ENVIRONMENT_NAME:
Ref: EnvironmentName
package_cmd: './package.sh'
11 changes: 11 additions & 0 deletions tests/mssql-custom-se.test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
test_metadata:
type: config
name: custom-sqlserver-se
description: Create RDS custom mssql se

# CLI to query engine and family: aws rds describe-db-engine-versions
engine: 'custom-sqlserver-se'
engine_version: '16.00.4085.2.v1'
family: 'custom-sqlserver-se-16.0'

storage_encrypted: true