beginning to add the other components to this
This commit is contained in:
parent
4eb4f067a7
commit
ceda931c76
144
rotate-ssh-keys
144
rotate-ssh-keys
@ -1,5 +1,18 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is designed to be run to manage SSH keys in AWS accounts
|
||||||
|
used for EC2 instances. It will handle key generation, AWS
|
||||||
|
registering (uploading), and rotating of the keys used on
|
||||||
|
EC2 instances.
|
||||||
|
|
||||||
|
The rotation process would on the creation of a new key identify
|
||||||
|
all instances using the older key(s) and remove the public key
|
||||||
|
values from the ~ec2-user/.ssh/authorized_keys file leaving only
|
||||||
|
the public key value for the new key.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import boto3
|
import boto3
|
||||||
@ -21,20 +34,50 @@ log = logging.getLogger()
|
|||||||
logging.getLogger('botocore').setLevel(logging.WARNING)
|
logging.getLogger('botocore').setLevel(logging.WARNING)
|
||||||
logging.getLogger('boto3').setLevel(logging.WARNING)
|
logging.getLogger('boto3').setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
# create a custom exception
|
||||||
|
class RotateKeyError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
"""
|
"""
|
||||||
Parse the arguments passed
|
Parse the arguments passed
|
||||||
"""
|
"""
|
||||||
argp = argparse.ArgumentParser()
|
argp = argparse.ArgumentParser()
|
||||||
argp.add_argument('--debug', action='store_true', help="Run in debug mode")
|
|
||||||
|
|
||||||
argp.add_argument(
|
argp.add_argument(
|
||||||
|
'action',
|
||||||
|
choices=[
|
||||||
|
'generate',
|
||||||
|
'add-only',
|
||||||
|
'highlander',
|
||||||
|
'lockdown',
|
||||||
|
'rotate',
|
||||||
|
'order-66',
|
||||||
|
'remove-only',
|
||||||
|
'delete-only',
|
||||||
|
],
|
||||||
|
help="Action to perform: generate, add-only will create a new key and "
|
||||||
|
"register with AWS. highlander, lockdown, rotate all will create "
|
||||||
|
"a new key, register with AWS, and remove the old key from AWS "
|
||||||
|
"and all the instances so that the key is rendered unusable. "
|
||||||
|
"remove-only, delete-only, order-66 will remove the key specified "
|
||||||
|
"by the --removal-key-name without generating a new key."
|
||||||
|
)
|
||||||
|
|
||||||
|
argp.add_argument(
|
||||||
|
'--debug',
|
||||||
|
action='store_true',
|
||||||
|
help="Run in debug mode"
|
||||||
|
)
|
||||||
|
|
||||||
|
account_access_group = argp.add_mutually_exclusive_group(required=True)
|
||||||
|
account_access_group.add_argument(
|
||||||
'-p', '--profile',
|
'-p', '--profile',
|
||||||
help="AWS profile name"
|
help="AWS profile name"
|
||||||
)
|
)
|
||||||
|
|
||||||
argp.add_argument(
|
account_access_group.add_argument(
|
||||||
'-r', '--role-arn',
|
'-r', '--role-arn',
|
||||||
help="Role ARN with key access to the AWS account"
|
help="Role ARN with key access to the AWS account"
|
||||||
)
|
)
|
||||||
@ -52,6 +95,14 @@ def parse_args():
|
|||||||
help="String to use for the new key name and searching for existing keys"
|
help="String to use for the new key name and searching for existing keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
argp.add_argument(
|
||||||
|
'--removal-key-name',
|
||||||
|
default="",
|
||||||
|
help="Specific full name of the key to remove (must match exactly). "
|
||||||
|
"This is ignored when action is not remove-only, delete-only, "
|
||||||
|
"or order-66."
|
||||||
|
)
|
||||||
|
|
||||||
return argp.parse_args()
|
return argp.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@ -196,6 +247,31 @@ def upload_key(session, key_name, public_key):
|
|||||||
return fingerprint
|
return fingerprint
|
||||||
|
|
||||||
|
|
||||||
|
def remove_key(session, key_name):
|
||||||
|
"""
|
||||||
|
Will remove the key to the AWS account with the provided name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session: An AWS session object to establish access to the AWS account
|
||||||
|
key_name: Name for the keypair
|
||||||
|
Returns:
|
||||||
|
True if successful, False if unsuccessful
|
||||||
|
"""
|
||||||
|
return_value = False
|
||||||
|
log.debug("Getting session client for EC2")
|
||||||
|
ec2_client = session.client('ec2')
|
||||||
|
|
||||||
|
try:
|
||||||
|
log.info("Removing the key")
|
||||||
|
response = ec2_client.delete_key_pair(KeyName=key_name)
|
||||||
|
log.info(f"Key {key_name} successfully removed")
|
||||||
|
return_value = True
|
||||||
|
except Exception as error:
|
||||||
|
log.error(f"Failed to upload key: {error}")
|
||||||
|
|
||||||
|
return return_value
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
||||||
@ -206,36 +282,50 @@ def main():
|
|||||||
logging.getLogger('botocore').setLevel(logging.WARNING)
|
logging.getLogger('botocore').setLevel(logging.WARNING)
|
||||||
logging.getLogger('boto3').setLevel(logging.WARNING)
|
logging.getLogger('boto3').setLevel(logging.WARNING)
|
||||||
|
|
||||||
log.info("Beginnging to generate new SSH key")
|
# get the session object to be used for all AWS access
|
||||||
|
session = get_session(profile_name=args.profile, role_arn=args.role_arn)
|
||||||
|
|
||||||
session = get_session(profile_name=args.profile)
|
if ['delete-only', 'remove-only', 'order-66'] in args.action:
|
||||||
|
# handle specific key removal
|
||||||
|
if args.removal_key_name):
|
||||||
|
remove_key(session, args.removal_key_name)
|
||||||
|
else:
|
||||||
|
raise RotateKeyException(f"--removal_key_name must be provided with {args.action}")
|
||||||
|
|
||||||
# create the new key pair in memory
|
else:
|
||||||
public_key, private_key = generate_ssh_keypair(args.key_size)
|
# handle the new key creation
|
||||||
|
log.info("Beginnging to generate new SSH key")
|
||||||
|
|
||||||
# get epoch of UTC time for the extension to make the name unique
|
# create the new key pair in memory
|
||||||
epoch_time = time.strftime("%s", time.gmtime())
|
public_key, private_key = generate_ssh_keypair(args.key_size)
|
||||||
key_name = f"{args.key_name_prefix}-{epoch_time}"
|
|
||||||
log.debug(f"key_name = {key_name}")
|
|
||||||
|
|
||||||
# write the key values to files
|
|
||||||
log.info(f"Exporting the public key to {key_name}.pub")
|
|
||||||
with open(f"{key_name}.pub", 'w') as fp:
|
|
||||||
fp.write(public_key.decode('utf-8'))
|
|
||||||
|
|
||||||
log.info(f"Exporting the private key to file {key_name}")
|
# get epoch of UTC time for the extension to make the name unique
|
||||||
with open(key_name, 'w') as fp:
|
epoch_time = time.strftime("%s", time.gmtime())
|
||||||
fp.write(private_key.decode('utf-8'))
|
key_name = f"{args.key_name_prefix}-{epoch_time}"
|
||||||
|
log.debug(f"key_name = {key_name}")
|
||||||
|
|
||||||
|
# write the key values to files
|
||||||
|
log.info(f"Exporting the public key to {key_name}.pub")
|
||||||
|
with open(f"{key_name}.pub", 'w') as fp:
|
||||||
|
fp.write(public_key.decode('utf-8'))
|
||||||
|
|
||||||
|
log.info(f"Exporting the private key to file {key_name}")
|
||||||
|
with open(key_name, 'w') as fp:
|
||||||
|
fp.write(private_key.decode('utf-8'))
|
||||||
|
|
||||||
|
log.debug("Setting permissions on private key file")
|
||||||
|
os.chmod(key_name, 0o600)
|
||||||
|
|
||||||
|
# this list is for rotating the older keys out of circulation
|
||||||
|
existing_keypairs = get_existing_keypairs(session, args.key_name_prefix)
|
||||||
|
|
||||||
|
# upload the new keypair to AWS account
|
||||||
|
fingerprint = upload_key(session, key_name, public_key)
|
||||||
|
|
||||||
|
# handle the clean up of the other keys
|
||||||
|
if ['lockdown', 'highlander', 'rotate'] in args.action:
|
||||||
|
log.info("Beginning key clean up phase")
|
||||||
|
|
||||||
log.debug("Setting permissions on private key file")
|
|
||||||
os.chmod(key_name, 0o600)
|
|
||||||
|
|
||||||
# this list is for rotating the older keys out of circulation
|
|
||||||
existing_keypairs = get_existing_keypairs(session, args.key_name_prefix)
|
|
||||||
|
|
||||||
# upload the new keypair to AWS account
|
|
||||||
fingerprint = upload_key(session, key_name, public_key)
|
|
||||||
|
|
||||||
log.info("Complete")
|
log.info("Complete")
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user