def fetch_cert_and_key(session: SessionProxy, certificate_arn, private_key_secret_name): """ Fetches Certificate and Private Key material from Amazon Certificate Manager and Secrets Manager :param session: Boto3 session :param certificate_arn: Valid ACM ARN :param private_key_secret_name: Valid Secrets Manager Name ID :return: Certificate, Certificate Chain, Private Key """ secrets = session.client('secretsmanager') acm = session.client('acm') log.info('Fetching custom Ingress Certificate from ACM ') log.debug('ACM ARN: %s', certificate_arn) cert_response = acm.get_certificate(CertificateArn=certificate_arn) log.debug('Certificate Data: %s', cert_response) cert = cert_response['Certificate'] cert_chain = cert_response['CertificateChain'] log.info('Fetching custom Ingress Private Key from Secrets Manager') log.debug('Secret Name: %s', private_key_secret_name) private_key = secrets.get_secret_value( SecretId=private_key_secret_name)['SecretString'] log.info('Secret fetched successfully') return cert, cert_chain, private_key
def create(obj: ProvisioningStatus, request: ResourceHandlerRequest, callback_context: MutableMapping[str, Any], session: SessionProxy): LOG.info("starting NEW RESOURCE with request\n{}".format(request)) progress: ProgressEvent = ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=request.desiredResourceState, callbackContext=callback_context, callbackDelaySeconds=15) env_name = get_name_from_request(request) if request.desiredResourceState.OwnerArn: owner_arn = request.desiredResourceState.OwnerArn else: owner_arn = "arn:aws:iam::{}:root".format( session.client('sts').get_caller_identity()['Account']) response = session.client('cloud9').create_environment_ec2( ownerArn=owner_arn, name=env_name, instanceType=request.desiredResourceState.InstanceType, automaticStopTimeMinutes=30) LOG.info("environment id: {}".format(response['environmentId'])) model: ResourceModel = request.desiredResourceState model.Name = env_name model.EnvironmentId = response['environmentId'] progress.callbackContext["ENVIRONMENT_NAME"] = env_name progress.callbackContext["ENVIRONMENT_ID"] = response['environmentId'] progress.callbackContext["LOCAL_STATUS"] = EnvironmentCreated() progress.status = OperationStatus.IN_PROGRESS progress.message = "Cloud9 Environment created" return progress
def get_environment_info(obj: ProvisioningStatus, request: ResourceHandlerRequest, callback_context: MutableMapping[str, Any], session: SessionProxy): LOG.info( "starting ENVIRONMENT_CREATED with callback_context\n{}\nand request\n{}" .format(callback_context, request)) progress: ProgressEvent = ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=request.desiredResourceState, callbackContext=callback_context, callbackDelaySeconds=15) environment_id = callback_context["ENVIRONMENT_ID"] LOG.info("environment id: {}".format(environment_id)) try: ec2_client = session.client("ec2") instance_filter = ec2_client.describe_instances( Filters=[{ 'Name': 'tag:aws:cloud9:environment', 'Values': [environment_id] }]) if len(instance_filter['Reservations']) < 1: LOG.info("instance not available from `describe instances` call") return progress if len(instance_filter['Reservations'][0]['Instances']) < 1: LOG.info( "instance not available from `describe instances` call, part deux" ) return progress instance_id = instance_filter['Reservations'][0]['Instances'][0][ 'InstanceId'] instance_state = instance_filter['Reservations'][0]['Instances'][0][ 'State']['Name'] c9_client = session.client("cloud9") environment_status = c9_client.describe_environment_status( environmentId=environment_id) LOG.info("Checking Environment and instance status") if (environment_status['status'] == 'ready') and (instance_state == 'running'): LOG.info("environment is ready and instance is running") progress.resourceModel.InstanceId = instance_id progress.callbackContext["INSTANCE_ID"] = instance_id progress.message = "Cloud9 Environment is stable" progress.callbackDelaySeconds = 0 if request.desiredResourceState.EBSVolumeSize: progress.callbackContext["LOCAL_STATUS"] = InstanceStable() else: progress.callbackContext["LOCAL_STATUS"] = ResizedInstance() except Exception as e: LOG.info('throwing: {}'.format(e)) raise (e) LOG.info("returning progress from ENVIRONMENT_CREATED {}".format(progress)) return progress
def create_and_attach_instance_profile(obj: ProvisioningStatus, request: ResourceHandlerRequest, callback_context: MutableMapping[str, Any], session: SessionProxy): LOG.info( "starting ATTACH_INSTANCE_PROFILE with callback_context\n{}\nand request\n{}" .format(callback_context, request)) progress: ProgressEvent = ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=request.desiredResourceState, callbackContext=callback_context, callbackDelaySeconds=30) instance_profile_arn = callback_context["INSTANCE_PROFILE_ARN"] instance_id = callback_context["INSTANCE_ID"] try: ec2_client = session.client('ec2') associate_profile_response = ec2_client.associate_iam_instance_profile( IamInstanceProfile={'Arn': instance_profile_arn}, InstanceId=instance_id) callback_context["ASSOCIATION_ID"] = associate_profile_response[ 'IamInstanceProfileAssociation']['AssociationId'] progress.callbackContext["LOCAL_STATUS"] = ProfileAttached() except Exception as e: print(e) LOG.info( "returning progress from ATTACH_INSTANCE_PROFILE {}".format(progress)) return progress
def handle_A(obj: ProvisioningStatus, request: ResourceHandlerRequest, callback_context: MutableMapping[str, Any], session: SessionProxy): LOG.info( "starting INSTANCE_STABLE with callback_context\n{}\nand request\n{}". format(callback_context, request)) progress: ProgressEvent = ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=request.desiredResourceState, callbackContext=callback_context, callbackDelaySeconds=0) instance_id = callback_context["INSTANCE_ID"] try: ec2_client = session.client("ec2") if request.desiredResourceState.EBSVolumeSize: resize_ebs(instance_id, int(request.desiredResourceState.EBSVolumeSize), ec2_client) progress.callbackContext["LOCAL_STATUS"] = ResizedInstance() progress.message = "Resized EBS Volume" except Exception as e: LOG.info('Can\'t resize instance: {}'.format(e)) raise (e) LOG.info("returning progress from INSTANCE_STABLE {}".format(progress)) return progress
def delete_contents_s3(s3_bucket, session: SessionProxy, filter: Optional[Mapping] = None): """ Clear out an S3 bucket. Handy for cleaning up if the CF Resource is deleted. Default is to delete all objects from the bucket. Optionally, pass a filter that is passed as **kwargs to the boto3.Bucket.objects.filter(**kwargs) call :param session: Boto SessionProxy :param filter: A Mapping of valid Filters for the AWS S3 API action `GetObjects` :param s3_bucket: Name of the S3 bucket to delete objects from :return None: """ s3 = session.resource('s3') bucket = s3.Bucket(s3_bucket) try: if filter is None: log.debug("Deleting bucket {}...".format(s3_bucket)) bucket.objects.all().delete() else: bucket.objects.filter(**filter).delete() except ClientError as e: # If a client error is thrown, then check that it was a 404 error. # If it was a 404 error, then the bucket does not exist. error_code = e.response['Error']['Code'] if error_code == "NoSuchBucket": log.debug("{} does not exist, skipping...".format(s3_bucket)) return else: log.error( "Failed to delete bucket, unhandled exception {}".format(e)) raise e except Exception as e: log.error("Failed to delete bucket, unhandled exception {}".format(e)) raise e
def send_command(obj: ProvisioningStatus, request: ResourceHandlerRequest, callback_context: MutableMapping[str, Any], session: SessionProxy): LOG.info("starting SEND_COMMAND with callback_context\n{}\nand request\n{}".format(callback_context, request)) progress: ProgressEvent = ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=request.desiredResourceState, callbackContext=callback_context, callbackDelaySeconds=0 ) instance_id = callback_context["INSTANCE_ID"] ssm_client = session.client('ssm') if not ssm_ready(ssm_client, instance_id): progress.callbackDelaySeconds=90 return progress if request.desiredResourceState.UserData: from io import BytesIO s3 = session.resource('s3') bucket = s3.Bucket(request.desiredResourceState.UserData.Bucket) obj = bucket.Object(request.desiredResourceState.UserData.Object) output = BytesIO() obj.download_fileobj(output) commands = get_preamble() + '\n' + output.getvalue().decode('utf-8') + '\n' else: commands = get_preamble() LOG.info("Sending command to %s : %s" % (instance_id, commands)) try: send_command_response = ssm_client.send_command( InstanceIds=[instance_id], DocumentName='AWS-RunShellScript', Parameters={'commands': commands.split('\n')}, CloudWatchOutputConfig={ 'CloudWatchLogGroupName': f'ssm-output-{instance_id}', 'CloudWatchOutputEnabled': True } ) progress.callbackContext["RUN_COMMAND_ID"] = send_command_response['Command']['CommandId'] progress.callbackContext["LOCAL_STATUS"] = CommandSent() except ssm_client.exceptions.InvalidInstanceId: LOG.info("Failed to execute SSM command. This happens some times when the box isn't ready yet. we'll retry in a minute.") LOG.info("returning progress from SEND_COMMAND {}".format(progress)) return progress
def _mock_get_session_proxy() -> SessionProxy: """Create and return a mock SessionProxy with a mock session client""" # Create a mock session session = MagicMock(name='session', ) # Create a mock session client session.client = MagicMock( name='client', return_value=MagicMock(name='client', ), ) # Return a SessionProxy with the mock session and client return SessionProxy(session, )
def delete_s3_file(s3_bucket, file_name, session: SessionProxy): """ Delete an individual file object from S3 :param session: Boto SessionProxy :param s3_bucket: Name of the S3 bucket :param file_name: Name of object to delete in S3 :return: """ client = session.client('s3') client.delete_object(Bucket=s3_bucket, Key=file_name)
def create_iam_role(obj: ProvisioningStatus, request: ResourceHandlerRequest, callback_context: MutableMapping[str, Any], session: SessionProxy): LOG.info( "starting CREATE_IAM_ROLE with callback_context\n{}\nand request\n{}". format(callback_context, request)) progress: ProgressEvent = ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=request.desiredResourceState, callbackContext=callback_context, callbackDelaySeconds=1) environment_id = callback_context["ENVIRONMENT_ID"] instance_id = callback_context["INSTANCE_ID"] environment_id = callback_context["ENVIRONMENT_ID"] environment_name = callback_context["ENVIRONMENT_NAME"] iam_client = session.client("iam") role_name = get_or_create_role(iam_client, f'{environment_name}-InstanceProfileRole', instance_id, environment_id) if request.desiredResourceState.Cloud9InstancePolicy: LOG.debug( f'attempting to add the following inline policy: {request.desiredResourceState.Cloud9InstancePolicy._serialize()}' ) iam_client.put_role_policy( RoleName=role_name, PolicyName=request.desiredResourceState.Cloud9InstancePolicy. PolicyName, PolicyDocument=json.dumps( request.desiredResourceState.Cloud9InstancePolicy. PolicyDocument._serialize())) iam_client.attach_role_policy( RoleName=role_name, PolicyArn='arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore') iam_client.attach_role_policy( RoleName=role_name, PolicyArn='arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy') LOG.info("Creating Instance Profile") create_instance_profile_response = iam_client.create_instance_profile( InstanceProfileName=f'{environment_name}-InstanceProfile', Path='/cdk/cloud9/environment') LOG.info("Attatching Role to Instance Profile") iam_client.add_role_to_instance_profile( InstanceProfileName=f'{environment_name}-InstanceProfile', RoleName=role_name) progress.callbackContext["LOCAL_STATUS"] = RoleCreated() progress.callbackContext[ "INSTANCE_PROFILE_ARN"] = create_instance_profile_response[ 'InstanceProfile']['Arn'] LOG.info("returning progress from CREATE_IAM_ROLE {}".format(progress)) return progress
def check_file_s3(s3_bucket, key, session: SessionProxy): """ Check if key exists in S3 bucket :param session: Boto SessionProxy :param s3_bucket: Name of the S3 bucket to check :param key: Object key :return: """ client = session.client('s3') try: client.head_object(Bucket=s3_bucket, Key=key) log.debug("File at location {} found".format(key)) return True except Exception as e: log.debug("File not found at {} and key {}".format(s3_bucket, key)) return False
def stabilize(obj: ProvisioningStatus, request: ResourceHandlerRequest, callback_context: MutableMapping[str, Any], session: SessionProxy): LOG.info("starting STABILIZED with callback_context\n{}\nand request\n{}".format(callback_context, request)) progress: ProgressEvent = ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=request.desiredResourceState, callbackContext=callback_context, callbackDelaySeconds=60 ) command_id = callback_context["RUN_COMMAND_ID"] instance_id = callback_context["INSTANCE_ID"] ssm_client = session.client('ssm') response = ssm_client.get_command_invocation(CommandId=command_id, InstanceId=instance_id) if response['Status'] in ['Pending', 'InProgress', 'Delayed']: return progress else: progress.status = OperationStatus.SUCCESS LOG.info("returning progress from STABILIZED {}".format(progress)) return progress
def send_command(obj: ProvisioningStatus, request: ResourceHandlerRequest, callback_context: MutableMapping[str, Any], session: SessionProxy): LOG.info( "starting SEND_COMMAND with callback_context\n{}\nand request\n{}". format(callback_context, request)) progress: ProgressEvent = ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=request.desiredResourceState, callbackContext=callback_context, callbackDelaySeconds=0) instance_id = callback_context["INSTANCE_ID"] ssm_client = session.client('ssm') if not ssm_ready(ssm_client, instance_id): progress.callbackDelaySeconds = 180 return progress with open('./cdk_cloud9_environment/run_command.sh', 'r') as myfile: commands = myfile.read() LOG.info("Sending command to %s : %s" % (instance_id, commands)) try: send_command_response = ssm_client.send_command( InstanceIds=[instance_id], DocumentName='AWS-RunShellScript', Parameters={'commands': commands.split('\n')}, CloudWatchOutputConfig={ 'CloudWatchLogGroupName': f'ssm-output-{instance_id}', 'CloudWatchOutputEnabled': True }) progress.callbackContext["RUN_COMMAND_ID"] = send_command_response[ 'Command']['CommandId'] progress.callbackContext["LOCAL_STATUS"] = CommandSent() except ssm_client.exceptions.InvalidInstanceId: LOG.info( "Failed to execute SSM command. This happens some times when the box isn't ready yet. we'll retry in a minute." ) LOG.info("returning progress from SEND_COMMAND {}".format(progress)) return progress
def _get_cross_session(session: SessionProxy, model: ResourceModel, session_name): """Returns session object for cross-account access. :return: Can be used to obtain session.client() or session.resource() """ LOG.setLevel(model.LogLevel) role_arn = "arn:aws:iam::{}:role{}{}".format(model.AccountId, model.AssumeRolePath, model.AssumeRoleName) LOG.debug("Assuming Cross session role: %s", role_arn) assumed_role = session.client("sts").assume_role( RoleArn=role_arn, RoleSessionName=session_name) session = boto3.Session( aws_access_key_id=assumed_role["Credentials"]["AccessKeyId"], aws_secret_access_key=assumed_role["Credentials"]["SecretAccessKey"], aws_session_token=assumed_role["Credentials"]["SessionToken"]) return session
def create_mock_session(): mock_session_impl = MagicMock(name="mock_session_impl") mock_session_impl.client = MagicMock(return_value=MagicMock(name="mock_afd_client")) mock_session = SessionProxy(session=mock_session_impl) return mock_session
def get_cross_cfn_client(session: SessionProxy, model: ResourceModel, session_name): session = _get_cross_session(session, model, session_name) return session.client("cloudformation", region_name=model.Region)