Skip to content

Commit ddd7190

Browse files
committed
v1.1.0 - KMS Support
1 parent d8d71df commit ddd7190

10 files changed

Lines changed: 163 additions & 58 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ __pycache__
55
.venv
66
*.egg-info
77
.vscode
8+
.DS_Store
89

910
# CDK asset staging directory
1011
.cdk.staging
1112
cdk.out
1213
auth
13-
transfer_sync_service/lambda/boto3_lambda_layer/*
14+
transfer_sync_service/lambda/boto3_*/*
1415
transfer_sync_service/lambda/sync_files/*
1516
!transfer_sync_service/lambda/sync_files/sync_files.py

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## 1.1.0 - 2024-10-25
6+
### Added features
7+
- Added the ability to define an optional KMS Key for the target buckets in the configuration files. This allows you to set up a default encryption with KMS on the target buckets.
8+
- Added KMS Encryption for the reporting bucket with a new dedicated key.
9+
- Added the ability to define Permission Boundaries for all the created roles.
10+
11+
### Changed
12+
- Modified `transfer_sync_service_stack` and how the lambda layer is created for boto3.
13+
- Centralized all the solution parameters in `configuration/solution_parameters/parameters.json`.
14+
- Updated CLI to support the new KMS Configuration.
15+
16+
### Dependencies
17+
- Bump lambda-powertools from 2.40.1 to 3.2.0.
18+
- Bump boto3 from 1.34.134 to 1.35.47 for lambda layers and environment.
19+
- Bump cdk-monitoring-constructs from 8.1.0 to 8.3.2.
20+
521
## 1.0.2 - 2024-09-25
622

723
### Added features

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ A combination of Lambda, Step Functions and Transfer Family features facilitates
4545
- The connector uses the configured security policy, trusted public keys and secrets stored in AWS Secrets Manager to ensure secure communication with the remote server.
4646

4747
4. **S3 Buckets**
48-
- Only one bucket is created by this solution to store the results generated by the Transfer Family SFTP Connector when Listing the remote SFTP directories.
48+
- Only one bucket is created by this solution to store the results generated by the Transfer Family SFTP Connector when Listing the remote SFTP directories. This bucket is encrypted using a KMS Customer managed keys created by the solution.
4949
- The solution can use as many S3 Buckets as needed for a target for the Transfer Family SFTP Connector Sync process, when the files are copied from the remote SFTP to local the local S3 Bucket. These S3 Buckets are defined in the Configuration Files.
5050

5151
5. **AWS Secrets Manager**
@@ -81,7 +81,8 @@ The configuration file structure and content needs the following data:
8181
{
8282
"LocalRepository": {
8383
"BucketName": <Local Bucket Name>,
84-
"Prefix": <Local Prefix>
84+
"Prefix": <Local Prefix>,
85+
(OPTIONAL) "KmsKeyArn": <KMS Key ARN used for the Bucket default encryption configuration>
8586
},
8687
"RemoteFolders": {
8788
"Folder": <Remote Folder to Sync>,
@@ -118,6 +119,13 @@ For the Cron expression, you can use any of the pre-defined TAGs for simplicity
118119
| @saturday | 0 0 ? * 7 * |
119120
| @every10min | 0/10 * * * ? * |
120121

122+
### Target Bucket KMS Encryption
123+
124+
The solution supports target S3 Buckets that use [server-side encryption with AWS KMS (SSE-KMS)](https://docs.aws.amazon.com/AmazonS3/latest/userguide/specifying-kms-encryption.html).
125+
If your target S3 Bucket is encrypted using KMS, you must specify the ARN of the KMS Key used for encryption in your configuration file under: `SyncSettings > LocalRepository > KmsKeyArn`
126+
127+
**Note:** The `KmsKeyArn` parameter is optional. **Only include it if your target bucket uses KMS encryption.**
128+
121129
### Replaceable Tags in Remote Folder Paths
122130

123131
The solution supports the use of replaceable tags in the remote folder paths. This feature allows for dynamic folder selection based on the current date (in UTC). The following tags are available:
@@ -184,6 +192,19 @@ This project is built using Python3 and CDK, before you start, make sure to have
184192
* Python venv
185193

186194
## Deployment
195+
196+
### Permission Boundaries
197+
If you are enforcing the usage of IAM Permission Boundaries for IAM Roles created in the account, you can update the [solution parameters file](configuration/solution_parameters/parameters.json) and add the managed policy ARN you are using to the `permission_boundary_policy_arn` parameter.
198+
199+
**Note:**
200+
- This step is optional. Only add this parameter if you're enforcing IAM Permission Boundaries in your account.
201+
- Ensure you have the correct ARN for your permission boundary policy.
202+
- If you're not using permission boundaries, you can omit this parameter or leave it as an empty string.
203+
204+
By setting this parameter, all IAM roles created by this solution will adhere to the specified permission boundary, enhancing your security posture and compliance with organizational policies.
205+
206+
### Step-by-Step deployment
207+
187208
Deploying the solution is easy, you just need to
188209

189210
MacOS and Linux:

cli.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,17 @@ def validate_schedule(answers, current):
8989

9090
raise errors.ValidationError('', reason='Invalid schedule. Use predefined tags or a valid AWS Cron expression.')
9191

92+
def validate_kms_key_arn(answers, current):
93+
if not current:
94+
return True
95+
kms_arn_pattern = r'^arn:aws:kms:[a-z0-9-]+:\d{12}:key/[a-f0-9-]{36}$'
96+
if not re.match(kms_arn_pattern, current):
97+
raise errors.ValidationError('', reason='Invalid KMS Key ARN format. Please enter a valid ARN.')
98+
return True
99+
92100
def edit_sync_settings(sync_settings):
93101
while True:
94-
choices = [f"{s['LocalRepository']['BucketName']} -> {s['RemoteFolders']['Folder']}" for s in sync_settings]
102+
choices = [f"{s['LocalRepository']['BucketName']} -> {s['RemoteFolders']['Folder']} (KMS: {s['LocalRepository'].get('KmsKeyArn', 'None')})" for s in sync_settings]
95103
choices.append("Add new sync setting")
96104
choices.append("Finish editing")
97105

@@ -120,18 +128,21 @@ def edit_sync_settings(sync_settings):
120128

121129
def prompt_sync_setting(existing=None):
122130
questions = [
123-
inquirer.Text('bucket_name', message="Enter BucketName",
131+
inquirer.Text('bucket_name', message="Enter the target S3 Bucket name",
124132
default=existing['LocalRepository']['BucketName'] if existing else None),
125-
inquirer.Text('prefix', message="Enter Prefix",
133+
inquirer.Text('prefix', message="Enter the target Prefix",
126134
default=existing['LocalRepository']['Prefix'] if existing else None),
135+
inquirer.Text('kms_key_arn', message="Enter KMS Key ARN (optional)",
136+
default=existing['LocalRepository'].get('KmsKeyArn', '') if existing else '',
137+
validate=validate_kms_key_arn),
127138
inquirer.Text('remote_folder', message="Enter Remote Folder (%year%, %month% and %day% tags are supported)",
128139
default=existing['RemoteFolders']['Folder'] if existing else None),
129140
inquirer.Confirm('recursive', message="Is it recursive?",
130141
default=existing['RemoteFolders']['Recursive'] if existing else True),
131142
]
132143

133144
answers = inquirer.prompt(questions)
134-
return {
145+
sync_setting = {
135146
"LocalRepository": {
136147
"BucketName": safe_bucket_name(answers['bucket_name']),
137148
"Prefix": safe_prefix(answers['prefix'])
@@ -141,6 +152,11 @@ def prompt_sync_setting(existing=None):
141152
"Recursive": answers['recursive']
142153
}
143154
}
155+
156+
if answers['kms_key_arn']:
157+
sync_setting['LocalRepository']['KmsKeyArn'] = answers['kms_key_arn']
158+
159+
return sync_setting
144160

145161
def fetch_host_key(hostname, port=22):
146162
try:
@@ -171,12 +187,13 @@ def confirm_config(config):
171187

172188
print_colored("\nSyncSettings:", Fore.CYAN)
173189
sync_table = PrettyTable()
174-
sync_table.field_names = ["Local", "Remote"]
190+
sync_table.field_names = ["Local", "Remote", "KMS Key ARN"]
175191
sync_table.align = "l"
176192
for setting in config.get('SyncSettings', []):
177193
local = f"{setting['LocalRepository']['BucketName']}/{setting['LocalRepository']['Prefix']}"
178194
remote = setting['RemoteFolders']['Folder']
179-
sync_table.add_row([local, remote])
195+
kms_key_arn = setting['LocalRepository'].get('KmsKeyArn', 'None')
196+
sync_table.add_row([local, remote, kms_key_arn])
180197
print(sync_table)
181198

182199
return inquirer.confirm("Do you want to save this configuration?", default=True)
@@ -241,12 +258,13 @@ def main():
241258
print(table)
242259
print_colored("\nSyncSettings:", Fore.CYAN)
243260
sync_table = PrettyTable()
244-
sync_table.field_names = ["Local", "Remote"]
261+
sync_table.field_names = ["Local", "Remote", "KMS Key ARN"]
245262
sync_table.align = "l"
246263
for setting in config.get('SyncSettings', []):
247264
local = f"{setting['LocalRepository']['BucketName']}/{setting['LocalRepository']['Prefix']}"
248265
remote = setting['RemoteFolders']['Folder']
249-
sync_table.add_row([local, remote])
266+
kms_key_arn = setting['LocalRepository'].get('KmsKeyArn', 'None')
267+
sync_table.add_row([local, remote, kms_key_arn])
250268
print(sync_table)
251269
continue
252270
elif answers['action'] == 'Delete configuration':
@@ -337,6 +355,7 @@ def main():
337355
else:
338356
manual_key = inquirer.text(message="Enter the public key manually")
339357
if manual_key and manual_key not in public_keys:
358+
public_keys
340359
public_keys.append(manual_key)
341360
elif manual_key in public_keys:
342361
print_colored("This key already exists in the configuration.", Fore.YELLOW)
@@ -375,4 +394,5 @@ def main():
375394
try:
376395
main()
377396
except Exception as e:
378-
print_colored(f"An error occurred. Please check the log file for details.", Fore.RED)
397+
print_colored(f"An error occurred: {str(e)}", Fore.RED)
398+
print_colored("Please check the log file for details.", Fore.RED)

configuration/examples/example-sftp-sync.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
{
99
"LocalRepository": {
1010
"BucketName": "my-local-bucket",
11-
"Prefix": "sftp-provider-1/full-sync"
11+
"Prefix": "sftp-provider-1/full-sync",
12+
"KmsKeyArn": "arn:aws:kms:<region>:<accountID>:key/<keyId>"
1213
},
1314
"RemoteFolders": {
1415
"Folder": "/home/folder-1",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"permission_boundary_policy_arn": "",
3+
"powertools_service_name": "transfer-sync-service",
4+
"powertools_log_level": "INFO",
5+
"boto_version": "1.35.47",
6+
"pyawscron_version": "1.0.7"
7+
}

requirements-dev.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
pytest==6.2.5
2-
aws-lambda-powertools~=2.40.1
3-
boto3~=1.34.134
2+
aws-lambda-powertools~=3.2.0
3+
boto3~=1.35.47
44
pyawscron==1.0.7

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
aws-cdk-lib~=2.150.0
1+
aws-cdk-lib~=2.163.1
22
constructs>=10.0.0,<11.0.0
33
paramiko~=3.4.0
44
inquirer~=3.4.0
55
colorama~=0.4.6
66
prettytable~=3.11.0
7-
cdk-monitoring-constructs~=8.1.0
7+
cdk-monitoring-constructs~=8.3.2

transfer_sync_service/lambda/get_list_status/get_list_status.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import boto3
2-
import botocore
32
import json
43
import copy
54
from aws_lambda_powertools import Logger

0 commit comments

Comments
 (0)