1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package hash 18 19import ( 20 "crypto/sha256" 21 "encoding/json" 22 "fmt" 23 24 "k8s.io/api/core/v1" 25) 26 27// ConfigMapHash returns a hash of the ConfigMap. 28// The Data, Kind, and Name are taken into account. 29func ConfigMapHash(cm *v1.ConfigMap) (string, error) { 30 encoded, err := encodeConfigMap(cm) 31 if err != nil { 32 return "", err 33 } 34 h, err := encodeHash(hash(encoded)) 35 if err != nil { 36 return "", err 37 } 38 return h, nil 39} 40 41// SecretHash returns a hash of the Secret. 42// The Data, Kind, Name, and Type are taken into account. 43func SecretHash(sec *v1.Secret) (string, error) { 44 encoded, err := encodeSecret(sec) 45 if err != nil { 46 return "", err 47 } 48 h, err := encodeHash(hash(encoded)) 49 if err != nil { 50 return "", err 51 } 52 return h, nil 53} 54 55// encodeConfigMap encodes a ConfigMap. 56// Data, Kind, and Name are taken into account. 57func encodeConfigMap(cm *v1.ConfigMap) (string, error) { 58 // json.Marshal sorts the keys in a stable order in the encoding 59 m := map[string]interface{}{ 60 "kind": "ConfigMap", 61 "name": cm.Name, 62 "data": cm.Data, 63 } 64 if cm.Immutable != nil { 65 m["immutable"] = *cm.Immutable 66 } 67 if len(cm.BinaryData) > 0 { 68 m["binaryData"] = cm.BinaryData 69 } 70 data, err := json.Marshal(m) 71 if err != nil { 72 return "", err 73 } 74 return string(data), nil 75} 76 77// encodeSecret encodes a Secret. 78// Data, Kind, Name, and Type are taken into account. 79func encodeSecret(sec *v1.Secret) (string, error) { 80 m := map[string]interface{}{ 81 "kind": "Secret", 82 "type": sec.Type, 83 "name": sec.Name, 84 "data": sec.Data, 85 } 86 if sec.Immutable != nil { 87 m["immutable"] = *sec.Immutable 88 } 89 // json.Marshal sorts the keys in a stable order in the encoding 90 data, err := json.Marshal(m) 91 if err != nil { 92 return "", err 93 } 94 return string(data), nil 95} 96 97// encodeHash extracts the first 40 bits of the hash from the hex string 98// (1 hex char represents 4 bits), and then maps vowels and vowel-like hex 99// characters to consonants to prevent bad words from being formed (the theory 100// is that no vowels makes it really hard to make bad words). Since the string 101// is hex, the only vowels it can contain are 'a' and 'e'. 102// We picked some arbitrary consonants to map to from the same character set as GenerateName. 103// See: https://github.com/kubernetes/apimachinery/blob/dc1f89aff9a7509782bde3b68824c8043a3e58cc/pkg/util/rand/rand.go#L75 104// If the hex string contains fewer than ten characters, returns an error. 105func encodeHash(hex string) (string, error) { 106 if len(hex) < 10 { 107 return "", fmt.Errorf("the hex string must contain at least 10 characters") 108 } 109 enc := []rune(hex[:10]) 110 for i := range enc { 111 switch enc[i] { 112 case '0': 113 enc[i] = 'g' 114 case '1': 115 enc[i] = 'h' 116 case '3': 117 enc[i] = 'k' 118 case 'a': 119 enc[i] = 'm' 120 case 'e': 121 enc[i] = 't' 122 } 123 } 124 return string(enc), nil 125} 126 127// hash hashes `data` with sha256 and returns the hex string 128func hash(data string) string { 129 return fmt.Sprintf("%x", sha256.Sum256([]byte(data))) 130} 131