copycerts_test.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /*
  2. Copyright 2019 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package copycerts
  14. import (
  15. "encoding/hex"
  16. "io/ioutil"
  17. "os"
  18. "path"
  19. "regexp"
  20. "testing"
  21. "github.com/lithammer/dedent"
  22. v1 "k8s.io/api/core/v1"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. fakeclient "k8s.io/client-go/kubernetes/fake"
  25. certutil "k8s.io/client-go/util/cert"
  26. keyutil "k8s.io/client-go/util/keyutil"
  27. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  28. kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  29. "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
  30. cryptoutil "k8s.io/kubernetes/cmd/kubeadm/app/util/crypto"
  31. testutil "k8s.io/kubernetes/cmd/kubeadm/test"
  32. )
  33. func TestGetDataFromInitConfig(t *testing.T) {
  34. certData := []byte("cert-data")
  35. tmpdir := testutil.SetupTempDir(t)
  36. defer os.RemoveAll(tmpdir)
  37. cfg := &kubeadmapi.InitConfiguration{}
  38. cfg.CertificatesDir = tmpdir
  39. key, err := CreateCertificateKey()
  40. if err != nil {
  41. t.Fatalf(dedent.Dedent("failed to create key.\nfatal error: %v"), err)
  42. }
  43. decodedKey, err := hex.DecodeString(key)
  44. if err != nil {
  45. t.Fatalf(dedent.Dedent("failed to decode key.\nfatal error: %v"), err)
  46. }
  47. if err := os.Mkdir(path.Join(tmpdir, "etcd"), 0755); err != nil {
  48. t.Fatalf(dedent.Dedent("failed to create etcd cert dir.\nfatal error: %v"), err)
  49. }
  50. certs := certsToTransfer(cfg)
  51. for name, path := range certs {
  52. if err := ioutil.WriteFile(path, certData, 0644); err != nil {
  53. t.Fatalf(dedent.Dedent("failed to write cert: %s\nfatal error: %v"), name, err)
  54. }
  55. }
  56. secretData, err := getDataFromDisk(cfg, decodedKey)
  57. if err != nil {
  58. t.Fatalf("failed to get secret data. fatal error: %v", err)
  59. }
  60. re := regexp.MustCompile(`[-._a-zA-Z0-9]+`)
  61. for name, data := range secretData {
  62. if !re.MatchString(name) {
  63. t.Fatalf(dedent.Dedent("failed to validate secretData\n %s isn't a valid secret key"), name)
  64. }
  65. decryptedData, err := cryptoutil.DecryptBytes(data, decodedKey)
  66. if string(certData) != string(decryptedData) {
  67. t.Fatalf(dedent.Dedent("can't decrypt cert: %s\nfatal error: %v"), name, err)
  68. }
  69. }
  70. }
  71. func TestCertsToTransfer(t *testing.T) {
  72. localEtcdCfg := &kubeadmapi.InitConfiguration{}
  73. externalEtcdCfg := &kubeadmapi.InitConfiguration{}
  74. externalEtcdCfg.Etcd = kubeadmapi.Etcd{}
  75. externalEtcdCfg.Etcd.External = &kubeadmapi.ExternalEtcd{}
  76. commonExpectedCerts := []string{
  77. kubeadmconstants.CACertName,
  78. kubeadmconstants.CAKeyName,
  79. kubeadmconstants.FrontProxyCACertName,
  80. kubeadmconstants.FrontProxyCAKeyName,
  81. kubeadmconstants.ServiceAccountPublicKeyName,
  82. kubeadmconstants.ServiceAccountPrivateKeyName,
  83. }
  84. tests := map[string]struct {
  85. config *kubeadmapi.InitConfiguration
  86. expectedCerts []string
  87. }{
  88. "local etcd": {
  89. config: localEtcdCfg,
  90. expectedCerts: append(
  91. []string{kubeadmconstants.EtcdCACertName, kubeadmconstants.EtcdCAKeyName},
  92. commonExpectedCerts...,
  93. ),
  94. },
  95. "external etcd": {
  96. config: externalEtcdCfg,
  97. expectedCerts: append(
  98. []string{externalEtcdCA, externalEtcdCert, externalEtcdKey},
  99. commonExpectedCerts...,
  100. ),
  101. },
  102. }
  103. for name, test := range tests {
  104. t.Run(name, func(t2 *testing.T) {
  105. certList := certsToTransfer(test.config)
  106. for _, cert := range test.expectedCerts {
  107. if _, found := certList[cert]; !found {
  108. t2.Fatalf(dedent.Dedent("failed to get list of certs to upload\ncert %s not found"), cert)
  109. }
  110. }
  111. })
  112. }
  113. }
  114. func TestCertOrKeyNameToSecretName(t *testing.T) {
  115. tests := []struct {
  116. keyName string
  117. expectedSecretName string
  118. }{
  119. {
  120. keyName: "apiserver-kubelet-client.crt",
  121. expectedSecretName: "apiserver-kubelet-client.crt",
  122. },
  123. {
  124. keyName: "etcd/ca.crt",
  125. expectedSecretName: "etcd-ca.crt",
  126. },
  127. {
  128. keyName: "etcd/healthcheck-client.crt",
  129. expectedSecretName: "etcd-healthcheck-client.crt",
  130. },
  131. }
  132. for _, tc := range tests {
  133. secretName := certOrKeyNameToSecretName(tc.keyName)
  134. if secretName != tc.expectedSecretName {
  135. t.Fatalf("secret name %s didn't match expected name %s", secretName, tc.expectedSecretName)
  136. }
  137. }
  138. }
  139. func TestUploadCerts(t *testing.T) {
  140. tmpdir := testutil.SetupTempDir(t)
  141. defer os.RemoveAll(tmpdir)
  142. secretKey, err := CreateCertificateKey()
  143. if err != nil {
  144. t.Fatalf("could not create certificate key: %v", err)
  145. }
  146. initConfiguration := testutil.GetDefaultInternalConfig(t)
  147. initConfiguration.ClusterConfiguration.CertificatesDir = tmpdir
  148. if err := certs.CreatePKIAssets(initConfiguration); err != nil {
  149. t.Fatalf("error creating PKI assets: %v", err)
  150. }
  151. cs := fakeclient.NewSimpleClientset()
  152. if err := UploadCerts(cs, initConfiguration, secretKey); err != nil {
  153. t.Fatalf("error uploading certs: %v", err)
  154. }
  155. rawSecretKey, err := hex.DecodeString(secretKey)
  156. if err != nil {
  157. t.Fatalf("error decoding key: %v", err)
  158. }
  159. secretMap, err := cs.CoreV1().Secrets(metav1.NamespaceSystem).Get(kubeadmconstants.KubeadmCertsSecret, metav1.GetOptions{})
  160. if err != nil {
  161. t.Fatalf("could not fetch secret: %v", err)
  162. }
  163. for certName, certPath := range certsToTransfer(initConfiguration) {
  164. secretCertData, err := cryptoutil.DecryptBytes(secretMap.Data[certOrKeyNameToSecretName(certName)], rawSecretKey)
  165. if err != nil {
  166. t.Fatalf("error decrypting secret data: %v", err)
  167. }
  168. diskCertData, err := ioutil.ReadFile(certPath)
  169. if err != nil {
  170. t.Fatalf("error reading certificate from disk: %v", err)
  171. }
  172. // Check that the encrypted contents on the secret match the contents on disk, and that all
  173. // the expected certificates are in the secret
  174. if string(secretCertData) != string(diskCertData) {
  175. t.Fatalf("cert %s does not have the expected contents. contents: %q; expected contents: %q", certName, string(secretCertData), string(diskCertData))
  176. }
  177. }
  178. }
  179. func TestDownloadCerts(t *testing.T) {
  180. secretKey, err := CreateCertificateKey()
  181. if err != nil {
  182. t.Fatalf("could not create certificate key: %v", err)
  183. }
  184. // Temporary directory where certificates will be generated
  185. tmpdir := testutil.SetupTempDir(t)
  186. defer os.RemoveAll(tmpdir)
  187. initConfiguration := testutil.GetDefaultInternalConfig(t)
  188. initConfiguration.ClusterConfiguration.CertificatesDir = tmpdir
  189. // Temporary directory where certificates will be downloaded to
  190. targetTmpdir := testutil.SetupTempDir(t)
  191. defer os.RemoveAll(targetTmpdir)
  192. initForDownloadConfiguration := testutil.GetDefaultInternalConfig(t)
  193. initForDownloadConfiguration.ClusterConfiguration.CertificatesDir = targetTmpdir
  194. if err := certs.CreatePKIAssets(initConfiguration); err != nil {
  195. t.Fatalf("error creating PKI assets: %v", err)
  196. }
  197. kubeadmCertsSecret := createKubeadmCertsSecret(t, initConfiguration, secretKey)
  198. cs := fakeclient.NewSimpleClientset(kubeadmCertsSecret)
  199. if err := DownloadCerts(cs, initForDownloadConfiguration, secretKey); err != nil {
  200. t.Fatalf("error downloading certs: %v", err)
  201. }
  202. const keyFileMode = 0600
  203. const certFileMode = 0644
  204. for certName, certPath := range certsToTransfer(initForDownloadConfiguration) {
  205. diskCertData, err := ioutil.ReadFile(certPath)
  206. if err != nil {
  207. t.Errorf("error reading certificate from disk: %v", err)
  208. }
  209. // Check that the written files are either certificates or keys, and that they have
  210. // the expected permissions
  211. if _, err := keyutil.ParsePublicKeysPEM(diskCertData); err == nil {
  212. if stat, err := os.Stat(certPath); err == nil {
  213. if stat.Mode() != keyFileMode {
  214. t.Errorf("key %q should have mode %#o, has %#o", certName, keyFileMode, stat.Mode())
  215. }
  216. } else {
  217. t.Errorf("could not stat key %q: %v", certName, err)
  218. }
  219. } else if _, err := certutil.ParseCertsPEM(diskCertData); err == nil {
  220. if stat, err := os.Stat(certPath); err == nil {
  221. if stat.Mode() != certFileMode {
  222. t.Errorf("cert %q should have mode %#o, has %#o", certName, certFileMode, stat.Mode())
  223. }
  224. } else {
  225. t.Errorf("could not stat cert %q: %v", certName, err)
  226. }
  227. } else {
  228. t.Errorf("secret %q was not identified as a cert or as a key", certName)
  229. }
  230. }
  231. }
  232. func createKubeadmCertsSecret(t *testing.T, cfg *kubeadmapi.InitConfiguration, secretKey string) *v1.Secret {
  233. decodedKey, err := hex.DecodeString(secretKey)
  234. if err != nil {
  235. t.Fatalf("error decoding key: %v", err)
  236. }
  237. secretData, err := getDataFromDisk(cfg, decodedKey)
  238. if err != nil {
  239. t.Fatalf("error creating secret data: %v", err)
  240. }
  241. return &v1.Secret{
  242. ObjectMeta: metav1.ObjectMeta{
  243. Name: kubeadmconstants.KubeadmCertsSecret,
  244. Namespace: metav1.NamespaceSystem,
  245. },
  246. Data: secretData,
  247. }
  248. }