Skip to content

Commit 3f5631d

Browse files
authored
Merge pull request #5 from Signiant/save-aws-creds
Add option to save credentials
2 parents 5c6b3c9 + c188ed0 commit 3f5631d

File tree

3 files changed

+115
-16
lines changed

3 files changed

+115
-16
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Keep file contents in sync with matching parameters in AWS Parameter Store
1515
- export PARAM_PREFIX=TESTING_
1616
- TESTING_param1.txt will be compared against param1.txt
1717
- TESTING_param2.conf will be compared against param2.conf
18+
- SAVE_AWS_CREDS - List of tuples (filename, profile name) to save AWS Credentials to (saved to credentials dir)
19+
- value should be space separated list, e.g. aws_cli_credentials default aws_boto_credentials prod
20+
- if a space is required in the profile name, e.g. profile prod, substitute # for a space, e.g. profile#prod
1821

1922
The docker container exposes /credentials as a volume - this can be shared with other
2023
containers or mounted to the local file system
@@ -26,7 +29,9 @@ containers or mounted to the local file system
2629
This example checks AWS Parameter Store in the default us-east-1 region every 600 seconds (10 minutes)
2730
for parameters containing 'TESTING_'. The credentials in AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are
2831
used to access the AWS Parameter Store. The parameter values will be checked against files in the local
29-
folder 'credentials-dir' which is mounted into the container at '/credentials'.
32+
folder 'credentials-dir' which is mounted into the container at '/credentials'. The credentials used to access
33+
the AWS Parameter Store will be saved to /credentials/aws_cli_credentials file with a profile name of 'default'
34+
and no (default) region specified.
3035

3136

3237
````
@@ -35,6 +40,7 @@ docker run -d -e "FREQUENCY=600" \
3540
-e "AWS_ACCESS_KEY_ID=MY_ACCESS_KEY_ID" \
3641
-e "AWS_SECRET_ACCESS_KEY=MY_SECRET_KEY" \
3742
-e "PARAM_PREFIX=TESTING_" \
43+
-e "SAVE_AWS_CREDS="aws_cli_credentials default None" \
3844
-v credentials-dir:/credentials \
3945
signiant/aws-parameter-syncer
4046
````

parameter_sync.py

Lines changed: 102 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717

1818
def get_sha256_hash(file_path):
19-
logger.debug('Hashing "%s" using SHA256' % file_path)
20-
BUF_SIZE = 65536 # lets read stuff in 64kb chunks!
19+
logger.debug(f'Hashing "{file_path}" using SHA256')
20+
BUF_SIZE = 65536 # let's read stuff in 64kb chunks!
2121
sha256 = hashlib.sha256()
2222
with open(file_path, 'rb') as f:
2323
while True:
@@ -31,7 +31,7 @@ def get_sha256_hash(file_path):
3131

3232

3333
def process_parameters_with_prefix(param_prefix, cred_path, aws_region, aws_access_key=None, aws_secret_key=None, dryrun=False):
34-
logger.debug('Searching for parameters with a prefix of %s' % param_prefix)
34+
logger.debug(f'Searching for parameters with a prefix of {param_prefix}')
3535

3636
def get_parameters(parameter_names_list):
3737
parameter_list = []
@@ -53,29 +53,29 @@ def process_parameter(param_name, param_value):
5353
if os.path.exists(full_cred_path):
5454
existing_file_sha256_hash = get_sha256_hash(full_cred_path)
5555
new_file_full_path = temp_dir + os.sep + filename + '.new'
56-
logger.debug('Storing retrieved value for parameter "%s" in "%s"' % (param_name, new_file_full_path))
56+
logger.debug(f'Storing retrieved value for parameter "{param_name}" in "{new_file_full_path}"')
5757
with open(new_file_full_path, 'w') as f:
5858
f.write(param_value)
5959
new_file_sha256_hash = get_sha256_hash(new_file_full_path)
6060
logger.debug('Comparing file hashes')
6161
if existing_file_sha256_hash != new_file_sha256_hash:
6262
if not existing_file_sha256_hash:
63-
logger.info('This is a new credentials file: "%s"' % filename)
63+
logger.info(f'This is a new credentials file: "{filename}"')
6464
else:
65-
logger.info("Contents don't match - replacing \"%s\" contents with value from parameter store" % full_cred_path)
65+
logger.info(f"Contents don't match - replacing \"{full_cred_path}\" contents with value from parameter store")
6666
if not dryrun:
6767
if os.path.exists(new_file_full_path) and os.stat(new_file_full_path).st_size > 0:
6868
shutil.copyfile(new_file_full_path, full_cred_path)
6969
else:
70-
logger.error('file %s is missing or zero length - NOT replacing' % new_file_full_path)
70+
logger.error(f'file {new_file_full_path} is missing or zero length - NOT replacing')
7171
else:
72-
logger.info('*** Dryrun selected - will NOT update "%s"' % full_cred_path)
72+
logger.info(f'*** Dryrun selected - will NOT update "{full_cred_path}"')
7373
else:
74-
logger.info('Contents of existing "%s" MATCH with value for "%s" from parameter store' % (full_cred_path, param_name))
74+
logger.info(f'Contents of existing "{full_cred_path}" MATCH with value for "{param_name}" from parameter store')
7575

