Skip to content

Commit

Permalink
cache: allow to set a local proxy endpoint for s3
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
elukey committed May 31, 2024
1 parent 5563b2d commit c5bb076
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 8 deletions.
11 changes: 10 additions & 1 deletion cache/s3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The s3cache config supports the following properties:
- `cache_control` (string): [Optional] the HTTP cache control header to set on the file when putting the file. defaults to ''.
- `content_type` (string): [Optional] the http MIME-type set on the file when putting the file. defaults to 'application/vnd.mapbox-vector-tile'.
- `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)

- `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.

## Credential chain
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:
Expand All @@ -43,3 +43,12 @@ $ export AWS_REGION=TEST_BUCKET_REGION
$ export AWS_ACCESS_KEY_ID=YOUR_AKID
$ export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
```

## Use a local proxy or sidecar (in k8s)
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:

```
SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
```

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).
47 changes: 40 additions & 7 deletions cache/s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"os"
"path/filepath"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
Expand Down Expand Up @@ -41,16 +42,18 @@ const (
ConfigKeyCacheControl = "cache_control" // defaults to ""
ConfigKeyContentType = "content_type" // defaults to "application/vnd.mapbox-vector-tile"
ConfigKeyS3ForcePath = "force_path_style"
ConfigKeyReqSigningHost = "req_signing_host"
)

const (
DefaultBasepath = ""
DefaultRegion = "us-east-1"
DefaultAccessKey = ""
DefaultSecretKey = ""
DefaultContentType = mvt.MimeType
DefaultEndpoint = ""
DefaultS3ForcePath = false
DefaultBasepath = ""
DefaultRegion = "us-east-1"
DefaultAccessKey = ""
DefaultSecretKey = ""
DefaultContentType = mvt.MimeType
DefaultEndpoint = ""
DefaultS3ForcePath = false
DefaultReqSigningHost = ""
)

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

// If a Proxy/Sidecar/etc.. is used, the Host header needs to
// be fixed to allow Request Signing to work.
// More info: https://github.com/aws/aws-sdk-go/issues/1473
reqSigningHost := DefaultReqSigningHost
reqSigningHost, err = config.String(ConfigKeyReqSigningHost, &reqSigningHost)
if err != nil {
return nil, err
}

if reqSigningHost != "" && endpoint == "" {
return nil, errors.New("The endpoint needs to be set if req_signing_host is set.")
}

// support for static credentials, this is not recommended by AWS but
// necessary for some environments
if accessKey != "" && secretKey != "" {
Expand Down Expand Up @@ -170,6 +186,23 @@ func New(config dict.Dicter) (cache.Interface, error) {
}
s3cache.Client = s3.New(sess)

// If req_signing_host is set, then the HTTP host header needs to be updated
// for signing, but replaced with the original value before connecting
// to the endpoint.
// The code is inspired by
// https://github.com/aws/aws-sdk-go/issues/1473#issuecomment-325509965
if reqSigningHost != "" {
s3cache.Client.Handlers.Sign.PushFront(func(r *request.Request) {
r.HTTPRequest.URL.Host = reqSigningHost
})
s3cache.Client.Handlers.Sign.PushBack(func(r *request.Request) {
// If the endpoint variable contains the http(s) protocol prefix,
// we need to sanitize it before adding it back.
endpoint = strings.TrimPrefix(strings.TrimPrefix(endpoint, "http://"), "https://")
r.HTTPRequest.URL.Host = endpoint
})
}

// check for control_access_list env var
acl := os.Getenv("AWS_ACL")
acl, err = config.String(ConfigKeyACL, &acl)
Expand Down

0 comments on commit c5bb076

Please sign in to comment.