Skip to content

Commit

Permalink
update code comments and kms decryption docs
Browse files Browse the repository at this point in the history
Signed-off-by: Sanskar Jaiswal <[email protected]>
  • Loading branch information
Sanskar Jaiswal committed May 9, 2022
1 parent 4a25078 commit 3be256f
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 50 deletions.
6 changes: 3 additions & 3 deletions controllers/kustomization_decryptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ type KustomizeDecryptor struct {
// vaultToken is the Hashicorp Vault token used to authenticate towards
// any Vault server.
vaultToken string
// awsCredsProvider is the AWS credentials provider object used to authenticate towards
// any AWS KMS.
// awsCredsProvider is the AWS credentials provider object used to authenticate
// towards any AWS KMS.
awsCredsProvider *awskms.CredsProvider
// azureToken is the Azure credential token used to authenticate towards
// any Azure Key Vault.
Expand Down Expand Up @@ -225,7 +225,7 @@ func (d *KustomizeDecryptor) ImportKeys(ctx context.Context) error {
}
case filepath.Ext(DecryptionAWSKmsFile):
if name == DecryptionAWSKmsFile {
if d.awsCredsProvider, err = awskms.LoadAwsKmsCredsProviderFromYaml(value); err != nil {
if d.awsCredsProvider, err = awskms.LoadCredsProviderFromYaml(value); err != nil {
return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err)
}
}
Expand Down
36 changes: 35 additions & 1 deletion docs/spec/v1beta2/kustomization.md
Original file line number Diff line number Diff line change
Expand Up @@ -1111,7 +1111,7 @@ kind: Secret
metadata:
name: sops-keys
namespace: default
stringData:
data:
sops.aws-kms: |
aws_access_key_id: some-access-key-id
aws_secret_access_key: some-aws-secret-access-key
Expand Down Expand Up @@ -1255,6 +1255,40 @@ kubectl -n flux-system annotate serviceaccount kustomize-controller \
eks.amazonaws.com/role-arn='arn:aws:iam::<ACCOUNT_ID>:role/<KMS-ROLE-NAME>'
```

Furthermore, you can also use the usual [environmentvariables used for specifying AWS
credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-list)
, by patching the kustomize-controller deployment:

```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kustomize-controller
namespace: flux-system
spec:
template:
spec:
containers:
- name: manager
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-creds
key: awsAccessKeyID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-creds
key: awsSecretAccessKey
- name: AWS_SESSION_TOKEN
valueFrom:
secretKeyRef:
name: aws-creds
key: awsSessionToken
```

In addition to this, the
[general SOPS documentation around KMS AWS applies](https://github.com/mozilla/sops#27kms-aws-profiles),
allowing you to specify e.g. a `SOPS_KMS_ARN` environment variable.
Expand Down
111 changes: 68 additions & 43 deletions internal/sops/awskms/keysource.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,36 +35,51 @@ import (
)

const (
arnRegex = `^arn:aws[\w-]*:kms:(.+):[0-9]+:(key|alias)/.+$`
stsSessionRegex = "[^a-zA-Z0-9=,.@-]+"
// arnRegex matches an AWS ARN.
// valid ARN example: arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48
arnRegex = `^arn:aws[\w-]*:kms:(.+):[0-9]+:(key|alias)/.+$`
// stsSessionRegex matches an AWS STS session name.
// valid STS session examples: john_s, sops@42WQm042
stsSessionRegex = "[^a-zA-Z0-9=,.@-_]+"
// kmsTTL is the duration after which a MasterKey requires rotation.
kmsTTL = time.Hour * 24 * 30 * 6
)

// MasterKey is a AWS KMS key used to encrypt and decrypt sops' data key.
// MasterKey is an AWS KMS key used to encrypt and decrypt sops' data key.
// Adapted from: https://github.com/mozilla/sops/blob/v3.7.2/kms/keysource.go#L39
// Modified to accept custom static credentials as opposed to using env vars by default
// and use aws-sdk-go-v2 instead of aws-sdk-go being used in upstream.
type MasterKey struct {
Arn string
Role string
EncryptedKey string
CreationDate time.Time
// AWS Role ARN associated with the KMS key.
Arn string
// AWS Role ARN used to assume a role through AWS STS.
Role string
// EncryptedKey stores the data key in it's encrypted form.
EncryptedKey string
// CreationDate is when this MasterKey was created.
CreationDate time.Time
// EncryptionContext provides additional context about the data key.
// Ref: https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context
EncryptionContext map[string]string

// credentialsProvider is used to configure the AWS config with the
// necessary credentials.
credentialsProvider aws.CredentialsProvider

// epResolver IS ONLY MEANT TO BE USED FOR TESTS.
// it can be used to override the endpoint that the AWS client resolves to
// by default. it's hacky but there is no other choice, since you can't
// specify the endpoint as an env var like you can do with an access key.
// epResolver can be used to override the endpoint the AWS client resolves
// to by default. This is mostly used for testing purposes as it can not be
// injected using e.g. an environment variable. The field is not publicly
// exposed, nor configurable.
epResolver aws.EndpointResolver
}

// CredsProvider is a wrapper around aws.CredentialsProvider used for authenticating
// when using AWS KMS.
// towards AWS KMS.
type CredsProvider struct {
credsProvider aws.CredentialsProvider
}

// NewCredsProvider returns a Creds object with the provided aws.CredentialsProvider
// NewCredsProvider returns a CredsProvider object with the provided aws.CredentialsProvider.
func NewCredsProvider(cp aws.CredentialsProvider) *CredsProvider {
return &CredsProvider{
credsProvider: cp,
Expand All @@ -76,9 +91,9 @@ func (c CredsProvider) ApplyToMasterKey(key *MasterKey) {
key.credentialsProvider = c.credsProvider
}

// LoadAwsKmsCredsProviderFromYaml parses the given yaml returns a CredsProvider object
// LoadCredsProviderFromYaml parses the given YAML returns a CredsProvider object
// which contains the credentials provider used for authenticating towards AWS KMS.
func LoadAwsKmsCredsProviderFromYaml(b []byte) (*CredsProvider, error) {
func LoadCredsProviderFromYaml(b []byte) (*CredsProvider, error) {
credInfo := struct {
AccessKeyID string `json:"aws_access_key_id"`
SecretAccessKey string `json:"aws_secret_access_key"`
Expand All @@ -93,17 +108,18 @@ func LoadAwsKmsCredsProviderFromYaml(b []byte) (*CredsProvider, error) {
}, nil
}

// EncryptedDataKey returns the encrypted data key this master key holds
// EncryptedDataKey returns the encrypted data key this master key holds.
func (key *MasterKey) EncryptedDataKey() []byte {
return []byte(key.EncryptedKey)
}

// SetEncryptedDataKey sets the encrypted data key for this master key
// SetEncryptedDataKey sets the encrypted data key for this master key.
func (key *MasterKey) SetEncryptedDataKey(enc []byte) {
key.EncryptedKey = string(enc)
}

// Encrypt takes a sops data key, encrypts it with KMS and stores the result in the EncryptedKey field
// Encrypt takes a SOPS data key, encrypts it with KMS and stores the result
// in the EncryptedKey field.
func (key *MasterKey) Encrypt(dataKey []byte) error {
cfg, err := key.createKMSConfig()
if err != nil {
Expand All @@ -122,7 +138,8 @@ func (key *MasterKey) Encrypt(dataKey []byte) error {
return nil
}

// EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet
// EncryptIfNeeded encrypts the provided sops' data key and encrypts it, if it
// has not been encrypted yet.
func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error {
if key.EncryptedKey == "" {
return key.Encrypt(dataKey)
Expand Down Expand Up @@ -155,15 +172,35 @@ func (key *MasterKey) Decrypt() ([]byte, error) {

// NeedsRotation returns whether the data key needs to be rotated or not.
func (key *MasterKey) NeedsRotation() bool {
return time.Since(key.CreationDate) > (time.Hour * 24 * 30 * 6)
return time.Since(key.CreationDate) > kmsTTL
}

// ToString converts the key to a string representation
// ToString converts the key to a string representation.
func (key *MasterKey) ToString() string {
return key.Arn
}

// NewMasterKey creates a new MasterKey from an ARN, role and context, setting the creation date to the current date
// ToMap converts the MasterKey to a map for serialization purposes.
func (key MasterKey) ToMap() map[string]interface{} {
out := make(map[string]interface{})
out["arn"] = key.Arn
if key.Role != "" {
out["role"] = key.Role
}
out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339)
out["enc"] = key.EncryptedKey
if key.EncryptionContext != nil {
outcontext := make(map[string]string)
for k, v := range key.EncryptionContext {
outcontext[k] = v
}
out["context"] = outcontext
}
return out
}

// NewMasterKey creates a new MasterKey from an ARN, role and context, setting the
// creation date to the current date.
func NewMasterKey(arn string, role string, context map[string]string) *MasterKey {
return &MasterKey{
Arn: arn,
Expand All @@ -173,7 +210,8 @@ func NewMasterKey(arn string, role string, context map[string]string) *MasterKey
}
}

// NewMasterKeyFromArn takes an ARN string and returns a new MasterKey for that ARN
// NewMasterKeyFromArn takes an ARN string and returns a new MasterKey for that
// ARN.
func NewMasterKeyFromArn(arn string, context map[string]string, awsProfile string) *MasterKey {
k := &MasterKey{}
arn = strings.Replace(arn, " ", "", -1)
Expand All @@ -189,11 +227,15 @@ func NewMasterKeyFromArn(arn string, context map[string]string, awsProfile strin
return k
}

// createKMSConfig returns a Config configured with the appropriate credentials.
func (key MasterKey) createKMSConfig() (*aws.Config, error) {
// Use the credentialsProvider if present, otherwise default to reading credentials
// from the environment.
cfg, err := config.LoadDefaultConfig(context.TODO(), func(lo *config.LoadOptions) error {
if key.credentialsProvider != nil {
lo.Credentials = key.credentialsProvider
}
// Set the epResolver, if present. Used ONLY for tests.
if key.epResolver != nil {
lo.EndpointResolver = key.epResolver
}
Expand All @@ -209,6 +251,8 @@ func (key MasterKey) createKMSConfig() (*aws.Config, error) {
return &cfg, nil
}

// createSTSConfig uses AWS STS to assume a role and returns a Config configured
// with that role's credentials.
func (key MasterKey) createSTSConfig(config *aws.Config) (*aws.Config, error) {
hostname, err := os.Hostname()
if err != nil {
Expand All @@ -223,7 +267,7 @@ func (key MasterKey) createSTSConfig(config *aws.Config) (*aws.Config, error) {

client := sts.NewFromConfig(*config)
input := &sts.AssumeRoleInput{
RoleArn: &key.Arn,
RoleArn: &key.Role,
RoleSessionName: &name,
}
out, err := client.AssumeRole(context.TODO(), input)
Expand All @@ -235,22 +279,3 @@ func (key MasterKey) createSTSConfig(config *aws.Config) (*aws.Config, error) {
)
return config, nil
}

// ToMap converts the MasterKey to a map for serialization purposes
func (key MasterKey) ToMap() map[string]interface{} {
out := make(map[string]interface{})
out["arn"] = key.Arn
if key.Role != "" {
out["role"] = key.Role
}
out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339)
out["enc"] = key.EncryptedKey
if key.EncryptionContext != nil {
outcontext := make(map[string]string)
for k, v := range key.EncryptionContext {
outcontext[k] = v
}
out["context"] = outcontext
}
return out
}
4 changes: 2 additions & 2 deletions internal/sops/awskms/keysource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func TestMain(m *testing.M) {
logger.Fatalf("could not set arn")
}

// Run the tests, but only if we succeeded in setting up the Vault server
// Run the tests, but only if we succeeded in setting up the AWS KMS server.
var code int
if err == nil {
code = m.Run()
Expand Down Expand Up @@ -276,7 +276,7 @@ aws_access_key_id: test-id
aws_secret_access_key: test-secret
aws_session_token: test-token
`)
credsProvider, err := LoadAwsKmsCredsProviderFromYaml(credsYaml)
credsProvider, err := LoadCredsProviderFromYaml(credsYaml)
g.Expect(err).ToNot(HaveOccurred())

creds, err := credsProvider.credsProvider.Retrieve(context.TODO())
Expand Down
2 changes: 1 addition & 1 deletion internal/sops/keyservice/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (o WithAgeIdentities) ApplyToServer(s *Server) {
s.ageIdentities = age.ParsedIdentities(o)
}

// WithAWSKeys configurs the AWS credentials on the Server
// WithAWSKeys configures the AWS credentials on the Server
type WithAWSKeys struct {
CredsProvider *awskms.CredsProvider
}
Expand Down

0 comments on commit 3be256f

Please sign in to comment.