7676
# Cleanup
7777
if new_file_full_path:
78-
logger.debug('Removing %s' % new_file_full_path)
78+
logger.debug(f'Removing {new_file_full_path}')
7979
os.remove(new_file_full_path)
8080

8181
def get_parameters_with_prefix(prefix, next_token=None):
@@ -84,7 +84,7 @@ def get_parameters_with_prefix(prefix, next_token=None):
8484
query_result = ssm.describe_parameters(Filters=[{'Key': 'Name', 'Values': [prefix]}], NextToken=next_token)
8585
else:
8686
query_result = ssm.describe_parameters(Filters=[{'Key': 'Name', 'Values': [prefix]}])
87-
logger.debug("Query result %s" % str(query_result))
87+
logger.debug(f"Query result {str(query_result)}")
8888
if 'ResponseMetadata' in query_result:
8989
if 'HTTPStatusCode' in query_result['ResponseMetadata']:
9090
if query_result['ResponseMetadata']['HTTPStatusCode'] == 200:
@@ -99,7 +99,7 @@ def get_parameters_with_prefix(prefix, next_token=None):
9999
else:
100100
logger.debug("No next token, storing")
101101
parameter_list.extend(query_result['Parameters'])
102-
logger.debug("Parameter List %s" % parameter_list)
102+
logger.debug(f"Parameter List {parameter_list}")
103103
return parameter_list
104104

105105

@@ -130,6 +130,78 @@ def get_parameters_with_prefix(prefix, next_token=None):
130130
process_parameter(parameter_name, parameter_value)
131131

132132

133+
def create_aws_cred_file(key_id, secret, file_location, cred_filename, profile_name, aws_region):
134+
file_path = f'{file_location}{os.sep}{cred_filename}'
135+
if os.path.exists(file_path):
136+
# File already exists - append to it
137+
with open(file_path, 'a') as cred_file:
138+
cred_file.write('\n')
139+
cred_file.write(f'[{profile_name}]\n')
140+
if aws_region:
141+
cred_file.write(f'region={aws_region}\n')
142+
cred_file.write(f'aws_access_key_id={key_id}\n')
143+
cred_file.write(f'aws_secret_access_key={secret}\n')
144+
else:
145+
with open(file_path, 'w') as cred_file:
146+
cred_file.write(f'[{profile_name}]\n')
147+
if aws_region:
148+
cred_file.write(f'region={aws_region}\n')
149+
cred_file.write(f'aws_access_key_id={key_id}\n')
150+
cred_file.write(f'aws_secret_access_key={secret}\n')
151+
return file_path
152+
153+
154+
def write_aws_cli_creds(key_id, secret, base_cred_path, aws_cred_list):
155+
aws_creds_tuples = []
156+
have_all_info = False
157+
for i, val in enumerate(aws_cred_list):
158+
if (i % 3) == 0:
159+
logging.debug(f'save-aws-creds - filename: {val}')
160+
filename = val
161+
elif (i % 3) == 1:
162+
logging.debug(f'save-aws-creds - profile name: {val}')
163+
if '#' in val:
164+
profile = val.replace('#', ' ')
165+
else:
166+
profile = val
167+
else:
168+
logging.debug(f'save-aws-creds - region: {val}')
169+
if 'none' in val.lower():
170+
region = None
171+
else:
172+
region = val
173+
have_all_info = True
174+
175+
if have_all_info:
176+
aws_creds_tuples.append((filename, profile, region))
177+
have_all_info = False
178+
179+
if len(aws_creds_tuples) > 0:
180+
logging.debug('Provided with the following tuples:')
181+
logging.debug(f'{aws_creds_tuples}')
182+
183+
# write to a tmp file first
184+
temp_dir = tempfile.gettempdir()
185+
if not os.path.exists(temp_dir):
186+
os.makedirs(temp_dir)
187+
188+
aws_cred_files = []
189+
for f, p, r in aws_creds_tuples:
190+
tmp_cred_filepath = create_aws_cred_file(key_id, secret, temp_dir, f, p, r)
191+
if tmp_cred_filepath not in aws_cred_files:
192+
aws_cred_files.append(tmp_cred_filepath)
193+
194+
# Now copy the tmp files to the base_cred_path
195+
for cred_file in aws_cred_files:
196+
filename = cred_file.split(os.sep)[-1]
197+
new_file_path = f"{base_cred_path}{os.sep}{filename}"
198+
logger.info(f'Saving AWS Credentials to {new_file_path}')
199+
shutil.copyfile(cred_file, new_file_path)
200+
# Cleanup
201+
logger.debug(f'Removing {cred_file}')
202+
os.remove(cred_file)
203+
204+
133205
if __name__ == "__main__":
134206

