forked from grafana/loki
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds support to S3 server side encryption using AWS KMS (grafana#3651)
* Adds support to S3 server side encryption using AWS KMS Signed-off-by: Lucas Miguel <[email protected]> * refatored based on PR review Signed-off-by: Lucas Miguel <[email protected]> * small refactor Signed-off-by: Lucas Miguel <[email protected]> * rebased master Signed-off-by: Lucas Miguel <[email protected]> * rebased master correctly Signed-off-by: Lucas Miguel <[email protected]> * added new line Signed-off-by: Lucas Miguel <[email protected]> * refactored Signed-off-by: Lucas Miguel <[email protected]> * reordered changelog Signed-off-by: Lucas Miguel <[email protected]> * refactored NewSSEParsedConfig Signed-off-by: Lucas Miguel <[email protected]> * removed unused struct Signed-off-by: Lucas Miguel <[email protected]> Co-authored-by: Lucas Vieira <[email protected]>
- Loading branch information
1 parent
ba96dcb
commit 9da230c
Showing
3 changed files
with
220 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package aws | ||
|
||
import ( | ||
"encoding/base64" | ||
"encoding/json" | ||
"flag" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
const ( | ||
// SSEKMS config type constant to configure S3 server side encryption using KMS | ||
// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html | ||
SSEKMS = "SSE-KMS" | ||
sseKMSType = "aws:kms" | ||
// SSES3 config type constant to configure S3 server side encryption with AES-256 | ||
// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html | ||
SSES3 = "SSE-S3" | ||
sseS3Type = "AES256" | ||
) | ||
|
||
// SSEParsedConfig configures server side encryption (SSE) | ||
// struct used internally to configure AWS S3 | ||
type SSEParsedConfig struct { | ||
ServerSideEncryption string | ||
KMSKeyID *string | ||
KMSEncryptionContext *string | ||
} | ||
|
||
// SSEConfig configures S3 server side encryption | ||
// struct that is going to receive user input (through config file or CLI) | ||
type SSEConfig struct { | ||
Type string `yaml:"type"` | ||
KMSKeyID string `yaml:"kms_key_id"` | ||
KMSEncryptionContext string `yaml:"kms_encryption_context"` | ||
} | ||
|
||
// RegisterFlagsWithPrefix adds the flags required to config this to the given FlagSet | ||
func (cfg *SSEConfig) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) { | ||
f.StringVar(&cfg.Type, prefix+"type", "", "Enable AWS Server Side Encryption. Only SSE-S3 and SSE-KMS are supported") | ||
f.StringVar(&cfg.KMSKeyID, prefix+"kms-key-id", "", "KMS Key ID used to encrypt objects in S3") | ||
f.StringVar(&cfg.KMSEncryptionContext, prefix+"kms-encryption-context", "", "KMS Encryption Context used for object encryption. It expects a JSON as a string.") | ||
} | ||
|
||
// NewSSEParsedConfig creates a struct to configure server side encryption (SSE) | ||
func NewSSEParsedConfig(cfg SSEConfig) (*SSEParsedConfig, error) { | ||
switch cfg.Type { | ||
case SSES3: | ||
return &SSEParsedConfig{ | ||
ServerSideEncryption: sseS3Type, | ||
}, nil | ||
case SSEKMS: | ||
if cfg.KMSKeyID == "" { | ||
return nil, errors.New("KMS key id must be passed when SSE-KMS encryption is selected") | ||
} | ||
|
||
parsedKMSEncryptionContext, err := parseKMSEncryptionContext(cfg.KMSEncryptionContext) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to parse KMS encryption context") | ||
} | ||
|
||
return &SSEParsedConfig{ | ||
ServerSideEncryption: sseKMSType, | ||
KMSKeyID: &cfg.KMSKeyID, | ||
KMSEncryptionContext: parsedKMSEncryptionContext, | ||
}, nil | ||
default: | ||
return nil, errors.New("SSE type is empty or invalid") | ||
} | ||
} | ||
|
||
func parseKMSEncryptionContext(kmsEncryptionContext string) (*string, error) { | ||
if kmsEncryptionContext == "" { | ||
return nil, nil | ||
} | ||
|
||
// validates if kmsEncryptionContext is a valid JSON | ||
jsonKMSEncryptionContext, err := json.Marshal(json.RawMessage(kmsEncryptionContext)) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to marshal KMS encryption context") | ||
} | ||
|
||
parsedKMSEncryptionContext := base64.StdEncoding.EncodeToString([]byte(jsonKMSEncryptionContext)) | ||
|
||
return &parsedKMSEncryptionContext, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package aws | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewSSEParsedConfig(t *testing.T) { | ||
kmsKeyID := "test" | ||
kmsEncryptionContext := `{"a": "bc", "b": "cd"}` | ||
// compact form of kmsEncryptionContext | ||
parsedKMSEncryptionContext := "eyJhIjoiYmMiLCJiIjoiY2QifQ==" | ||
|
||
tests := []struct { | ||
name string | ||
params SSEConfig | ||
expected *SSEParsedConfig | ||
expectedErr error | ||
}{ | ||
{ | ||
name: "Test SSE encryption with SSES3 type", | ||
params: SSEConfig{ | ||
Type: SSES3, | ||
}, | ||
expected: &SSEParsedConfig{ | ||
ServerSideEncryption: sseS3Type, | ||
}, | ||
}, | ||
{ | ||
name: "Test SSE encryption with SSEKMS type without context", | ||
params: SSEConfig{ | ||
Type: SSEKMS, | ||
KMSKeyID: kmsKeyID, | ||
}, | ||
expected: &SSEParsedConfig{ | ||
ServerSideEncryption: sseKMSType, | ||
KMSKeyID: &kmsKeyID, | ||
}, | ||
}, | ||
{ | ||
name: "Test SSE encryption with SSEKMS type with context", | ||
params: SSEConfig{ | ||
Type: SSEKMS, | ||
KMSKeyID: kmsKeyID, | ||
KMSEncryptionContext: kmsEncryptionContext, | ||
}, | ||
expected: &SSEParsedConfig{ | ||
ServerSideEncryption: sseKMSType, | ||
KMSKeyID: &kmsKeyID, | ||
KMSEncryptionContext: &parsedKMSEncryptionContext, | ||
}, | ||
}, | ||
{ | ||
name: "Test invalid SSE type", | ||
params: SSEConfig{ | ||
Type: "invalid", | ||
}, | ||
expectedErr: errors.New("SSE type is empty or invalid"), | ||
}, | ||
{ | ||
name: "Test SSE encryption with SSEKMS type without KMS Key ID", | ||
params: SSEConfig{ | ||
Type: SSEKMS, | ||
KMSKeyID: "", | ||
}, | ||
expectedErr: errors.New("KMS key id must be passed when SSE-KMS encryption is selected"), | ||
}, | ||
{ | ||
name: "Test SSE with invalid KMS encryption context JSON", | ||
params: SSEConfig{ | ||
Type: SSEKMS, | ||
KMSKeyID: kmsKeyID, | ||
KMSEncryptionContext: `INVALID_JSON`, | ||
}, | ||
expectedErr: errors.New("failed to parse KMS encryption context: failed to marshal KMS encryption context: json: error calling MarshalJSON for type json.RawMessage: invalid character 'I' looking for beginning of value"), | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
result, err := NewSSEParsedConfig(tt.params) | ||
if tt.expectedErr != nil { | ||
assert.Equal(t, tt.expectedErr.Error(), err.Error()) | ||
} | ||
assert.Equal(t, tt.expected, result) | ||
}) | ||
} | ||
} |