Skip to content

Commit

Permalink
Add support for configurable KMS CMK keys for S3 SSE (#8354)
Browse files Browse the repository at this point in the history
  • Loading branch information
xacrimon authored Dec 6, 2021
1 parent 8a3a164 commit 768cb38
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 0 deletions.
3 changes: 3 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
113 changes: 113 additions & 0 deletions docs/pages/setup/reference/backends.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions lib/events/s3sessions/s3handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down
5 changes: 5 additions & 0 deletions lib/events/s3sessions/s3stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 768cb38

Please sign in to comment.