1
16
17 package token
18
19 import (
20 "testing"
21 "time"
22
23 "github.com/pmezard/go-difflib/difflib"
24
25 v1 "k8s.io/api/core/v1"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 clientset "k8s.io/client-go/kubernetes"
28 fakeclient "k8s.io/client-go/kubernetes/fake"
29 "k8s.io/client-go/tools/clientcmd"
30 bootstrapapi "k8s.io/cluster-bootstrap/token/api"
31 tokenjws "k8s.io/cluster-bootstrap/token/jws"
32
33 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
34 "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
35 )
36
37 func TestRetrieveValidatedConfigInfo(t *testing.T) {
38 const (
39 caCert = `-----BEGIN CERTIFICATE-----
40 MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
41 cm5ldGVzMB4XDTE5MTEyMDAwNDk0MloXDTI5MTExNzAwNDk0MlowFTETMBEGA1UE
42 AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqQ
43 ctECzA8yFSuVYupOUYgrTmfQeKe/9BaDWagaq7ow9+I2IvsfWFvlrD8QQr8sea6q
44 xjq7TV67Vb4RxBaoYDA+yI5vIcujWUxULun64lu3Q6iC1sj2UnmUpIdgazRXXEkZ
45 vxA6EbAnoxA0+lBOn1CZWl23IQ4s70o2hZ7wIp/vevB88RRRjqtvgc5elsjsbmDF
46 LS7L1Zuye8c6gS93bR+VjVmSIfr1IEq0748tIIyXjAVCWPVCvuP41MlfPc/JVpZD
47 uD2+pO6ZYREcdAnOf2eD4/eLOMKko4L1dSFy9JKM5PLnOC0Zk0AYOd1vS8DTAfxj
48 XPEIY8OBYFhlsxf4TE8CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
49 /wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH/OYq8zyl1+zSTmuow3yI/15PL1
50 dl8hB7IKnZNWmC/LTdm/+noh3Sb1IdRv6HkKg/GUn0UMuRUngLhju3EO4ozJPQcX
51 quaxzgmTKNWJ6ErDvRvWhGX0ZcbdBfZv+dowyRqzd5nlJ49hC+NrtFFQq6P05BYn
52 7SemguqeXmXwIj2Sa+1DeR6lRm9o8shAYjnyThUFqaMn18kI3SANJ5vk/3DFrPEO
53 CKC9EzFku2kuxg2dM12PbRGZQ2o0K6HEZgrrIKTPOy3ocb8r9M0aSFhjOV/NqGA4
54 SaupXSW6XfvIi/UHoIbU3pNcsnUJGnQfQvip95XKk/gqcUr+m50vxgumxtA=
55 -----END CERTIFICATE-----`
56
57 caCertHash = "sha256:98be2e6d4d8a89aa308fb15de0c07e2531ce549c68dec1687cdd5c06f0826658"
58
59 expectedKubeconfig = `apiVersion: v1
60 clusters:
61 - cluster:
62 certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1URXlNREF3TkRrME1sb1hEVEk1TVRFeE56QXdORGswTWxvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTXFRCmN0RUN6QTh5RlN1Vll1cE9VWWdyVG1mUWVLZS85QmFEV2FnYXE3b3c5K0kySXZzZldGdmxyRDhRUXI4c2VhNnEKeGpxN1RWNjdWYjRSeEJhb1lEQSt5STV2SWN1aldVeFVMdW42NGx1M1E2aUMxc2oyVW5tVXBJZGdhelJYWEVrWgp2eEE2RWJBbm94QTArbEJPbjFDWldsMjNJUTRzNzBvMmhaN3dJcC92ZXZCODhSUlJqcXR2Z2M1ZWxzanNibURGCkxTN0wxWnV5ZThjNmdTOTNiUitWalZtU0lmcjFJRXEwNzQ4dElJeVhqQVZDV1BWQ3Z1UDQxTWxmUGMvSlZwWkQKdUQyK3BPNlpZUkVjZEFuT2YyZUQ0L2VMT01La280TDFkU0Z5OUpLTTVQTG5PQzBaazBBWU9kMXZTOERUQWZ4agpYUEVJWThPQllGaGxzeGY0VEU4Q0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFIL09ZcTh6eWwxK3pTVG11b3czeUkvMTVQTDEKZGw4aEI3SUtuWk5XbUMvTFRkbS8rbm9oM1NiMUlkUnY2SGtLZy9HVW4wVU11UlVuZ0xoanUzRU80b3pKUFFjWApxdWF4emdtVEtOV0o2RXJEdlJ2V2hHWDBaY2JkQmZaditkb3d5UnF6ZDVubEo0OWhDK05ydEZGUXE2UDA1QlluCjdTZW1ndXFlWG1Yd0lqMlNhKzFEZVI2bFJtOW84c2hBWWpueVRoVUZxYU1uMThrSTNTQU5KNXZrLzNERnJQRU8KQ0tDOUV6Rmt1Mmt1eGcyZE0xMlBiUkdaUTJvMEs2SEVaZ3JySUtUUE95M29jYjhyOU0wYVNGaGpPVi9OcUdBNApTYXVwWFNXNlhmdklpL1VIb0liVTNwTmNzblVKR25RZlF2aXA5NVhLay9ncWNVcittNTB2eGd1bXh0QT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==
63 server: https://127.0.0.1
64 name: somecluster
65 contexts:
66 - context:
67 cluster: somecluster
68 user: token-bootstrap-client
69 name: token-bootstrap-client@somecluster
70 current-context: token-bootstrap-client@somecluster
71 kind: Config
72 preferences: {}
73 users: null
74 `
75 )
76
77 tests := []struct {
78 name string
79 tokenID string
80 tokenSecret string
81 cfg *kubeadmapi.Discovery
82 configMap *fakeConfigMap
83 delayedJWSSignaturePatch bool
84 expectedError bool
85 }{
86 {
87
88 name: "valid: retrieve a valid kubeconfig with CA verification and delayed JWS signature",
89 tokenID: "123456",
90 tokenSecret: "abcdef1234567890",
91 cfg: &kubeadmapi.Discovery{
92 BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{
93 Token: "123456.abcdef1234567890",
94 CACertHashes: []string{caCertHash},
95 },
96 },
97 configMap: &fakeConfigMap{
98 name: bootstrapapi.ConfigMapClusterInfo,
99 data: map[string]string{},
100 },
101 delayedJWSSignaturePatch: true,
102 },
103 {
104
105 name: "valid: retrieve a valid kubeconfig with CA verification",
106 tokenID: "123456",
107 tokenSecret: "abcdef1234567890",
108 cfg: &kubeadmapi.Discovery{
109 BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{
110 Token: "123456.abcdef1234567890",
111 CACertHashes: []string{caCertHash},
112 },
113 },
114 configMap: &fakeConfigMap{
115 name: bootstrapapi.ConfigMapClusterInfo,
116 data: nil,
117 },
118 },
119 {
120
121 name: "valid: retrieve a valid kubeconfig without CA verification",
122 tokenID: "123456",
123 tokenSecret: "abcdef1234567890",
124 cfg: &kubeadmapi.Discovery{
125 BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{
126 Token: "123456.abcdef1234567890",
127 },
128 },
129 configMap: &fakeConfigMap{
130 name: bootstrapapi.ConfigMapClusterInfo,
131 data: nil,
132 },
133 },
134 {
135 name: "invalid: token format is invalid",
136 tokenID: "foo",
137 tokenSecret: "bar",
138 cfg: &kubeadmapi.Discovery{
139 BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{
140 Token: "foo.bar",
141 },
142 },
143 configMap: &fakeConfigMap{
144 name: bootstrapapi.ConfigMapClusterInfo,
145 data: nil,
146 },
147 expectedError: true,
148 },
149 {
150 name: "invalid: missing cluster-info ConfigMap",
151 tokenID: "123456",
152 tokenSecret: "abcdef1234567890",
153 cfg: &kubeadmapi.Discovery{
154 BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{
155 Token: "123456.abcdef1234567890",
156 },
157 },
158 configMap: &fakeConfigMap{
159 name: "baz",
160 data: nil,
161 },
162 expectedError: true,
163 },
164 {
165 name: "invalid: wrong JWS signature",
166 tokenID: "123456",
167 tokenSecret: "abcdef1234567890",
168 cfg: &kubeadmapi.Discovery{
169 BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{
170 Token: "123456.abcdef1234567890",
171 },
172 },
173 configMap: &fakeConfigMap{
174 name: bootstrapapi.ConfigMapClusterInfo,
175 data: map[string]string{
176 bootstrapapi.KubeConfigKey: "foo",
177 bootstrapapi.JWSSignatureKeyPrefix + "123456": "bar",
178 },
179 },
180 expectedError: true,
181 },
182 {
183 name: "invalid: missing key for JWSSignatureKeyPrefix",
184 tokenID: "123456",
185 tokenSecret: "abcdef1234567890",
186 cfg: &kubeadmapi.Discovery{
187 BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{
188 Token: "123456.abcdef1234567890",
189 },
190 },
191 configMap: &fakeConfigMap{
192 name: bootstrapapi.ConfigMapClusterInfo,
193 data: map[string]string{
194 bootstrapapi.KubeConfigKey: "foo",
195 },
196 },
197 expectedError: true,
198 },
199 {
200 name: "invalid: wrong CA cert hash",
201 tokenID: "123456",
202 tokenSecret: "abcdef1234567890",
203 cfg: &kubeadmapi.Discovery{
204 BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{
205 Token: "123456.abcdef1234567890",
206 CACertHashes: []string{"foo"},
207 },
208 },
209 configMap: &fakeConfigMap{
210 name: bootstrapapi.ConfigMapClusterInfo,
211 data: nil,
212 },
213 expectedError: true,
214 },
215 }
216
217 for _, test := range tests {
218 t.Run(test.name, func(t *testing.T) {
219 kubeconfig := buildSecureBootstrapKubeConfig("127.0.0.1", []byte(caCert), "somecluster")
220 kubeconfigBytes, err := clientcmd.Write(*kubeconfig)
221 if err != nil {
222 t.Fatalf("cannot marshal kubeconfig %v", err)
223 }
224
225
226 sig, err := tokenjws.ComputeDetachedSignature(string(kubeconfigBytes), test.tokenID, test.tokenSecret)
227 if err != nil {
228 t.Fatalf("cannot compute detached JWS signature: %v", err)
229 }
230
231
232 if test.delayedJWSSignaturePatch {
233 test.configMap.data = map[string]string{}
234 test.configMap.data[bootstrapapi.KubeConfigKey] = string(kubeconfigBytes)
235 }
236
237
238 if test.configMap.data == nil {
239 test.configMap.data = map[string]string{}
240 test.configMap.data[bootstrapapi.KubeConfigKey] = string(kubeconfigBytes)
241 test.configMap.data[bootstrapapi.JWSSignatureKeyPrefix+test.tokenID] = sig
242 }
243
244
245 client := fakeclient.NewSimpleClientset()
246 if err = test.configMap.createOrUpdate(client); err != nil {
247 t.Fatalf("could not create ConfigMap: %v", err)
248 }
249
250
251 timeout := time.Millisecond * 500
252 interval := time.Millisecond * 20
253
254
255 if test.delayedJWSSignaturePatch {
256 test.configMap.data[bootstrapapi.JWSSignatureKeyPrefix+test.tokenID] = sig
257 go func() {
258 time.Sleep(time.Millisecond * 60)
259 if err := test.configMap.createOrUpdate(client); err != nil {
260 t.Errorf("could not update the cluster-info ConfigMap with a JWS signature: %v", err)
261 }
262 }()
263 }
264
265
266 kubeconfig, err = retrieveValidatedConfigInfo(client, test.cfg, interval, timeout)
267 if (err != nil) != test.expectedError {
268 t.Errorf("expected error %v, got %v, error: %v", test.expectedError, err != nil, err)
269 }
270
271
272 if test.expectedError {
273 return
274 }
275
276
277 kubeconfigBytes, err = clientcmd.Write(*kubeconfig)
278 if err != nil {
279 t.Fatalf("cannot marshal resulted kubeconfig %v", err)
280 }
281 if string(kubeconfigBytes) != expectedKubeconfig {
282 t.Error("unexpected kubeconfig")
283 diff := difflib.UnifiedDiff{
284 A: difflib.SplitLines(expectedKubeconfig),
285 B: difflib.SplitLines(string(kubeconfigBytes)),
286 FromFile: "expected",
287 ToFile: "got",
288 Context: 10,
289 }
290 diffstr, err := difflib.GetUnifiedDiffString(diff)
291 if err != nil {
292 t.Fatalf("error generating unified diff string: %v", err)
293 }
294 t.Errorf("\n%s", diffstr)
295 }
296 })
297 }
298 }
299
300 type fakeConfigMap struct {
301 name string
302 data map[string]string
303 }
304
305 func (c *fakeConfigMap) createOrUpdate(client clientset.Interface) error {
306 return apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{
307 ObjectMeta: metav1.ObjectMeta{
308 Name: c.name,
309 Namespace: metav1.NamespacePublic,
310 },
311 Data: c.data,
312 })
313 }
314
View as plain text