Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration for encryption providers #46460

Merged

Conversation

sakshamsharma
Copy link
Contributor

@sakshamsharma sakshamsharma commented May 25, 2017

Additions

Allows providing a configuration file (using flag --experimental-encryption-provider-config) to use the existing AEAD transformer (with multiple keys) by composing mutable transformer, prefix transformer (for parsing providerId), another prefix transformer (for parsing keyId), and AES-GCM transformers (one for each key). Multiple providers can be configured using the configuration file.

Example configuration:

kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
    - namespaces
    providers:
    - aes:
        keys:
        - name: key1
          secret: c2vjcmv0iglzihnly3vyzq==
        - name: key2
          secret: dghpcybpcybwyxnzd29yza==
    - identity: {}

Need for configuration discussed in:
#41939
Encryption

Pathway of a read/write request:

  1. MutableTransformer
  2. PrefixTransformer reads the provider-id, and passes the request further if that matches.
  3. PrefixTransformer reads the key-id, and passes the request further if that matches.
  4. GCMTransformer tries decrypting and authenticating the cipher text in case of reads. Similarly for writes.

Caveats

  1. To keep the command line parameter parsing independent of the individual transformer's configuration, we need to convert the configuration to an interface{} and manually parse it in the transformer. Suggestions on better ways to do this are welcome.

  2. Flags --encryption-provider and --encrypt-resource (both mentioned in this document ) are not supported in this because they do not allow more than one provider, and the current format for the configuration file possibly supersedes their functionality.

  3. Currently, it can be tested by adding --experimental-encryption-provider-config=config.yml to hack/local-up-cluster.sh on line 511, and placing the above configuration in config.yml in the root project directory.

Previous discussion on these changes:
sakshamsharma#1

@jcbsmpsn @destijl @smarterclayton

TODO

  1. Investigate if we need to store keys on disk (per encryption.md)
  2. Look at alpha flag conventions
  3. Need to reserve k8s:enc prefix formally for encrypted data. Else find a better way to detect transformed data.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label May 25, 2017
@k8s-ci-robot
Copy link
Contributor

Hi @sakshamsharma. Thanks for your PR.

I'm waiting for a kubernetes member to verify that this patch is reasonable to test. If it is, they should reply with @k8s-bot ok to test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@k8s-ci-robot k8s-ci-robot added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label May 25, 2017
@k8s-github-robot k8s-github-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. release-note-label-needed labels May 25, 2017
@jcbsmpsn
Copy link
Contributor

@k8s-bot ok to test

@k8s-ci-robot k8s-ci-robot removed the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label May 25, 2017
if err != nil {
return err
}
if providerConfig.Kind == "AEAD" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/AEAD/k8s-aes-gcm/

Copy link
Contributor Author

