|
| 1 | +/** |
| 2 | + * @packageDocumentation |
| 3 | + * |
| 4 | + * Load balancers in AWS write access logs to an S3 bucket. This component can be used to create an AWS Glue table configured to query those logs with Athena. This component can be used on its own or automatically created by passing `accessLogsTable` parameter to the {@link loadBalancer} component. |
| 5 | + * |
| 6 | + * ```typescript |
| 7 | + * import * as saws from '@stackattack/aws'; |
| 8 | + * |
| 9 | + * const ctx = saws.context(); |
| 10 | + * const vpc = saws.vpc(ctx); |
| 11 | + * const logsBucket = saws.bucket(ctx); |
| 12 | + * const loadBalancer = saws.loadBalancer(ctx, { |
| 13 | + * network: vpc.network("public") |
| 14 | + * }); |
| 15 | + * |
| 16 | + * ``` |
| 17 | + * |
| 18 | + */ |
| 19 | + |
| 20 | +import * as aws from "@pulumi/aws"; |
| 21 | +import * as pulumi from "@pulumi/pulumi"; |
| 22 | +import type { Context } from "../context.js"; |
| 23 | +import { type BucketInput, getBucketId } from "./bucket.js"; |
| 24 | + |
| 25 | +/** |
| 26 | + * Configuration options for create a load balancer logs table |
| 27 | + */ |
| 28 | +export interface LoadBalancerLogsTableArgs { |
| 29 | + /** Name of the table used for query logs */ |
| 30 | + name: pulumi.Input<string>; |
| 31 | + /** Database name to create the table in; default to "default" */ |
| 32 | + database?: pulumi.Input<string>; |
| 33 | + /** Bucket where load balancer logs are stored */ |
| 34 | + bucket: pulumi.Input<BucketInput>; |
| 35 | + /** Prefix within the bucket where load balancer logs are stored. Note that all load balancer logs are stored with a prefix like "AWSLogs/<account_id>/elasticloadbalancing/<region>/". That will be appended automatically--if specified this should only include the prefix _before_ "AWSLogs/...". */ |
| 36 | + prefix?: pulumi.Input<string>; |
| 37 | + /** Whether to skip adding a prefix to the context */ |
| 38 | + noPrefix?: boolean; |
| 39 | +} |
| 40 | + |
| 41 | +export function loadBalancerLogsTable( |
| 42 | + ctx: Context, |
| 43 | + args: LoadBalancerLogsTableArgs, |
| 44 | +): aws.glue.CatalogTable { |
| 45 | + if (!args.noPrefix) { |
| 46 | + ctx = ctx.prefix("lb-logs-table"); |
| 47 | + } |
| 48 | + |
| 49 | + const region = aws.getRegionOutput(); |
| 50 | + const identity = aws.getCallerIdentityOutput(); |
| 51 | + const bucketName = getBucketId(args.bucket); |
| 52 | + |
| 53 | + const prefix = pulumi |
| 54 | + .output(args.prefix) |
| 55 | + .apply((prefix) => |
| 56 | + prefix ? (prefix.endsWith("/") ? prefix : `${prefix}/`) : "", |
| 57 | + ); |
| 58 | + |
| 59 | + const accessLogsStorageLocation = pulumi.interpolate`s3://${bucketName}/${prefix}/AWSLogs/${identity.accountId}/elasticloadbalancing/${region.name}/`; |
| 60 | + |
| 61 | + // Based on instructions here: |
| 62 | + // https://docs.aws.amazon.com/athena/latest/ug/application-load-balancer-logs.html#create-alb-table-partition-projection |
| 63 | + return new aws.glue.CatalogTable(ctx.id(), { |
| 64 | + databaseName: args.database ?? "default", |
| 65 | + name: args.name, |
| 66 | + tableType: "EXTERNAL_TABLE", |
| 67 | + parameters: { |
| 68 | + EXTERNAL: "TRUE", |
| 69 | + "projection.enabled": "true", |
| 70 | + "projection.day.type": "date", |
| 71 | + "projection.day.range": "2024/01/01,NOW", |
| 72 | + "projection.day.format": "yyyy/MM/dd", |
| 73 | + "projection.day.interval": "1", |
| 74 | + "projection.day.interval.unit": "DAYS", |
| 75 | + "storage.location.template": pulumi.interpolate`${accessLogsStorageLocation}\${day}`, |
| 76 | + }, |
| 77 | + storageDescriptor: { |
| 78 | + location: accessLogsStorageLocation, |
| 79 | + inputFormat: "org.apache.hadoop.mapred.TextInputFormat", |
| 80 | + outputFormat: |
| 81 | + "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat", |
| 82 | + serDeInfo: { |
| 83 | + parameters: { |
| 84 | + "serialization.format": "1", |
| 85 | + "input.regex": |
| 86 | + '([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*):([0-9]*) ([^ ]*)[:-]([0-9]*) ([-.0-9]*) ([-.0-9]*) ([-.0-9]*) (|[-0-9]*) (-|[-0-9]*) ([-0-9]*) ([-0-9]*) "([^ ]*) (.*) (- |[^ ]*)" "([^"]*)" ([A-Z0-9-_]+) ([A-Za-z0-9.-]*) ([^ ]*) "([^"]*)" "([^"]*)" "([^"]*)" ([-.0-9]*) ([^ ]*) "([^"]*)" "([^"]*)" "([^ ]*)" "([^s]+?)" "([^s]+)" "([^ ]*)" "([^ ]*)" ([^ ]*)(.*?)', |
| 87 | + }, |
| 88 | + serializationLibrary: "org.apache.hadoop.hive.serde2.RegexSerDe", |
| 89 | + }, |
| 90 | + columns: [ |
| 91 | + { |
| 92 | + name: "type", |
| 93 | + type: "string", |
| 94 | + }, |
| 95 | + { |
| 96 | + name: "time", |
| 97 | + type: "string", |
| 98 | + }, |
| 99 | + { |
| 100 | + name: "elb", |
| 101 | + type: "string", |
| 102 | + }, |
| 103 | + { |
| 104 | + name: "client_ip", |
| 105 | + type: "string", |
| 106 | + }, |
| 107 | + { |
| 108 | + name: "client_port", |
| 109 | + type: "int", |
| 110 | + }, |
| 111 | + { |
| 112 | + name: "target_ip", |
| 113 | + type: "string", |
| 114 | + }, |
| 115 | + { |
| 116 | + name: "target_port", |
| 117 | + type: "int", |
| 118 | + }, |
| 119 | + { |
| 120 | + name: "requesting_processing_time", |
| 121 | + type: "double", |
| 122 | + }, |
| 123 | + { |
| 124 | + name: "target_processing_time", |
| 125 | + type: "double", |
| 126 | + }, |
| 127 | + { |
| 128 | + name: "response_processing_time", |
| 129 | + type: "double", |
| 130 | + }, |
| 131 | + { |
| 132 | + name: "elb_status_code", |
| 133 | + type: "int", |
| 134 | + }, |
| 135 | + { |
| 136 | + name: "target_status_code", |
| 137 | + type: "int", |
| 138 | + }, |
| 139 | + { |
| 140 | + name: "received_bytes", |
| 141 | + type: "bigint", |
| 142 | + }, |
| 143 | + { |
| 144 | + name: "sent_bytes", |
| 145 | + type: "bigint", |
| 146 | + }, |
| 147 | + { |
| 148 | + name: "request_verb", |
| 149 | + type: "string", |
| 150 | + }, |
| 151 | + { |
| 152 | + name: "request_url", |
| 153 | + type: "string", |
| 154 | + }, |
| 155 | + { |
| 156 | + name: "request_proto", |
| 157 | + type: "string", |
| 158 | + }, |
| 159 | + { |
| 160 | + name: "user_agent", |
| 161 | + type: "string", |
| 162 | + }, |
| 163 | + { |
| 164 | + name: "ssl_cipher", |
| 165 | + type: "string", |
| 166 | + }, |
| 167 | + { |
| 168 | + name: "ssl_protocol", |
| 169 | + type: "string", |
| 170 | + }, |
| 171 | + { |
| 172 | + name: "target_group_arn", |
| 173 | + type: "string", |
| 174 | + }, |
| 175 | + { |
| 176 | + name: "trace_id", |
| 177 | + type: "string", |
| 178 | + }, |
| 179 | + { |
| 180 | + name: "domain_name", |
| 181 | + type: "string", |
| 182 | + }, |
| 183 | + { |
| 184 | + name: "chosen_cert_arn", |
| 185 | + type: "string", |
| 186 | + }, |
| 187 | + { |
| 188 | + name: "matched_rule_priority", |
| 189 | + type: "string", |
| 190 | + }, |
| 191 | + { |
| 192 | + name: "request_creation_time", |
| 193 | + type: "string", |
| 194 | + }, |
| 195 | + { |
| 196 | + name: "actions_executed", |
| 197 | + type: "string", |
| 198 | + }, |
| 199 | + { |
| 200 | + name: "redirect_url", |
| 201 | + type: "string", |
| 202 | + }, |
| 203 | + { |
| 204 | + name: "lambda_error_reason", |
| 205 | + type: "string", |
| 206 | + }, |
| 207 | + { |
| 208 | + name: "target_port_list", |
| 209 | + type: "string", |
| 210 | + }, |
| 211 | + { |
| 212 | + name: "target_status_code_list", |
| 213 | + type: "string", |
| 214 | + }, |
| 215 | + { |
| 216 | + name: "classification", |
| 217 | + type: "string", |
| 218 | + }, |
| 219 | + { |
| 220 | + name: "classification_reason", |
| 221 | + type: "string", |
| 222 | + }, |
| 223 | + { |
| 224 | + name: "conn_trace_id", |
| 225 | + type: "string", |
| 226 | + }, |
| 227 | + { |
| 228 | + name: "unhandled_postfix", |
| 229 | + type: "string", |
| 230 | + }, |
| 231 | + ], |
| 232 | + }, |
| 233 | + partitionKeys: [ |
| 234 | + { |
| 235 | + name: "day", |
| 236 | + type: "string", |
| 237 | + }, |
| 238 | + ], |
| 239 | + }); |
| 240 | +} |
0 commit comments