Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds SSE-KMS and SSE-C config to S3 Objstore #3064

Merged
merged 13 commits into from
Aug 25, 2020
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ We use *breaking* word for marking changes that are not backward compatible (rel

:warning: **WARNING** :warning: Thanos Rule's `/api/v1/rules` endpoint no longer returns the old, deprecated `partial_response_strategy`. The old, deprecated value has been fixed to `WARN` for quite some time. _Please_ use `partialResponseStrategy`.

:warning: **WARNING** :warning: The `sse_encryption` value is now deprecated in favour of `sse_config`. If you used `sse_encryption`, the migration strategy is to set up the following block:

```yaml

---
sse_config:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

type: SSE-S3
```


### Fixed

- [#2937](https://github.com/thanos-io/thanos/pull/2937) Receive: Fixing auto-configuration of --receive.local-endpoint
Expand All @@ -29,6 +39,7 @@ We use *breaking* word for marking changes that are not backward compatible (rel
- [#2976](https://github.com/thanos-io/thanos/pull/2976) Query: Better rounding for incoming query timestamps.
- [#2929](https://github.com/thanos-io/thanos/pull/2929) Mixin: Fix expression for 'unhealthy sidecar' alert and also increase the timeout for 10 minutes.
- [#3024](https://github.com/thanos-io/thanos/pull/3024) Query: consider group name and file for deduplication
- [#3064](https://github.com/thanos-io/thanos/pull/3064) s3: Add SSE/SSE-KMS/SSE-C configuration.

### Added

Expand Down
38 changes: 37 additions & 1 deletion docs/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ config:
access_key: ""
insecure: false
signature_version2: false
encrypt_sse: false
secret_key: ""
put_user_metadata: {}
http_config:
Expand All @@ -93,6 +92,11 @@ config:
trace:
enable: false
part_size: 134217728
sse_config:
type: ""
kms_key_id: ""
kms_encryption_context: {}
encryption_key: ""
```

At a minimum, you will need to provide a value for the `bucket`, `endpoint`, `access_key`, and `secret_key` keys. The rest of the keys are optional.
Expand All @@ -115,6 +119,38 @@ For debug and testing purposes you can set

* `trace.enable: true` to enable the minio client's verbose logging. Each request and response will be logged into the debug logger, so debug level logging must be enabled for this functionality.

#### S3 Server-Side Encryption

SSE can be configued using the `sse_config`. [SSE-S3](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html), [SSE-KMS](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html), and [SSE-C](https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html) are supported.

* If type is set to `SSE-S3` you do not need to configure other options.

* If type is set to `SSE-KMS` you must set `kms_key_id`. The `kms_encryption_context` is optional, as [AWS provides a default encryption context](https://docs.aws.amazon.com/kms/latest/developerguide/services-s3.html#s3-encryption-context).

* If type is set to `SSE-C` you must provide a path to the encryption key using `encryption_key`.

If the SSE Config block is set but the `type` is not one of `SSE-S3`, `SSE-KMS`, or `SSE-C`, an error is raised.

You will also need to apply the following AWS IAM policy for the user to access the KMS key:

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "KMSAccess",
"Effect": "Allow",
"Action": [
"kms:GenerateDataKey",
"kms:Encrypt",
"kms:Decrypt"
],
"Resource": "arn:aws:kms:<region>:<account>:key/<KMS key id>"
}
]
}
```

#### Credentials

By default Thanos will try to retrieve credentials from the following sources:
Expand Down
60 changes: 56 additions & 4 deletions pkg/objstore/s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
Expand All @@ -34,6 +35,15 @@ import (
// DirDelim is the delimiter used to model a directory structure in an object store bucket.
const DirDelim = "/"

// SSEKMS is the name of the SSE-KMS method for objectstore encryption.
const SSEKMS = "SSE-KMS"
jalev marked this conversation as resolved.
Show resolved Hide resolved

// SSEC is the name of the SSE-C method for objstore encryption.
const SSEC = "SSE-C"

// SSES3 is the name of the SSE-S3 method for objstore encryption.
const SSES3 = "SSE-S3"

var DefaultConfig = Config{
PutUserMetadata: map[string]string{},
HTTPConfig: HTTPConfig{
Expand All @@ -53,13 +63,22 @@ type Config struct {
AccessKey string `yaml:"access_key"`
Insecure bool `yaml:"insecure"`
SignatureV2 bool `yaml:"signature_version2"`
SSEEncryption bool `yaml:"encrypt_sse"`
SecretKey string `yaml:"secret_key"`
PutUserMetadata map[string]string `yaml:"put_user_metadata"`
HTTPConfig HTTPConfig `yaml:"http_config"`
TraceConfig TraceConfig `yaml:"trace"`
// PartSize used for multipart upload. Only used if uploaded object size is known and larger than configured PartSize.
PartSize uint64 `yaml:"part_size"`
PartSize uint64 `yaml:"part_size"`
SSEConfig SSEConfig `yaml:"sse_config"`
}

// SSEConfig deals with the configuration of SSE for Minio. The following options are valid:
// kmsencryptioncontext == https://docs.aws.amazon.com/kms/latest/developerguide/services-s3.html#s3-encryption-context
type SSEConfig struct {
Type string `yaml:"type"`
KMSKeyID string `yaml:"kms_key_id"`
KMSEncryptionContext map[string]string `yaml:"kms_encryption_context"`
EncryptionKey string `yaml:"encryption_key"`
}

type TraceConfig struct {
Expand Down Expand Up @@ -173,8 +192,32 @@ func NewBucketWithConfig(logger log.Logger, config Config, component string) (*B
client.SetAppInfo(fmt.Sprintf("thanos-%s", component), fmt.Sprintf("%s (%s)", version.Version, runtime.Version()))

var sse encrypt.ServerSide
if config.SSEEncryption {
sse = encrypt.NewSSE()
if config.SSEConfig.Type != "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this we can have just empty case for ""

Not a blocker (:

switch config.SSEConfig.Type {
case SSEKMS:
sse, err = encrypt.NewSSEKMS(config.SSEConfig.KMSKeyID, config.SSEConfig.KMSEncryptionContext)
if err != nil {
return nil, errors.Wrap(err, "initialize s3 client SSE-KMS")
}

case SSEC:
key, err := ioutil.ReadFile(config.SSEConfig.EncryptionKey)
if err != nil {
return nil, err
}

sse, err = encrypt.NewSSEC(key)
if err != nil {
return nil, errors.Wrap(err, "initialize s3 client SSE-C")
}

case SSES3:
sse = encrypt.NewSSE()

default:
sseErrMsg := errors.Errorf("Unsupported type %q was provided. Supported types are SSE-S3, SSE-KMS, SSE-C", config.SSEConfig.Type)
return nil, errors.Wrap(sseErrMsg, "Initialize s3 client SSE Config")
}
}

if config.TraceConfig.Enable {
Expand Down Expand Up @@ -211,6 +254,15 @@ func validate(conf Config) error {
if conf.AccessKey != "" && conf.SecretKey == "" {
return errors.New("no s3 secret_key specified while access_key is present in config file; either both should be present in config or envvars/IAM should be used.")
}

if conf.SSEConfig.Type == SSEC && conf.SSEConfig.EncryptionKey == "" {
return errors.New("encryption_key must be set if sse_config.type is set to 'SSE-C'")
}

if conf.SSEConfig.Type == SSEKMS && conf.SSEConfig.KMSKeyID == "" {
return errors.New("kms_key_id must be set if sse_config.type is set to 'SSE-KMS'")
}

return nil
}

Expand Down
85 changes: 85 additions & 0 deletions pkg/objstore/s3/s3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,91 @@ insecure: false`)
}
}

func TestParseConfig_SSEConfig(t *testing.T) {
input := []byte(`bucket: abdd
endpoint: "s3-endpoint"
sse_config:
type: SSE-S3`)

cfg, err := parseConfig(input)
testutil.Ok(t, err)
testutil.Ok(t, validate(cfg))

input2 := []byte(`bucket: abdd
endpoint: "s3-endpoint"
sse_config:
type: SSE-C`)

cfg, err = parseConfig(input2)
testutil.Ok(t, err)
testutil.NotOk(t, validate(cfg))

input3 := []byte(`bucket: abdd
endpoint: "s3-endpoint"
sse_config:
type: SSE-C
kms_key_id: qweasd`)

cfg, err = parseConfig(input3)
testutil.Ok(t, err)
testutil.NotOk(t, validate(cfg))

input4 := []byte(`bucket: abdd
endpoint: "s3-endpoint"
sse_config:
type: SSE-C
encryption_key: /some/file`)

cfg, err = parseConfig(input4)
testutil.Ok(t, err)
testutil.Ok(t, validate(cfg))

input5 := []byte(`bucket: abdd
endpoint: "s3-endpoint"
sse_config:
type: SSE-KMS`)

cfg, err = parseConfig(input5)
testutil.Ok(t, err)
testutil.NotOk(t, validate(cfg))

input6 := []byte(`bucket: abdd
endpoint: "s3-endpoint"
sse_config:
type: SSE-KMS
kms_key_id: abcd1234-ab12-cd34-1234567890ab`)

cfg, err = parseConfig(input6)
testutil.Ok(t, err)
testutil.Ok(t, validate(cfg))

input7 := []byte(`bucket: abdd
endpoint: "s3-endpoint"
sse_config:
type: SSE-KMS
kms_key_id: abcd1234-ab12-cd34-1234567890ab
kms_encryption_context:
key: value
something: else
a: b`)

cfg, err = parseConfig(input7)
testutil.Ok(t, err)
testutil.Ok(t, validate(cfg))

input8 := []byte(`bucket: abdd
endpoint: "s3-endpoint"
sse_config:
type: SSE-MagicKey
kms_key_id: abcd1234-ab12-cd34-1234567890ab
encryption_key: /some/file`)

cfg, err = parseConfig(input8)
testutil.Ok(t, err)
// Since the error handling for "proper type" if done as we're setting up the bucket.
testutil.Ok(t, validate(cfg))
}

func TestParseConfig_DefaultHTTPConfig(t *testing.T) {
input := []byte(`bucket: abcd
insecure: false`)
Expand Down