mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
kubelet: '--experimental-bootstrap-kubeconfig' refactor.
Move bootstrap functions to separate files. Split some of the functions into small sub-functions for reusability. Other cleanups
This commit is contained in:
parent
2e631d811c
commit
26a6623261
259
cmd/kubelet/app/bootstrap.go
Normal file
259
cmd/kubelet/app/bootstrap.go
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
_ "net/http/pprof"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/certificates"
|
||||||
|
unversionedcertificates "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/certificates/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
|
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||||
|
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||||
|
"k8s.io/kubernetes/pkg/fields"
|
||||||
|
utilcertificates "k8s.io/kubernetes/pkg/util/certificates"
|
||||||
|
"k8s.io/kubernetes/pkg/util/crypto"
|
||||||
|
"k8s.io/kubernetes/pkg/watch"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultKubeletClientCertificateFile = "kubelet-client.crt"
|
||||||
|
defaultKubeletClientKeyFile = "kubelet-client.key"
|
||||||
|
)
|
||||||
|
|
||||||
|
// bootstrapClientCert requests a client cert for kubelet if the kubeconfigPath file does not exist.
|
||||||
|
// The kubeconfig at bootstrapPath is used to request a client certificate from the API server.
|
||||||
|
// On success, a kubeconfig file referencing the generated key and obtained certificate is written to kubeconfigPath.
|
||||||
|
// The certificate and key file are stored in certDir.
|
||||||
|
func bootstrapClientCert(kubeconfigPath string, bootstrapPath string, certDir string, nodeName string) error {
|
||||||
|
// Short-circuit if the kubeconfig file already exists.
|
||||||
|
// TODO: inspect the kubeconfig, ensure a rest client can be built from it, verify client cert expiration, etc.
|
||||||
|
_, err := os.Stat(kubeconfigPath)
|
||||||
|
if err == nil {
|
||||||
|
glog.V(2).Infof("Kubeconfig %s exists, skipping bootstrap", kubeconfigPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
glog.Errorf("Error reading kubeconfig %s, skipping bootstrap: %v", kubeconfigPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(2).Info("Using bootstrap kubeconfig to generate TLS client cert, key and kubeconfig file")
|
||||||
|
|
||||||
|
bootstrapClientConfig, err := loadRESTClientConfig(bootstrapPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to load bootstrap kubeconfig: %v", err)
|
||||||
|
}
|
||||||
|
bootstrapClient, err := unversionedcertificates.NewForConfig(bootstrapClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create certificates signing request client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
success := false
|
||||||
|
|
||||||
|
// Get the private key.
|
||||||
|
keyPath, err := filepath.Abs(filepath.Join(certDir, defaultKubeletClientKeyFile))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to build bootstrap key path: %v", err)
|
||||||
|
}
|
||||||
|
keyData, generatedKeyFile, err := loadOrGenerateKeyFile(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if generatedKeyFile {
|
||||||
|
defer func() {
|
||||||
|
if !success {
|
||||||
|
if err := os.Remove(keyPath); err != nil {
|
||||||
|
glog.Warningf("Cannot clean up the key file %q: %v", keyPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the cert.
|
||||||
|
certPath, err := filepath.Abs(filepath.Join(certDir, defaultKubeletClientCertificateFile))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to build bootstrap client cert path: %v", err)
|
||||||
|
}
|
||||||
|
certData, err := RequestClientCertificate(bootstrapClient.CertificateSigningRequests(), keyData, nodeName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := crypto.WriteCertToPath(certPath, certData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if !success {
|
||||||
|
if err := os.Remove(certPath); err != nil {
|
||||||
|
glog.Warningf("Cannot clean up the cert file %q: %v", certPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Get the CA data from the bootstrap client config.
|
||||||
|
caFile, caData := bootstrapClientConfig.CAFile, []byte{}
|
||||||
|
if len(caFile) == 0 {
|
||||||
|
caData = bootstrapClientConfig.CAData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build resulting kubeconfig.
|
||||||
|
kubeconfigData := clientcmdapi.Config{
|
||||||
|
// Define a cluster stanza based on the bootstrap kubeconfig.
|
||||||
|
Clusters: map[string]*clientcmdapi.Cluster{"default-cluster": {
|
||||||
|
Server: bootstrapClientConfig.Host,
|
||||||
|
InsecureSkipTLSVerify: bootstrapClientConfig.Insecure,
|
||||||
|
CertificateAuthority: caFile,
|
||||||
|
CertificateAuthorityData: caData,
|
||||||
|
}},
|
||||||
|
// Define auth based on the obtained client cert.
|
||||||
|
AuthInfos: map[string]*clientcmdapi.AuthInfo{"default-auth": {
|
||||||
|
ClientCertificate: certPath,
|
||||||
|
ClientKey: keyPath,
|
||||||
|
}},
|
||||||
|
// Define a context that connects the auth info and cluster, and set it as the default
|
||||||
|
Contexts: map[string]*clientcmdapi.Context{"default-context": {
|
||||||
|
Cluster: "default-cluster",
|
||||||
|
AuthInfo: "default-auth",
|
||||||
|
Namespace: "default",
|
||||||
|
}},
|
||||||
|
CurrentContext: "default-context",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal to disk
|
||||||
|
if err := clientcmd.WriteToFile(kubeconfigData, kubeconfigPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
success = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadRESTClientConfig(kubeconfig string) (*restclient.Config, error) {
|
||||||
|
// Load structured kubeconfig data from the given path.
|
||||||
|
loader := &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}
|
||||||
|
loadedConfig, err := loader.Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Flatten the loaded data to a particular restclient.Config based on the current context.
|
||||||
|
return clientcmd.NewNonInteractiveClientConfig(
|
||||||
|
*loadedConfig,
|
||||||
|
loadedConfig.CurrentContext,
|
||||||
|
&clientcmd.ConfigOverrides{},
|
||||||
|
loader,
|
||||||
|
).ClientConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadOrGenerateKeyFile(keyPath string) (data []byte, wasGenerated bool, err error) {
|
||||||
|
loadedData, err := ioutil.ReadFile(keyPath)
|
||||||
|
if err == nil {
|
||||||
|
return loadedData, false, err
|
||||||
|
}
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return nil, false, fmt.Errorf("error loading key from %s: %v", keyPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedData, err := utilcertificates.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("error generating key: %v", err)
|
||||||
|
}
|
||||||
|
if err := crypto.WriteKeyToPath(keyPath, generatedData); err != nil {
|
||||||
|
return nil, false, fmt.Errorf("error writing key to %s: %v", keyPath, err)
|
||||||
|
}
|
||||||
|
return generatedData, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestClientCertificate will create a certificate signing request and send it to API server,
|
||||||
|
// then it will watch the object's status, once approved by API server, it will return the API
|
||||||
|
// server's issued certificate (pem-encoded). If there is any errors, or the watch timeouts,
|
||||||
|
// it will return an error.
|
||||||
|
func RequestClientCertificate(client unversionedcertificates.CertificateSigningRequestInterface, privateKeyData []byte, nodeName string) (certData []byte, err error) {
|
||||||
|
subject := &pkix.Name{
|
||||||
|
Organization: []string{"system:nodes"},
|
||||||
|
CommonName: fmt.Sprintf("system:node:%s", nodeName),
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, err := utilcertificates.ParsePrivateKey(privateKeyData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid private key for certificate request: %v", err)
|
||||||
|
}
|
||||||
|
csr, err := utilcertificates.NewCertificateRequest(privateKey, subject, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to generate certificate request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := client.Create(&certificates.CertificateSigningRequest{
|
||||||
|
// Username, UID, Groups will be injected by API server.
|
||||||
|
TypeMeta: unversioned.TypeMeta{Kind: "CertificateSigningRequest"},
|
||||||
|
ObjectMeta: api.ObjectMeta{GenerateName: "csr-"},
|
||||||
|
|
||||||
|
// TODO: For now, this is a request for a certificate with allowed usage of "TLS Web Client Authentication".
|
||||||
|
// Need to figure out whether/how to surface the allowed usage in the spec.
|
||||||
|
Spec: certificates.CertificateSigningRequestSpec{Request: csr},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot create certificate signing request: %v", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a default timeout = 3600s.
|
||||||
|
var defaultTimeoutSeconds int64 = 3600
|
||||||
|
resultCh, err := client.Watch(api.ListOptions{
|
||||||
|
Watch: true,
|
||||||
|
TimeoutSeconds: &defaultTimeoutSeconds,
|
||||||
|
FieldSelector: fields.OneTermEqualSelector("metadata.name", req.Name),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot watch on the certificate signing request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var status certificates.CertificateSigningRequestStatus
|
||||||
|
ch := resultCh.ResultChan()
|
||||||
|
|
||||||
|
for {
|
||||||
|
event, ok := <-ch
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Type == watch.Modified || event.Type == watch.Added {
|
||||||
|
if event.Object.(*certificates.CertificateSigningRequest).UID != req.UID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
status = event.Object.(*certificates.CertificateSigningRequest).Status
|
||||||
|
for _, c := range status.Conditions {
|
||||||
|
if c.Type == certificates.CertificateDenied {
|
||||||
|
return nil, fmt.Errorf("certificate signing request is not approved, reason: %v, message: %v", c.Reason, c.Message)
|
||||||
|
}
|
||||||
|
if c.Type == certificates.CertificateApproved && status.Certificate != nil {
|
||||||
|
return status.Certificate, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("watch channel closed")
|
||||||
|
}
|
85
cmd/kubelet/app/bootstrap_test.go
Normal file
85
cmd/kubelet/app/bootstrap_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
|
"k8s.io/kubernetes/pkg/util/diff"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadRESTClientConfig(t *testing.T) {
|
||||||
|
testData := []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Config
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
certificate-authority: ca-a.crt
|
||||||
|
server: https://cluster-a.com
|
||||||
|
name: cluster-a
|
||||||
|
- cluster:
|
||||||
|
certificate-authority-data: VGVzdA==
|
||||||
|
server: https://cluster-b.com
|
||||||
|
name: cluster-b
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: cluster-a
|
||||||
|
namespace: ns-a
|
||||||
|
user: user-a
|
||||||
|
name: context-a
|
||||||
|
- context:
|
||||||
|
cluster: cluster-b
|
||||||
|
namespace: ns-b
|
||||||
|
user: user-b
|
||||||
|
name: context-b
|
||||||
|
current-context: context-b
|
||||||
|
users:
|
||||||
|
- name: user-a
|
||||||
|
user:
|
||||||
|
token: mytoken-a
|
||||||
|
- name: user-b
|
||||||
|
user:
|
||||||
|
token: mytoken-b
|
||||||
|
`)
|
||||||
|
f, err := ioutil.TempFile("", "kubeconfig")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
ioutil.WriteFile(f.Name(), testData, os.FileMode(0755))
|
||||||
|
|
||||||
|
config, err := loadRESTClientConfig(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedConfig := &restclient.Config{
|
||||||
|
Host: "https://cluster-b.com",
|
||||||
|
TLSClientConfig: restclient.TLSClientConfig{
|
||||||
|
CAData: []byte(`Test`),
|
||||||
|
},
|
||||||
|
BearerToken: "mytoken-b",
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(config, expectedConfig) {
|
||||||
|
t.Errorf("Unexpected config: %s", diff.ObjectDiff(config, expectedConfig))
|
||||||
|
}
|
||||||
|
}
|
@ -102,7 +102,7 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) {
|
|||||||
fs.StringVar(&s.BootstrapKubeconfig, "experimental-bootstrap-kubeconfig", s.BootstrapKubeconfig, "<Warning: Experimental feature> Path to a kubeconfig file that will be used to get client certificate for kubelet. "+
|
fs.StringVar(&s.BootstrapKubeconfig, "experimental-bootstrap-kubeconfig", s.BootstrapKubeconfig, "<Warning: Experimental feature> Path to a kubeconfig file that will be used to get client certificate for kubelet. "+
|
||||||
"If the file specified by --kubeconfig does not exist, the bootstrap kubeconfig is used to request a client certificate from the API server. "+
|
"If the file specified by --kubeconfig does not exist, the bootstrap kubeconfig is used to request a client certificate from the API server. "+
|
||||||
"On success, a kubeconfig file referencing the generated key and obtained certificate is written to the path specified by --kubeconfig. "+
|
"On success, a kubeconfig file referencing the generated key and obtained certificate is written to the path specified by --kubeconfig. "+
|
||||||
"The certificate and key file will be stored in /var/run/kubernetes/.")
|
"The certificate and key file will be stored in the directory pointed by --cert-dir.")
|
||||||
fs.StringVar(&s.HostnameOverride, "hostname-override", s.HostnameOverride, "If non-empty, will use this string as identification instead of the actual hostname.")
|
fs.StringVar(&s.HostnameOverride, "hostname-override", s.HostnameOverride, "If non-empty, will use this string as identification instead of the actual hostname.")
|
||||||
fs.StringVar(&s.PodInfraContainerImage, "pod-infra-container-image", s.PodInfraContainerImage, "The image whose network/ipc namespaces containers in each pod will use.")
|
fs.StringVar(&s.PodInfraContainerImage, "pod-infra-container-image", s.PodInfraContainerImage, "The image whose network/ipc namespaces containers in each pod will use.")
|
||||||
fs.StringVar(&s.DockerEndpoint, "docker-endpoint", s.DockerEndpoint, "Use this for the docker endpoint to communicate with")
|
fs.StringVar(&s.DockerEndpoint, "docker-endpoint", s.DockerEndpoint, "Use this for the docker endpoint to communicate with")
|
||||||
|
@ -19,8 +19,6 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -41,14 +39,11 @@ import (
|
|||||||
"k8s.io/kubernetes/cmd/kubelet/app/options"
|
"k8s.io/kubernetes/cmd/kubelet/app/options"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/certificates"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/componentconfig"
|
"k8s.io/kubernetes/pkg/apis/componentconfig"
|
||||||
kubeExternal "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1"
|
kubeExternal "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1"
|
||||||
"k8s.io/kubernetes/pkg/capabilities"
|
"k8s.io/kubernetes/pkg/capabilities"
|
||||||
"k8s.io/kubernetes/pkg/client/chaosclient"
|
"k8s.io/kubernetes/pkg/client/chaosclient"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
unversionedcertificates "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/certificates/unversioned"
|
|
||||||
unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned"
|
unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/client/record"
|
"k8s.io/kubernetes/pkg/client/record"
|
||||||
"k8s.io/kubernetes/pkg/client/restclient"
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
@ -69,7 +64,6 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/kubelet/network"
|
"k8s.io/kubernetes/pkg/kubelet/network"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/server"
|
"k8s.io/kubernetes/pkg/kubelet/server"
|
||||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||||
utilcertificates "k8s.io/kubernetes/pkg/util/certificates"
|
|
||||||
utilconfig "k8s.io/kubernetes/pkg/util/config"
|
utilconfig "k8s.io/kubernetes/pkg/util/config"
|
||||||
"k8s.io/kubernetes/pkg/util/configz"
|
"k8s.io/kubernetes/pkg/util/configz"
|
||||||
"k8s.io/kubernetes/pkg/util/crypto"
|
"k8s.io/kubernetes/pkg/util/crypto"
|
||||||
@ -83,12 +77,6 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/util/wait"
|
"k8s.io/kubernetes/pkg/util/wait"
|
||||||
"k8s.io/kubernetes/pkg/version"
|
"k8s.io/kubernetes/pkg/version"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
"k8s.io/kubernetes/pkg/watch"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultKubeletClientCertificateFile = "/var/run/kubernetes/kubelet-client.crt"
|
|
||||||
defaultKubeletClientKeyFile = "/var/run/kubernetes/kubelet-client.key"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// bootstrapping interface for kubelet, targets the initialization protocol
|
// bootstrapping interface for kubelet, targets the initialization protocol
|
||||||
@ -347,8 +335,29 @@ func run(s *options.KubeletServer, kcfg *KubeletConfig) (err error) {
|
|||||||
|
|
||||||
if kcfg == nil {
|
if kcfg == nil {
|
||||||
var kubeClient, eventClient *clientset.Clientset
|
var kubeClient, eventClient *clientset.Clientset
|
||||||
|
var autoDetectCloudProvider bool
|
||||||
|
var cloud cloudprovider.Interface
|
||||||
|
|
||||||
|
if s.CloudProvider == kubeExternal.AutoDetectCloudProvider {
|
||||||
|
autoDetectCloudProvider = true
|
||||||
|
} else {
|
||||||
|
cloud, err = cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cloud == nil {
|
||||||
|
glog.V(2).Infof("No cloud provider specified: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile)
|
||||||
|
} else {
|
||||||
|
glog.V(2).Infof("Successfully initialized cloud provider: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if s.BootstrapKubeconfig != "" {
|
if s.BootstrapKubeconfig != "" {
|
||||||
if err := bootstrapClientCert(s); err != nil {
|
nodeName, err := getNodeName(cloud, nodeutil.GetHostname(s.HostnameOverride))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bootstrapClientCert(s.KubeConfig.Value(), s.BootstrapKubeconfig, s.CertDirectory, nodeName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -377,24 +386,12 @@ func run(s *options.KubeletServer, kcfg *KubeletConfig) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
kcfg = cfg
|
kcfg = cfg
|
||||||
|
kcfg.AutoDetectCloudProvider = autoDetectCloudProvider
|
||||||
|
kcfg.Cloud = cloud
|
||||||
kcfg.KubeClient = kubeClient
|
kcfg.KubeClient = kubeClient
|
||||||
kcfg.EventClient = eventClient
|
kcfg.EventClient = eventClient
|
||||||
|
|
||||||
if s.CloudProvider == kubeExternal.AutoDetectCloudProvider {
|
|
||||||
kcfg.AutoDetectCloudProvider = true
|
|
||||||
} else {
|
|
||||||
cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if cloud == nil {
|
|
||||||
glog.V(2).Infof("No cloud provider specified: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile)
|
|
||||||
} else {
|
|
||||||
glog.V(2).Infof("Successfully initialized cloud provider: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile)
|
|
||||||
kcfg.Cloud = cloud
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if kcfg.CAdvisorInterface == nil {
|
if kcfg.CAdvisorInterface == nil {
|
||||||
@ -457,240 +454,26 @@ func run(s *options.KubeletServer, kcfg *KubeletConfig) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// bootstrapClientCert will request a client cert for kubelet.
|
|
||||||
// If the file specified by --kubeconfig does not exist, the bootstrap kubeconfig is used
|
|
||||||
// to request a client certificate from the API server.
|
|
||||||
// On success, a kubeconfig file referencing the generated key and obtained certificate is
|
|
||||||
// written to the path specified by --kubeconfig.
|
|
||||||
// The certificate and key file will be stored in /var/run/kubernetes/.
|
|
||||||
func bootstrapClientCert(s *options.KubeletServer) error {
|
|
||||||
// Check the if --kubeconfig already has sufficient TLS client info.
|
|
||||||
kcfg, err := (&clientcmd.ClientConfigLoadingRules{ExplicitPath: s.KubeConfig.Value()}).Load()
|
|
||||||
if err == nil {
|
|
||||||
authInfo, err := getCurrentContextAuthInfo(kcfg)
|
|
||||||
if err == nil {
|
|
||||||
if containsSufficientTLSInfo(authInfo) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, we need to use the bootstrap kubeconfig to generate TLS client cert, key, and a kubeconfig
|
|
||||||
// to stored in --kubeconfig.
|
|
||||||
glog.V(2).Info("Using bootstrap kubeconfig to generate TLS client cert, key and kubeconfig file")
|
|
||||||
|
|
||||||
kcfg, err = (&clientcmd.ClientConfigLoadingRules{ExplicitPath: s.BootstrapKubeconfig}).Load()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load boostrap kubeconfig: %v", err)
|
|
||||||
}
|
|
||||||
authInfo, err := getCurrentContextAuthInfo(kcfg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load auth info in bootstrap kubeconfig: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
authInfo.ClientCertificate, authInfo.ClientKey, err = getClientCertAndKey(s, authInfo.ClientCertificate, authInfo.ClientKey)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to get cert from API server: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal and write the kubeconfig to disk.
|
|
||||||
data, err := json.Marshal(kcfg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to marshal the kubeconfig: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile(s.KubeConfig.Value(), data, 0644); err != nil {
|
|
||||||
return fmt.Errorf("unable to write the kubeconfig file at %q: %v", s.KubeConfig.Value(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCurrentContextAuthInfo returns the AuthInfo object that's referenced
|
|
||||||
// by the current context.
|
|
||||||
// If current context or auth info name is empty, then it will return an error.
|
|
||||||
// If AuthInfo is empty, a new auth info object will be created.
|
|
||||||
func getCurrentContextAuthInfo(config *clientcmdapi.Config) (*clientcmdapi.AuthInfo, error) {
|
|
||||||
ctx, ok := config.Contexts[config.CurrentContext]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unable to find current context %q", config.CurrentContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.AuthInfo == "" {
|
|
||||||
return nil, fmt.Errorf("unable to find the name of the authInfo in current context %q", config.CurrentContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := config.AuthInfos[ctx.AuthInfo]; !ok {
|
|
||||||
config.AuthInfos[ctx.AuthInfo] = clientcmdapi.NewAuthInfo()
|
|
||||||
}
|
|
||||||
return config.AuthInfos[ctx.AuthInfo], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(yifan): More detailed check on the cert / key content.
|
|
||||||
// CheckTLSInfo returns true if the authInfo contains client certificate data for client key data.
|
|
||||||
// Or if the client certificate or key file exists.
|
|
||||||
func containsSufficientTLSInfo(authInfo *clientcmdapi.AuthInfo) bool {
|
|
||||||
// We use '||' so that we won't override existing data in case of wrong setup.
|
|
||||||
if len(authInfo.ClientCertificateData) > 0 || len(authInfo.ClientKeyData) > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if crypto.FoundCertOrKey(authInfo.ClientCertificate, authInfo.ClientKey) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// getClientCertAndKey will:
|
|
||||||
// (1) Create a restful client for doing the certificate signing request.
|
|
||||||
// (2) Read existing key data from existingKeyPath if possible.
|
|
||||||
// (3) Pass 'requestClientCertificate()' the CSR client, existing key data, and node name to
|
|
||||||
// request for client certificate from the API server.
|
|
||||||
// (4) Once (3) succeeds, dump the certificate and key data to the given paths.
|
|
||||||
// On failure, the the certificate and key file will be cleaned up.
|
|
||||||
// If the existingCertPath or existingKeyPath is empty, then the function will use the default path, respectively:
|
|
||||||
// /var/run/kubernetes/kubelet-client.crt, /var/run/kubernetes/kubelet-client.key.
|
|
||||||
func getClientCertAndKey(s *options.KubeletServer, existingCertPath, existingKeyPath string) (certPath, keyPath string, err error) {
|
|
||||||
// (1).
|
|
||||||
clientConfig, err := kubeconfigClientConfig(s.BootstrapKubeconfig, s.APIServerList)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to create client config: %v", err)
|
|
||||||
}
|
|
||||||
client, err := unversionedcertificates.NewForConfig(clientConfig)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to create certificates signing request client: %v", err)
|
|
||||||
}
|
|
||||||
csrClient := client.CertificateSigningRequests()
|
|
||||||
|
|
||||||
// (2).
|
|
||||||
certPath, keyPath = existingCertPath, existingKeyPath
|
|
||||||
if certPath == "" {
|
|
||||||
certPath = defaultKubeletClientCertificateFile
|
|
||||||
}
|
|
||||||
if keyPath == "" {
|
|
||||||
keyPath = defaultKubeletClientKeyFile
|
|
||||||
|
|
||||||
}
|
|
||||||
existingKeyData, err := ioutil.ReadFile(keyPath)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return "", "", fmt.Errorf("unable to read key file %q: %v", keyPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// (3).
|
|
||||||
nodeName, err := getNodeName(s)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to get node name: %v", err)
|
|
||||||
}
|
|
||||||
certData, keyData, err := requestClientCertificate(csrClient, existingKeyData, nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to request certificate from API server: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// (4).
|
|
||||||
if err = crypto.WriteCertToPath(certPath, certData); err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to write certificate file %q: %v", certPath, err)
|
|
||||||
}
|
|
||||||
if err = crypto.WriteKeyToPath(keyPath, keyData); err != nil {
|
|
||||||
if err := os.Remove(certPath); err != nil {
|
|
||||||
glog.Warningf("Cannot clean up the certificate file %q: %v", certPath, err)
|
|
||||||
}
|
|
||||||
return "", "", fmt.Errorf("unable to write key file %q: %v", keyPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return certPath, keyPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNodeName returns the node name according to the cloud provider
|
// getNodeName returns the node name according to the cloud provider
|
||||||
// if cloud provider is specified. Otherwise, returns the host name of the node.
|
// if cloud provider is specified. Otherwise, returns the hostname of the node.
|
||||||
func getNodeName(s *options.KubeletServer) (string, error) {
|
func getNodeName(cloud cloudprovider.Interface, hostname string) (string, error) {
|
||||||
var err error
|
if cloud == nil {
|
||||||
var cloud cloudprovider.Interface
|
return hostname, nil
|
||||||
|
|
||||||
if s.CloudProvider != kubeExternal.AutoDetectCloudProvider {
|
|
||||||
cloud, err = cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hostName := nodeutil.GetHostname(s.HostnameOverride)
|
|
||||||
if cloud != nil {
|
|
||||||
instances, ok := cloud.Instances()
|
instances, ok := cloud.Instances()
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("failed to get instances from cloud provider")
|
return "", fmt.Errorf("failed to get instances from cloud provider")
|
||||||
}
|
}
|
||||||
return instances.CurrentNodeName(hostName)
|
|
||||||
}
|
|
||||||
return hostName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// requestClientCertificate will create a certificate signing request and send it to API server,
|
nodeName, err := instances.CurrentNodeName(hostname)
|
||||||
// then it will watch the object's status, once approved by API server, it will return the API
|
|
||||||
// server's issued certificate (pem-encoded). If there is any errors, or the watch timeouts,
|
|
||||||
// it will return an error.
|
|
||||||
// If the existingKeyData is empty, a new private key will be generated to create the certificate
|
|
||||||
// signing request.
|
|
||||||
func requestClientCertificate(client unversionedcertificates.CertificateSigningRequestInterface, existingKeyData []byte, nodeName string) (certData []byte, keyData []byte, err error) {
|
|
||||||
subject := &pkix.Name{
|
|
||||||
Organization: []string{"system:nodes"},
|
|
||||||
CommonName: fmt.Sprintf("system:node:%s", nodeName),
|
|
||||||
}
|
|
||||||
|
|
||||||
csr, keyData, err := utilcertificates.NewCertificateRequest(existingKeyData, subject, nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to generate certificate request: %v", err)
|
return "", fmt.Errorf("error fetching current instance name from cloud provider: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := client.Create(&certificates.CertificateSigningRequest{
|
glog.V(2).Infof("cloud provider determined current node name to be %s", nodeName)
|
||||||
TypeMeta: unversioned.TypeMeta{Kind: "CertificateSigningRequest"},
|
|
||||||
ObjectMeta: api.ObjectMeta{GenerateName: "csr-"},
|
|
||||||
|
|
||||||
// Username, UID, Groups will be injected by API server.
|
return nodeName, nil
|
||||||
Spec: certificates.CertificateSigningRequestSpec{Request: csr},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("cannot create certificate signing request: %v", err)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a default timeout = 3600s
|
|
||||||
var defaultTimeoutSeconds int64 = 3600
|
|
||||||
resultCh, err := client.Watch(api.ListOptions{
|
|
||||||
Watch: true,
|
|
||||||
TimeoutSeconds: &defaultTimeoutSeconds,
|
|
||||||
// Label and field selector are not used now.
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("cannot watch on the certificate signing request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var status certificates.CertificateSigningRequestStatus
|
|
||||||
ch := resultCh.ResultChan()
|
|
||||||
|
|
||||||
for {
|
|
||||||
event, ok := <-ch
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.Type == watch.Modified {
|
|
||||||
if event.Object.(*certificates.CertificateSigningRequest).UID != req.UID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
status = event.Object.(*certificates.CertificateSigningRequest).Status
|
|
||||||
for _, c := range status.Conditions {
|
|
||||||
if c.Type == certificates.CertificateDenied {
|
|
||||||
return nil, nil, fmt.Errorf("certificate signing request is not approved: %v, %v", c.Reason, c.Message)
|
|
||||||
}
|
|
||||||
if c.Type == certificates.CertificateApproved && status.Certificate != nil {
|
|
||||||
return status.Certificate, keyData, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, fmt.Errorf("watch channel closed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeTLS checks for a configured TLSCertFile and TLSPrivateKeyFile: if unspecified a new self-signed
|
// InitializeTLS checks for a configured TLSCertFile and TLSPrivateKeyFile: if unspecified a new self-signed
|
||||||
@ -782,13 +565,13 @@ func createClientConfig(s *options.KubeletServer) (*restclient.Config, error) {
|
|||||||
return nil, fmt.Errorf("cannot specify both --kubeconfig and --auth-path")
|
return nil, fmt.Errorf("cannot specify both --kubeconfig and --auth-path")
|
||||||
}
|
}
|
||||||
if s.KubeConfig.Provided() {
|
if s.KubeConfig.Provided() {
|
||||||
return kubeconfigClientConfig(s.KubeConfig.Value(), s.APIServerList)
|
return kubeconfigClientConfig(s)
|
||||||
}
|
}
|
||||||
if s.AuthPath.Provided() {
|
if s.AuthPath.Provided() {
|
||||||
return authPathClientConfig(s, false)
|
return authPathClientConfig(s, false)
|
||||||
}
|
}
|
||||||
// Try the kubeconfig default first, falling back to the auth path default.
|
// Try the kubeconfig default first, falling back to the auth path default.
|
||||||
clientConfig, err := kubeconfigClientConfig(s.KubeConfig.Value(), s.APIServerList)
|
clientConfig, err := kubeconfigClientConfig(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("Could not load kubeconfig file %s: %v. Trying auth path instead.", s.KubeConfig, err)
|
glog.Warningf("Could not load kubeconfig file %s: %v. Trying auth path instead.", s.KubeConfig, err)
|
||||||
return authPathClientConfig(s, true)
|
return authPathClientConfig(s, true)
|
||||||
@ -932,23 +715,10 @@ func RunKubelet(kcfg *KubeletConfig) error {
|
|||||||
kcfg.Hostname = nodeutil.GetHostname(kcfg.HostnameOverride)
|
kcfg.Hostname = nodeutil.GetHostname(kcfg.HostnameOverride)
|
||||||
|
|
||||||
if len(kcfg.NodeName) == 0 {
|
if len(kcfg.NodeName) == 0 {
|
||||||
// Query the cloud provider for our node name, default to Hostname
|
nodeName, err := getNodeName(kcfg.Cloud, kcfg.Hostname)
|
||||||
nodeName := kcfg.Hostname
|
|
||||||
if kcfg.Cloud != nil {
|
|
||||||
var err error
|
|
||||||
instances, ok := kcfg.Cloud.Instances()
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("failed to get instances from cloud provider")
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeName, err = instances.CurrentNodeName(kcfg.Hostname)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error fetching current instance name from cloud provider: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(2).Infof("cloud provider determined current node name to be %s", nodeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
kcfg.NodeName = nodeName
|
kcfg.NodeName = nodeName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +163,7 @@ executor-logv
|
|||||||
executor-path
|
executor-path
|
||||||
executor-suicide-timeout
|
executor-suicide-timeout
|
||||||
exit-on-lock-contention
|
exit-on-lock-contention
|
||||||
|
experimental-bootstrap-kubeconfig
|
||||||
experimental-flannel-overlay
|
experimental-flannel-overlay
|
||||||
experimental-keystone-url
|
experimental-keystone-url
|
||||||
experimental-nvidia-gpus
|
experimental-nvidia-gpus
|
||||||
|
@ -46,57 +46,64 @@ func ParseCertificateRequestObject(obj *certificates.CertificateSigningRequest)
|
|||||||
return csr, nil
|
return csr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificateRequest generates a PEM-encoded CSR using the supplied private
|
// GeneratePrivateKey returns PEM data containing a generated ECDSA private key
|
||||||
// key data, subject, and SANs. If the private key data is empty, it generates a
|
func GeneratePrivateKey() ([]byte, error) {
|
||||||
// new ECDSA P256 key to use and returns it together with the CSR data.
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader)
|
||||||
func NewCertificateRequest(keyData []byte, subject *pkix.Name, dnsSANs []string, ipSANs []net.IP) (csr []byte, key []byte, err error) {
|
|
||||||
var privateKey interface{}
|
|
||||||
var privateKeyPemBlock *pem.Block
|
|
||||||
|
|
||||||
if len(keyData) == 0 {
|
|
||||||
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ecdsaKey := privateKey.(*ecdsa.PrivateKey)
|
derBytes, err := x509.MarshalECPrivateKey(privateKey)
|
||||||
derBytes, err := x509.MarshalECPrivateKey(ecdsaKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKeyPemBlock = &pem.Block{
|
privateKeyPemBlock := &pem.Block{
|
||||||
Type: "EC PRIVATE KEY",
|
Type: "EC PRIVATE KEY",
|
||||||
Bytes: derBytes,
|
Bytes: derBytes,
|
||||||
}
|
}
|
||||||
} else {
|
return pem.EncodeToMemory(privateKeyPemBlock), nil
|
||||||
privateKeyPemBlock, _ = pem.Decode(keyData)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var sigType x509.SignatureAlgorithm
|
// ParsePrivateKey returns a private key parsed from a PEM block in the supplied data.
|
||||||
|
// Recognizes PEM blocks for "EC PRIVATE KEY" and "RSA PRIVATE KEY"
|
||||||
|
func ParsePrivateKey(keyData []byte) (interface{}, error) {
|
||||||
|
for {
|
||||||
|
var privateKeyPemBlock *pem.Block
|
||||||
|
privateKeyPemBlock, keyData = pem.Decode(keyData)
|
||||||
|
if privateKeyPemBlock == nil {
|
||||||
|
// we read all the PEM blocks and didn't recognize one
|
||||||
|
return nil, fmt.Errorf("no private key PEM block found")
|
||||||
|
}
|
||||||
|
|
||||||
switch privateKeyPemBlock.Type {
|
switch privateKeyPemBlock.Type {
|
||||||
case "EC PRIVATE KEY":
|
case "EC PRIVATE KEY":
|
||||||
privateKey, err = x509.ParseECPrivateKey(privateKeyPemBlock.Bytes)
|
return x509.ParseECPrivateKey(privateKeyPemBlock.Bytes)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
ecdsaKey := privateKey.(*ecdsa.PrivateKey)
|
|
||||||
switch ecdsaKey.Curve.Params().BitSize {
|
|
||||||
case 521:
|
|
||||||
sigType = x509.ECDSAWithSHA512
|
|
||||||
case 384:
|
|
||||||
sigType = x509.ECDSAWithSHA384
|
|
||||||
default:
|
|
||||||
sigType = x509.ECDSAWithSHA256
|
|
||||||
}
|
|
||||||
case "RSA PRIVATE KEY":
|
case "RSA PRIVATE KEY":
|
||||||
privateKey, err = x509.ParsePKCS1PrivateKey(privateKeyPemBlock.Bytes)
|
return x509.ParsePKCS1PrivateKey(privateKeyPemBlock.Bytes)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
rsaKey := privateKey.(*rsa.PrivateKey)
|
}
|
||||||
keySize := rsaKey.N.BitLen()
|
}
|
||||||
|
|
||||||
|
// NewCertificateRequest generates a PEM-encoded CSR using the supplied private key, subject, and SANs.
|
||||||
|
// privateKey must be a *ecdsa.PrivateKey or *rsa.PrivateKey.
|
||||||
|
func NewCertificateRequest(privateKey interface{}, subject *pkix.Name, dnsSANs []string, ipSANs []net.IP) (csr []byte, err error) {
|
||||||
|
var sigType x509.SignatureAlgorithm
|
||||||
|
|
||||||
|
switch privateKey := privateKey.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
switch privateKey.Curve {
|
||||||
|
case elliptic.P224(), elliptic.P256():
|
||||||
|
sigType = x509.ECDSAWithSHA256
|
||||||
|
case elliptic.P384():
|
||||||
|
sigType = x509.ECDSAWithSHA384
|
||||||
|
case elliptic.P521():
|
||||||
|
sigType = x509.ECDSAWithSHA512
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown elliptic curve: %v", privateKey.Curve)
|
||||||
|
}
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
keySize := privateKey.N.BitLen()
|
||||||
switch {
|
switch {
|
||||||
case keySize >= 4096:
|
case keySize >= 4096:
|
||||||
sigType = x509.SHA512WithRSA
|
sigType = x509.SHA512WithRSA
|
||||||
@ -105,8 +112,9 @@ func NewCertificateRequest(keyData []byte, subject *pkix.Name, dnsSANs []string,
|
|||||||
default:
|
default:
|
||||||
sigType = x509.SHA256WithRSA
|
sigType = x509.SHA256WithRSA
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, nil, fmt.Errorf("unsupported key type: %s", privateKeyPemBlock.Type)
|
return nil, fmt.Errorf("unsupported key type: %T", privateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
template := &x509.CertificateRequest{
|
template := &x509.CertificateRequest{
|
||||||
@ -118,7 +126,7 @@ func NewCertificateRequest(keyData []byte, subject *pkix.Name, dnsSANs []string,
|
|||||||
|
|
||||||
csr, err = x509.CreateCertificateRequest(cryptorand.Reader, template, privateKey)
|
csr, err = x509.CreateCertificateRequest(cryptorand.Reader, template, privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
csrPemBlock := &pem.Block{
|
csrPemBlock := &pem.Block{
|
||||||
@ -126,5 +134,5 @@ func NewCertificateRequest(keyData []byte, subject *pkix.Name, dnsSANs []string,
|
|||||||
Bytes: csr,
|
Bytes: csr,
|
||||||
}
|
}
|
||||||
|
|
||||||
return pem.EncodeToMemory(csrPemBlock), pem.EncodeToMemory(privateKeyPemBlock), nil
|
return pem.EncodeToMemory(csrPemBlock), nil
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package certificates
|
package certificates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -19,7 +35,11 @@ func TestNewCertificateRequest(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _, err = NewCertificateRequest(keyData, subject, dnsSANs, ipSANs)
|
key, err := ParsePrivateKey(keyData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = NewCertificateRequest(key, subject, dnsSANs, ipSANs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ func WriteCertToPath(certPath string, data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeCertToPath writes the pem-encoded key data to keyPath.
|
// WriteKeyToPath writes the pem-encoded key data to keyPath.
|
||||||
// The key file will be created with file mode 0600.
|
// The key file will be created with file mode 0600.
|
||||||
// If the key file already exists, it will be overwritten.
|
// If the key file already exists, it will be overwritten.
|
||||||
// The parent directory of the keyPath will be created as needed with file mode 0755.
|
// The parent directory of the keyPath will be created as needed with file mode 0755.
|
||||||
|
Loading…
Reference in New Issue
Block a user