135207
description = "Script to get all parameters from AWS Parameter\n"
@@ -147,6 +219,7 @@ def get_parameters_with_prefix(prefix, next_token=None):
147219
parser.add_argument("--param-prefix", help="Parameter prefix", dest='param_prefix', required=True)
148220
parser.add_argument("--credentials-path", help="Where credentials are stored", dest='cred_path', default='/credentials/')
149221
parser.add_argument("--verbose", help="Turn on DEBUG logging", action='store_true', required=False)
222+
parser.add_argument("--save-aws-creds", help="Save AWS Creds [filename, profile]", nargs='*')
150223
parser.add_argument("--dryrun", help="Do a dryrun - no changes will be performed", dest='dryrun',
151224
action='store_true', default=False,
152225
required=False)
@@ -163,8 +236,23 @@ def get_parameters_with_prefix(prefix, next_token=None):
163236
logger.critical('AWS Secret Access Key not set - cannot continue')
164237

165238
logger.debug('INIT')
166-
logger.info('Getting parameters with prefix %s from AWS Parameter Store' % args.param_prefix)
167-
logger.info('Parameter values will be compared against file contents in "%s" and updated if necessary' % args.cred_path)
239+
logger.info(f'Getting parameters with prefix {args.param_prefix} from AWS Parameter Store')
240+
logger.info(f'Parameter values will be compared against file contents in "{args.cred_path}" and updated if necessary')
168241
process_parameters_with_prefix(args.param_prefix, args.cred_path, args.aws_region,
169242
args.aws_access_key, args.aws_secret_key, args.dryrun)
243+
244+
if args.save_aws_creds:
245+
if args.aws_access_key and args.aws_secret_key:
246+
aws_access_key = args.aws_access_key
247+
aws_secret_key = args.aws_secret_key
248+
else:
249+
aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID')
250+
aws_secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
251+
252+
if len(args.save_aws_creds) % 3 != 0:
253+
logging.critical('Must provide filename, profile_name and region for aws creds')
254+
sys.exit(1)
255+
256+
result = write_aws_cli_creds(aws_access_key, aws_secret_key, args.cred_path, args.save_aws_creds)
257+
170258
logger.info('COMPLETE')

parameter_sync.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,17 @@ else
4444
echo "Frequency set to $FREQUENCY seconds"
4545
fi
4646

47+
if [ "$SAVE_AWS_CREDS" ]; then
48+
echo "Save AWS Creds enabled"
49+
SAVE_AWS_CREDS="--save-aws-creds ${SAVE_AWS_CREDS}"
50+
fi
51+
4752
# Loop forever, sleeping for our frequency
4853
while true
4954
do
5055
echo "Awoke to check for new credentials with prefix ${PARAM_PREFIX} in AWS Parameter Store"
5156

52-
python /parameter_sync.py --credentials-path ${CRED_FOLDER_PATH} --param-prefix ${PARAM_PREFIX} --aws-region ${AWS_REGION} ${VERBOSE}
57+
python /parameter_sync.py --credentials-path ${CRED_FOLDER_PATH} --param-prefix ${PARAM_PREFIX} --aws-region ${AWS_REGION} ${SAVE_AWS_CREDS} ${VERBOSE}
5358
echo "Sleeping for $FREQUENCY seconds"
5459
sleep $FREQUENCY
5560
echo

0 commit comments

Comments
 (0)