From 386fae45928ea769c3a7ba92e84ea4c79035257b Mon Sep 17 00:00:00 2001 From: Ilya Dmitrichenko Date: Fri, 16 Sep 2016 16:52:29 +0100 Subject: [PATCH] Refactor utils that deal with certs - merge `pkg/util/{crypto,certificates}` - add funcs from `github.com/kubernetes-incubator/bootkube/pkg/tlsutil` - ensure naming of funcs is fairly consistent --- .../app/controllermanager.go | 4 +- cmd/kubelet/app/bootstrap.go | 9 +- cmd/kubelet/app/server.go | 6 +- .../controllermanager/controllermanager.go | 4 +- hack/.linted_packages | 3 +- .../certificates/validation/validation.go | 4 +- pkg/apiserver/authenticator/authn.go | 4 +- pkg/client/restclient/config.go | 4 +- pkg/controller/certificates/controller.go | 4 +- pkg/genericapiserver/genericapiserver.go | 8 +- pkg/kubectl/describe.go | 4 +- pkg/kubelet/util/csr/csr.go | 6 +- pkg/util/cert/cert.go | 190 ++++++++++++++++ pkg/util/{certificates => cert}/csr.go | 49 +--- pkg/util/{certificates => cert}/csr_test.go | 8 +- pkg/util/cert/io.go | 108 +++++++++ pkg/util/cert/pem.go | 107 +++++++++ .../testdata/dontUseThisKey.pem | 0 pkg/util/crypto/crypto.go | 212 ------------------ .../pkg/auth/authenticator/token/oidc/oidc.go | 4 +- test/test_owners.csv | 2 +- 21 files changed, 446 insertions(+), 294 deletions(-) create mode 100644 pkg/util/cert/cert.go rename pkg/util/{certificates => cert}/csr.go (59%) rename pkg/util/{certificates => cert}/csr_test.go (85%) create mode 100644 pkg/util/cert/io.go create mode 100644 pkg/util/cert/pem.go rename pkg/util/{certificates => cert}/testdata/dontUseThisKey.pem (100%) delete mode 100644 pkg/util/crypto/crypto.go diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index a1ba77ffac1..48f4a1c6994 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -74,8 +74,8 @@ import ( quotainstall "k8s.io/kubernetes/pkg/quota/install" "k8s.io/kubernetes/pkg/runtime/serializer" "k8s.io/kubernetes/pkg/serviceaccount" + certutil "k8s.io/kubernetes/pkg/util/cert" "k8s.io/kubernetes/pkg/util/configz" - "k8s.io/kubernetes/pkg/util/crypto" "k8s.io/kubernetes/pkg/util/wait" "github.com/golang/glog" @@ -482,7 +482,7 @@ func StartControllers(s *options.CMServer, kubeClient *client.Client, kubeconfig if err != nil { return fmt.Errorf("error reading root-ca-file at %s: %v", s.RootCAFile, err) } - if _, err := crypto.CertsFromPEM(rootCA); err != nil { + if _, err := certutil.ParseCertsPEM(rootCA); err != nil { return fmt.Errorf("error parsing root-ca-file at %s: %v", s.RootCAFile, err) } } else { diff --git a/cmd/kubelet/app/bootstrap.go b/cmd/kubelet/app/bootstrap.go index c4e52b7faff..8efc87b3eee 100644 --- a/cmd/kubelet/app/bootstrap.go +++ b/cmd/kubelet/app/bootstrap.go @@ -30,8 +30,7 @@ import ( "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" "k8s.io/kubernetes/pkg/kubelet/util/csr" - utilcertificates "k8s.io/kubernetes/pkg/util/certificates" - "k8s.io/kubernetes/pkg/util/crypto" + certutil "k8s.io/kubernetes/pkg/util/cert" ) const ( @@ -97,7 +96,7 @@ func bootstrapClientCert(kubeconfigPath string, bootstrapPath string, certDir st if err != nil { return err } - if err := crypto.WriteCertToPath(certPath, certData); err != nil { + if err := certutil.WriteCert(certPath, certData); err != nil { return err } defer func() { @@ -171,11 +170,11 @@ func loadOrGenerateKeyFile(keyPath string) (data []byte, wasGenerated bool, err return nil, false, fmt.Errorf("error loading key from %s: %v", keyPath, err) } - generatedData, err := utilcertificates.GeneratePrivateKey() + generatedData, err := certutil.MakeEllipticPrivateKeyPEM() if err != nil { return nil, false, fmt.Errorf("error generating key: %v", err) } - if err := crypto.WriteKeyToPath(keyPath, generatedData); err != nil { + if err := certutil.WriteKey(keyPath, generatedData); err != nil { return nil, false, fmt.Errorf("error writing key to %s: %v", keyPath, err) } return generatedData, true, nil diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index ff4a53c7366..3c757312d46 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -61,9 +61,9 @@ import ( "k8s.io/kubernetes/pkg/kubelet/server" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/runtime" + certutil "k8s.io/kubernetes/pkg/util/cert" utilconfig "k8s.io/kubernetes/pkg/util/config" "k8s.io/kubernetes/pkg/util/configz" - "k8s.io/kubernetes/pkg/util/crypto" "k8s.io/kubernetes/pkg/util/flock" kubeio "k8s.io/kubernetes/pkg/util/io" "k8s.io/kubernetes/pkg/util/mount" @@ -486,8 +486,8 @@ func InitializeTLS(kc *componentconfig.KubeletConfiguration) (*server.TLSOptions if kc.TLSCertFile == "" && kc.TLSPrivateKeyFile == "" { kc.TLSCertFile = path.Join(kc.CertDirectory, "kubelet.crt") kc.TLSPrivateKeyFile = path.Join(kc.CertDirectory, "kubelet.key") - if !crypto.FoundCertOrKey(kc.TLSCertFile, kc.TLSPrivateKeyFile) { - if err := crypto.GenerateSelfSignedCert(nodeutil.GetHostname(kc.HostnameOverride), kc.TLSCertFile, kc.TLSPrivateKeyFile, nil, nil); err != nil { + if !certutil.CanReadCertOrKey(kc.TLSCertFile, kc.TLSPrivateKeyFile) { + if err := certutil.GenerateSelfSignedCert(nodeutil.GetHostname(kc.HostnameOverride), kc.TLSCertFile, kc.TLSPrivateKeyFile, nil, nil); err != nil { return nil, fmt.Errorf("unable to generate self signed cert: %v", err) } glog.V(4).Infof("Using self-signed cert (%s, %s)", kc.TLSCertFile, kc.TLSPrivateKeyFile) diff --git a/contrib/mesos/pkg/controllermanager/controllermanager.go b/contrib/mesos/pkg/controllermanager/controllermanager.go index 2ff3f7955f5..cee71cb1759 100644 --- a/contrib/mesos/pkg/controllermanager/controllermanager.go +++ b/contrib/mesos/pkg/controllermanager/controllermanager.go @@ -59,7 +59,7 @@ import ( "k8s.io/kubernetes/pkg/healthz" quotainstall "k8s.io/kubernetes/pkg/quota/install" "k8s.io/kubernetes/pkg/serviceaccount" - "k8s.io/kubernetes/pkg/util/crypto" + certutil "k8s.io/kubernetes/pkg/util/cert" "k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/contrib/mesos/pkg/profile" @@ -315,7 +315,7 @@ func (s *CMServer) Run(_ []string) error { if err != nil { return fmt.Errorf("error reading root-ca-file at %s: %v", s.RootCAFile, err) } - if _, err := crypto.CertsFromPEM(rootCA); err != nil { + if _, err := certutil.ParseCertsPEM(rootCA); err != nil { return fmt.Errorf("error parsing root-ca-file at %s: %v", s.RootCAFile, err) } } else { diff --git a/hack/.linted_packages b/hack/.linted_packages index 58a5f2d0675..fa74857abb9 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -156,9 +156,8 @@ pkg/storage pkg/storage/etcd3 pkg/storage/storagebackend/factory pkg/util/async -pkg/util/certificates +pkg/util/cert pkg/util/codeinspector -pkg/util/crypto pkg/util/flock pkg/util/flushwriter pkg/util/goroutinemap diff --git a/pkg/apis/certificates/validation/validation.go b/pkg/apis/certificates/validation/validation.go index b6257384485..1c12750fa47 100644 --- a/pkg/apis/certificates/validation/validation.go +++ b/pkg/apis/certificates/validation/validation.go @@ -21,7 +21,7 @@ import ( apivalidation "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/apis/certificates" - certutil "k8s.io/kubernetes/pkg/util/certificates" + certutil "k8s.io/kubernetes/pkg/util/cert" "k8s.io/kubernetes/pkg/util/validation/field" ) @@ -29,7 +29,7 @@ import ( // PEM-encoded PKCS#10 certificate signing request. If this is invalid, we must // not accept the CSR for further processing. func validateCSR(obj *certificates.CertificateSigningRequest) error { - csr, err := certutil.ParseCertificateRequestObject(obj) + csr, err := certutil.ParseCSR(obj) if err != nil { return err } diff --git a/pkg/apiserver/authenticator/authn.go b/pkg/apiserver/authenticator/authn.go index 12523fadb90..35e250a6cd8 100644 --- a/pkg/apiserver/authenticator/authn.go +++ b/pkg/apiserver/authenticator/authn.go @@ -23,7 +23,7 @@ import ( "k8s.io/kubernetes/pkg/auth/authenticator" "k8s.io/kubernetes/pkg/auth/authenticator/bearertoken" "k8s.io/kubernetes/pkg/serviceaccount" - "k8s.io/kubernetes/pkg/util/crypto" + certutil "k8s.io/kubernetes/pkg/util/cert" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth" @@ -183,7 +183,7 @@ func newServiceAccountAuthenticator(keyfile string, lookup bool, serviceAccountG // newAuthenticatorFromClientCAFile returns an authenticator.Request or an error func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) { - roots, err := crypto.CertPoolFromFile(clientCAFile) + roots, err := certutil.NewPool(clientCAFile) if err != nil { return nil, err } diff --git a/pkg/client/restclient/config.go b/pkg/client/restclient/config.go index 82c1ac2cf96..ae1c8e7b77f 100644 --- a/pkg/client/restclient/config.go +++ b/pkg/client/restclient/config.go @@ -32,7 +32,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util/crypto" + certutil "k8s.io/kubernetes/pkg/util/cert" "k8s.io/kubernetes/pkg/util/flowcontrol" "k8s.io/kubernetes/pkg/version" ) @@ -261,7 +261,7 @@ func InClusterConfig() (*Config, error) { } tlsClientConfig := TLSClientConfig{} rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountRootCAKey - if _, err := crypto.CertPoolFromFile(rootCAFile); err != nil { + if _, err := certutil.NewPool(rootCAFile); err != nil { glog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err) } else { tlsClientConfig.CAFile = rootCAFile diff --git a/pkg/controller/certificates/controller.go b/pkg/controller/certificates/controller.go index 865d198090e..b9f563add1a 100644 --- a/pkg/controller/certificates/controller.go +++ b/pkg/controller/certificates/controller.go @@ -30,7 +30,7 @@ import ( "k8s.io/kubernetes/pkg/client/record" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/runtime" - utilcertificates "k8s.io/kubernetes/pkg/util/certificates" + certutil "k8s.io/kubernetes/pkg/util/cert" utilruntime "k8s.io/kubernetes/pkg/util/runtime" "k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/pkg/util/workqueue" @@ -241,7 +241,7 @@ func (cc *CertificateController) maybeAutoApproveCSR(csr *certificates.Certifica return csr, nil } - x509cr, err := utilcertificates.ParseCertificateRequestObject(csr) + x509cr, err := certutil.ParseCSR(csr) if err != nil { utilruntime.HandleError(fmt.Errorf("unable to parse csr %q: %v", csr.Name, err)) return csr, nil diff --git a/pkg/genericapiserver/genericapiserver.go b/pkg/genericapiserver/genericapiserver.go index 95d846f701b..a8bd04e6c6d 100644 --- a/pkg/genericapiserver/genericapiserver.go +++ b/pkg/genericapiserver/genericapiserver.go @@ -45,7 +45,7 @@ import ( "k8s.io/kubernetes/pkg/genericapiserver/options" "k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util/crypto" + certutil "k8s.io/kubernetes/pkg/util/cert" utilnet "k8s.io/kubernetes/pkg/util/net" utilruntime "k8s.io/kubernetes/pkg/util/runtime" "k8s.io/kubernetes/pkg/util/sets" @@ -291,7 +291,7 @@ func (s *GenericAPIServer) Run(options *options.ServerRunOptions) { } if len(options.ClientCAFile) > 0 { - clientCAs, err := crypto.CertPoolFromFile(options.ClientCAFile) + clientCAs, err := certutil.NewPool(options.ClientCAFile) if err != nil { glog.Fatalf("Unable to load client CA file: %v", err) } @@ -311,8 +311,8 @@ func (s *GenericAPIServer) Run(options *options.ServerRunOptions) { alternateDNS := []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes"} // It would be nice to set a fqdn subject alt name, but only the kubelets know, the apiserver is clueless // alternateDNS = append(alternateDNS, "kubernetes.default.svc.CLUSTER.DNS.NAME") - if !crypto.FoundCertOrKey(options.TLSCertFile, options.TLSPrivateKeyFile) { - if err := crypto.GenerateSelfSignedCert(s.ClusterIP.String(), options.TLSCertFile, options.TLSPrivateKeyFile, alternateIPs, alternateDNS); err != nil { + if !certutil.CanReadCertOrKey(options.TLSCertFile, options.TLSPrivateKeyFile) { + if err := certutil.GenerateSelfSignedCert(s.ClusterIP.String(), options.TLSCertFile, options.TLSPrivateKeyFile, alternateIPs, alternateDNS); err != nil { glog.Errorf("Unable to generate self signed cert: %v", err) } else { glog.Infof("Using self-signed cert (%s, %s)", options.TLSCertFile, options.TLSPrivateKeyFile) diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 051753f7f30..b12eb100ff0 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -48,7 +48,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/qos" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/types" - utilcertificates "k8s.io/kubernetes/pkg/util/certificates" + certutil "k8s.io/kubernetes/pkg/util/cert" "k8s.io/kubernetes/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/sets" @@ -1965,7 +1965,7 @@ func (p *CertificateSigningRequestDescriber) Describe(namespace, name string, de return "", err } - cr, err := utilcertificates.ParseCertificateRequestObject(csr) + cr, err := certutil.ParseCSR(csr) if err != nil { return "", fmt.Errorf("Error parsing CSR: %v", err) } diff --git a/pkg/kubelet/util/csr/csr.go b/pkg/kubelet/util/csr/csr.go index cba5496bf6c..113a5e43243 100644 --- a/pkg/kubelet/util/csr/csr.go +++ b/pkg/kubelet/util/csr/csr.go @@ -25,7 +25,7 @@ import ( "k8s.io/kubernetes/pkg/apis/certificates" unversionedcertificates "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/certificates/unversioned" "k8s.io/kubernetes/pkg/fields" - utilcertificates "k8s.io/kubernetes/pkg/util/certificates" + certutil "k8s.io/kubernetes/pkg/util/cert" "k8s.io/kubernetes/pkg/watch" ) @@ -39,11 +39,11 @@ func RequestNodeCertificate(client unversionedcertificates.CertificateSigningReq CommonName: fmt.Sprintf("system:node:%s", nodeName), } - privateKey, err := utilcertificates.ParsePrivateKey(privateKeyData) + privateKey, err := certutil.ParsePrivateKeyPEM(privateKeyData) if err != nil { return nil, fmt.Errorf("invalid private key for certificate request: %v", err) } - csr, err := utilcertificates.NewCertificateRequest(privateKey, subject, nil, nil) + csr, err := certutil.MakeCSR(privateKey, subject, nil, nil) if err != nil { return nil, fmt.Errorf("unable to generate certificate request: %v", err) } diff --git a/pkg/util/cert/cert.go b/pkg/util/cert/cert.go new file mode 100644 index 00000000000..8617744a732 --- /dev/null +++ b/pkg/util/cert/cert.go @@ -0,0 +1,190 @@ +/* +Copyright 2014 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 cert + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + cryptorand "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math" + "math/big" + "net" + "time" +) + +const ( + rsaKeySize = 2048 + duration365d = time.Hour * 24 * 365 +) + +// Config containes the basic fields required for creating a certificate +type Config struct { + CommonName string + Organization []string + AltNames AltNames +} + +// AltNames contains the domain names and IP addresses that will be added +// to the API Server's x509 certificate SubAltNames field. The values will +// be passed directly to the x509.Certificate object. +type AltNames struct { + DNSNames []string + IPs []net.IP +} + +// NewPrivateKey creates an RSA private key +func NewPrivateKey() (*rsa.PrivateKey, error) { + return rsa.GenerateKey(cryptorand.Reader, rsaKeySize) +} + +// NewSelfSignedCACert creates a CA certificate +func NewSelfSignedCACert(cfg Config, key *rsa.PrivateKey) (*x509.Certificate, error) { + now := time.Now() + tmpl := x509.Certificate{ + SerialNumber: new(big.Int).SetInt64(0), + Subject: pkix.Name{ + CommonName: cfg.CommonName, + Organization: cfg.Organization, + }, + NotBefore: now.UTC(), + NotAfter: now.Add(duration365d * 10).UTC(), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + IsCA: true, + } + + certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key) + if err != nil { + return nil, err + } + return x509.ParseCertificate(certDERBytes) +} + +// NewSignedCert creates a signed certificate using the given CA certificate and key +func NewSignedCert(cfg Config, key *rsa.PrivateKey, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, error) { + serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64)) + if err != nil { + return nil, err + } + + certTmpl := x509.Certificate{ + Subject: pkix.Name{ + CommonName: cfg.CommonName, + Organization: caCert.Subject.Organization, + }, + DNSNames: cfg.AltNames.DNSNames, + IPAddresses: cfg.AltNames.IPs, + SerialNumber: serial, + NotBefore: caCert.NotBefore, + NotAfter: time.Now().Add(duration365d).UTC(), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + } + certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &certTmpl, caCert, key.Public(), caKey) + if err != nil { + return nil, err + } + return x509.ParseCertificate(certDERBytes) +} + +// MakeEllipticPrivateKeyPEM creates an ECDSA private key +func MakeEllipticPrivateKeyPEM() ([]byte, error) { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) + if err != nil { + return nil, err + } + + derBytes, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + return nil, err + } + + privateKeyPemBlock := &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: derBytes, + } + return pem.EncodeToMemory(privateKeyPemBlock), nil +} + +// GenerateSelfSignedCert creates a self-signed certificate and key for the given host. +// Host may be an IP or a DNS name +// You may also specify additional subject alt names (either ip or dns names) for the certificate +// The certificate will be created with file mode 0644. The key will be created with file mode 0600. +// If the certificate or key files already exist, they will be overwritten. +// Any parent directories of the certPath or keyPath will be created as needed with file mode 0755. +func GenerateSelfSignedCert(host, certPath, keyPath string, alternateIPs []net.IP, alternateDNS []string) error { + priv, err := rsa.GenerateKey(cryptorand.Reader, 2048) + if err != nil { + return err + } + + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()), + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 365), + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + + if ip := net.ParseIP(host); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, host) + } + + template.IPAddresses = append(template.IPAddresses, alternateIPs...) + template.DNSNames = append(template.DNSNames, alternateDNS...) + + derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return err + } + + // Generate cert + certBuffer := bytes.Buffer{} + if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return err + } + + // Generate key + keyBuffer := bytes.Buffer{} + if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil { + return err + } + + if err := WriteCert(certPath, certBuffer.Bytes()); err != nil { + return err + } + + if err := WriteKey(keyPath, keyBuffer.Bytes()); err != nil { + return err + } + + return nil +} diff --git a/pkg/util/certificates/csr.go b/pkg/util/cert/csr.go similarity index 59% rename from pkg/util/certificates/csr.go rename to pkg/util/cert/csr.go index 6f5e78348f2..fa946cdd8ab 100644 --- a/pkg/util/certificates/csr.go +++ b/pkg/util/cert/csr.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package certificates +package cert import ( "crypto/ecdsa" @@ -31,8 +31,8 @@ import ( "k8s.io/kubernetes/pkg/apis/certificates" ) -// ParseCertificateRequestObject extracts the CSR from the API object and decodes it. -func ParseCertificateRequestObject(obj *certificates.CertificateSigningRequest) (*x509.CertificateRequest, error) { +// ParseCSR extracts the CSR from the API object and decodes it. +func ParseCSR(obj *certificates.CertificateSigningRequest) (*x509.CertificateRequest, error) { // extract PEM from request object pemBytes := obj.Spec.Request block, _ := pem.Decode(pemBytes) @@ -46,48 +46,9 @@ func ParseCertificateRequestObject(obj *certificates.CertificateSigningRequest) return csr, nil } -// GeneratePrivateKey returns PEM data containing a generated ECDSA private key -func GeneratePrivateKey() ([]byte, error) { - privateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) - if err != nil { - return nil, err - } - - derBytes, err := x509.MarshalECPrivateKey(privateKey) - if err != nil { - return nil, err - } - - privateKeyPemBlock := &pem.Block{ - Type: "EC PRIVATE KEY", - Bytes: derBytes, - } - return pem.EncodeToMemory(privateKeyPemBlock), nil -} - -// 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 { - case "EC PRIVATE KEY": - return x509.ParseECPrivateKey(privateKeyPemBlock.Bytes) - case "RSA PRIVATE KEY": - return x509.ParsePKCS1PrivateKey(privateKeyPemBlock.Bytes) - } - } -} - -// NewCertificateRequest generates a PEM-encoded CSR using the supplied private key, subject, and SANs. +// MakeCSR 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) { +func MakeCSR(privateKey interface{}, subject *pkix.Name, dnsSANs []string, ipSANs []net.IP) (csr []byte, err error) { var sigType x509.SignatureAlgorithm switch privateKey := privateKey.(type) { diff --git a/pkg/util/certificates/csr_test.go b/pkg/util/cert/csr_test.go similarity index 85% rename from pkg/util/certificates/csr_test.go rename to pkg/util/cert/csr_test.go index 3ca229fb649..96973f7a92e 100644 --- a/pkg/util/certificates/csr_test.go +++ b/pkg/util/cert/csr_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package certificates +package cert import ( "crypto/x509/pkix" @@ -23,7 +23,7 @@ import ( "testing" ) -func TestNewCertificateRequest(t *testing.T) { +func TestMakeCSR(t *testing.T) { keyFile := "testdata/dontUseThisKey.pem" subject := &pkix.Name{ CommonName: "kube-worker", @@ -35,11 +35,11 @@ func TestNewCertificateRequest(t *testing.T) { if err != nil { t.Fatal(err) } - key, err := ParsePrivateKey(keyData) + key, err := ParsePrivateKeyPEM(keyData) if err != nil { t.Fatal(err) } - _, err = NewCertificateRequest(key, subject, dnsSANs, ipSANs) + _, err = MakeCSR(key, subject, dnsSANs, ipSANs) if err != nil { t.Error(err) } diff --git a/pkg/util/cert/io.go b/pkg/util/cert/io.go new file mode 100644 index 00000000000..9a3e1622f37 --- /dev/null +++ b/pkg/util/cert/io.go @@ -0,0 +1,108 @@ +/* +Copyright 2014 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 cert + +import ( + "crypto/x509" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +// CanReadCertOrKey returns true if the certificate or key files already exists, +// otherwise returns false. +func CanReadCertOrKey(certPath, keyPath string) bool { + if canReadFile(certPath) || canReadFile(keyPath) { + return true + } + + return false +} + +// If the file represented by path exists and +// readable, returns true otherwise returns false. +func canReadFile(path string) bool { + f, err := os.Open(path) + if err != nil { + return false + } + + defer f.Close() + + return true +} + +// WriteCert writes the pem-encoded certificate data to certPath. +// The certificate file will be created with file mode 0644. +// If the certificate file already exists, it will be overwritten. +// The parent directory of the certPath will be created as needed with file mode 0755. +func WriteCert(certPath string, data []byte) error { + if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0755)); err != nil { + return err + } + if err := ioutil.WriteFile(certPath, data, os.FileMode(0644)); err != nil { + return err + } + return nil +} + +// WriteKey writes the pem-encoded key data to keyPath. +// The key file will be created with file mode 0600. +// 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. +func WriteKey(keyPath string, data []byte) error { + if err := os.MkdirAll(filepath.Dir(keyPath), os.FileMode(0755)); err != nil { + return err + } + if err := ioutil.WriteFile(keyPath, data, os.FileMode(0600)); err != nil { + return err + } + return nil +} + +// NewPool returns an x509.CertPool containing the certificates in the given PEM-encoded file. +// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates +func NewPool(filename string) (*x509.CertPool, error) { + certs, err := certsFromFile(filename) + if err != nil { + return nil, err + } + pool := x509.NewCertPool() + for _, cert := range certs { + pool.AddCert(cert) + } + return pool, nil +} + +// certsFromFile returns the x509.Certificates contained in the given PEM-encoded file. +// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates +func certsFromFile(file string) ([]*x509.Certificate, error) { + if len(file) == 0 { + return nil, errors.New("error reading certificates from an empty filename") + } + pemBlock, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + certs, err := ParseCertsPEM(pemBlock) + if err != nil { + return nil, fmt.Errorf("error reading %s: %s", file, err) + } + return certs, nil +} diff --git a/pkg/util/cert/pem.go b/pkg/util/cert/pem.go new file mode 100644 index 00000000000..59e602d2f1c --- /dev/null +++ b/pkg/util/cert/pem.go @@ -0,0 +1,107 @@ +/* +Copyright 2014 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 cert + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" +) + +// EncodePublicKeyPEM returns PEM-endcode public data +func EncodePublicKeyPEM(key *rsa.PublicKey) ([]byte, error) { + der, err := x509.MarshalPKIXPublicKey(key) + if err != nil { + return []byte{}, err + } + block := pem.Block{ + Type: "PUBLIC KEY", + Bytes: der, + } + return pem.EncodeToMemory(&block), nil +} + +// EncodePrivateKeyPEM returns PEM-encoded private key data +func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte { + block := pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(key), + } + return pem.EncodeToMemory(&block) +} + +// EncodeCertPEM returns PEM-endcoded certificate data +func EncodeCertPEM(cert *x509.Certificate) []byte { + block := pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + } + return pem.EncodeToMemory(&block) +} + +// ParsePrivateKeyPEM 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 ParsePrivateKeyPEM(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 { + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(privateKeyPemBlock.Bytes) + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(privateKeyPemBlock.Bytes) + } + } +} + +// ParseCertsPEM returns the x509.Certificates contained in the given PEM-encoded byte array +// Returns an error if a certificate could not be parsed, or if the data does not contain any certificates +func ParseCertsPEM(pemCerts []byte) ([]*x509.Certificate, error) { + ok := false + certs := []*x509.Certificate{} + for len(pemCerts) > 0 { + var block *pem.Block + block, pemCerts = pem.Decode(pemCerts) + if block == nil { + break + } + // Only use PEM "CERTIFICATE" blocks without extra headers + if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { + continue + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return certs, err + } + + certs = append(certs, cert) + ok = true + } + + if !ok { + return certs, errors.New("could not read any certificates") + } + return certs, nil +} diff --git a/pkg/util/certificates/testdata/dontUseThisKey.pem b/pkg/util/cert/testdata/dontUseThisKey.pem similarity index 100% rename from pkg/util/certificates/testdata/dontUseThisKey.pem rename to pkg/util/cert/testdata/dontUseThisKey.pem diff --git a/pkg/util/crypto/crypto.go b/pkg/util/crypto/crypto.go deleted file mode 100644 index 7e926450844..00000000000 --- a/pkg/util/crypto/crypto.go +++ /dev/null @@ -1,212 +0,0 @@ -/* -Copyright 2014 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 crypto - -import ( - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "errors" - "fmt" - "io/ioutil" - "math/big" - "net" - "os" - "path/filepath" - "time" -) - -// FoundCertOrKey returns true if the certificate or key files already exists, -// otherwise returns false. -func FoundCertOrKey(certPath, keyPath string) bool { - if canReadFile(certPath) || canReadFile(keyPath) { - return true - } - - return false -} - -// If the file represented by path exists and -// readable, returns true otherwise returns false. -func canReadFile(path string) bool { - f, err := os.Open(path) - if err != nil { - return false - } - - defer f.Close() - - return true -} - -// GenerateSelfSignedCert creates a self-signed certificate and key for the given host. -// Host may be an IP or a DNS name -// You may also specify additional subject alt names (either ip or dns names) for the certificate -// The certificate will be created with file mode 0644. The key will be created with file mode 0600. -// If the certificate or key files already exist, they will be overwritten. -// Any parent directories of the certPath or keyPath will be created as needed with file mode 0755. -func GenerateSelfSignedCert(host, certPath, keyPath string, alternateIPs []net.IP, alternateDNS []string) error { - priv, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return err - } - - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()), - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour * 24 * 365), - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - IsCA: true, - } - - if ip := net.ParseIP(host); ip != nil { - template.IPAddresses = append(template.IPAddresses, ip) - } else { - template.DNSNames = append(template.DNSNames, host) - } - - template.IPAddresses = append(template.IPAddresses, alternateIPs...) - template.DNSNames = append(template.DNSNames, alternateDNS...) - - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) - if err != nil { - return err - } - - // Generate cert - certBuffer := bytes.Buffer{} - if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - return err - } - - // Generate key - keyBuffer := bytes.Buffer{} - if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil { - return err - } - - // Write cert - if err := WriteCertToPath(certPath, certBuffer.Bytes()); err != nil { - return err - } - - // Write key - if err := WriteKeyToPath(keyPath, keyBuffer.Bytes()); err != nil { - return err - } - - return nil -} - -// WriteCertToPath writes the pem-encoded certificate data to certPath. -// The certificate file will be created with file mode 0644. -// If the certificate file already exists, it will be overwritten. -// The parent directory of the certPath will be created as needed with file mode 0755. -func WriteCertToPath(certPath string, data []byte) error { - if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0755)); err != nil { - return err - } - if err := ioutil.WriteFile(certPath, data, os.FileMode(0644)); err != nil { - return err - } - return nil -} - -// WriteKeyToPath writes the pem-encoded key data to keyPath. -// The key file will be created with file mode 0600. -// 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. -func WriteKeyToPath(keyPath string, data []byte) error { - if err := os.MkdirAll(filepath.Dir(keyPath), os.FileMode(0755)); err != nil { - return err - } - if err := ioutil.WriteFile(keyPath, data, os.FileMode(0600)); err != nil { - return err - } - return nil -} - -// CertPoolFromFile returns an x509.CertPool containing the certificates in the given PEM-encoded file. -// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates -func CertPoolFromFile(filename string) (*x509.CertPool, error) { - certs, err := certificatesFromFile(filename) - if err != nil { - return nil, err - } - pool := x509.NewCertPool() - for _, cert := range certs { - pool.AddCert(cert) - } - return pool, nil -} - -// certificatesFromFile returns the x509.Certificates contained in the given PEM-encoded file. -// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates -func certificatesFromFile(file string) ([]*x509.Certificate, error) { - if len(file) == 0 { - return nil, errors.New("error reading certificates from an empty filename") - } - pemBlock, err := ioutil.ReadFile(file) - if err != nil { - return nil, err - } - certs, err := CertsFromPEM(pemBlock) - if err != nil { - return nil, fmt.Errorf("error reading %s: %s", file, err) - } - return certs, nil -} - -// CertsFromPEM returns the x509.Certificates contained in the given PEM-encoded byte array -// Returns an error if a certificate could not be parsed, or if the data does not contain any certificates -func CertsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) { - ok := false - certs := []*x509.Certificate{} - for len(pemCerts) > 0 { - var block *pem.Block - block, pemCerts = pem.Decode(pemCerts) - if block == nil { - break - } - // Only use PEM "CERTIFICATE" blocks without extra headers - if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { - continue - } - - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return certs, err - } - - certs = append(certs, cert) - ok = true - } - - if !ok { - return certs, errors.New("could not read any certificates") - } - return certs, nil -} diff --git a/plugin/pkg/auth/authenticator/token/oidc/oidc.go b/plugin/pkg/auth/authenticator/token/oidc/oidc.go index 4a6728f86a2..5f04b6e87da 100644 --- a/plugin/pkg/auth/authenticator/token/oidc/oidc.go +++ b/plugin/pkg/auth/authenticator/token/oidc/oidc.go @@ -40,7 +40,7 @@ import ( "github.com/coreos/go-oidc/oidc" "github.com/golang/glog" "k8s.io/kubernetes/pkg/auth/user" - "k8s.io/kubernetes/pkg/util/crypto" + certutil "k8s.io/kubernetes/pkg/util/cert" "k8s.io/kubernetes/pkg/util/net" "k8s.io/kubernetes/pkg/util/runtime" ) @@ -112,7 +112,7 @@ func New(opts OIDCOptions) (*OIDCAuthenticator, error) { var roots *x509.CertPool if opts.CAFile != "" { - roots, err = crypto.CertPoolFromFile(opts.CAFile) + roots, err = certutil.NewPool(opts.CAFile) if err != nil { return nil, fmt.Errorf("Failed to read the CA file: %v", err) } diff --git a/test/test_owners.csv b/test/test_owners.csv index e69398dbe61..f5db2a5cbc8 100644 --- a/test/test_owners.csv +++ b/test/test_owners.csv @@ -826,7 +826,7 @@ k8s.io/kubernetes/pkg/util/async,spxtr,1 k8s.io/kubernetes/pkg/util/atomic,kargakis,1 k8s.io/kubernetes/pkg/util/bandwidth,thockin,1 k8s.io/kubernetes/pkg/util/cache,thockin,1 -k8s.io/kubernetes/pkg/util/certificates,karlkfi,1 +k8s.io/kubernetes/pkg/util/cert,karlkfi,1 k8s.io/kubernetes/pkg/util/clock,zmerlynn,1 k8s.io/kubernetes/pkg/util/config,girishkalele,1 k8s.io/kubernetes/pkg/util/configz,ixdy,1