copycerts.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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. "fmt"
  17. "io/ioutil"
  18. "os"
  19. "path"
  20. "strings"
  21. "github.com/pkg/errors"
  22. v1 "k8s.io/api/core/v1"
  23. rbac "k8s.io/api/rbac/v1"
  24. apierrors "k8s.io/apimachinery/pkg/api/errors"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/runtime/schema"
  27. clientset "k8s.io/client-go/kubernetes"
  28. certutil "k8s.io/client-go/util/cert"
  29. keyutil "k8s.io/client-go/util/keyutil"
  30. bootstraputil "k8s.io/cluster-bootstrap/token/util"
  31. "k8s.io/klog"
  32. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  33. kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  34. nodebootstraptokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
  35. "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
  36. cryptoutil "k8s.io/kubernetes/cmd/kubeadm/app/util/crypto"
  37. rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1"
  38. )
  39. const (
  40. externalEtcdCA = "external-etcd-ca.crt"
  41. externalEtcdCert = "external-etcd.crt"
  42. externalEtcdKey = "external-etcd.key"
  43. )
  44. // createShortLivedBootstrapToken creates the token used to manager kubeadm-certs
  45. // and return the tokenID
  46. func createShortLivedBootstrapToken(client clientset.Interface) (string, error) {
  47. tokenStr, err := bootstraputil.GenerateBootstrapToken()
  48. if err != nil {
  49. return "", errors.Wrap(err, "error generating token to upload certs")
  50. }
  51. token, err := kubeadmapi.NewBootstrapTokenString(tokenStr)
  52. if err != nil {
  53. return "", errors.Wrap(err, "error creating upload certs token")
  54. }
  55. tokens := []kubeadmapi.BootstrapToken{{
  56. Token: token,
  57. Description: "Proxy for managing TTL for the kubeadm-certs secret",
  58. TTL: &metav1.Duration{
  59. Duration: kubeadmconstants.DefaultCertTokenDuration,
  60. },
  61. }}
  62. if err := nodebootstraptokenphase.CreateNewTokens(client, tokens); err != nil {
  63. return "", errors.Wrap(err, "error creating token")
  64. }
  65. return tokens[0].Token.ID, nil
  66. }
  67. //CreateCertificateKey returns a cryptographically secure random key
  68. func CreateCertificateKey() (string, error) {
  69. randBytes, err := cryptoutil.CreateRandBytes(kubeadmconstants.CertificateKeySize)
  70. if err != nil {
  71. return "", err
  72. }
  73. return hex.EncodeToString(randBytes), nil
  74. }
  75. //UploadCerts save certs needs to join a new control-plane on kubeadm-certs sercret.
  76. func UploadCerts(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, key string) error {
  77. fmt.Printf("[upload-certs] Storing the certificates in Secret %q in the %q Namespace\n", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem)
  78. decodedKey, err := hex.DecodeString(key)
  79. if err != nil {
  80. return errors.Wrap(err, "error decoding certificate key")
  81. }
  82. tokenID, err := createShortLivedBootstrapToken(client)
  83. if err != nil {
  84. return err
  85. }
  86. secretData, err := getDataFromDisk(cfg, decodedKey)
  87. if err != nil {
  88. return err
  89. }
  90. ref, err := getSecretOwnerRef(client, tokenID)
  91. if err != nil {
  92. return err
  93. }
  94. err = apiclient.CreateOrUpdateSecret(client, &v1.Secret{
  95. ObjectMeta: metav1.ObjectMeta{
  96. Name: kubeadmconstants.KubeadmCertsSecret,
  97. Namespace: metav1.NamespaceSystem,
  98. OwnerReferences: ref,
  99. },
  100. Data: secretData,
  101. })
  102. if err != nil {
  103. return err
  104. }
  105. return createRBAC(client)
  106. }
  107. func createRBAC(client clientset.Interface) error {
  108. err := apiclient.CreateOrUpdateRole(client, &rbac.Role{
  109. ObjectMeta: metav1.ObjectMeta{
  110. Name: kubeadmconstants.KubeadmCertsClusterRoleName,
  111. Namespace: metav1.NamespaceSystem,
  112. },
  113. Rules: []rbac.PolicyRule{
  114. rbachelper.NewRule("get").Groups("").Resources("secrets").Names(kubeadmconstants.KubeadmCertsSecret).RuleOrDie(),
  115. },
  116. })
  117. if err != nil {
  118. return err
  119. }
  120. return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{
  121. ObjectMeta: metav1.ObjectMeta{
  122. Name: kubeadmconstants.KubeadmCertsClusterRoleName,
  123. Namespace: metav1.NamespaceSystem,
  124. },
  125. RoleRef: rbac.RoleRef{
  126. APIGroup: rbac.GroupName,
  127. Kind: "Role",
  128. Name: kubeadmconstants.KubeadmCertsClusterRoleName,
  129. },
  130. Subjects: []rbac.Subject{
  131. {
  132. Kind: rbac.GroupKind,
  133. Name: kubeadmconstants.NodeBootstrapTokenAuthGroup,
  134. },
  135. },
  136. })
  137. }
  138. func getSecretOwnerRef(client clientset.Interface, tokenID string) ([]metav1.OwnerReference, error) {
  139. secretName := bootstraputil.BootstrapTokenSecretName(tokenID)
  140. secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
  141. if err != nil {
  142. return nil, errors.Wrap(err, "error to get token reference")
  143. }
  144. gvk := schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
  145. ref := metav1.NewControllerRef(secret, gvk)
  146. return []metav1.OwnerReference{*ref}, nil
  147. }
  148. func loadAndEncryptCert(certPath string, key []byte) ([]byte, error) {
  149. cert, err := ioutil.ReadFile(certPath)
  150. if err != nil {
  151. return nil, err
  152. }
  153. return cryptoutil.EncryptBytes(cert, key)
  154. }
  155. func certsToTransfer(cfg *kubeadmapi.InitConfiguration) map[string]string {
  156. certsDir := cfg.CertificatesDir
  157. certs := map[string]string{
  158. kubeadmconstants.CACertName: path.Join(certsDir, kubeadmconstants.CACertName),
  159. kubeadmconstants.CAKeyName: path.Join(certsDir, kubeadmconstants.CAKeyName),
  160. kubeadmconstants.FrontProxyCACertName: path.Join(certsDir, kubeadmconstants.FrontProxyCACertName),
  161. kubeadmconstants.FrontProxyCAKeyName: path.Join(certsDir, kubeadmconstants.FrontProxyCAKeyName),
  162. kubeadmconstants.ServiceAccountPublicKeyName: path.Join(certsDir, kubeadmconstants.ServiceAccountPublicKeyName),
  163. kubeadmconstants.ServiceAccountPrivateKeyName: path.Join(certsDir, kubeadmconstants.ServiceAccountPrivateKeyName),
  164. }
  165. if cfg.Etcd.External == nil {
  166. certs[kubeadmconstants.EtcdCACertName] = path.Join(certsDir, kubeadmconstants.EtcdCACertName)
  167. certs[kubeadmconstants.EtcdCAKeyName] = path.Join(certsDir, kubeadmconstants.EtcdCAKeyName)
  168. } else {
  169. certs[externalEtcdCA] = cfg.Etcd.External.CAFile
  170. certs[externalEtcdCert] = cfg.Etcd.External.CertFile
  171. certs[externalEtcdKey] = cfg.Etcd.External.KeyFile
  172. }
  173. return certs
  174. }
  175. func getDataFromDisk(cfg *kubeadmapi.InitConfiguration, key []byte) (map[string][]byte, error) {
  176. secretData := map[string][]byte{}
  177. for certName, certPath := range certsToTransfer(cfg) {
  178. cert, err := loadAndEncryptCert(certPath, key)
  179. if err == nil || (err != nil && os.IsNotExist(err)) {
  180. secretData[certOrKeyNameToSecretName(certName)] = cert
  181. } else {
  182. return nil, err
  183. }
  184. }
  185. return secretData, nil
  186. }
  187. // DownloadCerts downloads the certificates needed to join a new control plane.
  188. func DownloadCerts(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, key string) error {
  189. fmt.Printf("[download-certs] Downloading the certificates in Secret %q in the %q Namespace\n", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem)
  190. decodedKey, err := hex.DecodeString(key)
  191. if err != nil {
  192. return errors.Wrap(err, "error decoding certificate key")
  193. }
  194. secret, err := getSecret(client)
  195. if err != nil {
  196. return errors.Wrap(err, "error downloading the secret")
  197. }
  198. secretData, err := getDataFromSecret(secret, decodedKey)
  199. if err != nil {
  200. return errors.Wrap(err, "error decoding secret data with provided key")
  201. }
  202. for certOrKeyName, certOrKeyPath := range certsToTransfer(cfg) {
  203. certOrKeyData, found := secretData[certOrKeyNameToSecretName(certOrKeyName)]
  204. if !found {
  205. return errors.Errorf("the Secret does not include the required certificate or key - name: %s, path: %s", certOrKeyName, certOrKeyPath)
  206. }
  207. if len(certOrKeyData) == 0 {
  208. klog.V(1).Infof("[download-certs] Not saving %q to disk, since it is empty in the %q Secret\n", certOrKeyName, kubeadmconstants.KubeadmCertsSecret)
  209. continue
  210. }
  211. if err := writeCertOrKey(certOrKeyPath, certOrKeyData); err != nil {
  212. return err
  213. }
  214. }
  215. return nil
  216. }
  217. func writeCertOrKey(certOrKeyPath string, certOrKeyData []byte) error {
  218. if _, err := keyutil.ParsePublicKeysPEM(certOrKeyData); err == nil {
  219. return keyutil.WriteKey(certOrKeyPath, certOrKeyData)
  220. } else if _, err := certutil.ParseCertsPEM(certOrKeyData); err == nil {
  221. return certutil.WriteCert(certOrKeyPath, certOrKeyData)
  222. }
  223. return errors.New("unknown data found in Secret entry")
  224. }
  225. func getSecret(client clientset.Interface) (*v1.Secret, error) {
  226. secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(kubeadmconstants.KubeadmCertsSecret, metav1.GetOptions{})
  227. if err != nil {
  228. if apierrors.IsNotFound(err) {
  229. return nil, errors.Errorf("Secret %q was not found in the %q Namespace. This Secret might have expired. Please, run `kubeadm init phase upload-certs --upload-certs` on a control plane to generate a new one", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem)
  230. }
  231. return nil, err
  232. }
  233. return secret, nil
  234. }
  235. func getDataFromSecret(secret *v1.Secret, key []byte) (map[string][]byte, error) {
  236. secretData := map[string][]byte{}
  237. for secretName, encryptedSecret := range secret.Data {
  238. // In some cases the secret might have empty data if the secrets were not present on disk
  239. // when uploading. This can specially happen with external insecure etcd (no certs)
  240. if len(encryptedSecret) > 0 {
  241. cert, err := cryptoutil.DecryptBytes(encryptedSecret, key)
  242. if err != nil {
  243. // If any of the decrypt operations fail do not return a partial result,
  244. // return an empty result immediately
  245. return map[string][]byte{}, err
  246. }
  247. secretData[secretName] = cert
  248. } else {
  249. secretData[secretName] = []byte{}
  250. }
  251. }
  252. return secretData, nil
  253. }
  254. func certOrKeyNameToSecretName(certOrKeyName string) string {
  255. return strings.Replace(certOrKeyName, "/", "-", -1)
  256. }