-
Notifications
You must be signed in to change notification settings - Fork 40k
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
Add configuration for encryption providers #46460
Conversation
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 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-bot ok to test |
if err != nil { | ||
return err | ||
} | ||
if providerConfig.Kind == "AEAD" { |
There was a problem hiding this comment.
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/
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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/
/release-note-none |
c4ab7f0
to
a8ba07c
Compare
/assign @smarterclayton |
a8ba07c
to
076c961
Compare
resource: /registry/ | ||
` | ||
|
||
var incorrectConfig1 string = ` |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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/ |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
I had some email discussion about this with @sakshamsharma and I agree with @smarterclayton comments. I hopefully clarified a bunch of his questions offline. |
I have updated the code to allow specifying the resource names instead of the path now, as suggested by @smarterclayton . Thanks @wojtek-t ! 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 Update: One way I can think of possibly improving the situation is to move the parsing of configuration to |
@smarterclayton Done now. Sorry for the delay, was away from keyboard. |
/lgtm Although the queue is fairly stopped up |
[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 |
@k8s-bot pull-kubernetes-federation-e2e-gce test this |
@k8s-bot pull-kubernetes-e2e-kops-aws test this |
@k8s-bot pull-kubernetes-federation-e2e-gce test this |
1 similar comment
@k8s-bot pull-kubernetes-federation-e2e-gce test this |
/retest |
Looking at the history here. Apparently some commit broke the e2e test. All the recent PRs seem to have failed e2e tests. |
@sakshamsharma: The following test failed, say
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:" |
There was a problem hiding this comment.
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.
No it would not be easier :) It be a long term approach, but we still have
to prefix and version. JWT is much more complex at our existing storage
layer today with backwards compatibility taken into account.
…On Mon, Jun 5, 2017 at 8:06 AM, Mo Khan ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In staging/src/k8s.io/apiserver/pkg/server/options/
encryptionconfig/config.go
<#46460 (comment)>
:
> + "encoding/base64"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+
+ yaml "github.com/ghodss/yaml"
+
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apiserver/pkg/storage/value"
+ aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
+ "k8s.io/apiserver/pkg/storage/value/encrypt/identity"
+)
+
+const (
+ aesTransformerPrefixV1 = "k8s:enc:aes:v1:"
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.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#46460 (review)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABG_p85Iblb5DmWSWaGIYdXKFojsyiAoks5sA-8xgaJpZM4Nm2qw>
.
|
/retest
…On Mon, Jun 5, 2017 at 9:25 AM, Clayton Coleman ***@***.***> wrote:
No it would not be easier :) It be a long term approach, but we still
have to prefix and version. JWT is much more complex at our existing
storage layer today with backwards compatibility taken into account.
On Mon, Jun 5, 2017 at 8:06 AM, Mo Khan ***@***.***> wrote:
> ***@***.**** commented on this pull request.
> ------------------------------
>
> In staging/src/k8s.io/apiserver/pkg/server/options/encryptionco
> nfig/config.go
> <#46460 (comment)>
> :
>
> > + "encoding/base64"
> + "fmt"
> + "io"
> + "io/ioutil"
> + "os"
> +
> + yaml "github.com/ghodss/yaml"
> +
> + "k8s.io/apimachinery/pkg/runtime/schema"
> + "k8s.io/apiserver/pkg/storage/value"
> + aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
> + "k8s.io/apiserver/pkg/storage/value/encrypt/identity"
> +)
> +
> +const (
> + aesTransformerPrefixV1 = "k8s:enc:aes:v1:"
>
> 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.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <#46460 (review)>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ABG_p85Iblb5DmWSWaGIYdXKFojsyiAoks5sA-8xgaJpZM4Nm2qw>
> .
>
|
Automatic merge from submit-queue (batch tested with PRs 46550, 46663, 46816, 46820, 46460) |
We'll need to update this based on
#46916
…On Mon, Jun 5, 2017 at 7:44 PM, Kubernetes Submit Queue < ***@***.***> wrote:
Merged #46460 <#46460>.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#46460 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABG_p3oqEyJSOoGgIDB11t27dCfiqfDbks5sBJLFgaJpZM4Nm2qw>
.
|
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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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{} |
There was a problem hiding this comment.
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).
-
Do we really need to have 3 of them? What is the reason?
-
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?
There was a problem hiding this comment.
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 @@ | |||
/* |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
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:
Need for configuration discussed in:
#41939
Encryption
Pathway of a read/write request:
Caveats
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.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.Currently, it can be tested by adding
--experimental-encryption-provider-config=config.yml
tohack/local-up-cluster.sh
on line 511, and placing the above configuration inconfig.yml
in the root project directory.Previous discussion on these changes:
sakshamsharma#1
@jcbsmpsn @destijl @smarterclayton
TODO
k8s:enc
prefix formally for encrypted data. Else find a better way to detect transformed data.