diff --git a/README.md b/README.md index 437fba5a..c235c108 100644 --- a/README.md +++ b/README.md @@ -279,6 +279,9 @@ Supports basic HTTP authentication and Bearer token authentication via the SDK. [shopify-customer](shopify-customer): Facilitates customer self-service through the Shopify Customer Account API. Supports authenticated customer operations, including viewing and updating profiles, managing address books (list, create, update, delete, set default), and accessing order history. Features OAuth 2.0 with PKCE authentication helpers. +### AWS Security + +[aws](aws): Comprehensive AWS security monitoring integration for Security Hub, GuardDuty, CloudWatch, and CloudTrail. Supports Security Hub findings management (list, get details, update workflow status with notes, get security insights with enriched results), GuardDuty threat detection (list detectors, list and filter findings, get finding details, archive findings), CloudWatch metrics and alarms (list metrics, get metric data with time ranges, describe alarms by state/name/prefix, get alarm history with filtering, set alarm state for testing), CloudWatch Logs (describe log groups, filter log events with pattern matching, get log events from specific streams), and CloudTrail audit logging (lookup events by attribute with date filtering, describe trails, get trail status, get event selectors). Features custom authentication with AWS access key, secret key, and region configuration, async boto3 execution via thread pool executor, robust serialization for datetime/Decimal/bytes types, AWS-specific error code extraction, and parallel insight result fetching with asyncio.gather. Includes 20 actions across 5 AWS services covering security posture assessment, threat detection, infrastructure monitoring, log analysis, and audit trail investigation. Ideal for security operations, compliance monitoring, incident response, and cloud infrastructure observability workflows. ### Companies Register [companies-register](companies-register): Integration with the New Zealand Companies Register API v2 (MBIE) for managing registered NZ companies. Supports retrieving full company details and ETag snapshots, listing and updating company contacts (addresses, phone, email) with concurrency control via If-Match headers, adding new contacts for pre-incorporated companies, searching the NZ Post address file for valid delivery point identifiers, and filing annual returns with direct debit or credit card payment. Implements the UUID vs NZBN lifecycle rule — UUID for pre-incorporated companies, NZBN for registered companies (status 50). Address updates require a future effectiveDate (minimum 5 working days for Registered Office and Address for Service). Annual return declaration must be the exact certified statement string. Features dual authentication via Azure API Management subscription key and RealMe OAuth 2.0. Comprises 6 actions covering company details, contacts, address search, and annual return filing. Ideal for NZ business compliance automation, company maintenance workflows, and annual return management. diff --git a/aws/README.md b/aws/README.md new file mode 100644 index 00000000..005a974d --- /dev/null +++ b/aws/README.md @@ -0,0 +1,458 @@ +# AWS Integration for Autohive + +AWS security and monitoring integration covering Security Hub, GuardDuty, CloudWatch, and CloudTrail. + +## Description + +This integration provides access to core AWS security and monitoring services for investigating security findings, monitoring infrastructure metrics and logs, and auditing API activity. It uses boto3 directly with custom authentication (AWS access keys) and implements 20 actions across 5 service areas. + +**Services covered:** +- **AWS Security Hub** -- Centralized security findings and compliance insights +- **Amazon GuardDuty** -- Threat detection and finding management +- **Amazon CloudWatch Metrics & Alarms** -- Infrastructure metrics, alarms, and alarm history +- **Amazon CloudWatch Logs** -- Log group discovery and log event search +- **AWS CloudTrail** -- API activity auditing and trail configuration + +## Setup & Authentication + +This integration uses **custom authentication** with AWS IAM credentials. + +### Prerequisites + +1. An AWS account with the services you plan to use enabled (Security Hub, GuardDuty, etc.) +2. An IAM user with programmatic access (access key ID and secret access key) + +### Creating IAM Credentials + +1. Sign in to the [AWS IAM Console](https://console.aws.amazon.com/iam/) +2. Go to **Users** and select or create a user for Autohive +3. Under the **Security credentials** tab, click **Create access key** +4. Select **Third-party service** as the use case +5. Copy the **Access Key ID** and **Secret Access Key** (the secret is only shown once) + +### Setup Steps in Autohive + +1. Add the AWS integration in Autohive +2. Enter your **AWS Access Key ID** +3. Enter your **AWS Secret Access Key** +4. Enter your **AWS Region** (e.g. `us-east-1`, `eu-west-1`, `ap-southeast-2`) +5. Save and start using the integration actions + +### Required IAM Permissions + +For read-only access to all services, attach the **SecurityAudit** AWS managed policy to the IAM user. This covers all read actions across Security Hub, GuardDuty, CloudWatch, CloudTrail, and CloudWatch Logs. + +For the three write actions in this integration, add a custom inline policy with these additional permissions: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "securityhub:BatchUpdateFindings", + "guardduty:ArchiveFindings", + "cloudwatch:SetAlarmState" + ], + "Resource": "*" + } + ] +} +``` + +**Summary of permissions:** + +| Policy / Permission | Covers | +|---|---| +| `SecurityAudit` (managed policy) | All read actions across all 5 services | +| `securityhub:BatchUpdateFindings` | `update_finding_workflow` action | +| `guardduty:ArchiveFindings` | `archive_findings` action | +| `cloudwatch:SetAlarmState` | `set_alarm_state` action | + +## Action Results + +All actions return a standardized response structure: +- `result` (boolean): Indicates whether the action succeeded (true) or failed (false) +- `error` (string, optional): Contains error message if the action failed +- `error_code` (string, optional): AWS error code if the action failed +- Additional action-specific data fields + +## Actions (20 Total) + +### Security Hub (4 actions) + +#### `get_findings` +List and filter security findings from AWS Security Hub. + +**Inputs:** +- `filters` (optional): AWS Security Hub filter criteria in the GetFindings API format (e.g. SeverityLabel, ComplianceStatus, ResourceType filters) +- `max_results` (optional): Maximum number of findings to return (default: 20, max: 100) +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `findings`: List of Security Hub findings +- `next_token`: Pagination token for the next page of results +- `result`: Success status + +--- + +#### `get_finding_details` +Get detailed information about a specific Security Hub finding by its ARN. + +**Inputs:** +- `finding_arn` (required): The ARN of the Security Hub finding to retrieve + +**Outputs:** +- `finding`: The Security Hub finding details (or null if not found) +- `result`: Success status + +--- + +#### `update_finding_workflow` +Update the workflow status of one or more Security Hub findings. + +**Inputs:** +- `finding_arns` (required): List of finding ARNs to update +- `workflow_status` (required): The new workflow status to set. One of: `NEW`, `NOTIFIED`, `RESOLVED`, `SUPPRESSED` +- `note` (optional): Note to add to the findings + +**Outputs:** +- `processed_findings`: List of findings that were successfully updated +- `unprocessed_findings`: List of findings that could not be updated +- `result`: Success status + +--- + +#### `get_insights` +Get security insight results from AWS Security Hub. + +**Inputs:** +- `insight_arns` (optional): List of insight ARNs to retrieve. If not specified, returns all insights. +- `max_results` (optional): Maximum number of insights to return (default: 20) +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `insights`: List of Security Hub insights with name, filters, group_by_attribute, and results +- `next_token`: Pagination token for the next page of results +- `result`: Success status + +--- + +### GuardDuty (4 actions) + +#### `list_detectors` +List all GuardDuty detector IDs in the current AWS account and region. + +**Inputs:** +- `max_results` (optional): Maximum number of detector IDs to return (default: 50) +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `detector_ids`: List of GuardDuty detector IDs +- `next_token`: Pagination token for the next page of results +- `result`: Success status + +--- + +#### `list_guardduty_findings` +List and filter GuardDuty findings for a specific detector. + +**Inputs:** +- `detector_id` (required): The ID of the GuardDuty detector +- `finding_criteria` (optional): Criteria to filter findings (e.g. by severity, type, or resource) +- `sort_criteria` (optional): Criteria for sorting results (attribute name and order direction) +- `max_results` (optional): Maximum number of finding IDs to return (default: 50) +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `finding_ids`: List of GuardDuty finding IDs +- `next_token`: Pagination token for the next page of results +- `result`: Success status + +--- + +#### `get_guardduty_finding_details` +Get detailed information about one or more GuardDuty findings. + +**Inputs:** +- `detector_id` (required): The ID of the GuardDuty detector +- `finding_ids` (required): List of finding IDs to retrieve details for + +**Outputs:** +- `findings`: List of detailed GuardDuty findings +- `result`: Success status + +--- + +#### `archive_findings` +Archive one or more GuardDuty findings by their IDs. + +**Inputs:** +- `detector_id` (required): The ID of the GuardDuty detector +- `finding_ids` (required): List of finding IDs to archive + +**Outputs:** +- `success`: Whether the findings were successfully archived +- `result`: Success status + +--- + +### CloudWatch Metrics & Alarms (5 actions) + +#### `list_metrics` +List available CloudWatch metrics, optionally filtered by namespace, name, or dimensions. + +**Inputs:** +- `namespace` (optional): The namespace to filter metrics by (e.g. `AWS/EC2`, `AWS/RDS`, `AWS/Lambda`) +- `metric_name` (optional): The metric name to filter by (e.g. `CPUUtilization`, `NetworkIn`) +- `dimensions` (optional): List of dimension filters, each with Name and Value +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `metrics`: List of CloudWatch metrics with namespace, name, and dimensions +- `next_token`: Pagination token for the next page of results +- `result`: Success status + +--- + +#### `get_metric_data` +Retrieve CloudWatch metric statistics for one or more metrics over a specified time period. + +**Inputs:** +- `metric_data_queries` (required): List of metric data queries. Each query requires an `id` and either a `metric_stat` (with metric, period, stat) or an `expression`. +- `start_time` (required): Start of the time range in ISO 8601 format (e.g. `2024-01-01T00:00:00Z`) +- `end_time` (required): End of the time range in ISO 8601 format (e.g. `2024-01-02T00:00:00Z`) + +**Outputs:** +- `metric_data_results`: List of metric data results with timestamps and values +- `result`: Success status + +--- + +#### `describe_alarms` +List and filter CloudWatch alarms by name, prefix, or state. + +**Inputs:** +- `alarm_names` (optional): List of specific alarm names to retrieve +- `alarm_name_prefix` (optional): Prefix to filter alarm names by +- `state_value` (optional): Filter alarms by state. One of: `OK`, `ALARM`, `INSUFFICIENT_DATA` +- `max_records` (optional): Maximum number of alarm records to return (default: 50) +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `metric_alarms`: List of metric alarms +- `composite_alarms`: List of composite alarms +- `next_token`: Pagination token for the next page of results +- `result`: Success status + +--- + +#### `get_alarm_history` +Retrieve the history of state changes and actions for CloudWatch alarms. + +**Inputs:** +- `alarm_name` (optional): The name of the alarm to get history for. If not specified, returns history for all alarms. +- `alarm_types` (optional): Filter by alarm type (`MetricAlarm`, `CompositeAlarm`) +- `history_item_type` (optional): Filter by history item type. One of: `ConfigurationUpdate`, `StateUpdate`, `Action` +- `start_date` (optional): Start of the date range in ISO 8601 format +- `end_date` (optional): End of the date range in ISO 8601 format +- `max_records` (optional): Maximum number of history items to return (default: 50) +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `alarm_history_items`: List of alarm history items with timestamp, type, and summary +- `next_token`: Pagination token for the next page of results +- `result`: Success status + +--- + +#### `set_alarm_state` +Temporarily set the state of a CloudWatch alarm for testing or maintenance purposes. + +**Inputs:** +- `alarm_name` (required): The name of the alarm to set the state for +- `state_value` (required): The state value to set. One of: `OK`, `ALARM`, `INSUFFICIENT_DATA` +- `state_reason` (required): A human-readable reason for the state change + +**Outputs:** +- `success`: Whether the alarm state was successfully set +- `result`: Success status + +--- + +### CloudWatch Logs (3 actions) + +#### `describe_log_groups` +List CloudWatch Logs log groups, optionally filtered by name prefix. + +**Inputs:** +- `log_group_name_prefix` (optional): Prefix to filter log group names by +- `limit` (optional): Maximum number of log groups to return (default: 50, max: 50) +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `log_groups`: List of log groups with name, ARN, creation time, and stored bytes +- `next_token`: Pagination token for the next page of results +- `result`: Success status + +--- + +#### `filter_log_events` +Search and filter log events across one or more log streams within a log group. + +**Inputs:** +- `log_group_name` (required): The name of the log group to search +- `log_stream_names` (optional): List of log stream names to search within +- `filter_pattern` (optional): CloudWatch Logs filter pattern to match events (e.g. `ERROR`, `{ $.statusCode = 500 }`) +- `start_time` (optional): Start of the time range in ISO 8601 format +- `end_time` (optional): End of the time range in ISO 8601 format +- `limit` (optional): Maximum number of events to return (default: 50, max: 10000) +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `events`: List of matching log events with timestamp, message, and log stream name +- `searched_log_streams`: List of log streams that were searched +- `next_token`: Pagination token for the next page of results +- `result`: Success status + +--- + +#### `get_log_events` +Get log events from a specific log stream in a log group. + +**Inputs:** +- `log_group_name` (required): The name of the log group +- `log_stream_name` (required): The name of the log stream +- `start_time` (optional): Start of the time range in ISO 8601 format +- `end_time` (optional): End of the time range in ISO 8601 format +- `limit` (optional): Maximum number of events to return (default: 50, max: 10000) +- `start_from_head` (optional): If true, return events from the oldest first. If false, return from the newest first (default: true). +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `events`: List of log events with timestamp and message +- `next_forward_token`: Token for fetching the next set of events going forward in time +- `next_backward_token`: Token for fetching the next set of events going backward in time +- `result`: Success status + +--- + +### CloudTrail (4 actions) + +#### `lookup_events` +Search CloudTrail management events by attributes such as event name, user, or resource. + +**Inputs:** +- `lookup_attributes` (optional): List of lookup attributes to filter events. Each item has `attribute_key` (e.g. EventName, Username, ResourceType, ResourceName, EventSource) and `attribute_value`. +- `start_time` (optional): Start of the time range in ISO 8601 format +- `end_time` (optional): End of the time range in ISO 8601 format +- `max_results` (optional): Maximum number of events to return (default: 50, max: 50) +- `next_token` (optional): Pagination token from a previous request + +**Outputs:** +- `events`: List of CloudTrail events with event name, time, user, and resources +- `next_token`: Pagination token for the next page of results +- `result`: Success status + +--- + +#### `describe_trails` +List configured CloudTrail trails in the account. + +**Inputs:** +- `trail_name_list` (optional): List of trail names or ARNs to describe. If not specified, returns all trails. +- `include_shadow_trails` (optional): Whether to include shadow trails (replications of trails in other regions). Default: true. + +**Outputs:** +- `trails`: List of CloudTrail trail configurations +- `result`: Success status + +--- + +#### `get_trail_status` +Get the current logging status and latest delivery information for a CloudTrail trail. + +**Inputs:** +- `trail_name` (required): The trail name or ARN to get status for + +**Outputs:** +- `trail_status`: Trail status including logging state, latest delivery time, and any delivery errors +- `result`: Success status + +--- + +#### `get_event_selectors` +Get the event recording configuration for a CloudTrail trail, including management and data event selectors. + +**Inputs:** +- `trail_name` (required): The trail name or ARN to get event selectors for + +**Outputs:** +- `trail_arn`: The ARN of the trail +- `event_selectors`: List of event selectors configured on the trail +- `advanced_event_selectors`: List of advanced event selectors configured on the trail +- `result`: Success status + +--- + +## Requirements + +- `autohive-integrations-sdk` - The Autohive integrations SDK +- `boto3` - AWS SDK for Python + +## API Information + +- **AWS SDK**: boto3 +- **Authentication**: IAM access keys (custom auth) +- **Region**: Configured per integration instance +- **Documentation**: https://docs.aws.amazon.com/ + +## Important Notes + +- **Region-Scoped**: Each integration instance connects to a single AWS region. To monitor multiple regions, add separate integration instances. +- **Service Enablement**: Security Hub and GuardDuty must be enabled in your AWS account before their actions will work. CloudWatch and CloudTrail are enabled by default. +- **GuardDuty Workflow**: Use `list_detectors` first to get your detector ID, then pass it to `list_guardduty_findings`, `get_guardduty_finding_details`, and `archive_findings`. +- **Write Actions**: Only three actions perform writes: `update_finding_workflow` (Security Hub), `archive_findings` (GuardDuty), and `set_alarm_state` (CloudWatch). All other actions are read-only. +- **Pagination**: All list actions support pagination via `next_token`. When `next_token` is returned in a response, pass it to the next request to get the next page of results. +- **Time Formats**: All time-based inputs accept ISO 8601 format (e.g. `2024-01-15T00:00:00Z`). + +## Common Use Cases + +**Security Monitoring:** +- Review Security Hub findings filtered by severity or compliance status +- Investigate GuardDuty threat detections and archive resolved findings +- Update finding workflow status to track remediation progress + +**Infrastructure Monitoring:** +- Query CloudWatch metrics for EC2, RDS, Lambda, and other services +- Check alarm states and review alarm history for incidents +- Temporarily set alarm state during maintenance windows + +**Log Analysis:** +- Search CloudWatch Logs for errors or specific patterns across log groups +- Retrieve log events from specific streams for debugging +- Discover available log groups and their sizes + +**Audit & Compliance:** +- Look up CloudTrail events to audit who made API calls and when +- Verify trail configurations and logging status +- Review event selectors to confirm what activity is being recorded + +## Version History + +- **1.0.0** - Initial release with 20 actions + - Security Hub: get_findings, get_finding_details, update_finding_workflow, get_insights (4 actions) + - GuardDuty: list_detectors, list_guardduty_findings, get_guardduty_finding_details, archive_findings (4 actions) + - CloudWatch Metrics & Alarms: list_metrics, get_metric_data, describe_alarms, get_alarm_history, set_alarm_state (5 actions) + - CloudWatch Logs: describe_log_groups, filter_log_events, get_log_events (3 actions) + - CloudTrail: lookup_events, describe_trails, get_trail_status, get_event_selectors (4 actions) + +## Sources + +- [AWS Security Hub API Reference](https://docs.aws.amazon.com/securityhub/1.0/APIReference/) +- [Amazon GuardDuty API Reference](https://docs.aws.amazon.com/guardduty/latest/APIReference/) +- [Amazon CloudWatch API Reference](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/) +- [Amazon CloudWatch Logs API Reference](https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/) +- [AWS CloudTrail API Reference](https://docs.aws.amazon.com/awscloudtrail/latest/APIReference/) +- [boto3 Documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) diff --git a/aws/actions/__init__.py b/aws/actions/__init__.py new file mode 100644 index 00000000..d3eb00ac --- /dev/null +++ b/aws/actions/__init__.py @@ -0,0 +1,30 @@ +from actions.security_hub import ( # noqa: F401 + GetFindingsAction, + GetFindingDetailsAction, + UpdateFindingWorkflowAction, + GetInsightsAction, +) +from actions.guardduty import ( # noqa: F401 + ListDetectorsAction, + ListGuardDutyFindingsAction, + GetGuardDutyFindingDetailsAction, + ArchiveFindingsAction, +) +from actions.cloudwatch import ( # noqa: F401 + ListMetricsAction, + GetMetricDataAction, + DescribeAlarmsAction, + GetAlarmHistoryAction, + SetAlarmStateAction, +) +from actions.cloudwatch_logs import ( # noqa: F401 + DescribeLogGroupsAction, + FilterLogEventsAction, + GetLogEventsAction, +) +from actions.cloudtrail import ( # noqa: F401 + LookupEventsAction, + DescribeTrailsAction, + GetTrailStatusAction, + GetEventSelectorsAction, +) diff --git a/aws/actions/cloudtrail.py b/aws/actions/cloudtrail.py new file mode 100644 index 00000000..0c7cfb46 --- /dev/null +++ b/aws/actions/cloudtrail.py @@ -0,0 +1,98 @@ +from autohive_integrations_sdk import ActionHandler, ExecutionContext +from aws import aws +from helpers import create_boto3_client, run_sync, success_result, error_result +from typing import Dict, Any +from datetime import datetime + + +@aws.action("lookup_events") +class LookupEventsAction(ActionHandler): + """Search CloudTrail management events by attributes such as event name or user.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "cloudtrail") + kwargs = {"MaxResults": inputs.get("max_results", 50)} + if inputs.get("lookup_attributes"): + kwargs["LookupAttributes"] = [ + { + "AttributeKey": attr["attribute_key"], + "AttributeValue": attr["attribute_value"], + } + for attr in inputs["lookup_attributes"] + ] + if inputs.get("start_time"): + kwargs["StartTime"] = datetime.fromisoformat( + inputs["start_time"].replace("Z", "+00:00") + ) + if inputs.get("end_time"): + kwargs["EndTime"] = datetime.fromisoformat( + inputs["end_time"].replace("Z", "+00:00") + ) + if inputs.get("next_token"): + kwargs["NextToken"] = inputs["next_token"] + response = await run_sync(client.lookup_events, **kwargs) + return success_result( + { + "events": response.get("Events", []), + "next_token": response.get("NextToken"), + } + ) + except Exception as e: + return error_result(e) + + +@aws.action("describe_trails") +class DescribeTrailsAction(ActionHandler): + """List configured CloudTrail trails in the account.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "cloudtrail") + kwargs = {} + if inputs.get("trail_name_list"): + kwargs["trailNameList"] = inputs["trail_name_list"] + kwargs["includeShadowTrails"] = inputs.get("include_shadow_trails", True) + response = await run_sync(client.describe_trails, **kwargs) + return success_result({"trails": response.get("trailList", [])}) + except Exception as e: + return error_result(e) + + +@aws.action("get_trail_status") +class GetTrailStatusAction(ActionHandler): + """Get the current logging status and latest delivery info for a trail.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "cloudtrail") + kwargs = {"Name": inputs["trail_name"]} + response = await run_sync(client.get_trail_status, **kwargs) + trail_status = { + k: v for k, v in response.items() if k != "ResponseMetadata" + } + return success_result({"trail_status": trail_status}) + except Exception as e: + return error_result(e) + + +@aws.action("get_event_selectors") +class GetEventSelectorsAction(ActionHandler): + """Get the event recording configuration for a CloudTrail trail.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "cloudtrail") + kwargs = {"TrailName": inputs["trail_name"]} + response = await run_sync(client.get_event_selectors, **kwargs) + return success_result( + { + "trail_arn": response.get("TrailARN"), + "event_selectors": response.get("EventSelectors", []), + "advanced_event_selectors": response.get( + "AdvancedEventSelectors", [] + ), + } + ) + except Exception as e: + return error_result(e) diff --git a/aws/actions/cloudwatch.py b/aws/actions/cloudwatch.py new file mode 100644 index 00000000..4416499f --- /dev/null +++ b/aws/actions/cloudwatch.py @@ -0,0 +1,137 @@ +from autohive_integrations_sdk import ActionHandler, ExecutionContext +from aws import aws +from helpers import create_boto3_client, run_sync, success_result, error_result +from typing import Dict, Any +from datetime import datetime + + +@aws.action("list_metrics") +class ListMetricsAction(ActionHandler): + """List available CloudWatch metrics, optionally filtered by namespace, name, or dimensions.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "cloudwatch") + kwargs = {} + if inputs.get("namespace"): + kwargs["Namespace"] = inputs["namespace"] + if inputs.get("metric_name"): + kwargs["MetricName"] = inputs["metric_name"] + if inputs.get("dimensions"): + kwargs["Dimensions"] = inputs["dimensions"] + if inputs.get("next_token"): + kwargs["NextToken"] = inputs["next_token"] + response = await run_sync(client.list_metrics, **kwargs) + return success_result( + { + "metrics": response.get("Metrics", []), + "next_token": response.get("NextToken"), + } + ) + except Exception as e: + return error_result(e) + + +@aws.action("get_metric_data") +class GetMetricDataAction(ActionHandler): + """Retrieve CloudWatch metric data for one or more metrics over a time period.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "cloudwatch") + start_time = datetime.fromisoformat( + inputs["start_time"].replace("Z", "+00:00") + ) + end_time = datetime.fromisoformat(inputs["end_time"].replace("Z", "+00:00")) + kwargs = { + "MetricDataQueries": inputs["metric_data_queries"], + "StartTime": start_time, + "EndTime": end_time, + } + response = await run_sync(client.get_metric_data, **kwargs) + return success_result( + {"metric_data_results": response.get("MetricDataResults", [])} + ) + except Exception as e: + return error_result(e) + + +@aws.action("describe_alarms") +class DescribeAlarmsAction(ActionHandler): + """List and filter CloudWatch alarms by name, prefix, or state.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "cloudwatch") + kwargs = {"MaxRecords": inputs.get("max_records", 50)} + if inputs.get("alarm_names"): + kwargs["AlarmNames"] = inputs["alarm_names"] + if inputs.get("alarm_name_prefix"): + kwargs["AlarmNamePrefix"] = inputs["alarm_name_prefix"] + if inputs.get("state_value"): + kwargs["StateValue"] = inputs["state_value"] + if inputs.get("next_token"): + kwargs["NextToken"] = inputs["next_token"] + response = await run_sync(client.describe_alarms, **kwargs) + return success_result( + { + "metric_alarms": response.get("MetricAlarms", []), + "composite_alarms": response.get("CompositeAlarms", []), + "next_token": response.get("NextToken"), + } + ) + except Exception as e: + return error_result(e) + + +@aws.action("get_alarm_history") +class GetAlarmHistoryAction(ActionHandler): + """Retrieve the history of state changes and actions for CloudWatch alarms.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "cloudwatch") + kwargs = {"MaxRecords": inputs.get("max_records", 50)} + if inputs.get("alarm_name"): + kwargs["AlarmName"] = inputs["alarm_name"] + if inputs.get("alarm_types"): + kwargs["AlarmTypes"] = inputs["alarm_types"] + if inputs.get("history_item_type"): + kwargs["HistoryItemType"] = inputs["history_item_type"] + if inputs.get("start_date"): + kwargs["StartDate"] = datetime.fromisoformat( + inputs["start_date"].replace("Z", "+00:00") + ) + if inputs.get("end_date"): + kwargs["EndDate"] = datetime.fromisoformat( + inputs["end_date"].replace("Z", "+00:00") + ) + if inputs.get("next_token"): + kwargs["NextToken"] = inputs["next_token"] + response = await run_sync(client.describe_alarm_history, **kwargs) + return success_result( + { + "alarm_history_items": response.get("AlarmHistoryItems", []), + "next_token": response.get("NextToken"), + } + ) + except Exception as e: + return error_result(e) + + +@aws.action("set_alarm_state") +class SetAlarmStateAction(ActionHandler): + """Temporarily set the state of a CloudWatch alarm for testing or maintenance.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "cloudwatch") + kwargs = { + "AlarmName": inputs["alarm_name"], + "StateValue": inputs["state_value"], + "StateReason": inputs["state_reason"], + } + await run_sync(client.set_alarm_state, **kwargs) + return success_result({"success": True}) + except Exception as e: + return error_result(e) diff --git a/aws/actions/cloudwatch_logs.py b/aws/actions/cloudwatch_logs.py new file mode 100644 index 00000000..0c0d4743 --- /dev/null +++ b/aws/actions/cloudwatch_logs.py @@ -0,0 +1,97 @@ +from autohive_integrations_sdk import ActionHandler, ExecutionContext +from aws import aws +from helpers import create_boto3_client, run_sync, success_result, error_result +from typing import Dict, Any +from datetime import datetime + + +def _iso_to_epoch_ms(iso_string: str) -> int: + dt = datetime.fromisoformat(iso_string.replace("Z", "+00:00")) + return int(dt.timestamp() * 1000) + + +@aws.action("describe_log_groups") +class DescribeLogGroupsAction(ActionHandler): + """List CloudWatch Logs log groups, optionally filtered by name prefix.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "logs") + kwargs = {"limit": inputs.get("limit", 50)} + if inputs.get("log_group_name_prefix"): + kwargs["logGroupNamePrefix"] = inputs["log_group_name_prefix"] + if inputs.get("next_token"): + kwargs["nextToken"] = inputs["next_token"] + response = await run_sync(client.describe_log_groups, **kwargs) + return success_result( + { + "log_groups": response.get("logGroups", []), + "next_token": response.get("nextToken"), + } + ) + except Exception as e: + return error_result(e) + + +@aws.action("filter_log_events") +class FilterLogEventsAction(ActionHandler): + """Search and filter log events across log streams within a log group.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "logs") + kwargs = { + "logGroupName": inputs["log_group_name"], + "limit": inputs.get("limit", 50), + } + if inputs.get("log_stream_names"): + kwargs["logStreamNames"] = inputs["log_stream_names"] + if inputs.get("filter_pattern"): + kwargs["filterPattern"] = inputs["filter_pattern"] + if inputs.get("start_time"): + kwargs["startTime"] = _iso_to_epoch_ms(inputs["start_time"]) + if inputs.get("end_time"): + kwargs["endTime"] = _iso_to_epoch_ms(inputs["end_time"]) + if inputs.get("next_token"): + kwargs["nextToken"] = inputs["next_token"] + response = await run_sync(client.filter_log_events, **kwargs) + return success_result( + { + "events": response.get("events", []), + "searched_log_streams": response.get("searchedLogStreams", []), + "next_token": response.get("nextToken"), + } + ) + except Exception as e: + return error_result(e) + + +@aws.action("get_log_events") +class GetLogEventsAction(ActionHandler): + """Get log events from a specific log stream in a log group.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "logs") + kwargs = { + "logGroupName": inputs["log_group_name"], + "logStreamName": inputs["log_stream_name"], + "limit": inputs.get("limit", 50), + "startFromHead": inputs.get("start_from_head", True), + } + if inputs.get("start_time"): + kwargs["startTime"] = _iso_to_epoch_ms(inputs["start_time"]) + if inputs.get("end_time"): + kwargs["endTime"] = _iso_to_epoch_ms(inputs["end_time"]) + if inputs.get("next_token"): + kwargs["nextToken"] = inputs["next_token"] + response = await run_sync(client.get_log_events, **kwargs) + return success_result( + { + "events": response.get("events", []), + "next_forward_token": response.get("nextForwardToken"), + "next_backward_token": response.get("nextBackwardToken"), + } + ) + except Exception as e: + return error_result(e) diff --git a/aws/actions/guardduty.py b/aws/actions/guardduty.py new file mode 100644 index 00000000..ca91f25b --- /dev/null +++ b/aws/actions/guardduty.py @@ -0,0 +1,87 @@ +from autohive_integrations_sdk import ActionHandler, ExecutionContext +from aws import aws +from helpers import create_boto3_client, run_sync, success_result, error_result +from typing import Dict, Any + + +@aws.action("list_detectors") +class ListDetectorsAction(ActionHandler): + """List all GuardDuty detector IDs in the current account and region.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "guardduty") + kwargs = {"MaxResults": inputs.get("max_results", 50)} + if inputs.get("next_token"): + kwargs["NextToken"] = inputs["next_token"] + response = await run_sync(client.list_detectors, **kwargs) + return success_result( + { + "detector_ids": response.get("DetectorIds", []), + "next_token": response.get("NextToken"), + } + ) + except Exception as e: + return error_result(e) + + +@aws.action("list_guardduty_findings") +class ListGuardDutyFindingsAction(ActionHandler): + """List and filter GuardDuty findings for a specific detector.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "guardduty") + kwargs = { + "DetectorId": inputs["detector_id"], + "MaxResults": inputs.get("max_results", 50), + } + if inputs.get("finding_criteria"): + kwargs["FindingCriteria"] = inputs["finding_criteria"] + if inputs.get("sort_criteria"): + kwargs["SortCriteria"] = inputs["sort_criteria"] + if inputs.get("next_token"): + kwargs["NextToken"] = inputs["next_token"] + response = await run_sync(client.list_findings, **kwargs) + return success_result( + { + "finding_ids": response.get("FindingIds", []), + "next_token": response.get("NextToken"), + } + ) + except Exception as e: + return error_result(e) + + +@aws.action("get_guardduty_finding_details") +class GetGuardDutyFindingDetailsAction(ActionHandler): + """Get detailed information about one or more GuardDuty findings.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "guardduty") + kwargs = { + "DetectorId": inputs["detector_id"], + "FindingIds": inputs["finding_ids"], + } + response = await run_sync(client.get_findings, **kwargs) + return success_result({"findings": response.get("Findings", [])}) + except Exception as e: + return error_result(e) + + +@aws.action("archive_findings") +class ArchiveFindingsAction(ActionHandler): + """Archive one or more GuardDuty findings by their IDs.""" + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "guardduty") + kwargs = { + "DetectorId": inputs["detector_id"], + "FindingIds": inputs["finding_ids"], + } + await run_sync(client.archive_findings, **kwargs) + return success_result({"success": True}) + except Exception as e: + return error_result(e) diff --git a/aws/actions/security_hub.py b/aws/actions/security_hub.py new file mode 100644 index 00000000..06d1b40d --- /dev/null +++ b/aws/actions/security_hub.py @@ -0,0 +1,184 @@ +""" +AWS Security Hub actions - Findings management and security insights. +""" + +import asyncio +from autohive_integrations_sdk import ActionHandler, ExecutionContext +from aws import aws +from helpers import create_boto3_client, run_sync, success_result, error_result +from typing import Dict, Any + + +@aws.action("get_findings") +class GetFindingsAction(ActionHandler): + """ + List and filter security findings from AWS Security Hub. + + Supports optional filters in the GetFindings API format, pagination + via next_token, and a configurable max_results limit. + """ + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "securityhub") + kwargs = {"MaxResults": inputs.get("max_results", 20)} + if inputs.get("filters"): + kwargs["Filters"] = inputs["filters"] + if inputs.get("next_token"): + kwargs["NextToken"] = inputs["next_token"] + response = await run_sync(client.get_findings, **kwargs) + return success_result( + { + "findings": response.get("Findings", []), + "next_token": response.get("NextToken"), + } + ) + except Exception as e: + return error_result(e) + + +@aws.action("get_finding_details") +class GetFindingDetailsAction(ActionHandler): + """ + Get detailed information about a specific Security Hub finding by its ARN. + + Uses the GetFindings API with an Id filter set to EQUALS the provided + finding_arn, and returns the first matching finding or null. + """ + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "securityhub") + finding_arn = inputs["finding_arn"] + kwargs = { + "Filters": {"Id": [{"Value": finding_arn, "Comparison": "EQUALS"}]}, + "MaxResults": 1, + } + response = await run_sync(client.get_findings, **kwargs) + findings = response.get("Findings", []) + finding = findings[0] if findings else None + return success_result({"finding": finding}) + except Exception as e: + return error_result(e) + + +@aws.action("update_finding_workflow") +class UpdateFindingWorkflowAction(ActionHandler): + """ + Update the workflow status of one or more Security Hub findings. + + Accepts a list of finding ARNs, looks up each finding to obtain its + ProductArn, then calls BatchUpdateFindings to set the new workflow + status. An optional note can be attached to the findings. + """ + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "securityhub") + finding_arns = inputs["finding_arns"] + workflow_status = inputs["workflow_status"] + note = inputs.get("note") + + # Look up findings in batches of 100 (AWS API limit) to get ProductArn + findings = [] + for i in range(0, len(finding_arns), 100): + batch = finding_arns[i : i + 100] + lookup_kwargs = { + "Filters": { + "Id": [{"Value": arn, "Comparison": "EQUALS"} for arn in batch] + }, + "MaxResults": len(batch), + } + lookup_response = await run_sync(client.get_findings, **lookup_kwargs) + findings.extend(lookup_response.get("Findings", [])) + + # Build FindingIdentifiers from the looked-up findings + finding_identifiers = [ + {"Id": f["Id"], "ProductArn": f["ProductArn"]} for f in findings + ] + + if not finding_identifiers: + return success_result( + { + "processed_findings": [], + "unprocessed_findings": [ + { + "FindingIdentifier": {"Id": arn}, + "ErrorCode": "FindingNotFound", + "ErrorMessage": "Finding not found", + } + for arn in finding_arns + ], + } + ) + + update_kwargs = { + "FindingIdentifiers": finding_identifiers, + "Workflow": {"Status": workflow_status}, + } + + if note: + update_kwargs["Note"] = { + "Text": note, + "UpdatedBy": "autohive-integration", + } + + response = await run_sync(client.batch_update_findings, **update_kwargs) + return success_result( + { + "processed_findings": response.get("ProcessedFindings", []), + "unprocessed_findings": response.get("UnprocessedFindings", []), + } + ) + except Exception as e: + return error_result(e) + + +@aws.action("get_insights") +class GetInsightsAction(ActionHandler): + """ + Get security insight results from AWS Security Hub. + + Retrieves insight ARNs via GetInsights, then fetches result details + for each insight using GetInsightResults. Supports filtering by + specific insight ARNs and pagination. + """ + + async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): + try: + client = create_boto3_client(context, "securityhub") + kwargs = {"MaxResults": inputs.get("max_results", 20)} + if inputs.get("insight_arns"): + kwargs["InsightArns"] = inputs["insight_arns"] + if inputs.get("next_token"): + kwargs["NextToken"] = inputs["next_token"] + response = await run_sync(client.get_insights, **kwargs) + insights = response.get("Insights", []) + + # Fetch results for each insight in parallel + async def fetch_insight_result(insight): + insight_data = { + "insight_arn": insight.get("InsightArn"), + "name": insight.get("Name"), + "filters": insight.get("Filters"), + "group_by_attribute": insight.get("GroupByAttribute"), + } + try: + result_response = await run_sync( + client.get_insight_results, InsightArn=insight["InsightArn"] + ) + insight_data["results"] = result_response.get("InsightResults", {}) + except Exception as inner_e: + insight_data["results"] = None + insight_data["error"] = str(inner_e) + return insight_data + + enriched_insights = await asyncio.gather( + *[fetch_insight_result(insight) for insight in insights] + ) + + return success_result( + {"insights": enriched_insights, "next_token": response.get("NextToken")} + ) + except Exception as e: + return error_result(e) diff --git a/aws/aws.py b/aws/aws.py new file mode 100644 index 00000000..f8997413 --- /dev/null +++ b/aws/aws.py @@ -0,0 +1,8 @@ +from autohive_integrations_sdk import Integration +import os + +config_path = os.path.join(os.path.dirname(__file__), "config.json") +aws = Integration.load(config_path) + +# Import actions to register handlers +import actions # noqa: F401, E402 diff --git a/aws/config.json b/aws/config.json new file mode 100644 index 00000000..3cc5c8f3 --- /dev/null +++ b/aws/config.json @@ -0,0 +1,1070 @@ +{ + "name": "aws", + "display_name": "Amazon Web Services", + "version": "1.0.0", + "description": "AWS security and monitoring integration for Security Hub, GuardDuty, CloudWatch, and CloudTrail", + "entry_point": "aws.py", + "auth": { + "type": "custom", + "title": "AWS Credentials", + "fields": { + "type": "object", + "properties": { + "aws_access_key_id": { + "type": "string", + "format": "text", + "label": "AWS Access Key ID", + "help_text": "Your AWS Access Key ID from IAM" + }, + "aws_secret_access_key": { + "type": "string", + "format": "password", + "label": "AWS Secret Access Key", + "help_text": "Your AWS Secret Access Key from IAM" + }, + "aws_region": { + "type": "string", + "format": "text", + "label": "AWS Region", + "help_text": "AWS region (e.g. us-east-1, eu-west-1)" + } + } + } + }, + "actions": { + "get_findings": { + "display_name": "Get Security Hub Findings", + "description": "List and filter security findings from AWS Security Hub", + "input_schema": { + "type": "object", + "properties": { + "filters": { + "type": "object", + "description": "AWS Security Hub filter criteria in the GetFindings API format (e.g. SeverityLabel, ComplianceStatus, ResourceType filters)" + }, + "max_results": { + "type": "integer", + "description": "Maximum number of findings to return (default: 20, max: 100)", + "default": 20, + "minimum": 1, + "maximum": 100 + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": [] + }, + "output_schema": { + "type": "object", + "properties": { + "findings": { + "type": "array", + "description": "List of Security Hub findings" + }, + "next_token": { + "type": ["string", "null"], + "description": "Pagination token for the next page of results" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "get_finding_details": { + "display_name": "Get Security Hub Finding Details", + "description": "Get detailed information about a specific Security Hub finding by its ARN", + "input_schema": { + "type": "object", + "properties": { + "finding_arn": { + "type": "string", + "description": "The ARN of the Security Hub finding to retrieve" + } + }, + "required": ["finding_arn"] + }, + "output_schema": { + "type": "object", + "properties": { + "finding": { + "type": "object", + "description": "The Security Hub finding details" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "update_finding_workflow": { + "display_name": "Update Finding Workflow Status", + "description": "Update the workflow status of one or more Security Hub findings", + "input_schema": { + "type": "object", + "properties": { + "finding_arns": { + "type": "array", + "description": "List of finding ARNs to update", + "items": { + "type": "string" + } + }, + "workflow_status": { + "type": "string", + "description": "The new workflow status to set", + "enum": ["NEW", "NOTIFIED", "RESOLVED", "SUPPRESSED"] + }, + "note": { + "type": "string", + "description": "Optional note to add to the findings" + } + }, + "required": ["finding_arns", "workflow_status"] + }, + "output_schema": { + "type": "object", + "properties": { + "processed_findings": { + "type": "array", + "description": "List of finding ARNs that were successfully updated" + }, + "unprocessed_findings": { + "type": "array", + "description": "List of finding ARNs that could not be updated" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "get_insights": { + "display_name": "Get Security Hub Insights", + "description": "Get security insight results from AWS Security Hub", + "input_schema": { + "type": "object", + "properties": { + "insight_arns": { + "type": "array", + "description": "List of insight ARNs to retrieve. If not specified, returns all insights.", + "items": { + "type": "string" + } + }, + "max_results": { + "type": "integer", + "description": "Maximum number of insights to return (default: 20)", + "default": 20, + "minimum": 1 + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": [] + }, + "output_schema": { + "type": "object", + "properties": { + "insights": { + "type": "array", + "description": "List of Security Hub insights" + }, + "next_token": { + "type": ["string", "null"], + "description": "Pagination token for the next page of results" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "list_detectors": { + "display_name": "List GuardDuty Detectors", + "description": "List all GuardDuty detector IDs in the current AWS account and region", + "input_schema": { + "type": "object", + "properties": { + "max_results": { + "type": "integer", + "description": "Maximum number of detector IDs to return (default: 50)", + "default": 50, + "minimum": 1 + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": [] + }, + "output_schema": { + "type": "object", + "properties": { + "detector_ids": { + "type": "array", + "description": "List of GuardDuty detector IDs", + "items": { + "type": "string" + } + }, + "next_token": { + "type": ["string", "null"], + "description": "Pagination token for the next page of results" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "list_guardduty_findings": { + "display_name": "List GuardDuty Findings", + "description": "List and filter GuardDuty findings for a specific detector", + "input_schema": { + "type": "object", + "properties": { + "detector_id": { + "type": "string", + "description": "The ID of the GuardDuty detector" + }, + "finding_criteria": { + "type": "object", + "description": "Criteria to filter findings (e.g. by severity, type, or resource)" + }, + "sort_criteria": { + "type": "object", + "description": "Criteria for sorting results (attribute name and order direction)" + }, + "max_results": { + "type": "integer", + "description": "Maximum number of finding IDs to return (default: 50)", + "default": 50, + "minimum": 1 + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": ["detector_id"] + }, + "output_schema": { + "type": "object", + "properties": { + "finding_ids": { + "type": "array", + "description": "List of GuardDuty finding IDs", + "items": { + "type": "string" + } + }, + "next_token": { + "type": ["string", "null"], + "description": "Pagination token for the next page of results" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "get_guardduty_finding_details": { + "display_name": "Get GuardDuty Finding Details", + "description": "Get detailed information about one or more GuardDuty findings", + "input_schema": { + "type": "object", + "properties": { + "detector_id": { + "type": "string", + "description": "The ID of the GuardDuty detector" + }, + "finding_ids": { + "type": "array", + "description": "List of finding IDs to retrieve details for", + "items": { + "type": "string" + } + } + }, + "required": ["detector_id", "finding_ids"] + }, + "output_schema": { + "type": "object", + "properties": { + "findings": { + "type": "array", + "description": "List of detailed GuardDuty findings" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "archive_findings": { + "display_name": "Archive GuardDuty Findings", + "description": "Archive one or more GuardDuty findings by their IDs", + "input_schema": { + "type": "object", + "properties": { + "detector_id": { + "type": "string", + "description": "The ID of the GuardDuty detector" + }, + "finding_ids": { + "type": "array", + "description": "List of finding IDs to archive", + "items": { + "type": "string" + } + } + }, + "required": ["detector_id", "finding_ids"] + }, + "output_schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Whether the findings were successfully archived" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "list_metrics": { + "display_name": "List CloudWatch Metrics", + "description": "List available CloudWatch metrics, optionally filtered by namespace, name, or dimensions", + "input_schema": { + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "The namespace to filter metrics by (e.g. AWS/EC2, AWS/RDS, AWS/Lambda)" + }, + "metric_name": { + "type": "string", + "description": "The metric name to filter by (e.g. CPUUtilization, NetworkIn)" + }, + "dimensions": { + "type": "array", + "description": "List of dimension filters, each with Name and Value", + "items": { + "type": "object" + } + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": [] + }, + "output_schema": { + "type": "object", + "properties": { + "metrics": { + "type": "array", + "description": "List of CloudWatch metrics with namespace, name, and dimensions" + }, + "next_token": { + "type": ["string", "null"], + "description": "Pagination token for the next page of results" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "get_metric_data": { + "display_name": "Get CloudWatch Metric Data", + "description": "Retrieve CloudWatch metric statistics for one or more metrics over a specified time period", + "input_schema": { + "type": "object", + "properties": { + "metric_data_queries": { + "type": "array", + "description": "List of metric data queries in AWS PascalCase format. Each query requires 'Id' and either 'MetricStat' (with 'Metric', 'Period', 'Stat') or 'Expression'.", + "items": { + "type": "object" + } + }, + "start_time": { + "type": "string", + "description": "Start of the time range in ISO 8601 format (e.g. 2024-01-01T00:00:00Z)" + }, + "end_time": { + "type": "string", + "description": "End of the time range in ISO 8601 format (e.g. 2024-01-02T00:00:00Z)" + } + }, + "required": ["metric_data_queries", "start_time", "end_time"] + }, + "output_schema": { + "type": "object", + "properties": { + "metric_data_results": { + "type": "array", + "description": "List of metric data results with timestamps and values" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "describe_alarms": { + "display_name": "Describe CloudWatch Alarms", + "description": "List and filter CloudWatch alarms by name, prefix, or state", + "input_schema": { + "type": "object", + "properties": { + "alarm_names": { + "type": "array", + "description": "List of specific alarm names to retrieve", + "items": { + "type": "string" + } + }, + "alarm_name_prefix": { + "type": "string", + "description": "Prefix to filter alarm names by" + }, + "state_value": { + "type": "string", + "description": "Filter alarms by state", + "enum": ["OK", "ALARM", "INSUFFICIENT_DATA"] + }, + "max_records": { + "type": "integer", + "description": "Maximum number of alarm records to return (default: 50)", + "default": 50, + "minimum": 1 + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": [] + }, + "output_schema": { + "type": "object", + "properties": { + "metric_alarms": { + "type": "array", + "description": "List of metric alarms" + }, + "composite_alarms": { + "type": "array", + "description": "List of composite alarms" + }, + "next_token": { + "type": ["string", "null"], + "description": "Pagination token for the next page of results" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "get_alarm_history": { + "display_name": "Get CloudWatch Alarm History", + "description": "Retrieve the history of state changes and actions for CloudWatch alarms", + "input_schema": { + "type": "object", + "properties": { + "alarm_name": { + "type": "string", + "description": "The name of the alarm to get history for. If not specified, returns history for all alarms." + }, + "alarm_types": { + "type": "array", + "description": "Filter by alarm type (MetricAlarm, CompositeAlarm)", + "items": { + "type": "string" + } + }, + "history_item_type": { + "type": "string", + "description": "Filter by history item type", + "enum": ["ConfigurationUpdate", "StateUpdate", "Action"] + }, + "start_date": { + "type": "string", + "description": "Start of the date range in ISO 8601 format" + }, + "end_date": { + "type": "string", + "description": "End of the date range in ISO 8601 format" + }, + "max_records": { + "type": "integer", + "description": "Maximum number of history items to return (default: 50)", + "default": 50, + "minimum": 1 + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": [] + }, + "output_schema": { + "type": "object", + "properties": { + "alarm_history_items": { + "type": "array", + "description": "List of alarm history items with timestamp, type, and summary" + }, + "next_token": { + "type": ["string", "null"], + "description": "Pagination token for the next page of results" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "set_alarm_state": { + "display_name": "Set CloudWatch Alarm State", + "description": "Temporarily set the state of a CloudWatch alarm for testing or maintenance purposes", + "input_schema": { + "type": "object", + "properties": { + "alarm_name": { + "type": "string", + "description": "The name of the alarm to set the state for" + }, + "state_value": { + "type": "string", + "description": "The state value to set", + "enum": ["OK", "ALARM", "INSUFFICIENT_DATA"] + }, + "state_reason": { + "type": "string", + "description": "A human-readable reason for the state change" + } + }, + "required": ["alarm_name", "state_value", "state_reason"] + }, + "output_schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Whether the alarm state was successfully set" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "describe_log_groups": { + "display_name": "Describe CloudWatch Log Groups", + "description": "List CloudWatch Logs log groups, optionally filtered by name prefix", + "input_schema": { + "type": "object", + "properties": { + "log_group_name_prefix": { + "type": "string", + "description": "Prefix to filter log group names by" + }, + "limit": { + "type": "integer", + "description": "Maximum number of log groups to return (default: 50, max: 50)", + "default": 50, + "minimum": 1, + "maximum": 50 + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": [] + }, + "output_schema": { + "type": "object", + "properties": { + "log_groups": { + "type": "array", + "description": "List of log groups with name, ARN, creation time, and stored bytes" + }, + "next_token": { + "type": ["string", "null"], + "description": "Pagination token for the next page of results" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "filter_log_events": { + "display_name": "Filter CloudWatch Log Events", + "description": "Search and filter log events across one or more log streams within a log group", + "input_schema": { + "type": "object", + "properties": { + "log_group_name": { + "type": "string", + "description": "The name of the log group to search" + }, + "log_stream_names": { + "type": "array", + "description": "List of log stream names to search within", + "items": { + "type": "string" + } + }, + "filter_pattern": { + "type": "string", + "description": "CloudWatch Logs filter pattern to match events (e.g. 'ERROR', '{ $.statusCode = 500 }')" + }, + "start_time": { + "type": "string", + "description": "Start of the time range in ISO 8601 format" + }, + "end_time": { + "type": "string", + "description": "End of the time range in ISO 8601 format" + }, + "limit": { + "type": "integer", + "description": "Maximum number of events to return (default: 50, max: 10000)", + "default": 50, + "minimum": 1, + "maximum": 10000 + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": ["log_group_name"] + }, + "output_schema": { + "type": "object", + "properties": { + "events": { + "type": "array", + "description": "List of matching log events with timestamp, message, and log stream name" + }, + "searched_log_streams": { + "type": "array", + "description": "List of log streams that were searched" + }, + "next_token": { + "type": ["string", "null"], + "description": "Pagination token for the next page of results" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "get_log_events": { + "display_name": "Get CloudWatch Log Events", + "description": "Get log events from a specific log stream in a log group", + "input_schema": { + "type": "object", + "properties": { + "log_group_name": { + "type": "string", + "description": "The name of the log group" + }, + "log_stream_name": { + "type": "string", + "description": "The name of the log stream" + }, + "start_time": { + "type": "string", + "description": "Start of the time range in ISO 8601 format" + }, + "end_time": { + "type": "string", + "description": "End of the time range in ISO 8601 format" + }, + "limit": { + "type": "integer", + "description": "Maximum number of events to return (default: 50, max: 10000)", + "default": 50, + "minimum": 1, + "maximum": 10000 + }, + "start_from_head": { + "type": "boolean", + "description": "If true, return events from the oldest first. If false, return from the newest first (default: true).", + "default": true + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": ["log_group_name", "log_stream_name"] + }, + "output_schema": { + "type": "object", + "properties": { + "events": { + "type": "array", + "description": "List of log events with timestamp and message" + }, + "next_forward_token": { + "type": ["string", "null"], + "description": "Token for fetching the next set of events going forward in time" + }, + "next_backward_token": { + "type": ["string", "null"], + "description": "Token for fetching the next set of events going backward in time" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "lookup_events": { + "display_name": "Lookup CloudTrail Events", + "description": "Search CloudTrail management events by attributes such as event name, user, or resource", + "input_schema": { + "type": "object", + "properties": { + "lookup_attributes": { + "type": "array", + "description": "List of lookup attributes to filter events. Each item has 'attribute_key' (e.g. EventName, Username, ResourceType, ResourceName, EventSource) and 'attribute_value'.", + "items": { + "type": "object" + } + }, + "start_time": { + "type": "string", + "description": "Start of the time range in ISO 8601 format" + }, + "end_time": { + "type": "string", + "description": "End of the time range in ISO 8601 format" + }, + "max_results": { + "type": "integer", + "description": "Maximum number of events to return (default: 50, max: 50)", + "default": 50, + "minimum": 1, + "maximum": 50 + }, + "next_token": { + "type": "string", + "description": "Pagination token from a previous request" + } + }, + "required": [] + }, + "output_schema": { + "type": "object", + "properties": { + "events": { + "type": "array", + "description": "List of CloudTrail events with event name, time, user, and resources" + }, + "next_token": { + "type": ["string", "null"], + "description": "Pagination token for the next page of results" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "describe_trails": { + "display_name": "Describe CloudTrail Trails", + "description": "List configured CloudTrail trails in the account", + "input_schema": { + "type": "object", + "properties": { + "trail_name_list": { + "type": "array", + "description": "List of trail names or ARNs to describe. If not specified, returns all trails.", + "items": { + "type": "string" + } + }, + "include_shadow_trails": { + "type": "boolean", + "description": "Whether to include shadow trails (replications of trails in other regions). Default: true.", + "default": true + } + }, + "required": [] + }, + "output_schema": { + "type": "object", + "properties": { + "trails": { + "type": "array", + "description": "List of CloudTrail trail configurations" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "get_trail_status": { + "display_name": "Get CloudTrail Trail Status", + "description": "Get the current logging status and latest delivery information for a CloudTrail trail", + "input_schema": { + "type": "object", + "properties": { + "trail_name": { + "type": "string", + "description": "The trail name or ARN to get status for" + } + }, + "required": ["trail_name"] + }, + "output_schema": { + "type": "object", + "properties": { + "trail_status": { + "type": "object", + "description": "Trail status including logging state, latest delivery time, and any delivery errors" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + }, + "get_event_selectors": { + "display_name": "Get CloudTrail Event Selectors", + "description": "Get the event recording configuration for a CloudTrail trail, including management and data event selectors", + "input_schema": { + "type": "object", + "properties": { + "trail_name": { + "type": "string", + "description": "The trail name or ARN to get event selectors for" + } + }, + "required": ["trail_name"] + }, + "output_schema": { + "type": "object", + "properties": { + "trail_arn": { + "type": "string", + "description": "The ARN of the trail" + }, + "event_selectors": { + "type": "array", + "description": "List of event selectors configured on the trail" + }, + "advanced_event_selectors": { + "type": "array", + "description": "List of advanced event selectors configured on the trail" + }, + "result": { + "type": "boolean", + "description": "Whether the operation was successful" + }, + "error": { + "type": ["string", "null"], + "description": "Error message if the operation failed" + }, + "error_code": { + "type": ["string", "null"], + "description": "AWS-specific error code if the operation failed (e.g. AccessDeniedException, ThrottlingException)" + } + } + } + } + } +} diff --git a/aws/helpers.py b/aws/helpers.py new file mode 100644 index 00000000..99a8a86f --- /dev/null +++ b/aws/helpers.py @@ -0,0 +1,58 @@ +import asyncio +import functools +from datetime import datetime, date +from decimal import Decimal +from typing import Any, Dict + +import boto3 +from autohive_integrations_sdk import ActionResult, ExecutionContext + + +def create_boto3_client(context: ExecutionContext, service_name: str): + credentials = context.auth.get("credentials", {}) + access_key = credentials.get("aws_access_key_id") + secret_key = credentials.get("aws_secret_access_key") + if not access_key or not secret_key: + raise ValueError( + "AWS credentials are missing: aws_access_key_id and aws_secret_access_key are required" + ) + return boto3.client( + service_name, + aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + region_name=credentials.get("aws_region", "us-east-1"), + ) + + +async def run_sync(func, *args, **kwargs): + loop = asyncio.get_running_loop() + return await loop.run_in_executor(None, functools.partial(func, *args, **kwargs)) + + +def serialize_response(obj: Any) -> Any: + if isinstance(obj, (datetime, date)): + return obj.isoformat() + if isinstance(obj, Decimal): + return float(obj) + if isinstance(obj, bytes): + return obj.decode("utf-8", errors="replace") + if isinstance(obj, dict): + return {k: serialize_response(v) for k, v in obj.items()} + if isinstance(obj, (list, tuple)): + return [serialize_response(i) for i in obj] + return obj + + +def success_result(data: Dict[str, Any]) -> ActionResult: + return ActionResult(data={"result": True, **serialize_response(data)}) + + +def error_result(e: Exception) -> ActionResult: + error_msg = str(e) + error_code = "" + if hasattr(e, "response"): + error_code = e.response.get("Error", {}).get("Code", "") + error_msg = e.response.get("Error", {}).get("Message", error_msg) + return ActionResult( + data={"result": False, "error": error_msg, "error_code": error_code} + ) diff --git a/aws/icon.png b/aws/icon.png new file mode 100644 index 00000000..125731ec Binary files /dev/null and b/aws/icon.png differ diff --git a/aws/requirements.txt b/aws/requirements.txt new file mode 100644 index 00000000..96b5aede --- /dev/null +++ b/aws/requirements.txt @@ -0,0 +1,2 @@ +autohive-integrations-sdk~=1.0.2 +boto3 diff --git a/aws/tests/__init__.py b/aws/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/aws/tests/context.py b/aws/tests/context.py new file mode 100644 index 00000000..49f11d51 --- /dev/null +++ b/aws/tests/context.py @@ -0,0 +1,6 @@ +import os +import sys + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from aws import aws as integration # noqa: F401 diff --git a/aws/tests/test_aws.py b/aws/tests/test_aws.py new file mode 100644 index 00000000..2fab759f --- /dev/null +++ b/aws/tests/test_aws.py @@ -0,0 +1,452 @@ +""" +AWS Integration Tests + +Tests all 20 AWS actions across Security Hub, GuardDuty, +CloudWatch, CloudWatch Logs, and CloudTrail. + +To run these tests: +1. Update the credentials below with valid AWS access keys +2. Run: python tests/test_aws.py +""" + +import asyncio +from context import integration +from autohive_integrations_sdk import ExecutionContext + +TEST_AUTH = { + "credentials": { + "aws_access_key_id": "YOUR_ACCESS_KEY_ID", + "aws_secret_access_key": "YOUR_SECRET_ACCESS_KEY", # nosec B105 + "aws_region": "us-east-1", + } +} + + +# ============================================================================= +# Security Hub Actions +# ============================================================================= + + +async def test_get_findings(): + """Test retrieving Security Hub findings.""" + print("\n=== Testing get_findings ===") + inputs = {"max_results": 10} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action("get_findings", inputs, context) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_get_finding_details(): + """Test retrieving details for a specific Security Hub finding.""" + print("\n=== Testing get_finding_details ===") + inputs = { + "finding_arn": "arn:aws:securityhub:us-east-1:123456789012:finding/example" + } + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "get_finding_details", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_update_finding_workflow(): + """Test updating the workflow status of Security Hub findings.""" + print("\n=== Testing update_finding_workflow ===") + inputs = { + "finding_arns": ["arn:aws:securityhub:us-east-1:123456789012:finding/example"], + "workflow_status": "RESOLVED", + "note": "Resolved via Autohive", + } + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "update_finding_workflow", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_get_insights(): + """Test retrieving Security Hub insights.""" + print("\n=== Testing get_insights ===") + inputs = {"max_results": 5} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action("get_insights", inputs, context) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +# ============================================================================= +# GuardDuty Actions +# ============================================================================= + + +async def test_list_detectors(): + """Test listing GuardDuty detectors.""" + print("\n=== Testing list_detectors ===") + inputs = {"max_results": 10} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action("list_detectors", inputs, context) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_list_guardduty_findings(): + """Test listing GuardDuty findings for a detector.""" + print("\n=== Testing list_guardduty_findings ===") + inputs = {"detector_id": "YOUR_DETECTOR_ID", "max_results": 10} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "list_guardduty_findings", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_get_guardduty_finding_details(): + """Test retrieving details for specific GuardDuty findings.""" + print("\n=== Testing get_guardduty_finding_details ===") + inputs = {"detector_id": "YOUR_DETECTOR_ID", "finding_ids": ["example-finding-id"]} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "get_guardduty_finding_details", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_archive_findings(): + """Test archiving GuardDuty findings.""" + print("\n=== Testing archive_findings ===") + inputs = {"detector_id": "YOUR_DETECTOR_ID", "finding_ids": ["example-finding-id"]} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "archive_findings", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +# ============================================================================= +# CloudWatch Actions +# ============================================================================= + + +async def test_list_metrics(): + """Test listing CloudWatch metrics.""" + print("\n=== Testing list_metrics ===") + inputs = {"namespace": "AWS/EC2"} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action("list_metrics", inputs, context) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_get_metric_data(): + """Test retrieving CloudWatch metric data.""" + print("\n=== Testing get_metric_data ===") + inputs = { + "metric_data_queries": [ + { + "Id": "m1", + "MetricStat": { + "Metric": {"Namespace": "AWS/EC2", "MetricName": "CPUUtilization"}, + "Period": 300, + "Stat": "Average", + }, + } + ], + "start_time": "2024-01-01T00:00:00Z", + "end_time": "2024-01-02T00:00:00Z", + } + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "get_metric_data", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_describe_alarms(): + """Test describing CloudWatch alarms.""" + print("\n=== Testing describe_alarms ===") + inputs = {"max_records": 10} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "describe_alarms", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_get_alarm_history(): + """Test retrieving CloudWatch alarm history.""" + print("\n=== Testing get_alarm_history ===") + inputs = {"max_records": 10} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "get_alarm_history", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_set_alarm_state(): + """Test setting the state of a CloudWatch alarm.""" + print("\n=== Testing set_alarm_state ===") + inputs = { + "alarm_name": "test-alarm", + "state_value": "OK", + "state_reason": "Testing via Autohive", + } + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "set_alarm_state", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +# ============================================================================= +# CloudWatch Logs Actions +# ============================================================================= + + +async def test_describe_log_groups(): + """Test describing CloudWatch log groups.""" + print("\n=== Testing describe_log_groups ===") + inputs = {"limit": 10} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "describe_log_groups", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_filter_log_events(): + """Test filtering CloudWatch log events.""" + print("\n=== Testing filter_log_events ===") + inputs = {"log_group_name": "/aws/lambda/test-function", "limit": 10} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "filter_log_events", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_get_log_events(): + """Test retrieving CloudWatch log events from a specific stream.""" + print("\n=== Testing get_log_events ===") + inputs = { + "log_group_name": "/aws/lambda/test-function", + "log_stream_name": "test-stream", + "limit": 10, + } + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action("get_log_events", inputs, context) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +# ============================================================================= +# CloudTrail Actions +# ============================================================================= + + +async def test_lookup_events(): + """Test looking up CloudTrail events.""" + print("\n=== Testing lookup_events ===") + inputs = {"max_results": 10} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action("lookup_events", inputs, context) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_describe_trails(): + """Test describing CloudTrail trails.""" + print("\n=== Testing describe_trails ===") + inputs = {} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "describe_trails", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_get_trail_status(): + """Test retrieving the status of a CloudTrail trail.""" + print("\n=== Testing get_trail_status ===") + inputs = {"trail_name": "management-events"} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "get_trail_status", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +async def test_get_event_selectors(): + """Test retrieving event selectors for a CloudTrail trail.""" + print("\n=== Testing get_event_selectors ===") + inputs = {"trail_name": "management-events"} + async with ExecutionContext(auth=TEST_AUTH) as context: + try: + result = await integration.execute_action( + "get_event_selectors", inputs, context + ) + print(f"Result: {result}") + return result + except Exception as e: + print(f"Error: {e}") + return None + + +# ============================================================================= +# Run All Tests +# ============================================================================= + + +async def run_all_tests(): + """Run all 20 AWS integration tests and print a summary.""" + print("=" * 60) + print("AWS Integration Tests") + print("=" * 60) + + tests = [ + # Security Hub + ("get_findings", test_get_findings), + ("get_finding_details", test_get_finding_details), + ("update_finding_workflow", test_update_finding_workflow), + ("get_insights", test_get_insights), + # GuardDuty + ("list_detectors", test_list_detectors), + ("list_guardduty_findings", test_list_guardduty_findings), + ("get_guardduty_finding_details", test_get_guardduty_finding_details), + ("archive_findings", test_archive_findings), + # CloudWatch + ("list_metrics", test_list_metrics), + ("get_metric_data", test_get_metric_data), + ("describe_alarms", test_describe_alarms), + ("get_alarm_history", test_get_alarm_history), + ("set_alarm_state", test_set_alarm_state), + # CloudWatch Logs + ("describe_log_groups", test_describe_log_groups), + ("filter_log_events", test_filter_log_events), + ("get_log_events", test_get_log_events), + # CloudTrail + ("lookup_events", test_lookup_events), + ("describe_trails", test_describe_trails), + ("get_trail_status", test_get_trail_status), + ("get_event_selectors", test_get_event_selectors), + ] + + results = [] + for name, test_func in tests: + try: + result = await test_func() + if result is not None: + results.append((name, "PASSED")) + else: + results.append((name, "FAILED: returned None")) + except Exception as e: + results.append((name, f"FAILED: {e}")) + print(f"Error in {name}: {e}") + + print("\n" + "=" * 60) + print("Test Results Summary") + print("=" * 60) + passed = 0 + failed = 0 + for name, status in results: + tag = "PASS" if status == "PASSED" else "FAIL" + print(f"[{tag}] {name}: {status}") + if status == "PASSED": + passed += 1 + else: + failed += 1 + print(f"\nTotal: {passed + failed} | Passed: {passed} | Failed: {failed}") + + +if __name__ == "__main__": + asyncio.run(run_all_tests())