From 768cb38b0591c389ef30f3b4e86fc3dad4e7fbae Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 6 Dec 2021 23:46:28 +0100 Subject: [PATCH] Add support for configurable KMS CMK keys for S3 SSE (#8354) --- constants.go | 3 + docs/pages/setup/reference/backends.mdx | 113 ++++++++++++++++++++++++ lib/events/s3sessions/s3handler.go | 10 +++ lib/events/s3sessions/s3stream.go | 5 ++ 4 files changed, 131 insertions(+) diff --git a/constants.go b/constants.go index 92fb85b936a..0e0b3b3cce1 100644 --- a/constants.go +++ b/constants.go @@ -349,6 +349,9 @@ const ( // DisableServerSideEncryption is an optional switch to opt out of SSE in case the provider does not support it DisableServerSideEncryption = "disablesse" + // SSEKMSKey is an optional switch to use an KMS CMK key for S3 SSE. + SSEKMSKey = "sse_kms_key" + // SchemeFile is a local disk file storage SchemeFile = "file" diff --git a/docs/pages/setup/reference/backends.mdx b/docs/pages/setup/reference/backends.mdx index 40841086205..131a3103725 100644 --- a/docs/pages/setup/reference/backends.mdx +++ b/docs/pages/setup/reference/backends.mdx @@ -215,6 +215,119 @@ These optional `GET` parameters control how Teleport interacts with an S3 endpoi - `endpoint=mys3.example.com` - connect to a custom S3 endpoint. - `insecure=true` - set to `true` or `false`. If `true`, TLS will be disabled. - `disablesse=true` - set to `true` or `false`. If `true`, S3 server-side encryption will be disabled. If `false`, aws:kms (Key Management Service) will be used for server-side encryption. Other SSE types are not supported at this time. +- `sse_kms_key=kms_key_id` - If set to a valid AWS KMS CMK key ID all objects uploaded to S3 will be encrypted with this key. Details can be found below. + +### S3 Server Side Encryption + +Teleport supports using a custom AWS KMS Customer Managed Key for encrypting objects uploaded to S3. +This allows you to restrict who can read objects like session recordings separately from those that have read +access to a bucket by restricting key access. + +The `sse_kms_key` parameter above can be set to any valid KMS CMK ID corresponding to a symmetric standard spec KMS key. +Example template KMS key policies are provided below for common usage cases. IAM users do not have access to any +key by default. Permissions have to be explicitly granted in the policy. + +#### Encryption/Decryption + +This policy allows an IAM user to encrypt and decrypt objects. +This allows a cluster auth to write and play back session recordings. + +Replace `[iam-key-admin-arn]` with the IAM ARN of the user(s) that should have +administrative key access and `[auth-node-iam-arn]` with the IAM ARN +of the user the Teleport auth nodes are using. + +```json +{ + "Id": "Teleport Encryption and Decryption", + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Teleport CMK Admin", + "Effect": "Allow", + "Principal": { + "AWS": "[iam-key-admin-arn]" + }, + "Action": "kms:*", + "Resource": "*" + }, + { + "Sid": "Teleport CMK Auth", + "Effect": "Allow", + "Principal": { + "AWS": "[auth-node-iam-arn]" + }, + "Action": [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ], + "Resource": "*" + } + ] +} +``` + +### Encryption/Decryption with separate clusters + +This policy allows specifying separate IAM users for encryption and decryption. +This can be used to set up a multi cluster configuration where the main cluster +cannot play back session recordings but only write them. +A separate cluster authenticating as a different IAM user with decryption access +can be used for playing back the session recordings. + +Replace `[iam-key-admin-arn]` with the IAM ARN of the user(s) that should have +administrative key access, `[iam-node-write-arn]` with the IAM ARN of the user the +main write-only cluster auth nodes are using and `[iam-node-read-arn]` with the +IAM ARN of the user used by the read-only cluster. + +For this to work the second cluster has to be connected to the same audit log as the main cluster. +This is needed to detect session recordings. + +```json +{ + "Id": "Teleport Separate Encryption and Decryption", + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Teleport CMK Admin", + "Effect": "Allow", + "Principal": { + "AWS": "[iam-key-admin-arn]" + }, + "Action": "kms:*", + "Resource": "*" + }, + { + "Sid": "Teleport CMK Auth Encrypt", + "Effect": "Allow", + "Principal": { + "AWS": "[auth-node-write-arn]" + }, + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ], + "Resource": "*" + }, + { + "Sid": "Teleport CMK Auth Decrypt", + "Effect": "Allow", + "Principal": { + "AWS": "[auth-node-read-arn]" + }, + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Resource": "*" + } + ] +} +``` ## DynamoDB diff --git a/lib/events/s3sessions/s3handler.go b/lib/events/s3sessions/s3handler.go index dbb396309a7..25bc09af030 100644 --- a/lib/events/s3sessions/s3handler.go +++ b/lib/events/s3sessions/s3handler.go @@ -58,6 +58,8 @@ type Config struct { Session *awssession.Session // Credentials if supplied are used in tests Credentials *credentials.Credentials + // SSEKMSKey specifies the optional custom CMK used for KMS SSE. + SSEKMSKey string } // SetFromURL sets values on the Config from the supplied URI @@ -83,6 +85,9 @@ func (s *Config) SetFromURL(in *url.URL, inRegion string) error { } s.DisableServerSideEncryption = disableServerSideEncryption } + if val := in.Query().Get(teleport.SSEKMSKey); val != "" { + s.SSEKMSKey = val + } s.Region = region s.Bucket = in.Host s.Path = in.Path @@ -176,6 +181,10 @@ func (h *Handler) Upload(ctx context.Context, sessionID session.ID, reader io.Re } if !h.Config.DisableServerSideEncryption { uploadInput.ServerSideEncryption = aws.String(s3.ServerSideEncryptionAwsKms) + + if h.Config.SSEKMSKey != "" { + uploadInput.SSEKMSKeyId = aws.String(h.Config.SSEKMSKey) + } } _, err = h.uploader.UploadWithContext(ctx, uploadInput) if err != nil { @@ -202,6 +211,7 @@ func (h *Handler) Download(ctx context.Context, sessionID session.ID, writer io. Key: aws.String(h.path(sessionID)), VersionId: aws.String(versionID), }) + if err != nil { return ConvertS3Error(err) } diff --git a/lib/events/s3sessions/s3stream.go b/lib/events/s3sessions/s3stream.go index 58949cd21fe..af528a711e9 100644 --- a/lib/events/s3sessions/s3stream.go +++ b/lib/events/s3sessions/s3stream.go @@ -47,6 +47,10 @@ func (h *Handler) CreateUpload(ctx context.Context, sessionID session.ID) (*even } if !h.Config.DisableServerSideEncryption { input.ServerSideEncryption = aws.String(s3.ServerSideEncryptionAwsKms) + + if h.Config.SSEKMSKey != "" { + input.SSEKMSKeyId = aws.String(h.Config.SSEKMSKey) + } } resp, err := h.client.CreateMultipartUploadWithContext(ctx, input) @@ -75,6 +79,7 @@ func (h *Handler) UploadPart(ctx context.Context, upload events.StreamUpload, pa Body: partBody, PartNumber: aws.Int64(partNumber), } + resp, err := h.client.UploadPartWithContext(ctx, params) if err != nil { return nil, ConvertS3Error(err)