Skip to content

Commit c5bb076

Browse files
committed
cache: allow to set a local proxy endpoint for s3
If Tegola is configured to use a s3 cache endpoint like http://localhost:1234, the AWS request signing step is not done corretly and the real S3 endpoint will return a HTTP 403 stating that the request signing header value doesn't match what it expects. This is a typical use case in k8s, where the HTTP connection go through a local sidecar/proxy. In our case, we have an internal S3 endpoint (implemented via Openstack Swift) and we noticed an increase in CPU usage in the pod running tegola when the TLS cert of the S3 endpoint changed (probably due to high requirements for the Cipher suite). We would like to implement in the local proxy connection pooling and other similar performance improvements, without necessarily changing any of the Tegola's code or settings.
1 parent 5563b2d commit c5bb076

File tree

2 files changed

+50
-8
lines changed

2 files changed

+50
-8
lines changed

cache/s3/README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ The s3cache config supports the following properties:
2222
- `cache_control` (string): [Optional] the HTTP cache control header to set on the file when putting the file. defaults to ''.
2323
- `content_type` (string): [Optional] the http MIME-type set on the file when putting the file. defaults to 'application/vnd.mapbox-vector-tile'.
2424
- `force_path_style` (bool): [Optional] use path-style addressing instead of virtual hosted-style addressing (i.e. http://s3.amazonaws.com/BUCKET/KEY instead of http://BUCKET.s3.amazonaws.com/KEY)
25-
25+
- `req_signing_host` (string): [Optional] force AWS request signing to use a different Host value, useful when `endpoint` is set to a a local proxy/sidecar.
2626

2727
## Credential chain
2828
If the `aws_access_key_id` and `aws_secret_access_key` are not set, then the [credential provider chain](http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html) will be used. The provider chain supports multiple methods for passing credentials, one of which is setting environment variables. For example:
@@ -43,3 +43,12 @@ $ export AWS_REGION=TEST_BUCKET_REGION
4343
$ export AWS_ACCESS_KEY_ID=YOUR_AKID
4444
$ export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
4545
```
46+
47+
## Use a local proxy or sidecar (in k8s)
48+
If `endpoint` is set to a local reverse proxy (like `http://localhost:1234`), then AWS request signing will not work: the real S3 endpoint will return a HTTP 403 error saying:
49+
50+
```
51+
SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
52+
```
53+
54+
To make it work, the `req_signing_host` is a special parameter that forces Tegola to use a different HTTP Host header value when the AWS sdk signs the request to be sent to the real S3 endpoint. It needs to be set to the Host header (so no http:// prefixes etc..) of the real S3 endpoint (behind the reverse proxy for example).

cache/s3/s3.go

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"os"
99
"path/filepath"
10+
"strings"
1011

1112
"github.com/aws/aws-sdk-go/aws"
1213
"github.com/aws/aws-sdk-go/aws/awserr"
@@ -41,16 +42,18 @@ const (
4142
ConfigKeyCacheControl = "cache_control" // defaults to ""
4243
ConfigKeyContentType = "content_type" // defaults to "application/vnd.mapbox-vector-tile"
4344
ConfigKeyS3ForcePath = "force_path_style"
45+
ConfigKeyReqSigningHost = "req_signing_host"
4446
)
4547

4648
const (
47-
DefaultBasepath = ""
48-
DefaultRegion = "us-east-1"
49-
DefaultAccessKey = ""
50-
DefaultSecretKey = ""
51-
DefaultContentType = mvt.MimeType
52-
DefaultEndpoint = ""
53-
DefaultS3ForcePath = false
49+
DefaultBasepath = ""
50+
DefaultRegion = "us-east-1"
51+
DefaultAccessKey = ""
52+
DefaultSecretKey = ""
53+
DefaultContentType = mvt.MimeType
54+
DefaultEndpoint = ""
55+
DefaultS3ForcePath = false
56+
DefaultReqSigningHost = ""
5457
)
5558

5659
// testData is used during New() to confirm the ability to write, read and purge the cache
@@ -141,6 +144,19 @@ func New(config dict.Dicter) (cache.Interface, error) {
141144
return nil, err
142145
}
143146

147+
// If a Proxy/Sidecar/etc.. is used, the Host header needs to
148+
// be fixed to allow Request Signing to work.
149+
// More info: https://github.com/aws/aws-sdk-go/issues/1473
150+
reqSigningHost := DefaultReqSigningHost
151+
reqSigningHost, err = config.String(ConfigKeyReqSigningHost, &reqSigningHost)
152+
if err != nil {
153+
return nil, err
154+
}
155+
156+
if reqSigningHost != "" && endpoint == "" {
157+
return nil, errors.New("The endpoint needs to be set if req_signing_host is set.")
158+
}
159+
144160
// support for static credentials, this is not recommended by AWS but
145161
// necessary for some environments
146162
if accessKey != "" && secretKey != "" {
@@ -170,6 +186,23 @@ func New(config dict.Dicter) (cache.Interface, error) {
170186
}
171187
s3cache.Client = s3.New(sess)
172188

189+
// If req_signing_host is set, then the HTTP host header needs to be updated
190+
// for signing, but replaced with the original value before connecting
191+
// to the endpoint.
192+
// The code is inspired by
193+
// https://github.com/aws/aws-sdk-go/issues/1473#issuecomment-325509965
194+
if reqSigningHost != "" {
195+
s3cache.Client.Handlers.Sign.PushFront(func(r *request.Request) {
196+
r.HTTPRequest.URL.Host = reqSigningHost
197+
})
198+
s3cache.Client.Handlers.Sign.PushBack(func(r *request.Request) {
199+
// If the endpoint variable contains the http(s) protocol prefix,
200+
// we need to sanitize it before adding it back.
201+
endpoint = strings.TrimPrefix(strings.TrimPrefix(endpoint, "http://"), "https://")
202+
r.HTTPRequest.URL.Host = endpoint
203+
})
204+
}
205+
173206
// check for control_access_list env var
174207
acl := os.Getenv("AWS_ACL")
175208
acl, err = config.String(ConfigKeyACL, &acl)

0 commit comments

Comments
 (0)