Skip to content

Commit

Permalink
Added GuardDuty/list_findings.py
Browse files Browse the repository at this point in the history
  • Loading branch information
kyhau committed Mar 20, 2020
1 parent fbb5271 commit 1d5c412
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .aliases
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ alias aws-ec2-unreachable='python ${ARKI_REPO_HOME}/EC2-VPC/list_ec2_unreachable
alias aws-ddb-start-localddb='. ${ARKI_REPO_HOME}/DynamoDB/start_dynamodb_local.sh'
alias aws-ddb-start-localddb-container='. ${ARKI_REPO_HOME}/DynamoDB/start_dynamodb_local_docker.sh'

alias aws-guardduty-findings='python ${ARKI_REPO_HOME}/GuardDuty/list_findings.py'

alias aws-rds-instances='python ${ARKI_REPO_HOME}/RDS/list_rds_instances.py'

alias aws-s3-size='aws s3 ls --summarize --human-readable --recursive s3://'
Expand Down
131 changes: 131 additions & 0 deletions GuardDuty/list_findings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from boto3.session import Session
from botocore.exceptions import ClientError
import click
from configparser import ConfigParser
import logging
from os.path import expanduser, join
import yaml

# Update the root logger to get messages at DEBUG and above
logging.getLogger().setLevel(logging.DEBUG)
logging.getLogger("botocore").setLevel(logging.CRITICAL)
logging.getLogger("boto3").setLevel(logging.CRITICAL)
logging.getLogger("urllib3").setLevel(logging.CRITICAL)

aws_profiles = []
try:
cp = ConfigParser()
cp.read(join(expanduser("~"), ".aws", "credentials"))
aws_profiles = cp.sections()
except Exception as e:
logging.error(e)

accounts_processed = []

# See https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings.html#guardduty_findings-severity
severity_defaults = {
"high": 7,
"medium": 4,
"low": 0,
}


def list_detectors(client):
ret = []
paginator = client.get_paginator("list_detectors")
for page in paginator.paginate():
l = page.get("DetectorIds")
if l is not None:
ret.extend(l)
return ret


def get_finding_ids(client, detector_id, num, finding_type, severity):
finding_criteria = {
"Criterion": {
"severity": {"Gte": severity_defaults[severity]},
}
}
if finding_type:
finding_criteria["Criterion"]["type"] = {"Eq": [finding_type]}

return client.list_findings(
DetectorId=detector_id,
FindingCriteria=finding_criteria,
SortCriteria={"AttributeName": "createdAt", "OrderBy": "DESC"},
MaxResults=num,
).get("FindingIds", [])


def list_action(session, aws_region, profile, finding_id, num, finding_type, severity):
account_id = session.client("sts").get_caller_identity()["Account"]
if account_id in accounts_processed:
return
accounts_processed.append(account_id)

regions = session.get_available_regions("guardduty") if aws_region == "all" else [aws_region]
for region in regions:
logging.debug(f"Checking {account_id} {profile} {region}")
try:
client = session.client("guardduty", region_name=region)

detector_ids = list_detectors(client)
if not detector_ids:
logging.info(f"No detector is found. Skipped {account_id} {region}.")
continue

for detector_id in detector_ids:
finding_ids = [finding_id] if finding_id else get_finding_ids(client, detector_id, num, finding_type, severity)
findings = client.get_findings(DetectorId=detector_id, FindingIds=finding_ids).get("Findings", [])
for finding in findings:
print("--------------------------------------------------------------------------------")
print(yaml.dump(finding))

if finding_id and findings:
return findings[0]

except ClientError as e:
error_code = e.response["Error"]["Code"]
if error_code in ["AuthFailure", "UnrecognizedClientException"]:
logging.warning(f"Unable to process region {region}: {error_code}")
elif error_code == "AccessDenied":
logging.warning(f"Unable to process account {account_id}: {e}")
elif error_code == "InvalidInstanceID.NotFound":
pass
else:
raise
except Exception as e:
logging.error(e)
import traceback
traceback.print_exc()


