Skip to content

Commit

Permalink
Adds SSE-KMS and SSE-C config to S3 Objstore (#3064)
Browse files Browse the repository at this point in the history
* Adds SSE config to S3 Objstore

Signed-off-by: James Bach <[email protected]>

* Document sse_config block in storage.md

Signed-off-by: James Bach <[email protected]>

* run make docs

Signed-off-by: James Bach <[email protected]>

* Add SSE config options to changelog

Signed-off-by: James Bach <[email protected]>

* Update changelog

Signed-off-by: James Bach <[email protected]>

* Define explicit SSE 'Type'

Signed-off-by: James Bach <[email protected]>

* Update docs again for real

Signed-off-by: James Bach <[email protected]>

* Fix bad logic for SSE-KMS

Signed-off-by: James Bach <[email protected]>

* Adds example policy for KMS documentation

Signed-off-by: James Bach <[email protected]>

* Changes SSE strings into constants

Signed-off-by: James Bach <[email protected]>

* Make SSE error message prettier

Signed-off-by: James Bach <[email protected]>

* Export consant with comments

Signed-off-by: James Bach <[email protected]>

* Group consts in pkg/s3

Signed-off-by: James Bach <[email protected]>
  • Loading branch information
jalev authored Aug 25, 2020
1 parent 0b8507c commit 61b8382
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 7 deletions.
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:
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
66 changes: 60 additions & 6 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 @@ -31,8 +32,19 @@ import (
"gopkg.in/yaml.v2"
)

// DirDelim is the delimiter used to model a directory structure in an object store bucket.
const DirDelim = "/"
const (
// DirDelim is the delimiter used to model a directory structure in an object store bucket.
DirDelim = "/"

// SSEKMS is the name of the SSE-KMS method for objectstore encryption.
SSEKMS = "SSE-KMS"

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

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

var DefaultConfig = Config{
PutUserMetadata: map[string]string{},
Expand All @@ -53,13 +65,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 +194,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 != "" {
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 +256,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

0 comments on commit 61b8382

Please sign in to comment.