Skip to content
This repository has been archived by the owner on Jan 24, 2025. It is now read-only.

Commit

Permalink
Add Client Side Encryption with KMS
Browse files Browse the repository at this point in the history
  • Loading branch information
wmrmrx committed Dec 3, 2024
1 parent 1dcd059 commit fd328bb
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 15 deletions.
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ module github.com/vmware-tanzu/velero-plugin-for-aws
go 1.22.8

require (
github.com/aws/aws-sdk-go-v2 v1.24.1
github.com/aws/aws-sdk-go-v2 v1.26.1
github.com/aws/aws-sdk-go-v2/config v1.26.3
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11
github.com/aws/aws-sdk-go-v2/service/ec2 v1.143.0
github.com/aws/aws-sdk-go-v2/service/kms v1.30.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0
github.com/aws/smithy-go v1.19.0
github.com/aws/smithy-go v1.20.2
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/pflag v1.0.5
Expand All @@ -22,8 +23,8 @@ require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
Expand Down
18 changes: 10 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
github.com/aws/aws-sdk-go-v2/config v1.26.3 h1:dKuc2jdp10y13dEEvPqWxqLoc0vF3Z9FC45MvuQSxOA=
Expand All @@ -10,10 +10,10 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tC
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 h1:I6lAa3wBWfCz/cKkOpAcumsETRkFAl70sWi8ItcMEsM=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11/go.mod h1:be1NIO30kJA23ORBLqPo1LttEM6tPNSEcjkd1eKzNW0=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 h1:5oE2WzJE56/mVveuDZPJESKlg/00AaS2pY2QZcnxg4M=
Expand All @@ -28,6 +28,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIG
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 h1:KOxnQeWy5sXyS37fdKEvAsGHOr9fa/qvwxfJurR/BzE=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10/go.mod h1:jMx5INQFYFYB3lQD9W0D8Ohgq6Wnl7NYOJ2TQndbulI=
github.com/aws/aws-sdk-go-v2/service/kms v1.30.1 h1:SBn4I0fJXF9FYOVRSVMWuhvEKoAHDikjGpS3wlmw5DE=
github.com/aws/aws-sdk-go-v2/service/kms v1.30.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 h1:PJTdBMsyvra6FtED7JZtDpQrIAflYDHFoZAu/sKYkwU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 h1:dGrs+Q/WzhsiUKh82SfTVN66QzyulXuMDTV/G8ZxOac=
Expand All @@ -36,8 +38,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 h1:Yf2MIo9x+0tyv76GljxzqA3W
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
Expand Down
141 changes: 141 additions & 0 deletions velero-plugin-for-aws/kms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package main

import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"github.com/aws/aws-sdk-go-v2/service/kms"
"github.com/pkg/errors"
"io"
"os"
"time"
)

const keySize = 32

// Binary format
// 4 bytes length of encrypted key
// [length of encrypted key] [ encrypted key ] [ cyphertext ]

func EncryptKMS(svc *kms.Client, data []byte) ([]byte, error) {
KMS_KEY_ID := os.Getenv("KMS_KEY_ID")
if KMS_KEY_ID == "" {
return nil, errors.Errorf("KMS_KEY_ID not set!")
}

key := make([]byte, keySize)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return nil, err
}

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()

res, err := svc.Encrypt(ctx, &kms.EncryptInput{
KeyId: &KMS_KEY_ID,
Plaintext: key,
})
if err != nil {
return nil, err
}
encryptedKey := res.CiphertextBlob

lenOfEncryptedKey := uint64(len(encryptedKey))
prefix := make([]byte, 8)
binary.LittleEndian.PutUint64(prefix, lenOfEncryptedKey)

ciphertext, err := EncryptWithKey(key, data)
if err != nil {
return nil, err
}

return append(append(prefix, encryptedKey...), ciphertext...), nil
}