################################################################################
# Entry point

@click.command()
@click.option("--profile", "-p", help="AWS profile name.")
@click.option("--findingid", "-i", help="Finding ID. Optional.", default=None)
@click.option("--num", "-n", help="Number of records returned for each detector/account/region. Default: 5.", default=5)
@click.option("--findingtype", "-t", help="Recon Finding Type (e.g. Recon:IAMUser/UserPermissions). Optional.", default=None)
@click.option("--severity", "-s", help="Severity: high, medium, low. Default: high.", default="high")
@click.option("--region", "-r", help="AWS Region; use 'all' for all regions. Default: ap-southeast-2.", default="ap-southeast-2")
#@click.option("--detailed", "-d", is_flag=True)
def main(profile, region, findingid, num, findingtype, severity):
profile_names = [profile] if profile else aws_profiles

for profile_name in profile_names:
try:
session = Session(profile_name=profile_name)
ret = list_action(session, region, profile_name, findingid, num, findingtype, severity)
if ret is not None:
break

except ClientError as e:
if e.response["Error"]["Code"] == "ExpiredToken":
logging.warning(f"{profile_name} token expired. Skipped")
else:
raise


if __name__ == "__main__": main()
5 changes: 3 additions & 2 deletions SSM/Patching/SSM-PatchManager-CommonResources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ Parameters:
BucketName:
Description: The name of the S3 bucket to be created.
Type: String
NotificationEmailAddress:
NotificationEmail:
Description: Email address for the notification of Patch Management events.
Type: String

Resources:
PatchManagerLogBucket:
Type: AWS::S3::Bucket
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
Properties:
BucketName: !Ref BucketName
Expand All @@ -34,7 +35,7 @@ Resources:
Properties:
TopicName: PatchManagerNotification
Subscription:
- Endpoint: !Ref NotificationEmailAddress
- Endpoint: !Ref NotificationEmail
Protocol: email
Tags:
- Key: Billing
Expand Down
59 changes: 15 additions & 44 deletions SSM/Patching/SSM-PatchManager-RunPatchBaseline-Install-Window.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@ Description: >-
Each maintenance window has a schedule, a duration, a set of registered targets, and a set of registered tasks.
Parameters:
DocumentHash:
Description: >-
Document hash of AWS-RunPatchBaseline.
This value tends to not change from Account to Account unless it is updated by AWS.
You can verify it here: AWS Systems Manager/Documents/Document Name: AWS-RunPatchBaseline.
Type: String
Default: 7fe2f2bc4494ad06537fa13b9b59e1671d9adce4571708316a323eafcc1e398a
NotificationEmailAddress:
NotificationEmail:
Description: Email address for the notification of Patch Management events.
Type: String
PatchGroupValue:
Expand All @@ -28,6 +21,7 @@ Parameters:
Type: String
Default: Mon1am
AllowedValues:
- Every15min
- Mon1am
- Tue1am
ConstraintDescription: must be a supported schedule
Expand All @@ -38,12 +32,14 @@ Conditions:
Mappings:
Schedules:
Every15min:
Schedule: "cron(0/15 * * * ? *)"
TagValue: "Every 15min"
Mon1am:
Schedule: "cron(0 1 ? * MON *)"
TagMaintenanceWindow: "Mon 1am"
TagValue: "Mon 1am"
Tue1am:
Schedule: "cron(0 1 ? * TUE *)"
TagMaintenanceWindow: "Tue 1am"
TagValue: "Tue 1am"

Resources:
MaintenanceWindow:
Expand All @@ -55,40 +51,31 @@ Resources:
Cutoff: 2
Description: !Join ["", [
"Maintenance window (",
!FindInMap [Schedules, !Ref ScheduleWindowName, TagMaintenanceWindow],
!FindInMap [Schedules, !Ref ScheduleWindowName, TagValue],
") for performing patching operations on instances using AWS-RunPatchBaseline"]]
# The duration of the maintenance window in hours (1-24).
Duration: 3
Name: !Sub ${AWS::StackName}-Window
Schedule: !FindInMap [Schedules, !Ref ScheduleWindowName, Schedule]
ScheduleTimezone: !Ref ScheduleTimeZoneIana
Tags:
- Key: Project
Value: PatchManagement
- Key: Group
Value: PatchManagement
- Key: Description
Value: Perform patching operations on instances using AWS-RunPatchBaseline
- Key: User
Value: !Ref NotificationEmailAddress
- Key: CostCentre
- Key: Billing
Value: PatchManagement

