From f07156dfff0fb659ad06d5d38a163e2f12d25bdb Mon Sep 17 00:00:00 2001 From: misraved Date: Tue, 26 Nov 2024 11:54:59 +0530 Subject: [PATCH 1/3] Add aws_config_delivery_channel table closes #2325 --- aws/plugin.go | 1 + aws/table_aws_config_delivery_channel.go | 205 +++++++++++++++++++++ docs/tables/aws_config_delivery_channel.md | 149 +++++++++++++++ 3 files changed, 355 insertions(+) create mode 100644 aws/table_aws_config_delivery_channel.go create mode 100644 docs/tables/aws_config_delivery_channel.md diff --git a/aws/plugin.go b/aws/plugin.go index fd72d1b01..c9ea62e0f 100644 --- a/aws/plugin.go +++ b/aws/plugin.go @@ -164,6 +164,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "aws_config_aggregate_authorization": tableAwsConfigAggregateAuthorization(ctx), "aws_config_configuration_recorder": tableAwsConfigConfigurationRecorder(ctx), "aws_config_conformance_pack": tableAwsConfigConformancePack(ctx), + "aws_config_delivery_channel": tableAwsConfigDeliveryChannel(ctx), "aws_config_retention_configuration": tableAwsConfigRetentionConfiguration(ctx), "aws_config_rule": tableAwsConfigRule(ctx), "aws_cost_by_account_daily": tableAwsCostByLinkedAccountDaily(ctx), diff --git a/aws/table_aws_config_delivery_channel.go b/aws/table_aws_config_delivery_channel.go new file mode 100644 index 000000000..37ae61832 --- /dev/null +++ b/aws/table_aws_config_delivery_channel.go @@ -0,0 +1,205 @@ +package aws + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" + + configservicev1 "github.com/aws/aws-sdk-go/service/configservice" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" +) + +//// TABLE DEFINITION + +func tableAwsConfigDeliveryChannel(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "aws_config_delivery_channel", + Description: "AWS Config Delivery Channel", + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"name", "region"}), + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: shouldIgnoreErrors([]string{"NoSuchDeliveryChannelException"}), + }, + Hydrate: getConfigDeliveryChannel, + Tags: map[string]string{"service": "config", "action": "DescribeDeliveryChannels"}, + }, + List: &plugin.ListConfig{ + Hydrate: listConfigDeliveryChannels, + Tags: map[string]string{"service": "config", "action": "DescribeDeliveryChannels"}, + }, + GetMatrixItemFunc: SupportedRegionMatrix(configservicev1.EndpointsID), + Columns: awsRegionalColumns([]*plugin.Column{ + { + Name: "name", + Description: "The name of the delivery channel.", + Type: proto.ColumnType_STRING, + }, + { + Name: "arn", + Description: "The Amazon Resource Name (ARN) of the delivery channel.", + Type: proto.ColumnType_STRING, + Hydrate: getAwsDeliveryChannelARN, + Transform: transform.FromValue(), + }, + { + Name: "s3_bucket_name", + Description: "The name of the Amazon S3 bucket to which AWS Config delivers configuration snapshots and configuration history files.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("S3BucketName"), + }, + { + Name: "s3_key_prefix", + Description: "The prefix for the specified Amazon S3 bucket.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("S3KeyPrefix"), + }, + { + Name: "s3_kms_key_arn", + Description: "The Amazon Resource Name (ARN) of the KMS key.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("S3KmsKeyArn"), + }, + { + Name: "sns_topic_arn", + Description: "The Amazon Resource Name (ARN) of the Amazon SNS topic to which AWS Config sends notifications about configuration changes.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("SnsTopicARN"), + }, + { + Name: "delivery_frequency", + Description: "The frequency with which the AWS Config delivers configuration snapshots to the Amazon S3 bucket.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ConfigSnapshotDeliveryProperties.DeliveryFrequency"), + }, + { + Name: "status", + Description: "The current status of the delivery channel.", + Type: proto.ColumnType_JSON, + Hydrate: getConfigDeliveryChannelStatus, + Transform: transform.FromValue(), + }, + // Standard columns + { + Name: "akas", + Description: resourceInterfaceDescription("akas"), + Type: proto.ColumnType_JSON, + Hydrate: getAwsDeliveryChannelARN, + Transform: transform.FromValue().Transform(transform.EnsureStringArray), + }, + { + Name: "title", + Description: resourceInterfaceDescription("title"), + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name"), + }, + }), + } +} + +//// LIST FUNCTION + +func listConfigDeliveryChannels(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create session + svc, err := ConfigClient(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("aws_config_delivery_channel.listConfigDeliveryChannels", "get_client_error", err) + return nil, err + } + + input := &configservice.DescribeDeliveryChannelsInput{} + + op, err := svc.DescribeDeliveryChannels(ctx, input) + if err != nil { + plugin.Logger(ctx).Error("aws_config_delivery_channel.listConfigDeliveryChannels", "api_error", err) + return nil, err + } + + if op.DeliveryChannels != nil { + for _, deliveryChannel := range op.DeliveryChannels { + d.StreamListItem(ctx, deliveryChannel) + + // Context can be cancelled due to manual cancellation or limit being reached + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getConfigDeliveryChannel(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + name := d.EqualsQuals["name"].GetStringValue() + + // Create session + svc, err := ConfigClient(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("aws_config_delivery_channel.getConfigDeliveryChannel", "get_client_error", err) + return nil, err + } + + params := &configservice.DescribeDeliveryChannelsInput{ + DeliveryChannelNames: []string{name}, + } + + op, err := svc.DescribeDeliveryChannels(ctx, params) + if err != nil { + plugin.Logger(ctx).Error("aws_config_delivery_channel.getConfigDeliveryChannel", "api_error", err) + return nil, err + } + + if op != nil && len(op.DeliveryChannels) > 0 { + return op.DeliveryChannels[0], nil + } + + return nil, nil +} + +func getConfigDeliveryChannelStatus(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + deliveryChannel := h.Item.(types.DeliveryChannel) + + // Create session + svc, err := ConfigClient(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("aws_config_delivery_channel.getConfigDeliveryChannelStatus", "get_client_error", err) + return nil, err + } + + params := &configservice.DescribeDeliveryChannelStatusInput{ + DeliveryChannelNames: []string{*deliveryChannel.Name}, + } + + status, err := svc.DescribeDeliveryChannelStatus(ctx, params) + if err != nil { + plugin.Logger(ctx).Error("aws_config_delivery_channel.getConfigDeliveryChannelStatus", "api_error", err) + return nil, err + } + + if len(status.DeliveryChannelsStatus) < 1 { + return nil, nil + } + + return status.DeliveryChannelsStatus[0], nil +} + +func getAwsDeliveryChannelARN(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + region := d.EqualsQualString(matrixKeyRegion) + + deliveryChannel := h.Item.(types.DeliveryChannel) + + c, err := getCommonColumns(ctx, d, h) + if err != nil { + plugin.Logger(ctx).Error("aws_config_delivery_channel.getAwsDeliveryChannelARN", "api_error", err) + return nil, err + } + commonColumnData := c.(*awsCommonColumnData) + arn := "arn:" + commonColumnData.Partition + ":config:" + region + ":" + commonColumnData.AccountId + ":delivery-channel" + "/" + *deliveryChannel.Name + + return arn, nil +} diff --git a/docs/tables/aws_config_delivery_channel.md b/docs/tables/aws_config_delivery_channel.md new file mode 100644 index 000000000..0dfdfb578 --- /dev/null +++ b/docs/tables/aws_config_delivery_channel.md @@ -0,0 +1,149 @@ +--- +title: "Steampipe Table: aws_config_delivery_channel - Query AWS Config Delivery Channels using SQL" +description: "Allows users to query AWS Config Delivery Channels" +--- + +# Table: aws_config_delivery_channel - Query AWS Config Delivery Channels using SQL + +The AWS Config Delivery Channel is a feature that enables AWS Config to deliver configuration snapshots and configuration change notifications to specified destinations. It plays a key role in ensuring that your configuration data is stored securely and notifications are sent promptly for compliance or operational purposes. + +## Table Usage Guide + +The `aws_config_delivery_channel` table in Steampipe provides insights into the Delivery Channels associated with AWS Config. This table enables DevOps engineers, security analysts, and cloud administrators to query delivery channel details such as the destination S3 bucket, SNS topic for notifications, and delivery status. Use this table to ensure your configuration change data is being delivered correctly and troubleshoot delivery-related issues. + +## Examples + +### Retrieve basic delivery channel information +Get a detailed view of your AWS Config Delivery Channels, including their destinations and notification settings. + +```sql+postgres +select + name, + s3_bucket_name, + s3_key_prefix, + sns_topic_arn, + delivery_frequency, + status, + title, + akas +from + aws_config_delivery_channel; +``` + +```sql+sqlite +select + name, + s3_bucket_name, + s3_key_prefix, + sns_topic_arn, + delivery_frequency, + status, + title, + akas +from + aws_config_delivery_channel; +``` + +### List delivery channels with no SNS topic +Identify delivery channels that do not have an SNS topic configured for notifications. This can help ensure you have proper alerting mechanisms in place. + +```sql+postgres +select + name, + s3_bucket_name, + sns_topic_arn +from + aws_config_delivery_channel +where + sns_topic_arn is null; +``` + +```sql+sqlite +select + name, + s3_bucket_name, + sns_topic_arn +from + aws_config_delivery_channel +where + sns_topic_arn is null; +``` + +### Check delivery channels with delivery failures +Discover delivery channels with failed deliveries to address issues in your AWS Config setup. + +```sql+postgres +select + name, + status ->> 'LastStatus' as last_status, + status ->> 'LastStatusChangeTime' as last_status_change_time, + status ->> 'LastErrorCode' as last_error_code, + status ->> 'LastErrorMessage' as last_error_message +from + aws_config_delivery_channel +where + status ->> 'LastStatus' = 'FAILURE'; +``` + +```sql+sqlite +select + name, + json_extract(status, '$.LastStatus') as last_status, + json_extract(status, '$.LastStatusChangeTime') as last_status_change_time, + json_extract(status, '$.LastErrorCode') as last_error_code, + json_extract(status, '$.LastErrorMessage') as last_error_message +from + aws_config_delivery_channel +where + json_extract(status, '$.LastStatus') = 'FAILURE'; +``` + +### List delivery channels sending to a specific S3 bucket +Query the delivery channels that are configured to send data to a particular S3 bucket. + +```sql+postgres +select + name, + s3_bucket_name, + sns_topic_arn, + delivery_frequency +from + aws_config_delivery_channel +where + s3_bucket_name = 'test-bucket-delivery-channel'; +``` + +```sql+sqlite +select + name, + s3_bucket_name, + sns_topic_arn, + delivery_frequency +from + aws_config_delivery_channel +where + s3_bucket_name = 'test-bucket-delivery-channel'; +``` + +### Analyze delivery frequency of all channels +Get an overview of how often your delivery channels send data, ensuring they align with organizational requirements. + +```sql+postgres +select + name, + delivery_frequency, + s3_bucket_name, + sns_topic_arn +from + aws_config_delivery_channel; +``` + +```sql+sqlite +select + name, + delivery_frequency, + s3_bucket_name, + sns_topic_arn +from + aws_config_delivery_channel; +``` \ No newline at end of file From d93d85fa55dc3a5f7d09a20fd92bc511228c5d46 Mon Sep 17 00:00:00 2001 From: misraved Date: Fri, 29 Nov 2024 13:00:44 +0530 Subject: [PATCH 2/3] Update table per review comments --- aws/table_aws_config_delivery_channel.go | 64 +++++++----------------- 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/aws/table_aws_config_delivery_channel.go b/aws/table_aws_config_delivery_channel.go index 37ae61832..21baa14d8 100644 --- a/aws/table_aws_config_delivery_channel.go +++ b/aws/table_aws_config_delivery_channel.go @@ -19,16 +19,17 @@ func tableAwsConfigDeliveryChannel(_ context.Context) *plugin.Table { return &plugin.Table{ Name: "aws_config_delivery_channel", Description: "AWS Config Delivery Channel", - Get: &plugin.GetConfig{ - KeyColumns: plugin.AllColumns([]string{"name", "region"}), + List: &plugin.ListConfig{ + Hydrate: listConfigDeliveryChannels, + KeyColumns: []*plugin.KeyColumn{ + { + Name: "name", + Require: plugin.Optional, + }, + }, IgnoreConfig: &plugin.IgnoreConfig{ ShouldIgnoreErrorFunc: shouldIgnoreErrors([]string{"NoSuchDeliveryChannelException"}), }, - Hydrate: getConfigDeliveryChannel, - Tags: map[string]string{"service": "config", "action": "DescribeDeliveryChannels"}, - }, - List: &plugin.ListConfig{ - Hydrate: listConfigDeliveryChannels, Tags: map[string]string{"service": "config", "action": "DescribeDeliveryChannels"}, }, GetMatrixItemFunc: SupportedRegionMatrix(configservicev1.EndpointsID), @@ -38,13 +39,6 @@ func tableAwsConfigDeliveryChannel(_ context.Context) *plugin.Table { Description: "The name of the delivery channel.", Type: proto.ColumnType_STRING, }, - { - Name: "arn", - Description: "The Amazon Resource Name (ARN) of the delivery channel.", - Type: proto.ColumnType_STRING, - Hydrate: getAwsDeliveryChannelARN, - Transform: transform.FromValue(), - }, { Name: "s3_bucket_name", Description: "The name of the Amazon S3 bucket to which AWS Config delivers configuration snapshots and configuration history files.", @@ -82,12 +76,13 @@ func tableAwsConfigDeliveryChannel(_ context.Context) *plugin.Table { Hydrate: getConfigDeliveryChannelStatus, Transform: transform.FromValue(), }, - // Standard columns + + // Steampipe standard columns { Name: "akas", Description: resourceInterfaceDescription("akas"), Type: proto.ColumnType_JSON, - Hydrate: getAwsDeliveryChannelARN, + Hydrate: getAwsDeliveryChannelAkas, Transform: transform.FromValue().Transform(transform.EnsureStringArray), }, { @@ -112,6 +107,12 @@ func listConfigDeliveryChannels(ctx context.Context, d *plugin.QueryData, _ *plu input := &configservice.DescribeDeliveryChannelsInput{} + // Additonal Filter + equalQuals := d.EqualsQuals + if equalQuals["name"] != nil { + input.DeliveryChannelNames = []string{equalQuals["name"].GetStringValue()} + } + op, err := svc.DescribeDeliveryChannels(ctx, input) if err != nil { plugin.Logger(ctx).Error("aws_config_delivery_channel.listConfigDeliveryChannels", "api_error", err) @@ -134,33 +135,6 @@ func listConfigDeliveryChannels(ctx context.Context, d *plugin.QueryData, _ *plu //// HYDRATE FUNCTIONS -func getConfigDeliveryChannel(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - name := d.EqualsQuals["name"].GetStringValue() - - // Create session - svc, err := ConfigClient(ctx, d) - if err != nil { - plugin.Logger(ctx).Error("aws_config_delivery_channel.getConfigDeliveryChannel", "get_client_error", err) - return nil, err - } - - params := &configservice.DescribeDeliveryChannelsInput{ - DeliveryChannelNames: []string{name}, - } - - op, err := svc.DescribeDeliveryChannels(ctx, params) - if err != nil { - plugin.Logger(ctx).Error("aws_config_delivery_channel.getConfigDeliveryChannel", "api_error", err) - return nil, err - } - - if op != nil && len(op.DeliveryChannels) > 0 { - return op.DeliveryChannels[0], nil - } - - return nil, nil -} - func getConfigDeliveryChannelStatus(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { deliveryChannel := h.Item.(types.DeliveryChannel) @@ -188,14 +162,14 @@ func getConfigDeliveryChannelStatus(ctx context.Context, d *plugin.QueryData, h return status.DeliveryChannelsStatus[0], nil } -func getAwsDeliveryChannelARN(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { +func getAwsDeliveryChannelAkas(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { region := d.EqualsQualString(matrixKeyRegion) deliveryChannel := h.Item.(types.DeliveryChannel) c, err := getCommonColumns(ctx, d, h) if err != nil { - plugin.Logger(ctx).Error("aws_config_delivery_channel.getAwsDeliveryChannelARN", "api_error", err) + plugin.Logger(ctx).Error("aws_config_delivery_channel.getAwsDeliveryChannelAkas", "api_error", err) return nil, err } commonColumnData := c.(*awsCommonColumnData) From 3e0c768c5cffa70fb7db6175fa3914ecfb835bcd Mon Sep 17 00:00:00 2001 From: Keep Focused Date: Fri, 29 Nov 2024 14:58:35 +0700 Subject: [PATCH 3/3] Updated the query and the title --- docs/tables/aws_config_delivery_channel.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tables/aws_config_delivery_channel.md b/docs/tables/aws_config_delivery_channel.md index 0dfdfb578..fe1218964 100644 --- a/docs/tables/aws_config_delivery_channel.md +++ b/docs/tables/aws_config_delivery_channel.md @@ -44,7 +44,7 @@ from aws_config_delivery_channel; ``` -### List delivery channels with no SNS topic +### List delivery channels without SNS topic configured Identify delivery channels that do not have an SNS topic configured for notifications. This can help ensure you have proper alerting mechanisms in place. ```sql+postgres @@ -82,7 +82,7 @@ select from aws_config_delivery_channel where - status ->> 'LastStatus' = 'FAILURE'; + (status ->> 'LastStatus') = 'FAILURE'; ``` ```sql+sqlite @@ -146,4 +146,4 @@ select sns_topic_arn from aws_config_delivery_channel; -``` \ No newline at end of file +```