@sakshamsharma sakshamsharma May 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for skipping the -v1 suffix here?
Update: Shall read version from the version: parameter in config file.

}), nil

} else {
return nil, fmt.Errorf("no valid keys found in configuration for AEAD transformer")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/AEAD/k8s-aes-gcm-v1/

@jcbsmpsn
Copy link
Contributor

/release-note-none

@k8s-ci-robot k8s-ci-robot added release-note-none Denotes a PR that doesn't merit a release note. and removed release-note-label-needed labels May 25, 2017
@sakshamsharma sakshamsharma force-pushed the location_transformer branch 3 times, most recently from c4ab7f0 to a8ba07c Compare May 25, 2017 22:52
@jcbsmpsn
Copy link
Contributor

/assign @smarterclayton

resource: /registry/
`

var incorrectConfig1 string = `
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

give these descriptive names. "badConfigMissingSecret"

maxMatch = length
ts = []int{i}
} else if length == maxMatch {
ts = append(ts, i)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any point in using a slice? Even if there are two the same you just arbitrarily pick the first one. Seems like you could remove this line and it would work exactly the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what you suggest will be correct if you need to do writes. But if you're doing a read, you may need to try out all the transformers which could have written to that location in the past. So for instance, someone configures a transformer1 to write to location A. Then, some days later, the person wants to migrate to transformer2 for location A. There would still be some resources which were written with transformer1, and have not been migrated over yet. Thus we need to return all the transformers which could have written to that location, to be able to decrypt the information.

Now there is an issue with my implementation as well, something I had not noticed earlier:
If you initially use transformer1 for writing to location /a/, and then add transformer2 for writing to location /a/b, when you try to read /a/b/c, this function would not return transformer1 in the list. So we shall not be able to decrypt the resource because the TransformFromStorage shall now not use transformer1. I had not noticed this issue earlier.

An approach may be to try all (there won't be many, in most cases) transformers while reading, and only one transformer (what you suggested) while writing. Might have a performance impact, possibly.

Another approach would be to design a good migration scheme which shall detect a change in configuration, and will auto-migrate data to the new configuration. That way we know exactly which transformer was used to write to each location. I suppose this will require persisting the last configuration on disk in plaintext probably, and checking the current configuration against that at cluster bootup (we could, of course, omit the key secrets from this backup).

}

// TransformFromStorage collects all possible transformers (based on their location parameter) and
// attempts to decrypt using each of them
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guessing seems dangerous here. Do we need to add the resource type into the ciphertext spec with the key id?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've put my thoughts on this in the comment on longestMatchingTransformers

func TestEncryptionProviderConfigCorrectParsing(t *testing.T) {
testConfigFile := ".test-enc-provider-correct"

err := ioutil.WriteFile(testConfigFile, []byte(correctConfig), 0600)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could put the parsing code in another function that accepts io.Reader and pass buffers here, avoiding needing to actually write these to disk. Then the top level function just opens the file and passes it to the function that does the actual parsing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do that.

secret: dGhpcyBpcyBwYXNzd29yZA==
- name: key3
secret: azhzIHNlY3JldCBzdG9yZQ==
resource: /registry/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't be exposing etcd keys in this config. This config should be able kubernetes resource types. pkg/storage/storage_factory.go should accept a group resource override that includes a transformer - that transformer should be global.

I don't see a strong reason to do anything more complicated than identifying specific resources to encrypt (by opaquely passing in an encryption provider). We deliberately do not expose details of storage location because they are mutable.

func NewGCMTransformerFromConfig(config map[string]interface{}) (value.Transformer, error) {

// Obtain list of keys as []interface{}
if keysInterface, ok := config["keys"].([]interface{}); ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally don't do this - we define API types and include the config that way. Anything that reads from an API config should be outside this package (consuming it) and result in a properly instantiated prefix transformer.

}

// GetKeyDataFromConfig parses a valid interface to a KeyConfig object
func GetKeyDataFromConfig(config interface{}) (KeyConfig, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too generic - it's better to have a package that takes a concrete config api (i.e. Kube API structs) type and then knows how to turn that into a concrete AES PrefixTransformer, and then have a fairly top level package in apiserver be consumable (pkg/storage/encryption/options.go) to pass into the StorageConfig.

You can define your config api types in a place where config apis would generally belong - however, I know we are trying not to add more things to kubernetes/pkg/apis/componentconfig. @deads2k where would api server specific config types go today?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose my previous comment (alternative configuration style) can help address this. Let me know if it is not so.

)

// LocationTransformer holds a transformer interface and the location of the resources it has to be used for.
type LocationTransformer struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above I don't think we should be doing any sort of transformation by key. A storage is configured with a value transformer - we have 1 storage per api resource, and the level of encryption should be at one or more api resources, not at key level.

@wojtek-t
Copy link
Member

I had some email discussion about this with @sakshamsharma and I agree with @smarterclayton comments. I hopefully clarified a bunch of his questions offline.

@wojtek-t wojtek-t self-requested a review May 29, 2017 09:26
@k8s-github-robot k8s-github-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels May 30, 2017
@sakshamsharma
Copy link
Contributor Author

sakshamsharma commented May 30, 2017

I have updated the code to allow specifying the resource names instead of the path now, as suggested by @smarterclayton . Thanks @wojtek-t !
Let me know if that portion looks fine now. I've also acted on @destijl 's recommendations.

As for the configuration comments, I am waiting on the request to @deads2k in the review above. I agree that the current way of parsing the configuration in storage/value/encrypt/aes is sub-optimal, but some more explanation on where to add the config-parsing would be a great help.

Update: One way I can think of possibly improving the situation is to move the parsing of configuration to staging/apiserver/pkg/server/options/encryption_provider_config.go.
Although, anything other than the current setup seems awkward because we have to keep logic pertaining to the AES transformer (the fact that it needs to read keys, for instance) outside the code for the transformer itself. This was deemed desirable in some conversation, which I cannot find now. Can we somehow achieve this (transformer stays coupled with its configuration) as well as the recommended changes (configuration parsing stays in API files)?

@sakshamsharma
Copy link
Contributor Author

@smarterclayton Done now. Sorry for the delay, was away from keyboard.

@smarterclayton
Copy link
Contributor

/lgtm

Although the queue is fairly stopped up

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jun 2, 2017
@k8s-github-robot
Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: sakshamsharma, smarterclayton

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@sakshamsharma
Copy link
Contributor Author

@k8s-bot pull-kubernetes-federation-e2e-gce test this

@sakshamsharma
Copy link
Contributor Author

@k8s-bot pull-kubernetes-e2e-kops-aws test this

@smarterclayton
Copy link
Contributor

@k8s-bot pull-kubernetes-federation-e2e-gce test this

1 similar comment
@sakshamsharma
Copy link
Contributor Author

@k8s-bot pull-kubernetes-federation-e2e-gce test this

@sakshamsharma
Copy link
Contributor Author

/retest

@sakshamsharma
Copy link
Contributor Author

sakshamsharma commented Jun 5, 2017

Looking at the history here.

Apparently some commit broke the e2e test. All the recent PRs seem to have failed e2e tests.

@k8s-ci-robot
Copy link
Contributor

@sakshamsharma: The following test failed, say /retest to rerun them all:

Test name Commit Details Rerun command
pull-kubernetes-federation-e2e-gce 9760d00 link @k8s-bot pull-kubernetes-federation-e2e-gce test this

Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

)

const (
aesTransformerPrefixV1 = "k8s:enc:aes:v1:"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we use this prefix to determine if and how we encrypted an object on disk? Wouldn't it be easier to use something like go-jose / JWE to store this information in a tamper proof way? We may still need k8s:enc: to prevent any collisions, but the algorithm would not need to be part of the prefix.

@smarterclayton
Copy link
Contributor

smarterclayton commented Jun 5, 2017 via email

@smarterclayton
Copy link
Contributor

smarterclayton commented Jun 5, 2017 via email

@k8s-github-robot
Copy link

Automatic merge from submit-queue (batch tested with PRs 46550, 46663, 46816, 46820, 46460)

@k8s-github-robot k8s-github-robot merged commit f893cdd into kubernetes:master Jun 5, 2017
@smarterclayton
Copy link
Contributor

smarterclayton commented Jun 5, 2017 via email

k8s-github-robot pushed a commit that referenced this pull request Jun 16, 2017
Automatic merge from submit-queue

Add encryption provider support via environment variables

These changes are needed to allow cloud providers to use the encryption providers as an alpha feature. The version checks can be done in the respective cloud providers'.

Context: #46460 and #46916

@destijl @jcbsmpsn @smarterclayton
@@ -388,7 +388,7 @@ func (s *store) List(ctx context.Context, key, resourceVersion string, pred stor

elems := make([]*elemForDecode, 0, len(getResp.Kvs))
for _, kv := range getResp.Kvs {
data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(key))
data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(kv.Key))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sakshamsharma Could you explain what this change does, please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention is to have the location of the resource as the key. Since the key was written using its own location, it must be authenticated (while decrypting) using its own location as well. kv.Key is its location. key was a top level location (example: /api/Secrets/). When I was attempting to write integration tests (which couldn't get done at that time though), I discovered this issue.

Let me know if I went wrong somewhere.


// encryptIdentityTransformer performs no transformation on provided data, but validates
// that the data is not encrypted data during TransformFromStorage
type identityTransformer struct{}
Copy link
Contributor

@php-coder php-coder Jun 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sakshamsharma @smarterclayton This is the 3rd identityTransformer in our codebase (first, second).

  1. Do we really need to have 3 of them? What is the reason?

  2. I see that this implementation has check for k8s:enc: prefix. Why 2 others don't have such check? Should we copy it to them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new identity transformer needs to return an error if it finds something that is encrypted. Without that check, the PrefixTransformer cannot try to use other transformers, because it will never see this one failing.

I had originally just modified the one in transformer.go to do this, but @smarterclayton reasoned that the identity transformer (which is used for all the cases where there is no encryption) should only be returning the data. Which is why, we now have a separate one for cases where we want to add more logic.

@@ -0,0 +1,172 @@
/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sakshamsharma @smarterclayton I've just noticed that test named encryptionconfig_test.go while source file has name config.go. I can prepare a patch and rename it, if needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package name is encryptionconfig. It may be violating some convention, but I'm not entirely sure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file will be renamed in #53249

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note-none Denotes a PR that doesn't merit a release note. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.