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 --append-hash flag to kubectl create configmap/secret #49961

Merged
merged 1 commit into from
Aug 30, 2017

Conversation

mtaufen
Copy link
Contributor

@mtaufen mtaufen commented Aug 1, 2017

What this PR does / why we need it:
Specifying this new flag will automatically hash the configmap/secret
contents with sha256 and append the first 40 hex-encoded bits of the
hash to the name of the configmap/secret. This is especially useful for
workflows that generate configmaps/secrets from files (e.g.
--from-file).

See this Google doc for more background:
https://docs.google.com/document/d/1x1fJ3pGRx20ujR-Y89HUAw8glUL8-ygaztLkkmQeCdU/edit

Release note:

Adds --append-hash flag to kubectl create configmap/secret, which will append a short hash of the configmap/secret contents to the name during creation.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Aug 1, 2017
@k8s-github-robot k8s-github-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. release-note Denotes a PR that will be considered when it comes time to generate release notes. labels Aug 1, 2017
@mtaufen
Copy link
Contributor Author

mtaufen commented Aug 1, 2017

/cc @bgrant0607 @pwittrock @erictune

@mtaufen mtaufen added area/kubectl sig/cli Categorizes an issue or PR as relevant to SIG CLI. labels Aug 1, 2017
@mtaufen mtaufen added this to the v1.8 milestone Aug 1, 2017
@dims
Copy link
Member

dims commented Aug 1, 2017

/unassign @dims

@liggitt
Copy link
Member

liggitt commented Aug 1, 2017

this seems like a rarely called-for option that I find it odd to bake into these commands... if you want a name based on a hash of the contents, you can create that yourself external to the command.

cc @kubernetes/sig-cli-pr-reviews

@mtaufen
Copy link
Contributor Author

mtaufen commented Aug 1, 2017 via email

@bgrant0607
Copy link
Member

@liggitt This is one part of a solution to #22368. My expected usage model is for users to do kubectl create configmap from their source data whenever their source data changes, and then update env and volume references from Deployment, DaemonSet, etc. to refer to the new resource. That enables a predictable rolling update.

Copy link
Member

@kow3ns kow3ns left a comment

Choose a reason for hiding this comment

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

The approach your taking will concatenate the upper 40 bits of the SHA2-256 hash of the contents with a user supplied name to provide a unique name for a ConfigMap. This differs from the approach taken by the controllers when generating unique names in that it doesn't handle collisions. Given the low probability of collision based on the expected number "versions" of the ConfigMap, the collision resistance of SHA2-256, and the use case, this is probably okay, but, if a collision does occur how does a user resolve it? Would they just delete the existing ConfigMap and create a new one?

)

// HashConfigMap returns a hash of the EncodeConfigMap encoding of `cm`
func HashConfigMap(cm *api.ConfigMap) string {
Copy link
Member

Choose a reason for hiding this comment

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

hash.Hash stutters - linter will not like it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah just noticed that. Thanks.

}

