...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/discovery/token/token_test.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/discovery/token

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    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  			// This is the default behavior. The JWS signature is patched after the cluster-info ConfigMap is created
    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  			// Same as above expect this test creates the ConfigMap with the JWS signature
   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  			// Skipping CA verification is also supported
   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  			// Generate signature of the insecure kubeconfig
   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  			// If the JWS signature is delayed, only add the kubeconfig
   232  			if test.delayedJWSSignaturePatch {
   233  				test.configMap.data = map[string]string{}
   234  				test.configMap.data[bootstrapapi.KubeConfigKey] = string(kubeconfigBytes)
   235  			}
   236  
   237  			// Populate the default cluster-info data
   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  			// Create a fake client and create the cluster-info ConfigMap
   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  			// Set arbitrary discovery timeout and retry interval
   251  			timeout := time.Millisecond * 500
   252  			interval := time.Millisecond * 20
   253  
   254  			// Patch the JWS signature after a short delay
   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  			// Retrieve validated configuration
   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  			// Return if an error is expected
   272  			if test.expectedError {
   273  				return
   274  			}
   275  
   276  			// Validate the resulted kubeconfig
   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