github.com/mweagle/Sparta@v1.15.0/docs_source/content/reference/apigateway/request_params.md (about) 1 --- 2 date: 2016-03-09T19:56:50+01:00 3 title: Request Parameters 4 weight: 11 5 --- 6 7 # Request Parameters 8 9 This example demonstrates how to accept client request params supplied as HTTP query params and return an expiring S3 URL to access content. 10 The source for this is the [s3ItemInfo](https://github.com/mweagle/SpartaImager/blob/master/application.go#L149) 11 function defined as part of the [SpartaApplication](https://github.com/mweagle/SpartaApplication). 12 13 ## Lambda Definition 14 15 Our function will accept two params: 16 17 * `bucketName` : The S3 bucket name storing the asset 18 * `keyName` : The S3 item key 19 20 Those params will be passed as part of the URL query string. The function will fetch the item metadata, generate an expiring URL for public S3 access, and return a JSON response body with the item data. 21 22 Because [s3ItemInfo](https://github.com/mweagle/SpartaImager/blob/master/application.go#L149) is expected to be invoked by the API Gateway, we'll use the AWS Lambda Go type in the function signature: 23 24 ```go 25 import ( 26 spartaAPIGateway "github.com/mweagle/Sparta/aws/apigateway" 27 spartaEvents "github.com/mweagle/Sparta/aws/events" 28 ) 29 30 func s3ItemInfo(ctx context.Context, 31 apigRequest spartaEvents.APIGatewayRequest) (*spartaAPIGateway.Response, error) { 32 logger, _ := ctx.Value(sparta.ContextKeyLogger).(*logrus.Logger) 33 lambdaContext, _ := awsLambdaContext.FromContext(ctx) 34 35 logger.WithFields(logrus.Fields{ 36 "RequestID": lambdaContext.AwsRequestID, 37 }).Info("Request received") 38 39 getObjectInput := &s3.GetObjectInput{ 40 Bucket: aws.String(apigRequest.QueryParams["bucketName"]), 41 Key: aws.String(apigRequest.QueryParams["keyName"]), 42 } 43 44 awsSession := spartaAWS.NewSession(logger) 45 svc := s3.New(awsSession) 46 result, err := svc.GetObject(getObjectInput) 47 if nil != err { 48 return nil, err 49 } 50 presignedReq, _ := svc.GetObjectRequest(getObjectInput) 51 url, err := presignedReq.Presign(5 * time.Minute) 52 if nil != err { 53 return nil, err 54 } 55 return spartaAPIGateway.NewResponse(http.StatusOK, 56 &itemInfoResponse{ 57 S3: result, 58 URL: url, 59 }), nil 60 } 61 ``` 62 63 The [sparta.APIGatewayRequest](https://godoc.org/github.com/mweagle/Sparta/aws/events#APIGatewayRequest) fields 64 correspond to the Integration Response Mapping template discussed in the [previous example](/reference/apigateway/echo_event) 65 (see the full mapping template [here](/reference/apigateway). 66 67 Once the event is unmarshaled, we can use it to fetch the S3 item info: 68 69 ```go 70 getObjectInput := &s3.GetObjectInput{ 71 Bucket: aws.String(lambdaEvent.QueryParams["bucketName"]), 72 Key: aws.String(lambdaEvent.QueryParams["keyName"]), 73 } 74 ``` 75 76 Assuming there are no errors (including the case where the item does not exist), the 77 remainder of the function fetches the data, generates a presigned URL, and returns a JSON response whose 78 shape matches the Sparta default [mapping templates](https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html): 79 80 ```go 81 awsSession := spartaAWS.NewSession(logger) 82 svc := s3.New(awsSession) 83 result, err := svc.GetObject(getObjectInput) 84 if nil != err { 85 return nil, err 86 } 87 presignedReq, _ := svc.GetObjectRequest(getObjectInput) 88 url, err := presignedReq.Presign(5 * time.Minute) 89 if nil != err { 90 return nil, err 91 } 92 return spartaAPIGateway.NewResponse(http.StatusOK, 93 &itemInfoResponse{ 94 S3: result, 95 URL: url, 96 }), nil 97 ``` 98 99 ## API Gateway 100 101 The next step is to create a new [API](https://godoc.org/github.com/mweagle/Sparta#API) instance via `sparta.NewAPIGateway()` 102 103 ```go 104 apiStage := sparta.NewStage("v1") 105 apiGateway := sparta.NewAPIGateway("SpartaImagerAPI", apiStage) 106 ``` 107 108 ## Lambda Binding 109 110 Next we create an `sparta.LambdaAWSInfo` struct that references the `s3ItemInfo` function: 111 112 ```go 113 var iamDynamicRole = sparta.IAMRoleDefinition{} 114 iamDynamicRole.Privileges = append(iamDynamicRole.Privileges, 115 sparta.IAMRolePrivilege{ 116 Actions: []string{"s3:GetObject"}, 117 Resource: resourceArn, 118 }) 119 s3ItemInfoLambdaFn, _ := sparta.NewAWSLambda(sparta.LambdaName(s3ItemInfo), 120 s3ItemInfo, 121 iamDynamicRole) 122 s3ItemInfoOptions.Options = &sparta.LambdaFunctionOptions{ 123 Description: "Get information about an item in S3 via querystring params", 124 MemorySize: 128, 125 Timeout: 10, 126 } 127 ``` 128 129 A few items to note here: 130 131 * We're providing a custom `LambdaFunctionOptions` in case the request to S3 to get item metadata exceeds the default 3 second timeout. 132 * We also add a custom `iamDynamicRole.Privileges` entry to the `Privileges` slice that authorizes the lambda function to _only_ access objects in a single bucket (_resourceArn_). 133 * This bucket ARN is externally created and the ARN provided to this code. 134 * While the API will accept any _bucketName_ value, it is only authorized to access a single bucket. 135 136 ## Resources 137 138 The next step is to associate a URL path with the `sparta.LambdaAWSInfo` struct that represents the `s3ItemInfo` function. This will be the relative path component used to reference our lambda function via the API Gateway. 139 140 ```go 141 apiGatewayResource, _ := api.NewResource("/info", s3ItemInfoLambdaFn) 142 method, err := apiGatewayResource.NewMethod("GET", http.StatusOK) 143 if err != nil { 144 return nil, err 145 } 146 ``` 147 148 ## Whitelist Input 149 150 The final step is to add the whitelisted parameters to the Method definition. 151 152 ```go 153 // Whitelist query string params 154 method.Parameters["method.request.querystring.keyName"] = true 155 method.Parameters["method.request.querystring.bucketName"] = true 156 ``` 157 158 Note that the keynames in the `method.Parameters` map must be of the form: **method.request.{location}.{name}** where location is one of: 159 160 * `querystring` 161 * `path` 162 * `header` 163 164 See the [REST documentation](http://docs.aws.amazon.com/apigateway/api-reference/resource/method/#requestParameters) for more information. 165 166 ## Provision 167 168 With everything configured, let's provision the stack: 169 170 ```nohighlight 171 go run application.go --level debug provision --s3Bucket $S3_BUCKET 172 ``` 173 174 and check the results. 175 176 ## Verify 177 178 As this Sparta application includes an API Gateway definition, the stack `Outputs` includes the API Gateway URL: 179 180 ```text 181 INFO[0243] Stack Outputs ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 182 INFO[0243] APIGatewayURL Description="API Gateway URL" Value="https://xccmsl98p1.execute-api.us-west-2.amazonaws.com/v1" 183 INFO[0243] Stack provisioned CreationTime="2018-12-11 14:56:41.051 +0000 UTC" StackId="arn:aws:cloudformation:us-west-2:123412341234:stack/SpartaImager-mweagle/f7b7d3e0-fd54-11e8-9064-0aa3372404a6" StackName=SpartaImager-mweagle 184 INFO[0243] ════════════════════════════════════════════════ 185 ``` 186 187 Let's fetch an item we know exists: 188 189 ```nohighlight 190 $ curl -vs "https://xccmsl98p1.execute-api.us-west-2.amazonaws.com/v1/info?keyName=twitterAvatar.jpg&bucketName=weagle-public" 191 192 * Trying 13.32.254.241... 193 * TCP_NODELAY set 194 * Connected to xccmsl98p1.execute-api.us-west-2.amazonaws.com (13.32.254.241) port 443 (#0) 195 * ALPN, offering h2 196 * ALPN, offering http/1.1 197 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH 198 * successfully set certificate verify locations: 199 * CAfile: /etc/ssl/cert.pem 200 CApath: none 201 * TLSv1.2 (OUT), TLS handshake, Client hello (1): 202 * TLSv1.2 (IN), TLS handshake, Server hello (2): 203 * TLSv1.2 (IN), TLS handshake, Certificate (11): 204 * TLSv1.2 (IN), TLS handshake, Server key exchange (12): 205 * TLSv1.2 (IN), TLS handshake, Server finished (14): 206 * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): 207 * TLSv1.2 (OUT), TLS change cipher, Client hello (1): 208 * TLSv1.2 (OUT), TLS handshake, Finished (20): 209 * TLSv1.2 (IN), TLS change cipher, Client hello (1): 210 * TLSv1.2 (IN), TLS handshake, Finished (20): 211 * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 212 * ALPN, server accepted to use h2 213 * Server certificate: 214 * subject: CN=*.execute-api.us-west-2.amazonaws.com 215 * start date: Oct 9 00:00:00 2018 GMT 216 * expire date: Oct 9 12:00:00 2019 GMT 217 * subjectAltName: host "xccmsl98p1.execute-api.us-west-2.amazonaws.com" matched cert's "*.execute-api.us-west-2.amazonaws.com" 218 * issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon 219 * SSL certificate verify ok. 220 * Using HTTP2, server supports multi-use 221 * Connection state changed (HTTP/2 confirmed) 222 * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 223 * Using Stream ID: 1 (easy handle 0x7ff68b802c00) 224 > GET /v1/info?keyName=twitterAvatar.jpg&bucketName=weagle-public HTTP/2 225 > Host: xccmsl98p1.execute-api.us-west-2.amazonaws.com 226 > User-Agent: curl/7.54.0 227 > Accept: */* 228 > 229 * Connection state changed (MAX_CONCURRENT_STREAMS updated)! 230 < HTTP/2 200 231 < content-type: application/json 232 < content-length: 1539 233 < date: Tue, 11 Dec 2018 15:08:56 GMT 234 < x-amzn-requestid: aded8786-fd56-11e8-836c-dff86eb3938d 235 < access-control-allow-origin: * 236 < access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key 237 < x-amz-apigw-id: Rv3pRH8jPHcFTfA= 238 < access-control-allow-methods: * 239 < x-amzn-trace-id: Root=1-5c0fd308-f576dae00848eb44535a5c70;Sampled=0 240 < x-cache: Miss from cloudfront 241 < via: 1.1 8ddadd1ab84a7f1bef108d6a72eccf06.cloudfront.net (CloudFront) 242 < x-amz-cf-id: OO01Dua9x5dHyXr-arKJ3LKu2ahbPYv5ESqUg2lAhlzLJDQTLVyW_A== 243 < 244 {"S3":{"AcceptRanges":"bytes","Body":{},"CacheControl":null,"ContentDisposition":null,"ContentEncoding":null,"ContentLanguage":null,"ContentLength":613560,"ContentRange":null,"ContentType":"image/jpeg","DeleteMarker":null,"ETag":"\"7250a1802a5e2f94532b9ee38429a3fd\"","Expiration":null,"Expires":null,"LastModified":"2018-03-14T14:55:19Z","Metadata":{},"MissingMeta":null,"ObjectLockLegalHoldStatus":null,"ObjectLockMode":null,"ObjectLockRetainUntilDate":null,"PartsCount":null,"ReplicationStatus":null,"RequestCharged":null,"Restore":null,"SSECustomerAlgorithm":null,"SSECustomerKeyMD5":null,"SSEKMSKeyId":null,"ServerSideEncryption":null,"StorageClass":null,"TagCount":null,"VersionId":null,"WebsiteRedirectLocation":null},"URL":"https://weagle-public.s3.us-west-2.amazonaws.com/twitterAvatar.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAQMUWTUUFF65WLRLE%2F20181211%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20181211T150856Z&X-Amz-Expires=300&X-Amz-Security-Token=FQoGZXIvYXdzEIH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDMMVITmbkwrrxznAHCL9AaUQwfC%2F%2F6go%2FKBZigDuI4BLLwJzqiwhquTZ9TR1oxVKOAA0h6WzWUEfjjOjZK56SFk3cIJ%2FjKIBmImKpTIGyN7fn48s6N51RFFxra2Mamrp1pDqEcP4VswnJH8C5Q7ZfmltJDiFqLbd4FCQdgoGT228Ls49Uo24EyT%2B%2BTL%2Fl0sKTVYtI1MbGSK%2B%2BKZ6rpPEsyR%2FTuIdeDvA1P%2BRlMEyvr0NhO7Wpf7ZZMs3taNcUMQDRmARyIgAp87ziwIavUTaPqbgpGNqJ6XAO%2Byf3y0g9JurYj44HrwpLWmuF5g%2B%2FtLv8VikzqD8GuWARJuo%2BPlH54KmcMrbXBpLq9sZl2Io3KO%2F4AU%3D&X-Amz-SignedHeaders=host&X-Amz-Signature=88976d33d4cdefff02265e1f40e4d18005231672f1a6e41ad12733f0ce97e91b"} 245 ``` 246 247 Pretty printing the response body: 248 249 ```json 250 { 251 "S3": { 252 "AcceptRanges": "bytes", 253 "Body": {}, 254 "CacheControl": null, 255 "ContentDisposition": null, 256 "ContentEncoding": null, 257 "ContentLanguage": null, 258 "ContentLength": 613560, 259 "ContentRange": null, 260 "ContentType": "image/jpeg", 261 "DeleteMarker": null, 262 "ETag": "\"7250a1802a5e2f94532b9ee38429a3fd\"", 263 "Expiration": null, 264 "Expires": null, 265 "LastModified": "2018-03-14T14:55:19Z", 266 "Metadata": {}, 267 "MissingMeta": null, 268 "ObjectLockLegalHoldStatus": null, 269 "ObjectLockMode": null, 270 "ObjectLockRetainUntilDate": null, 271 "PartsCount": null, 272 "ReplicationStatus": null, 273 "RequestCharged": null, 274 "Restore": null, 275 "SSECustomerAlgorithm": null, 276 "SSECustomerKeyMD5": null, 277 "SSEKMSKeyId": null, 278 "ServerSideEncryption": null, 279 "StorageClass": null, 280 "TagCount": null, 281 "VersionId": null, 282 "WebsiteRedirectLocation": null 283 }, 284 "URL": "https://weagle-public.s3.us-west-2.amazonaws.com/twitterAvatar.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAQMUWTUUFF65WLRLE%2F20181211%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20181211T150856Z&X-Amz-Expires=300&X-Amz-Security-Token=FQoGZXIvYXdzEIH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDMMVITmbkwrrxznAHCL9AaUQwfC%2F%2F6go%2FKBZigDuI4BLLwJzqiwhquTZ9TR1oxVKOAA0h6WzWUEfjjOjZK56SFk3cIJ%2FjKIBmImKpTIGyN7fn48s6N51RFFxra2Mamrp1pDqEcP4VswnJH8C5Q7ZfmltJDiFqLbd4FCQdgoGT228Ls49Uo24EyT%2B%2BTL%2Fl0sKTVYtI1MbGSK%2B%2BKZ6rpPEsyR%2FTuIdeDvA1P%2BRlMEyvr0NhO7Wpf7ZZMs3taNcUMQDRmARyIgAp87ziwIavUTaPqbgpGNqJ6XAO%2Byf3y0g9JurYj44HrwpLWmuF5g%2B%2FtLv8VikzqD8GuWARJuo%2BPlH54KmcMrbXBpLq9sZl2Io3KO%2F4AU%3D&X-Amz-SignedHeaders=host&X-Amz-Signature=88976d33d4cdefff02265e1f40e4d18005231672f1a6e41ad12733f0ce97e91b" 285 } 286 ``` 287 288 What about an item that we know doesn't exist, but is in the bucket our lambda function has privileges to access: 289 290 ```text 291 $ curl -vs "https://xccmsl98p1.execute-api.us-west-2.amazonaws.com/v1/info?keyName=NOT_HERE.jpg&bucketName=weagle-public" 292 293 * Trying 13.32.254.241... 294 * TCP_NODELAY set 295 * Connected to xccmsl98p1.execute-api.us-west-2.amazonaws.com (13.32.254.241) port 443 (#0) 296 * ALPN, offering h2 297 * ALPN, offering http/1.1 298 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH 299 * successfully set certificate verify locations: 300 * CAfile: /etc/ssl/cert.pem 301 CApath: none 302 * TLSv1.2 (OUT), TLS handshake, Client hello (1): 303 * TLSv1.2 (IN), TLS handshake, Server hello (2): 304 * TLSv1.2 (IN), TLS handshake, Certificate (11): 305 * TLSv1.2 (IN), TLS handshake, Server key exchange (12): 306 * TLSv1.2 (IN), TLS handshake, Server finished (14): 307 * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): 308 * TLSv1.2 (OUT), TLS change cipher, Client hello (1): 309 * TLSv1.2 (OUT), TLS handshake, Finished (20): 310 * TLSv1.2 (IN), TLS change cipher, Client hello (1): 311 * TLSv1.2 (IN), TLS handshake, Finished (20): 312 * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 313 * ALPN, server accepted to use h2 314 * Server certificate: 315 * subject: CN=*.execute-api.us-west-2.amazonaws.com 316 * start date: Oct 9 00:00:00 2018 GMT 317 * expire date: Oct 9 12:00:00 2019 GMT 318 * subjectAltName: host "xccmsl98p1.execute-api.us-west-2.amazonaws.com" matched cert's "*.execute-api.us-west-2.amazonaws.com" 319 * issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon 320 * SSL certificate verify ok. 321 * Using HTTP2, server supports multi-use 322 * Connection state changed (HTTP/2 confirmed) 323 * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 324 * Using Stream ID: 1 (easy handle 0x7f9e4f00b600) 325 > GET /v1/info?keyName=twitterAvatarArgh.jpg&bucketName=weagle HTTP/2 326 > Host: xccmsl98p1.execute-api.us-west-2.amazonaws.com 327 > User-Agent: curl/7.54.0 328 > Accept: */* 329 > 330 * Connection state changed (MAX_CONCURRENT_STREAMS updated)! 331 < HTTP/2 404 332 < content-type: application/json 333 < content-length: 177 334 < date: Tue, 11 Dec 2018 15:21:18 GMT 335 < x-amzn-requestid: 675edef9-fd58-11e8-ae45-3fac75041f4d 336 < access-control-allow-origin: * 337 < access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key 338 < x-amz-apigw-id: Rv5dAETkvHcFvYg= 339 < access-control-allow-methods: * 340 < x-amzn-trace-id: Root=1-5c0fd5ec-1d8bba64519f71126c12b4d6;Sampled=0 341 < x-cache: Error from cloudfront 342 < via: 1.1 4c4ed81695980f3c6829b9fd229bd0f8.cloudfront.net (CloudFront) 343 < x-amz-cf-id: ZT5R4BUSAkZpT46s_wCjBImHsM3w6mHFlYG0lnfwONSkPCgxzOQ_lQ== 344 < 345 {"error":"AccessDenied: Access Denied\n\tstatus code: 403, request id: A10C69E17E4C9D00, host id: pAnhP+tg9rDh0yP5FJyC8bSnj1GJJjJvAFXwiluW4yHnVvt5EvkvkpKA4UzjJmCoFyI8hGST6YE="} 346 * Connection #0 to host xccmsl98p1.execute-api.us-west-2.amazonaws.com left intact 347 ``` 348 349 And finally, what if we try to access a bucket that our lambda function isn't authorized to access: 350 351 ```text 352 $ curl -vs "https://xccmsl98p1.execute-api.us-west-2.amazonaws.com/v1/info?keyName=NOT_HERE.jpg&bucketName=VERY_PRIVATE_BUCKET" 353 354 * Trying 13.32.254.241... 355 * TCP_NODELAY set 356 * Connected to xccmsl98p1.execute-api.us-west-2.amazonaws.com (13.32.254.241) port 443 (#0) 357 * ALPN, offering h2 358 * ALPN, offering http/1.1 359 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH 360 * successfully set certificate verify locations: 361 * CAfile: /etc/ssl/cert.pem 362 CApath: none 363 * TLSv1.2 (OUT), TLS handshake, Client hello (1): 364 * TLSv1.2 (IN), TLS handshake, Server hello (2): 365 * TLSv1.2 (IN), TLS handshake, Certificate (11): 366 * TLSv1.2 (IN), TLS handshake, Server key exchange (12): 367 * TLSv1.2 (IN), TLS handshake, Server finished (14): 368 * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): 369 * TLSv1.2 (OUT), TLS change cipher, Client hello (1): 370 * TLSv1.2 (OUT), TLS handshake, Finished (20): 371 * TLSv1.2 (IN), TLS change cipher, Client hello (1): 372 * TLSv1.2 (IN), TLS handshake, Finished (20): 373 * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 374 * ALPN, server accepted to use h2 375 * Server certificate: 376 * subject: CN=*.execute-api.us-west-2.amazonaws.com 377 * start date: Oct 9 00:00:00 2018 GMT 378 * expire date: Oct 9 12:00:00 2019 GMT 379 * subjectAltName: host "xccmsl98p1.execute-api.us-west-2.amazonaws.com" matched cert's "*.execute-api.us-west-2.amazonaws.com" 380 * issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon 381 * SSL certificate verify ok. 382 * Using HTTP2, server supports multi-use 383 * Connection state changed (HTTP/2 confirmed) 384 * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 385 * Using Stream ID: 1 (easy handle 0x7f9e4f00b600) 386 > GET /v1/info?keyName=twitterAvatarArgh.jpg&bucketName=weagle HTTP/2 387 > Host: xccmsl98p1.execute-api.us-west-2.amazonaws.com 388 > User-Agent: curl/7.54.0 389 > Accept: */* 390 > 391 * Connection state changed (MAX_CONCURRENT_STREAMS updated)! 392 < HTTP/2 404 393 < content-type: application/json 394 < content-length: 177 395 < date: Tue, 11 Dec 2018 15:21:18 GMT 396 < x-amzn-requestid: 675edef9-fd58-11e8-ae45-3fac75041f4d 397 < access-control-allow-origin: * 398 < access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key 399 < x-amz-apigw-id: Rv5dAETkvHcFvYg= 400 < access-control-allow-methods: * 401 < x-amzn-trace-id: Root=1-5c0fd5ec-1d8bba64519f71126c12b4d6;Sampled=0 402 < x-cache: Error from cloudfront 403 < via: 1.1 4c4ed81695980f3c6829b9fd229bd0f8.cloudfront.net (CloudFront) 404 < x-amz-cf-id: ZT5R4BUSAkZpT46s_wCjBImHsM3w6mHFlYG0lnfwONSkPCgxzOQ_lQ== 405 < 406 {"error":"AccessDenied: Access Denied\n\tstatus code: 403, request id: A10C69E17E4C9D00, host id: pAnhP+tg9rDh0yP5FJyC8bSnj1GJJjJvAFXwiluW4yHnVvt5EvkvkpKA4UzjJmCoFyI8hGST6YE="} 407 * Connection #0 to host xccmsl98p1.execute-api.us-west-2.amazonaws.com left intact 408 ``` 409 410 ## Cleanup 411 412 Before moving on, remember to decommission the service via: 413 414 ```nohighlight 415 go run application.go delete 416 ``` 417 418 ## Conclusion 419 420 With this example we've walked through a simple example that whitelists user input, uses IAM Roles to 421 limit what S3 buckets a lambda function may access, and returns an _application/json_ response to the caller.