// HashSecret returns a hash of the EncodeSecret encoding of `sec`
func HashSecret(sec *api.Secret) string {
Copy link
Member

Choose a reason for hiding this comment

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

Same as above - linter will be pissed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks

@mtaufen
Copy link
Contributor Author

mtaufen commented Aug 1, 2017

I don't think we should resolve collisions. The chance of a collision is very low (~1 in 200 million, with 100 concurrent ConfigMaps with the same name prefix). I think the reduced complexity is worth those odds. If someone does hit a collision, it's pretty easy to manually change the source data by adding a bogus ConfigMap key. And you should never have to do this twice in your lifetime (assuming <100 ConfigMaps that share a prefix, and infrequent changes, which is reasonable).

@kow3ns
Copy link
Member

kow3ns commented Aug 2, 2017

/retest

@bgrant0607
Copy link
Member

+1 to @mtaufen's proposal of how to deal with collisions. I think we can do without additional complexity for now.

// with `,`. If m is non-empty, there is a trailing comma in the pre-hash serialization. If m is empty,
// there is no trailing comma. The entire encoding starts with "{" and ends with "}".
func encodeMapStringString(m map[string]string) string {
kv := make([][]string, len(m))
Copy link
Member

Choose a reason for hiding this comment

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

[]string should be enough? Store keys of m in it and sort those keys. You can get value from m[key] when encoding to a string

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, thanks Janet!


// encodeConfigMap encodes a ConfigMap by encoding the Data with EncodeMapStringString and wrapping with {} braces
func encodeConfigMap(cm *api.ConfigMap) string {
return fmt.Sprintf("{data:%s,}", encodeMapStringString(cm.Data))
Copy link
Member

@liggitt liggitt Aug 26, 2017

Choose a reason for hiding this comment

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

including the pre-hash name and the kind (Secret, ConfigMap, etc) would prevent comparison between two hash suffixes based solely on the name of the object (e.g. you could deduce secrets foo-014fbff9a07c and bar-014fbff9a07c likely contain identical data based only on their names)


// encodeSecret encodes a Secret by encoding the Data with EncodeMapStringByte and
// appending ",type:"+sec.Type+"," to the encoded string, and wraps with {} braces
func encodeSecret(sec *api.Secret) string {
Copy link
Member

Choose a reason for hiding this comment

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

same here, json.Marshal(map[string]interface{}{"kind":"Secret","name":sec.Name,"data":sec.Data}) seems more straightforward

// We picked some arbitrary consonants to map to from the same character set as GenerateName.
// See: https://github.com/kubernetes/apimachinery/blob/dc1f89aff9a7509782bde3b68824c8043a3e58cc/pkg/util/rand/rand.go#L75
func encodeHash(hex string) string {
enc := []rune(hex[:10])
Copy link
Member

Choose a reason for hiding this comment

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

length check on hex

@mtaufen mtaufen force-pushed the kubectl-hash branch 3 times, most recently from 0c0b9b8 to dbbb846 Compare August 28, 2017 19:35
@@ -333,7 +332,8 @@ func Convert_extensions_DaemonSetList_To_v1beta1_DaemonSetList(in *extensions.Da

func autoConvert_v1beta1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *v1beta1.DaemonSetSpec, out *extensions.DaemonSetSpec, s conversion.Scope) error {
out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector))
if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil {
// TODO: Inefficient conversion - can we improve it?
Copy link
Member

Choose a reason for hiding this comment

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

you got a bad generation here... these diffs shouldn't exist

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Huh. I'll look into it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

// Data, Kind, and Name are taken into account.
func encodeConfigMap(cm *api.ConfigMap) (string, error) {
// json.Marshal sorts the keys in a stable order in the encoding
data, err := json.Marshal(cm.Data)
Copy link
Member

Choose a reason for hiding this comment

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

any reason not to delegate the full serialization to json.Marshal rather than mixing with sprinf?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Including all of ObjectMeta makes the hash dependent on too many unnecessary fields, IMO. And it would automatically bind us to any future ObjectMeta (or Secret/ConfigMap) fields, which we may not want to include in the hash.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cleared up on Slack; comment recommended json.Marshal(map[string]interface{}) over Sprintf. Agree this is better.

@liggitt
Copy link
Member

liggitt commented Aug 28, 2017

other than fixing the generated change and the preference for delegating the serialization fully to json.Marshal, no other comments from me

Specifying this new flag will automatically hash the configmap/secret
contents with sha256 and append the first 40 hex-encoded bits of the
hash to the name of the configmap/secret. This is especially useful for
workflows that generate configmaps/secrets from files (e.g.
--from-file).

Note that vowels and vowel-like characters in the hash are remapped to
consonants to make it more difficult to accidentally form bad words.

See this Google doc for more background:
https://docs.google.com/document/d/1x1fJ3pGRx20ujR-Y89HUAw8glUL8-ygaztLkkmQeCdU/edit
@mtaufen
Copy link
Contributor Author

mtaufen commented Aug 28, 2017

/retest

@liggitt
Copy link
Member

liggitt commented Aug 29, 2017

LGTM

@janetkuo
Copy link
Member

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Aug 29, 2017
@janetkuo
Copy link
Member

/approve no-issue

@k8s-github-robot
Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: apelisse, janetkuo, mtaufen

Associated issue requirement bypassed by: janetkuo

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

@k8s-github-robot k8s-github-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 29, 2017
@fejta-bot
Copy link

/retest
This bot automatically retries jobs that failed/flaked on approved PRs (send feedback to @fejta).

Review the full test history for this PR.

@k8s-github-robot
Copy link

/test all [submit-queue is verifying that this PR is safe to merge]

@k8s-github-robot
Copy link

Automatic merge from submit-queue (batch tested with PRs 49961, 50005, 50738, 51045, 49927)

@k8s-github-robot k8s-github-robot merged commit 2cf5118 into kubernetes:master Aug 30, 2017
@bgrant0607 bgrant0607 mentioned this pull request Sep 22, 2017
@anguslees
Copy link
Member

anguslees commented Oct 1, 2017

Repeating my comment on the "background doc" here for visibility:

[When used on Secrets] This exposes the contents of the secret in the hash, and could be used in a dictionary attack to discover the plaintext secret.

My recommendation is to add a sizeable salt when hashing Secrets, and include the salt in the output hash value (unfortunately making it longer).
Another alternative that would be acceptable for user-namespace Secrets (but not "well known" kube-system Secrets?) is to include the object-name prefix and namespace in the hash. (Makes the pre-computed dictionary table non-portable)

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. area/kubectl 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 Denotes a PR that will be considered when it comes time to generate release notes. sig/cli Categorizes an issue or PR as relevant to SIG CLI. 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.