def audit(resource, remediate=False): is_compliant = True if resource["type"] != "region": raise Exception("Mismatched type. Expected {} but received {}".format( "region", resource["type"])) issues = [] if not is_guardduty_enabled(resource): issues.append("GuardDuty not enabled") is_compliant = False if not is_config_enabled(resource): issues.append("Config is not enabled") is_compliant = False if not is_cloudtrail_enabled(resource): issues.append("CloudTrail is not enabled") is_compliant = False if not is_flow_logs_enabled(resource): issues.append("VPC Flow Logs are not enabled everywhere") is_compliant = False if resource["region"] == "us-east-1": if not root_has_mfa(resource): issues.append("Root user does not have MFA") is_compliant = False if not has_compliant_password_policy(resource): issues.append("Password policy is not compliant") is_compliant = False if not is_compliant: # No remediations are peformed for these issues send_notification(", ".join(issues), "", resource) return is_compliant
def check_public_dev_elb(loadbalancer, elb, EC2_INSTANCE_IGNORE_LIST, resource, remediate): # enumerate instances, enumeration can be skipped if we can change how we do event translation # by adding few moer fields in resource[] which is sent to remeditor is_compliant = True loadbalancer_name = loadbalancer["LoadBalancerName"] for instance in loadbalancer["Instances"]: instanceid = instance["InstanceId"] instance_elb_info = { "Id": instanceid, "loadbalancer": loadbalancer_name, "type": "classic", } if instanceid not in str(EC2_INSTANCE_IGNORE_LIST): is_compliant = False issue = "Dev EC2 {} is Public - via elbv1 {}".format( instanceid, loadbalancer_name ) if remediate: if not remediate_instance(loadbalancer_name, instanceid, elb): issue += " - Not remediated" send_notification( issue, "Instance ELB Information: {}".format(", ".join(instance_elb_info)), resource, ) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "iam_role": raise Exception("Mismatched type. Expected {} but received {}".format( "iam_role", resource["type"])) # Get a session in the account where this resource is iam = get_session_for_account(resource["account"], resource["region"], "iam") role_is_permissive = False try: role = iam.get_role(RoleName=resource["id"])["Role"] policy = role["AssumeRolePolicyDocument"] policy = Policy(policy) role_is_permissive = policy.is_internet_accessible() except Exception as e: print(e) print("No role policy: {}".format(resource["id"])) if role_is_permissive: is_compliant = False issue = "IAM role {} is publicly exposed".format(resource["id"]) if remediate: if not remediation_make_role_restricted(resource, iam): issue += " - Not remediated" send_notification(issue, "", resource) if is_compliant: print("Role is compliant: {}".format(resource["id"])) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "security_group": raise Exception( "Mismatched type. Expected {} but received {}".format( "security_group", resource["type"] ) ) # Get a session in the account where this resource is ec2 = get_session_for_account(resource["account"], resource["region"], "ec2") security_group = ec2.describe_security_groups(GroupIds=[resource["id"]]) security_group = security_group["SecurityGroups"][0] # Check if this allows ingress from 0.0.0.0/0 or the IPv6 equivalent ::/0 allows_public_ingress = False for permission in security_group.get("IpPermissions", []): for ip_range in permission.get("IpRanges", []): if "/0" in ip_range.get("CidrIp", "") or "/0" in ip_range.get( "CidrIpv6", "" ): allows_public_ingress = True print("Security Group allows public ingress: {}".format(permission)) if allows_public_ingress: is_compliant = False issue = "Security Group {} not compliant - Allows public ingress - Not remediated".format( resource["id"] ) send_notification(issue, "", resource) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "ecs_service": raise Exception( "Mismatched type. Expected {} but received {}".format( "ecs_service", resource["type"] ) ) # Get a session in the account where this resource is ecs = get_session_for_account(resource["account"], resource["region"], "ecs") ecs_is_public = False try: ## List all ECS Clusters ecs_clusters = ecs.list_clusters() ## Now get all cluster ARNs from ECS clusters json cluster_arns = ecs_clusters["clusterArns"] ## For each cluster, try to find the named service ecs_description = [] service_cluster = "" for cluster in cluster_arns: svc_description = ecs.describe_services( cluster = cluster, services = [resource["id"]] ) ## If the services array contains an object then set ecs_description to the services array of the cluster ## Set cluster variable to the cluster ARN if len(svc_description['services']) > 0: ecs_description = svc_description['services'] service_cluster = cluster for ecs_svc in ecs_description: if ecs_svc['networkConfiguration']['awsvpcConfiguration']['assignPublicIp'] == 'ENABLED': ecs_is_public = True except Exception as e: print(e) print("No ECS Services Definition: {}".format(resource["id"])) if ecs_is_public: is_compliant = False issue = "ECS {} is public via Public IP".format(resource["id"]) if remediate: for ecs_svc in ecs_description: is_compliant = remediation_make_ecs_private(resource, ecs, service_cluster,ecs_svc['networkConfiguration']['awsvpcConfiguration']) if not is_compliant: issue += " - Not remediated" send_notification(issue, "", resource) if is_compliant: print("ECS is private: {}".format(resource["id"])) return is_compliant
def enumerate_instances(elbv2, loadbalancer_arn, resource, remediate): is_compliant = True # get list of dev account(s) - keep it as list to add more dev accounts if os.environ.get("DEV_ACCOUNTS", None) is not None: dev_accounts = os.environ["DEV_ACCOUNTS"].split(",") else: dev_accounts = None if os.environ.get("EC2_INSTANCE_IGNORE_LIST", None) is not None: EC2_INSTANCE_IGNORE_LIST = os.environ[ "EC2_INSTANCE_IGNORE_LIST"].split(",") else: EC2_INSTANCE_IGNORE_LIST = None load_balancer = elbv2.describe_load_balancers( LoadBalancerArns=[loadbalancer_arn])["LoadBalancers"][0] loadbalancer_name = load_balancer["LoadBalancerName"] loadbalncer_type = load_balancer["Type"] loadbalancer_arn = load_balancer["LoadBalancerArn"] loadbalancer_scheme = load_balancer["Scheme"] if loadbalancer_scheme == "internet-facing" and resource[ "account"] in dev_accounts: target_attribute = elbv2.describe_target_groups( LoadBalancerArn=loadbalancer_arn) for target_group in target_attribute["TargetGroups"]: target_group_arn = target_group["TargetGroupArn"] target_group_health = elbv2.describe_target_health( TargetGroupArn=target_group_arn) for target in target_group_health["TargetHealthDescriptions"]: target_id = target["Target"].get("Id") if target_id.startswith("i-"): instance_elb_info = { "Id": target_id, "loadbalancer": loadbalancer_arn, "type": loadbalncer_type, "targetgroup": target_group_arn, } # check if instance is not whitelisted if target_id not in str(EC2_INSTANCE_IGNORE_LIST): is_compliant = False issue = "Dev EC2 {} is Public - via elbv2 {}".format( target_id, loadbalancer_name) if remediate: if not deregister_targets(elbv2, target_group_arn, target_id): issue += " - Not remediated" send_notification( issue, "Instance ELB Information: {}".format( ", ".join(instance_elb_info)), resource, ) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "ami": raise Exception("Mismatched type. Expected {} but received {}".format( "ami", resource["type"])) # Get a session in the account where this resource is ec2 = get_session_for_account(resource["account"], resource["region"], "ec2") image_attribute = ec2.describe_image_attribute( Attribute="launchPermission", ImageId=resource["id"]) # Get the accounts in the org all_account_in_org = fetch_all_accounts() description = ("image_attribute:" + str(image_attribute)) # Check the permissions launchpermission = image_attribute["LaunchPermissions"] # Check if it is shared publicly if "all" in str(launchpermission): is_compliant = False issue = "EC2 AMI %s in account %s is public" % ( resource["id"], resource["account"], ) if remediate: if not remediation_remove_all(ec2, resource): issue += " - Not remediated" send_notification(issue, description, resource) # Check if it is shared with any accounts that are not in the org if "UserId" in str(launchpermission): for userid in launchpermission: if userid["UserId"] not in str(all_account_in_org): is_compliant = False issue = "AMI %s in account %s is shared with unknown account %s" % ( resource["id"], resource["account"], userid["UserId"], ) if remediate: if not remediation_remove_userid(ec2, resource, userid): issue += " - Not remediated" send_notification(issue, description, resource) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "rds_snapshot": raise Exception("Mismatched type. Expected {} but received {}".format( "rds_snapshot", resource["type"])) # Get a session in the account where this resource is rds = get_session_for_account(resource["account"], resource["region"], "rds") snapshot = rds.describe_db_snapshot_attributes( DBSnapshotIdentifier=resource["id"]) if not snapshot: print("Resource {} not found".format(resource["id"])) return True attributes = snapshot.get("DBSnapshotAttributesResult", {}).get("DBSnapshotAttributes", {}) description = ("db_snapshot_details:" + str(snapshot) + "\n\n" + "snapshot_attribute:" + str(attributes)) all_account_in_org = fetch_all_accounts() # Look at the attributes to see if this snapshot is shared publicly or with unknown accounts for attribute in attributes: if attribute["AttributeName"] != "restore": continue for shared_id in attribute["AttributeValues"]: if shared_id == "all": is_compliant = False issue = "RDS DB snapshot %s in account %s is public" % ( resource["id"], resource["account"], ) if remediate: if not remediation_remove_all(rds, resource): issue += " - Not remediated" send_notification(issue, description, resource) elif shared_id not in str(all_account_in_org): is_compliant = False issue = ( "RDS DB snapshot %s in account %s is shared with unknown account" % (resource["id"], resource["account"])) if remediate: if not remediation_remove_shared(rds, resource, shared_id): issue += " - Not remediated" send_notification(issue, description, resource) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "elb": raise Exception( "Mismatched type. Expected {} but received {}".format( "elb", resource["type"] ) ) # get list of dev account(s) - keep it as list to add staging and future dev accounts if os.environ.get("DEV_ACCOUNTS", None) is not None: dev_accounts = os.environ["DEV_ACCOUNTS"].split(",") else: dev_accounts = None if os.environ.get("EC2_INSTANCE_IGNORE_LIST", None) is not None: EC2_INSTANCE_IGNORE_LIST = os.environ["EC2_INSTANCE_IGNORE_LIST"].split(",") else: EC2_INSTANCE_IGNORE_LIST = None # Get a session in the account where this resource is elb = get_session_for_account(resource["account"], resource["region"], "elb") load_balancer = elb.describe_load_balancers(LoadBalancerNames=[resource["id"]])[ "LoadBalancerDescriptions" ][0] load_balancer_scheme = load_balancer["Scheme"] # Add check for public facing dev ec2 instances if ( load_balancer_scheme == "internet-facing" and resource["account"] in dev_accounts ): is_compliant = check_public_dev_elb( load_balancer, elb, EC2_INSTANCE_IGNORE_LIST, resource, remediate ) # Check all required tags have been set assigned_tags = elb.describe_tags(LoadBalancerNames=[resource["id"]])[ "TagDescriptions" ][0].get("Tags", []) if is_missing_tags(assigned_tags): is_compliant = False issue = "ELB {} not compliant - Missing required tags - Not remediated".format( resource["id"] ) send_notification( issue, "Required tags: {}".format(", ".join(get_required_tags())), resource ) return is_compliant
def dev_public_ec2_remediation(ec2, resource, remediate): filters = [ { "Name": "instance-id", "Values": [ resource["id"], ] }, ] addresses = ec2.describe_addresses(Filters=filters).get("Addresses") if addresses: is_compliant = False for address in addresses: association_id = address.get("AssociationId") issue = "Dev EC2 {} is Public - via elastic ip".format( resource["id"]) if remediate: if not remediation_private_dev_instance( ec2, resource, association_id): issue += " - Not remediated" send_notification(issue, "", resource) filters_iface = [ { "Name": "attachment.instance-id", "Values": [ resource["id"], ] }, ] network_ifaces = ec2.describe_network_interfaces(Filters=filters_iface, ) for network_iface in network_ifaces["NetworkInterfaces"]: if "AssociationId" not in str(network_iface) and "PublicIp" in str( network_iface): is_compliant = False issue = "Dev EC2 {} is public via primary interface".format( resource["id"]) if remediate: if not remediation_terminate_ec2(ec2, resource, False): issue += " - Not remediated" send_notification(issue, "", resource) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "sqs": raise Exception( "Mismatched type. Expected {} but received {}".format( "sqs", resource["type"] ) ) # Get a session in the account where this resource is sqs = get_session_for_account(resource["account"], resource["region"], "sqs") # Get the policy policy_string = sqs.get_queue_attributes( QueueUrl=resource["id"], AttributeNames=["Policy"] ) if policy_string is None: return is_compliant policy_string = policy_string.get("Attributes", {}).get("Policy", {}) if len(policy_string) == 0: # Policy is empty or not present return is_compliant policy = json.loads(policy_string) description = "Policy " + policy_string # Check if it is public policy = Policy(policy) if policy.is_internet_accessible(): is_compliant = False issue = "SQS {} is public".format(resource["id"]) if remediate: if not remediation_make_private(sqs, resource): issue += " - Not remediated" send_notification(issue, description, resource) # TODO Check for unknown accounts being allowed access return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "kms_key": raise Exception("Mismatched type. Expected {} but received {}".format( "kms_key", resource["type"])) # Get a session in the account where this resource is kms = get_session_for_account(resource["account"], resource["region"], "kms") # Remediation for Key Policy key_policy_is_non_compliant = False try: bad_policies = find_bad_policies(resource, kms) if bad_policies: key_policy_is_non_compliant = True except Exception as e: print(e) print("No KMS Keys: {}".format(resource["id"])) if key_policy_is_non_compliant: is_compliant = False issue = "KMS Key: {} - has a non restrictive key policy".format( resource["id"]) if remediate: for policy in bad_policies: is_compliant = remediation_kms_policy(resource, kms, policy) if not is_compliant: issue += " - Not remediated" send_notification(issue, "", resource) if is_compliant: print("KMS Key is compliant: {}".format(resource["id"])) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "lambda_function": raise Exception( "Mismatched type. Expected {} but received {}".format( "lambda_function", resource["type"] ) ) # Get a session in the account where this resource is lambda_ = get_session_for_account(resource["account"], resource["region"], "lambda") lambda_is_public = False try: response_policy = lambda_.get_policy(FunctionName=resource["id"]) policy = json.loads(response_policy["Policy"]) policy = Policy(policy) lambda_is_public = policy.is_internet_accessible() except Exception as e: print(e) print("No lambda policy: {}".format(resource["id"])) if lambda_is_public: is_compliant = False issue = "Lambda {} is public via resource policy".format(resource["id"]) if remediate: for statement in policy.statements: if '*' in statement.principals: if not remediation_make_lambda_private(resource, lambda_, statement.statement["Sid"]): issue += " - Not remediated" send_notification(issue, "", resource) if is_compliant: print("lambda is private: {}".format(resource["id"])) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "ecs_task_set": raise Exception("Mismatched type. Expected {} but received {}".format( "ecs_task_set", resource["type"])) # Get a session in the account where this resource is ecs = get_session_for_account(resource["account"], resource["region"], "ecs") ecs_is_public = False try: ## List all ECS Clusters ecs_clusters = ecs.list_clusters() ## Now get all cluster ARNs from ECS clusters json cluster_arns = ecs_clusters["clusterArns"] ## For each cluster X Service: try to find the named taskSet identified_cluster = "" identified_service = "" task_sets_to_audit = [] for cluster in cluster_arns: print("ECS: Cluster ARN {}".format(cluster)) ## Get the ServiceARNs for each Cluster paginator = ecs.get_paginator('list_services') resp = paginator.paginate(cluster=cluster) service_arns = resp['serviceArns'] while 'nextToken' in resp: resp = paginator.paginate(cluster=cluster, nextToken=resp['nextToken']) service_arns = service_arns + resp['serviceArns'] ## Now Loop through every possible cluster arn x service arn for the the possible ECS Task Set: for service in service_arns: temp_task_set = ecs.describe_task_sets( cluster=cluster, service=service, task_sets=[resource['id']])['taskSets'] if len(temp_task_set) > 0: identified_cluster = cluster identified_service = service task_sets_to_audit += temp_task_set ## Using the task set that was identified ## Audit the task set to make sure that the there is no public ip assignment. ## If public ip is assigned then the only thing that can be done ## is deleting the task set bad_task_sets = [] for task_set in task_sets_to_audit: if task_set['networkConfiguration']['awsvpcConfiguration'][ 'assignPublicIp'] == 'ENABLED': bad_task_sets.append(task_set["id"]) ecs_is_public = True except Exception as e: print(e) print("No ECS Task Set Definition: {}".format(resource["id"])) if ecs_is_public: is_compliant = False for bad_task_set in bad_task_sets: issue = "ECS Task Set {} is public via Public IP Assignment".format( resource["id"]) if remediate: is_compliant = remediation_delete_task_set( resource, bad_task_set, ecs, identified_cluster, identified_service) if not is_compliant: issue += " - Not remediated" send_notification(issue, "", resource) if is_compliant: print("ECS is private: {}".format(resource["id"])) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "s3_bucket": raise Exception("Mismatched type. Expected {} but received {}".format( "s3_bucket", resource["type"])) buckets_to_ignore = os.environ.get("S3_BUCKET_IGNORE_LIST", "") if resource["id"] in buckets_to_ignore.split(","): return True # Get a session in the account where this resource is s3 = get_session_for_account(resource["account"], resource["region"], "s3") policy_is_public = False try: status = s3.get_bucket_policy_status(Bucket=resource["id"]) policy_is_public = status["PolicyStatus"]["IsPublic"] except Exception as e: print(e) print("No bucket policy: {}".format(resource["id"])) if policy_is_public: is_compliant = False issue = "S3 bucket {} is public".format(resource["id"]) if remediate: if not remediation_make_policy_private(s3, resource): issue += " - Not remediated" send_notification(issue, "", resource) acl_is_public = False acl = s3.get_bucket_acl(Bucket=resource["id"]) for i in range(len(acl["Grants"])): grantee_id = acl["Grants"][i]["Grantee"] if "http://acs.amazonaws.com/groups/global/AllUsers" in str( grantee_id ) or "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" in str( grantee_id): acl_is_public = True break if acl_is_public: is_compliant = False issue = "S3 bucket {} is public".format(resource["id"]) if remediate: if not remediation_make_acl_private(s3, resource): issue += " - Not remediated" send_notification(issue, "", resource) if is_compliant: print("bucket is private: {}".format(resource["id"])) # Ensure required tags exist assigned_tags = [] try: assigned_tags = s3.get_bucket_tagging(Bucket=resource["id"])["TagSet"] except Exception as e: # If no tags exist, we get an exception that doesn't appear to be defined to catch, so we generically # catch the exception and look for the key phrase to indicate this problem, and if we can't find it, we re-raise it if "NoSuchTagSet" not in str(e): raise e if is_missing_tags(assigned_tags): is_compliant = False issue = "S3 bucket {} not compliant - Missing required tags - Not remediated".format( resource["id"]) send_notification( issue, "Required tags: {}".format(", ".join(get_required_tags())), resource) # Check the bucket policy for some things policy = None try: policy_string = s3.get_bucket_policy(Bucket=resource["id"])["Policy"] policy = json.loads(policy_string) except Exception as e: if "NoSuchBucketPolicy" in str(e): print("No bucket policy for {}".format(resource["id"])) else: print(e) raise e if not denies_unencrypted_uploads(policy): #To-Do add a check for bucket encryption setting is_compliant = False return False issue = "S3 bucket {} not compliant - Does not deny unencrypted uploads".format( resource["id"]) if remediate: if not remediation_make_policy_private(s3, resource): issue += " - Not remediated" send_notification(issue, "", resource) if not denies_lack_of_tls(policy): is_compliant = False #To-Do add a check for bucket encryption setting return False issue = "S3 bucket {} not compliant - Does not deny non-TLS communications".format( resource["id"]) if remediate: if not remediation_make_policy_private(s3, resource): issue += " - Not remediated" send_notification(issue, "", resource) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "ecs_task": raise Exception( "Mismatched type. Expected {} but received {}".format( "ecs_task_set", resource["type"] ) ) # Get a session in the account where this resource is ecs = get_session_for_account(resource["account"], resource["region"], "ecs") ## In order to check if ECS is public, we need to perform an ENI lookup against EC2 ec2 = get_session_for_account(resource["account"], resource["region"], "ec2") ecs_is_public = False try: ## List all ECS Clusters ecs_clusters = ecs.list_clusters() ## Now get all cluster ARNs from ECS clusters json cluster_arns = ecs_clusters["clusterArns"] ## For each cluster list all tasks and find noncompliant tasks. non_compliant_tasks = [] task_cluster = "" for cluster in cluster_arns: all_cluster_tasks = ecs.list_tasks(cluster=cluster)["taskArns"] for task_ in all_cluster_tasks: ecs_tasks = ecs.describe_tasks(cluster=cluster,tasks=[task_])["tasks"] print("ECS TASK: {}".format(ecs_tasks)) # Check if ENI is public for task in ecs_tasks: # for each Task, look for the ElasticNetworkInterface (ENI) attachement and if there is, then check if ENI is public eni_is_public = False for attachment in task["attachments"]: if attachment["type"] == "ElasticNetworkInterface": eni_id = "" for el in attachment["details"]: if el["name"] == "networkInterfaceId": eni_id = el["value"] eni_desc = ec2.describe_network_interfaces(NetworkInterfaceIds=[eni_id]) ## Determine if ENI is public: for eni in eni_desc["NetworkInterfaces"]: # If there is a public IP assignment to the ENI, then the ENI is considered public if eni["Association"]["PublicIp"] != "": ecs_is_public = True task_cluster = cluster task_data = { "cluster": cluster, "taskArn": task["taskArn"] } non_compliant_tasks.append(task_data) break except Exception as e: print(e) print("No ECS Tasks: {}".format(resource["id"])) if ecs_is_public: is_compliant = False # Remediate every issue found in non_compliant_tasks for bad_task in non_compliant_tasks: issue = "ECS Task {} is public via public IP Assignment".format(bad_task["taskArn"]) if remediate: is_compliant = remediation_make_ecs_task_private(bad_task["taskArn"],ecs,bad_task["cluster"]) if not is_compliant: issue += " - Not remediated" send_notification(issue, "", resource) if is_compliant: print("ECS is private: {}".format(resource["id"])) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "ec2": raise Exception("Mismatched type. Expected {} but received {}".format( "ec2", resource["type"])) # get list of dev account(s) - keep it as list to add staging and future dev accounts if os.environ.get("DEV_ACCOUNTS", None) is not None: dev_accounts = os.environ["DEV_ACCOUNTS"].split(",") else: dev_accounts = None if os.environ.get("EC2_INSTANCE_IGNORE_LIST", None) is not None: EC2_INSTANCE_IGNORE_LIST = os.environ[ "EC2_INSTANCE_IGNORE_LIST"].split(",") else: EC2_INSTANCE_IGNORE_LIST = None # Get a session in the account where this resource is ec2 = get_session_for_account(resource["account"], resource["region"], "ec2") instances = ec2.describe_instances(InstanceIds=[resource["id"]]) # We looked for a specific instance id, so ensure it has been returned. if (len(instances["Reservations"]) != 1 or len(instances["Reservations"][0]["Instances"]) != 1): print("Resource {} not found".format(resource["id"])) return True instance = instances["Reservations"][0]["Instances"][0] # check for dev instances and include the whitelist for ec2 if resource["account"] in dev_accounts and resource[ "id"] not in EC2_INSTANCE_IGNORE_LIST: is_compliant = dev_public_ec2_remediation(ec2, resource, remediate) if instance["State"]["Name"] != "running": # Instance is stopped, or still starting # TODO If the instance is still starting, we should recheck it later. return True # Check if IMDSv2 is enforced if (instance["MetadataOptions"]["HttpEndpoint"] == "enabled" and instance["MetadataOptions"]["HttpTokens"] != "required"): # IMDS v1 is still allowed is_compliant = False issue = "EC2 {} not compliant - IMDSv1 still allowed".format( resource["id"]) if remediate: if not remediation_enforce_IMDSv2(ec2, resource): issue += " - Not remediated" send_notification(issue, "", resource) # Check the required tags have been set assigned_tags = instance.get("Tags", []) if is_missing_tags(assigned_tags): is_compliant = False issue = "EC2 {} not compliant - Missing required tags".format( resource["id"]) if remediate: if not remediation_stop_instance(ec2, resource): issue += " - Not remediated" send_notification( issue, "Required tags: {}".format(", ".join(get_required_tags())), resource) return is_compliant
def audit(resource, remediate=False): MAX_INACTIVE_PASSWORD_DAYS = 90 MAX_INACTIVE_ACCESS_KEY_DAYS = 90 is_compliant = True if resource["type"] != "iam_user": raise Exception("Mismatched type. Expected {} but received {}".format( "iam_user", resource["type"])) # Get a session in the account where this resource is iam = get_session_for_account(resource["account"], resource["region"], "iam") description = "" user = iam.get_user(UserName=resource["id"])["User"] create_date = user["CreateDate"] utc = UTC() t_minus_1_days = datetime.now(utc) - timedelta(days=1) login_create_date = False # If there is a login profile, then this user has a console login (ie. a password) try: login_profile = iam.get_login_profile(UserName=resource["id"]) login_create_date = login_profile.get("LoginProfile", {}).get("CreateDate", False) except iam.exceptions.NoSuchEntityException: # No login profile print("No login profile found") pass if login_create_date: # User has a password login # Ensure they have an MFA user_mfa = iam.list_mfa_devices(UserName=resource["id"]) if (len(user_mfa["MFADevices"]) == 0 and login_profile and login_create_date < t_minus_1_days): # User has no MFA device, but does have a password login, and their password login was created more than 1 day ago is_compliant = False issue = "IAM user {} in account {} has a password login but no MFA".format( resource["id"], resource["account"]) print(issue) if remediate: if not remediation_remove_password(iam, resource, "lack of MFA"): issue += " - Not remediated" send_notification(issue, description, resource) # Ensure they've logged in within MAX_INACTIVE_PASSWORD_DAYS last_login = user.get("PasswordLastUsed", None) t_minus_max_inactive_password_days = datetime.now(utc) - timedelta( days=MAX_INACTIVE_PASSWORD_DAYS) if last_login is None: # User has never logged in. Check how old this user is. if login_create_date < t_minus_max_inactive_password_days: is_compliant = False issue = "IAM user {} in account {} has not logged in ever, and their user was created more than {} days ago".format( resource["id"], resource["account"], MAX_INACTIVE_PASSWORD_DAYS) print(issue) if remediate: if not remediation_remove_password( iam, resource, "password inactive for over {} days".format( MAX_INACTIVE_PASSWORD_DAYS), ): issue += " - Not remediated" send_notification(issue, description, resource) else: # User has logged in. Check long ago it was. if last_login < t_minus_max_inactive_password_days: # User has not logged in for more than MAX_INACTIVE_PASSWORD_DAYS is_compliant = False issue = "IAM user {} in account {} has a password, but has not logged in for over {} days".format( resource["id"], resource["account"], MAX_INACTIVE_PASSWORD_DAYS) if remediate: if not remediation_remove_password( iam, resource, "password inactive for over {} days".format( MAX_INACTIVE_PASSWORD_DAYS), ): issue += " - Not remediated" send_notification(issue, description, resource) # Get access keys for the user keys = iam.list_access_keys(UserName=resource["id"]) t_minus_max_inactive_key_days = datetime.now(utc) - timedelta( days=MAX_INACTIVE_ACCESS_KEY_DAYS) for k in keys["AccessKeyMetadata"]: last_used_response = iam.get_access_key_last_used( AccessKeyId=k["AccessKeyId"]) last_used_date = last_used_response["AccessKeyLastUsed"].get( "LastUsedDate", None) if last_used_date is None: if k["CreateDate"] < t_minus_max_inactive_key_days: # Access key is old and unused is_compliant = False issue = "IAM user {} in account {} has an access key that has not been used for over {} days".format( resource["id"], resource["account"], MAX_INACTIVE_ACCESS_KEY_DAYS) if remediate: if not remediation_remove_access_key( iam, resource, k["AccessKeyId"]): issue += " - Not remediated" send_notification(issue, description, resource) elif last_used_date < t_minus_max_inactive_key_days: # Access key has not been used for over 100 days is_compliant = False issue = "IAM user {} in account {} has an access key that has not been used for over {} days".format( resource["id"], resource["account"], MAX_INACTIVE_ACCESS_KEY_DAYS) if remediate: if not remediation_remove_access_key(iam, resource, k["AccessKeyId"]): issue += " - Not remediated" send_notification(issue, description, resource) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "redshift": raise Exception("Mismatched type. Expected {} but received {}".format( "redshift", resource["type"])) # Get a session in the account where this resource is redshift = get_session_for_account(resource["account"], resource["region"], "redshift") cluster = redshift.describe_clusters( ClusterIdentifier=resource["id"])["Clusters"][0] if cluster[ "ClusterStatus"] != "available" or "ClusterCreateTime" not in cluster: # For clusters that are still starting we should check again repeat_invocation(resource) return True if cluster["PubliclyAccessible"]: is_compliant = False issue = "Redshift {} is not compliant - Is public".format( resource["id"]) if remediate: if not remediation_make_private(redshift, resource): issue += " - Not remediated" send_notification(issue, "", resource) if not cluster["Encrypted"]: is_compliant = False issue = "Redshift {} is not compliant - Not encrypted".format( resource["id"]) if remediate: if not remediation_make_encrypted(redshift, resource): issue += " - Not remediated" send_notification(issue, "", resource) if is_missing_tags(cluster["Tags"]): is_compliant = False issue = "Redshift {} not compliant - Missing required tags - Not remediated".format( resource["id"]) # You cannot stop a redshift cluster, so we just file the issue send_notification( issue, "Required tags: {}".format(", ".join(get_required_tags())), resource) # Check that access requires TLS requires_tls = False for param_group in cluster["ClusterParameterGroups"]: # You can have multiple parameter groups applied to a redshift cluster that have different settings. # I believe if one requires TLS then that must win, so I just ensure that at least one of the active # parameters groups has this setting. # Only look at parameter groups that are in-sync if param_group["ParameterApplyStatus"] != "in-sync": continue # Look through the parameters for require_ssl and ensure it is set to "true" parameters = redshift.describe_cluster_parameters( ParameterGroupName=param_group["ParameterGroupName"])["Parameters"] for parameter in parameters: if (parameter["ParameterName"] == "require_ssl" and parameter["ParameterValue"] == "true"): requires_tls = True if not requires_tls: is_compliant = False issue = "Redshift {} not compliant - Not enforcing TLS - Not remediated".format( resource["id"]) # You cannot stop a redshift cluster, so we just file the issue send_notification(issue, "", resource) return is_compliant
def audit(resource, remediate=False): is_compliant = True if resource["type"] != "rds": raise Exception("Mismatched type. Expected {} but received {}".format( "rds", resource["type"])) # Get a session in the account where this resource is rds = get_session_for_account(resource["account"], resource["region"], "rds") instance = rds.describe_db_instances(DBInstanceIdentifier=resource["id"]) if not instance: print("Resource {} not found".format(resource["id"])) return True instance = instance["DBInstances"][0] encrypted = instance["StorageEncrypted"] public_access = instance["PubliclyAccessible"] dbstatus = instance["DBInstanceStatus"] # Only check databases that were started within the past 60 minutes time_difference = int(os.environ.get("db_check_time", 60)) utc = UTC() threshold_check = datetime.now(utc) - timedelta(minutes=time_difference) # Ignore instances that are not running if dbstatus in ["stopped"]: return True if dbstatus not in ["available"]: # For databases that are still starting we should check again repeat_invocation(resource) return True if "InstanceCreateTime" in instance: db_create_time = instance["InstanceCreateTime"] if db_create_time <= threshold_check: if not encrypted: is_compliant = False issue = "RDS {} not compliant - Storage not Encrypted".format( resource["id"]) if remediate: if not remediation_stop_instance(rds, resource, instance): issue += " - Not remediated" send_notification(issue, "", resource) if public_access: is_compliant = False issue = "RDS {} not compliant - PubliclyAccessible".format( resource["id"]) if remediate: if not remediation_make_private(rds, resource): issue += " - Not remediated" send_notification(issue, "", resource) # Get tags for database assigned_tags = rds.list_tags_for_resource( ResourceName=instance["DBInstanceArn"])["TagList"] if is_missing_tags(assigned_tags): is_compliant = False issue = "RDS {} not compliant - Missing required tags".format( resource["id"]) #only notify if rds has no tags, uncomment to stop instances with no tag if remediate: if not remediation_stop_instance(rds, resource, instance): issue += " - Not remediated" send_notification( issue, "Required tags: {}".format(", ".join(get_required_tags())), resource) return is_compliant