MaintenanceWindowTarget:
Type: AWS::SSM::MaintenanceWindowTarget
DependsOn: MaintenanceWindow
Properties:
Name: !Sub ${AWS::StackName}-Target
Description: !Join ["", [
"Patch EC2 instances with tag Maintenance Window (",
!FindInMap [Schedules, !Ref ScheduleWindowName, TagMaintenanceWindow],
!FindInMap [Schedules, !Ref ScheduleWindowName, TagValue],
")"]]
WindowId: !Ref MaintenanceWindow
ResourceType: INSTANCE
Targets:
- Key: "tag:Maintenance Window"
Values:
- !FindInMap [Schedules, !Ref ScheduleWindowName, TagMaintenanceWindow]
- !FindInMap [Schedules, !Ref ScheduleWindowName, TagValue]
- !If
- PatchGroupSpecified
- Key: "tag:Patch Group"
Expand Down Expand Up @@ -130,15 +117,7 @@ Resources:
- !ImportValue PatchManagerLogBucketArn
- !Join [/, [!ImportValue PatchManagerLogBucketArn, "*"]]
Tags:
- Key: Project
Value: PatchManagement
- Key: Group
Value: PatchManagement
- Key: Description
Value: Perform patching operations on instances using AWS-RunPatchBaseline
- Key: User
Value: !Ref NotificationEmailAddress
- Key: CostCentre
- Key: Billing
Value: PatchManagement

RunCommandSnsNotificationServiceRole:
Expand All @@ -165,20 +144,11 @@ Resources:
Resource:
- "*"
Tags:
- Key: Project
Value: PatchManagement
- Key: Group
Value: PatchManagement
- Key: Description
Value: Perform patching operations on instances using AWS-RunPatchBaseline
- Key: User
Value: !Ref NotificationEmailAddress
- Key: CostCentre
- Key: Billing
Value: PatchManagement

MaintenanceWindowRunCommandTask:
Type: AWS::SSM::MaintenanceWindowTask
DependsOn: [MaintenanceWindow, MaintenanceWindowTarget, MaintenanceWindowServiceRole, RunCommandSnsNotificationServiceRole]
Properties:
Description: Install patches from a patch baseline to a Linux or Windows instances using AWS-RunPatchBaseline.
MaxConcurrency: 50%
Expand All @@ -194,8 +164,6 @@ Resources:
TaskInvocationParameters:
MaintenanceWindowRunCommandParameters:
Comment: Install patches from a patch baseline to a Linux or Windows instances using AWS-RunPatchBaseline.
DocumentHash: !Ref DocumentHash
DocumentHashType: Sha256
NotificationConfig:
NotificationArn: !ImportValue PatchManagerSnsTopicArn
NotificationEvents: # Valid values: All, InProgress, Success, TimedOut, Cancelled, Failed
Expand Down Expand Up @@ -226,6 +194,9 @@ Outputs:
MaintenanceWindowID:
Description: Maintenance window ID
Value: !Ref MaintenanceWindow
MaintenanceWindowServiceRoleArn:
Description: ARN of the IAM service role that allows Maintenance Windows to interact with other AWS services.
Value: !GetAtt MaintenanceWindowServiceRole.Arn
RunCommandSnsNotificationServiceRoleArn:
Description: ARN of the IAM service role to use to publish SNS notifications for maintenance window Run Command tasks.
Value: !GetAtt RunCommandSnsNotificationServiceRole.Arn
Loading

0 comments on commit 1d5c412

Please sign in to comment.