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