func DecryptKMS(svc *kms.Client, data []byte) ([]byte, error) {
KMS_KEY_ID := os.Getenv("KMS_KEY_ID")
if KMS_KEY_ID == "" {
return nil, errors.Errorf("KMS_KEY_ID not set!")
}

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()

lenOfEncryptedKeyBytes, data := data[:8], data[8:]
lenOfEncryptedKey := binary.LittleEndian.Uint64(lenOfEncryptedKeyBytes)
encryptedKey, data := data[:lenOfEncryptedKey], data[lenOfEncryptedKey:]
res, err := svc.Decrypt(ctx, &kms.DecryptInput{
KeyId: &KMS_KEY_ID,
CiphertextBlob: encryptedKey,
})
if err != nil {
return nil, err
}
key := res.Plaintext

plaintext, err := DecryptWithKey(key, data)
if err != nil {
return nil, err
}

return plaintext, nil
}

func EncryptWithKey(key, plaintext []byte) ([]byte, error) {
// Create a new AES cipher block using the provided key
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

// Create a GCM cipher block with the AES cipher and nonce
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

nonce := make([]byte, aesgcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}

// Encrypt the data
ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil)

// Append the nonce to the ciphertext
ciphertext = append(nonce, ciphertext...)

return ciphertext, nil

}

func DecryptWithKey(key, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

// Create a GCM cipher block with the AES cipher and nonce
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

if len(ciphertext) < aesgcm.NonceSize() {
return nil, errors.New("ciphertext is too short")
}

// Extract the nonce from the ciphertext
nonce := ciphertext[:aesgcm.NonceSize()]
ciphertext = ciphertext[aesgcm.NonceSize():]

// Decrypt the data
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}

return plaintext, nil
}
39 changes: 36 additions & 3 deletions velero-plugin-for-aws/object_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ import (
"github.com/sirupsen/logrus"

veleroplugin "github.com/vmware-tanzu/velero/pkg/plugin/framework"

"bytes"

"github.com/aws/aws-sdk-go-v2/service/kms"
)

const (
Expand Down Expand Up @@ -72,6 +76,7 @@ type ObjectStore struct {
s3 s3Interface
preSignS3 s3PresignInterface
s3Uploader *manager.Uploader
kms *kms.Client
kmsKeyID string
sseCustomerKey string
signatureVersion string
Expand Down Expand Up @@ -173,6 +178,7 @@ func (o *ObjectStore) Init(config map[string]string) error {
}
o.s3 = client
o.s3Uploader = manager.NewUploader(client)
o.kms = kms.NewFromConfig(cfg)
o.kmsKeyID = kmsKeyID
o.serverSideEncryption = serverSideEncryption
o.tagging = tagging
Expand Down Expand Up @@ -250,10 +256,21 @@ func readCustomerKey(customerKeyEncryptionFile string) (string, error) {
}

func (o *ObjectStore) PutObject(bucket, key string, body io.Reader) error {
bodyContent, err := io.ReadAll(body)
if err != nil {
return err
}

encryptedContent, err := EncryptKMS(o.kms, bodyContent)
if err != nil {
return err
}
encryptedBody := bytes.NewReader(encryptedContent)

input := &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
Body: body,
Body: encryptedBody,
Tagging: aws.String(o.tagging),
}

Expand All @@ -276,7 +293,7 @@ func (o *ObjectStore) PutObject(bucket, key string, body io.Reader) error {
input.ChecksumAlgorithm = types.ChecksumAlgorithm(o.checksumAlg)
}

_, err := o.s3Uploader.Upload(context.Background(), input)
_, err = o.s3Uploader.Upload(context.Background(), input)

return errors.Wrapf(err, "error putting object %s", key)
}
Expand Down Expand Up @@ -336,7 +353,23 @@ func (o *ObjectStore) GetObject(bucket, key string) (io.ReadCloser, error) {
return nil, errors.Wrapf(err, "error getting object %s", key)
}

return output.Body, nil
bodyContent, err := io.ReadAll(output.Body)
if err != nil {
return nil, err
}

err = output.Body.Close()
if err != nil {
return nil, err
}

decryptedContent, err := DecryptKMS(o.kms, bodyContent)
if err != nil {
return nil, err
}
decryptedBody := io.NopCloser(bytes.NewReader(decryptedContent))

return decryptedBody, nil
}

func (o *ObjectStore) ListCommonPrefixes(bucket, prefix, delimiter string) ([]string, error) {
Expand Down

0 comments on commit fd328bb

Please sign in to comment.