diff --git a/addons/addons.go b/addons/addons.go index 2be92fddf..81b7d2cfc 100644 --- a/addons/addons.go +++ b/addons/addons.go @@ -2,6 +2,7 @@ package addons import ( "fmt" + "k8s.io/client-go/transport" "strconv" "github.com/rancher/rke/k8s" @@ -27,7 +28,7 @@ func getAddonJob(addonName, nodeName, image string, isDelete bool) (string, erro return templates.CompileTemplateFromMap(templates.AddonJobTemplate, jobConfig) } -func AddonJobExists(addonJobName, kubeConfigPath string, k8sWrapTransport k8s.WrapTransport) (bool, error) { +func AddonJobExists(addonJobName, kubeConfigPath string, k8sWrapTransport transport.WrapperFunc) (bool, error) { k8sClient, err := k8s.NewClient(kubeConfigPath, k8sWrapTransport) if err != nil { return false, err diff --git a/authz/authz.go b/authz/authz.go index 253f76d4e..c4ff2fc53 100644 --- a/authz/authz.go +++ b/authz/authz.go @@ -6,9 +6,10 @@ import ( "github.com/rancher/rke/k8s" "github.com/rancher/rke/log" "github.com/rancher/rke/templates" + "k8s.io/client-go/transport" ) -func ApplyJobDeployerServiceAccount(ctx context.Context, kubeConfigPath string, k8sWrapTransport k8s.WrapTransport) error { +func ApplyJobDeployerServiceAccount(ctx context.Context, kubeConfigPath string, k8sWrapTransport transport.WrapperFunc) error { log.Infof(ctx, "[authz] Creating rke-job-deployer ServiceAccount") k8sClient, err := k8s.NewClient(kubeConfigPath, k8sWrapTransport) if err != nil { @@ -24,7 +25,7 @@ func ApplyJobDeployerServiceAccount(ctx context.Context, kubeConfigPath string, return nil } -func ApplySystemNodeClusterRoleBinding(ctx context.Context, kubeConfigPath string, k8sWrapTransport k8s.WrapTransport) error { +func ApplySystemNodeClusterRoleBinding(ctx context.Context, kubeConfigPath string, k8sWrapTransport transport.WrapperFunc) error { log.Infof(ctx, "[authz] Creating system:node ClusterRoleBinding") k8sClient, err := k8s.NewClient(kubeConfigPath, k8sWrapTransport) if err != nil { @@ -37,7 +38,7 @@ func ApplySystemNodeClusterRoleBinding(ctx context.Context, kubeConfigPath strin return nil } -func ApplyKubeAPIClusterRole(ctx context.Context, kubeConfigPath string, k8sWrapTransport k8s.WrapTransport) error { +func ApplyKubeAPIClusterRole(ctx context.Context, kubeConfigPath string, k8sWrapTransport transport.WrapperFunc) error { log.Infof(ctx, "[authz] Creating kube-apiserver proxy ClusterRole and ClusterRoleBinding") k8sClient, err := k8s.NewClient(kubeConfigPath, k8sWrapTransport) if err != nil { diff --git a/authz/psp.go b/authz/psp.go index a8cd06328..b27108836 100644 --- a/authz/psp.go +++ b/authz/psp.go @@ -3,12 +3,14 @@ package authz import ( "context" + "k8s.io/client-go/transport" + "github.com/rancher/rke/k8s" "github.com/rancher/rke/log" "github.com/rancher/rke/templates" ) -func ApplyDefaultPodSecurityPolicy(ctx context.Context, kubeConfigPath string, k8sWrapTransport k8s.WrapTransport) error { +func ApplyDefaultPodSecurityPolicy(ctx context.Context, kubeConfigPath string, k8sWrapTransport transport.WrapperFunc) error { log.Infof(ctx, "[authz] Applying default PodSecurityPolicy") k8sClient, err := k8s.NewClient(kubeConfigPath, k8sWrapTransport) if err != nil { @@ -21,7 +23,7 @@ func ApplyDefaultPodSecurityPolicy(ctx context.Context, kubeConfigPath string, k return nil } -func ApplyDefaultPodSecurityPolicyRole(ctx context.Context, kubeConfigPath, namespace string, k8sWrapTransport k8s.WrapTransport) error { +func ApplyDefaultPodSecurityPolicyRole(ctx context.Context, kubeConfigPath, namespace string, k8sWrapTransport transport.WrapperFunc) error { log.Infof(ctx, "[authz] Applying default PodSecurityPolicy Role and RoleBinding in %s", namespace) k8sClient, err := k8s.NewClient(kubeConfigPath, k8sWrapTransport) if err != nil { diff --git a/cluster/certificates.go b/cluster/certificates.go index c35f16027..cd4584898 100644 --- a/cluster/certificates.go +++ b/cluster/certificates.go @@ -10,9 +10,9 @@ import ( "github.com/rancher/rke/k8s" "github.com/rancher/rke/log" "github.com/rancher/rke/pki" + "github.com/rancher/rke/pki/cert" "github.com/rancher/rke/services" "github.com/sirupsen/logrus" - "k8s.io/client-go/util/cert" ) func SetUpAuthentication(ctx context.Context, kubeCluster, currentCluster *Cluster, fullState *FullState) error { diff --git a/cluster/cluster.go b/cluster/cluster.go index 1820e4b7e..e8527d119 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/rancher/rke/metadata" + "github.com/rancher/rke/pki/cert" "github.com/docker/docker/api/types" "github.com/rancher/rke/authz" @@ -16,6 +16,7 @@ import ( "github.com/rancher/rke/hosts" "github.com/rancher/rke/k8s" "github.com/rancher/rke/log" + "github.com/rancher/rke/metadata" "github.com/rancher/rke/pki" "github.com/rancher/rke/services" "github.com/rancher/rke/util" @@ -26,7 +27,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/cert" + "k8s.io/client-go/transport" ) type Cluster struct { @@ -46,7 +47,7 @@ type Cluster struct { EtcdReadyHosts []*hosts.Host ForceDeployCerts bool InactiveHosts []*hosts.Host - K8sWrapTransport k8s.WrapTransport + K8sWrapTransport transport.WrapperFunc KubeClient *kubernetes.Clientset KubernetesServiceIP net.IP LocalKubeConfigPath string @@ -264,7 +265,7 @@ func rebuildLocalAdminConfig(ctx context.Context, kubeCluster *Cluster) error { return nil } -func isLocalConfigWorking(ctx context.Context, localKubeConfigPath string, k8sWrapTransport k8s.WrapTransport) bool { +func isLocalConfigWorking(ctx context.Context, localKubeConfigPath string, k8sWrapTransport transport.WrapperFunc) bool { if _, err := GetK8sVersion(localKubeConfigPath, k8sWrapTransport); err != nil { log.Infof(ctx, "[reconcile] Local config is not valid, rebuilding admin config") return false diff --git a/cluster/reconcile.go b/cluster/reconcile.go index 4bf3b1cb7..27d0e2c02 100644 --- a/cluster/reconcile.go +++ b/cluster/reconcile.go @@ -10,11 +10,11 @@ import ( "github.com/rancher/rke/k8s" "github.com/rancher/rke/log" "github.com/rancher/rke/pki" + "github.com/rancher/rke/pki/cert" "github.com/rancher/rke/services" v3 "github.com/rancher/types/apis/management.cattle.io/v3" "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/util/cert" ) const ( diff --git a/cluster/state.go b/cluster/state.go index 861754e41..4dfcdc4dd 100644 --- a/cluster/state.go +++ b/cluster/state.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "k8s.io/client-go/transport" + "github.com/rancher/rke/hosts" "github.com/rancher/rke/k8s" "github.com/rancher/rke/log" @@ -130,7 +132,7 @@ func GetStateFromKubernetes(ctx context.Context, kubeCluster *Cluster) (*Cluster } } -func GetK8sVersion(localConfigPath string, k8sWrapTransport k8s.WrapTransport) (string, error) { +func GetK8sVersion(localConfigPath string, k8sWrapTransport transport.WrapperFunc) (string, error) { logrus.Debugf("[version] Using %s to connect to Kubernetes cluster..", localConfigPath) k8sClient, err := k8s.NewClient(localConfigPath, k8sWrapTransport) if err != nil { diff --git a/cmd/cert.go b/cmd/cert.go index 7348a249c..ccf71999d 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -8,10 +8,10 @@ import ( "github.com/rancher/rke/hosts" "github.com/rancher/rke/log" "github.com/rancher/rke/pki" + "github.com/rancher/rke/pki/cert" "github.com/rancher/rke/services" - "github.com/rancher/types/apis/management.cattle.io/v3" + v3 "github.com/rancher/types/apis/management.cattle.io/v3" "github.com/urfave/cli" - "k8s.io/client-go/util/cert" ) func CertificateCommand() cli.Command { diff --git a/cmd/up.go b/cmd/up.go index 6227720c8..37b213426 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -11,9 +11,9 @@ import ( "github.com/rancher/rke/hosts" "github.com/rancher/rke/log" "github.com/rancher/rke/pki" - "github.com/rancher/types/apis/management.cattle.io/v3" + "github.com/rancher/rke/pki/cert" + v3 "github.com/rancher/types/apis/management.cattle.io/v3" "github.com/urfave/cli" - "k8s.io/client-go/util/cert" ) const DINDWaitTime = 3 diff --git a/hosts/dialer.go b/hosts/dialer.go index def6254f5..1f730f3ee 100644 --- a/hosts/dialer.go +++ b/hosts/dialer.go @@ -2,12 +2,12 @@ package hosts import ( "fmt" + "k8s.io/client-go/transport" "net" "net/http" "strings" "time" - "github.com/rancher/rke/k8s" "github.com/rancher/types/apis/management.cattle.io/v3" "golang.org/x/crypto/ssh" ) @@ -33,10 +33,10 @@ type dialer struct { type DialersOptions struct { DockerDialerFactory DialerFactory LocalConnDialerFactory DialerFactory - K8sWrapTransport k8s.WrapTransport + K8sWrapTransport transport.WrapperFunc } -func GetDialerOptions(d, l DialerFactory, w k8s.WrapTransport) DialersOptions { +func GetDialerOptions(d, l DialerFactory, w transport.WrapperFunc) DialersOptions { return DialersOptions{ DockerDialerFactory: d, LocalConnDialerFactory: l, @@ -221,7 +221,7 @@ func (d *dialer) getBastionHostTunnelConn() (*ssh.Client, error) { return ssh.NewClient(newClientConn, channels, sshRequest), nil } -func BastionHostWrapTransport(bastionHost v3.BastionHost) (k8s.WrapTransport, error) { +func BastionHostWrapTransport(bastionHost v3.BastionHost) (transport.WrapperFunc, error) { bastionDialer := &dialer{ sshAddress: fmt.Sprintf("%s:%s", bastionHost.Address, bastionHost.Port), diff --git a/k8s/job.go b/k8s/job.go index 6e852b984..72cbc1dd1 100644 --- a/k8s/job.go +++ b/k8s/job.go @@ -2,14 +2,13 @@ package k8s import ( "fmt" - "github.com/sirupsen/logrus" "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/kubernetes" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/transport" ) type JobStatus struct { @@ -17,7 +16,7 @@ type JobStatus struct { Created bool } -func ApplyK8sSystemJob(jobYaml, kubeConfigPath string, k8sWrapTransport WrapTransport, timeout int, addonUpdated bool) error { +func ApplyK8sSystemJob(jobYaml, kubeConfigPath string, k8sWrapTransport transport.WrapperFunc, timeout int, addonUpdated bool) error { job := v1.Job{} if err := decodeYamlResource(&job, jobYaml); err != nil { return err diff --git a/k8s/k8s.go b/k8s/k8s.go index 139c3291b..7bf7a87cb 100644 --- a/k8s/k8s.go +++ b/k8s/k8s.go @@ -2,7 +2,7 @@ package k8s import ( "bytes" - "net/http" + "k8s.io/client-go/transport" "time" yamlutil "k8s.io/apimachinery/pkg/util/yaml" @@ -19,9 +19,7 @@ const ( type k8sCall func(*kubernetes.Clientset, interface{}) error -type WrapTransport func(rt http.RoundTripper) http.RoundTripper - -func NewClient(kubeConfigPath string, k8sWrapTransport WrapTransport) (*kubernetes.Clientset, error) { +func NewClient(kubeConfigPath string, k8sWrapTransport transport.WrapperFunc) (*kubernetes.Clientset, error) { // use the current admin kubeconfig config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath) if err != nil { diff --git a/pki/cert/cert.go b/pki/cert/cert.go new file mode 100644 index 000000000..7af1a5f30 --- /dev/null +++ b/pki/cert/cert.go @@ -0,0 +1,245 @@ +/* +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" + "errors" + "fmt" + "math" + "math/big" + "net" + "time" +) + +const ( + rsaKeySize = 2048 + duration365d = time.Hour * 24 * 365 +) + +// Config contains the basic fields required for creating a certificate +type Config struct { + CommonName string + Organization []string + AltNames AltNames + Usages []x509.ExtKeyUsage +} + +// 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 + } + if len(cfg.CommonName) == 0 { + return nil, errors.New("must specify a CommonName") + } + if len(cfg.Usages) == 0 { + return nil, errors.New("must specify at least one ExtKeyUsage") + } + + certTmpl := x509.Certificate{ + Subject: pkix.Name{ + CommonName: cfg.CommonName, + Organization: cfg.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: cfg.Usages, + } + 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: ECPrivateKeyBlockType, + Bytes: derBytes, + } + return pem.EncodeToMemory(privateKeyPemBlock), nil +} + +// GenerateSelfSignedCertKey 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 +func GenerateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS []string) ([]byte, []byte, error) { + caKey, err := rsa.GenerateKey(cryptorand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + caTemplate := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: fmt.Sprintf("%s-ca@%d", host, time.Now().Unix()), + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 365), + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + IsCA: true, + } + + caDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &caTemplate, &caTemplate, &caKey.PublicKey, caKey) + if err != nil { + return nil, nil, err + } + + caCertificate, err := x509.ParseCertificate(caDERBytes) + if err != nil { + return nil, nil, err + } + + priv, err := rsa.GenerateKey(cryptorand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + template := x509.Certificate{ + SerialNumber: big.NewInt(2), + 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, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: 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, caCertificate, &priv.PublicKey, caKey) + if err != nil { + return nil, nil, err + } + + // Generate cert, followed by ca + certBuffer := bytes.Buffer{} + if err := pem.Encode(&certBuffer, &pem.Block{Type: CertificateBlockType, Bytes: derBytes}); err != nil { + return nil, nil, err + } + if err := pem.Encode(&certBuffer, &pem.Block{Type: CertificateBlockType, Bytes: caDERBytes}); err != nil { + return nil, nil, err + } + + // Generate key + keyBuffer := bytes.Buffer{} + if err := pem.Encode(&keyBuffer, &pem.Block{Type: RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil { + return nil, nil, err + } + + return certBuffer.Bytes(), keyBuffer.Bytes(), nil +} + +// FormatBytesCert receives byte array certificate and formats in human-readable format +func FormatBytesCert(cert []byte) (string, error) { + block, _ := pem.Decode(cert) + c, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return "", fmt.Errorf("failed to parse certificate [%v]", err) + } + return FormatCert(c), nil +} + +// FormatCert receives certificate and formats in human-readable format +func FormatCert(c *x509.Certificate) string { + var ips []string + for _, ip := range c.IPAddresses { + ips = append(ips, ip.String()) + } + altNames := append(ips, c.DNSNames...) + res := fmt.Sprintf( + "Issuer: CN=%s | Subject: CN=%s | CA: %t\n", + c.Issuer.CommonName, c.Subject.CommonName, c.IsCA, + ) + res += fmt.Sprintf("Not before: %s Not After: %s", c.NotBefore, c.NotAfter) + if len(altNames) > 0 { + res += fmt.Sprintf("\nAlternate Names: %v", altNames) + } + return res +} diff --git a/pki/cert/csr.go b/pki/cert/csr.go new file mode 100644 index 000000000..39a6751f7 --- /dev/null +++ b/pki/cert/csr.go @@ -0,0 +1,75 @@ +/* +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 cert + +import ( + cryptorand "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "net" +) + +// MakeCSR generates a PEM-encoded CSR using the supplied private key, subject, and SANs. +// All key types that are implemented via crypto.Signer are supported (This includes *rsa.PrivateKey and *ecdsa.PrivateKey.) +func MakeCSR(privateKey interface{}, subject *pkix.Name, dnsSANs []string, ipSANs []net.IP) (csr []byte, err error) { + template := &x509.CertificateRequest{ + Subject: *subject, + DNSNames: dnsSANs, + IPAddresses: ipSANs, + } + + return MakeCSRFromTemplate(privateKey, template) +} + +// MakeCSRFromTemplate generates a PEM-encoded CSR using the supplied private +// key and certificate request as a template. All key types that are +// implemented via crypto.Signer are supported (This includes *rsa.PrivateKey +// and *ecdsa.PrivateKey.) +func MakeCSRFromTemplate(privateKey interface{}, template *x509.CertificateRequest) ([]byte, error) { + t := *template + t.SignatureAlgorithm = sigType(privateKey) + + csrDER, err := x509.CreateCertificateRequest(cryptorand.Reader, &t, privateKey) + if err != nil { + return nil, err + } + + csrPemBlock := &pem.Block{ + Type: CertificateRequestBlockType, + Bytes: csrDER, + } + + return pem.EncodeToMemory(csrPemBlock), nil +} + +func sigType(privateKey interface{}) x509.SignatureAlgorithm { + // Customize the signature for RSA keys, depending on the key size + if privateKey, ok := privateKey.(*rsa.PrivateKey); ok { + keySize := privateKey.N.BitLen() + switch { + case keySize >= 4096: + return x509.SHA512WithRSA + case keySize >= 3072: + return x509.SHA384WithRSA + default: + return x509.SHA256WithRSA + } + } + return x509.UnknownSignatureAlgorithm +} diff --git a/pki/cert/io.go b/pki/cert/io.go new file mode 100644 index 000000000..a41f8054a --- /dev/null +++ b/pki/cert/io.go @@ -0,0 +1,158 @@ +/* +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" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +// CanReadCertAndKey returns true if the certificate and key files already exists, +// otherwise returns false. If lost one of cert and key, returns error. +func CanReadCertAndKey(certPath, keyPath string) (bool, error) { + certReadable := canReadFile(certPath) + keyReadable := canReadFile(keyPath) + + if certReadable == false && keyReadable == false { + return false, nil + } + + if certReadable == false { + return false, fmt.Errorf("error reading %s, certificate and key must be supplied as a pair", certPath) + } + + if keyReadable == false { + return false, fmt.Errorf("error reading %s, certificate and key must be supplied as a pair", keyPath) + } + + return true, nil +} + +// 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 + } + return ioutil.WriteFile(certPath, data, os.FileMode(0644)) +} + +// 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 + } + return ioutil.WriteFile(keyPath, data, os.FileMode(0600)) +} + +// LoadOrGenerateKeyFile looks for a key in the file at the given path. If it +// can't find one, it will generate a new key and store it there. +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 := MakeEllipticPrivateKeyPEM() + if err != nil { + return nil, false, fmt.Errorf("error generating key: %v", err) + } + if err := WriteKey(keyPath, generatedData); err != nil { + return nil, false, fmt.Errorf("error writing key to %s: %v", keyPath, err) + } + return generatedData, true, 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) { + 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 +} + +// PrivateKeyFromFile returns the private key in rsa.PrivateKey or ecdsa.PrivateKey format from a given PEM-encoded file. +// Returns an error if the file could not be read or if the private key could not be parsed. +func PrivateKeyFromFile(file string) (interface{}, error) { + data, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + key, err := ParsePrivateKeyPEM(data) + if err != nil { + return nil, fmt.Errorf("error reading private key file %s: %v", file, err) + } + return key, nil +} + +// PublicKeysFromFile returns the public keys in rsa.PublicKey or ecdsa.PublicKey format from a given PEM-encoded file. +// Reads public keys from both public and private key files. +func PublicKeysFromFile(file string) ([]interface{}, error) { + data, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + keys, err := ParsePublicKeysPEM(data) + if err != nil { + return nil, fmt.Errorf("error reading public key file %s: %v", file, err) + } + return keys, nil +} diff --git a/pki/cert/pem.go b/pki/cert/pem.go new file mode 100644 index 000000000..b99e36651 --- /dev/null +++ b/pki/cert/pem.go @@ -0,0 +1,269 @@ +/* +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/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" +) + +const ( + // ECPrivateKeyBlockType is a possible value for pem.Block.Type. + ECPrivateKeyBlockType = "EC PRIVATE KEY" + // RSAPrivateKeyBlockType is a possible value for pem.Block.Type. + RSAPrivateKeyBlockType = "RSA PRIVATE KEY" + // PrivateKeyBlockType is a possible value for pem.Block.Type. + PrivateKeyBlockType = "PRIVATE KEY" + // PublicKeyBlockType is a possible value for pem.Block.Type. + PublicKeyBlockType = "PUBLIC KEY" + // CertificateBlockType is a possible value for pem.Block.Type. + CertificateBlockType = "CERTIFICATE" + // CertificateRequestBlockType is a possible value for pem.Block.Type. + CertificateRequestBlockType = "CERTIFICATE REQUEST" +) + +// EncodePublicKeyPEM returns PEM-encoded public data +func EncodePublicKeyPEM(key *rsa.PublicKey) ([]byte, error) { + der, err := x509.MarshalPKIXPublicKey(key) + if err != nil { + return []byte{}, err + } + block := pem.Block{ + Type: PublicKeyBlockType, + Bytes: der, + } + return pem.EncodeToMemory(&block), nil +} + +// EncodePrivateKeyPEM returns PEM-encoded private key data +func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte { + block := pem.Block{ + Type: RSAPrivateKeyBlockType, + Bytes: x509.MarshalPKCS1PrivateKey(key), + } + return pem.EncodeToMemory(&block) +} + +// EncodeCertPEM returns PEM-endcoded certificate data +func EncodeCertPEM(cert *x509.Certificate) []byte { + block := pem.Block{ + Type: CertificateBlockType, + 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", "RSA PRIVATE KEY", or "PRIVATE KEY" +func ParsePrivateKeyPEM(keyData []byte) (interface{}, error) { + var privateKeyPemBlock *pem.Block + for { + privateKeyPemBlock, keyData = pem.Decode(keyData) + if privateKeyPemBlock == nil { + break + } + + switch privateKeyPemBlock.Type { + case ECPrivateKeyBlockType: + // ECDSA Private Key in ASN.1 format + if key, err := x509.ParseECPrivateKey(privateKeyPemBlock.Bytes); err == nil { + return key, nil + } + case RSAPrivateKeyBlockType: + // RSA Private Key in PKCS#1 format + if key, err := x509.ParsePKCS1PrivateKey(privateKeyPemBlock.Bytes); err == nil { + return key, nil + } + case PrivateKeyBlockType: + // RSA or ECDSA Private Key in unencrypted PKCS#8 format + if key, err := x509.ParsePKCS8PrivateKey(privateKeyPemBlock.Bytes); err == nil { + return key, nil + } + } + + // tolerate non-key PEM blocks for compatibility with things like "EC PARAMETERS" blocks + // originally, only the first PEM block was parsed and expected to be a key block + } + + // we read all the PEM blocks and didn't recognize one + return nil, fmt.Errorf("data does not contain a valid RSA or ECDSA private key") +} + +// ParsePublicKeysPEM is a helper function for reading an array of rsa.PublicKey or ecdsa.PublicKey from a PEM-encoded byte array. +// Reads public keys from both public and private key files. +func ParsePublicKeysPEM(keyData []byte) ([]interface{}, error) { + var block *pem.Block + keys := []interface{}{} + for { + // read the next block + block, keyData = pem.Decode(keyData) + if block == nil { + break + } + + // test block against parsing functions + if privateKey, err := parseRSAPrivateKey(block.Bytes); err == nil { + keys = append(keys, &privateKey.PublicKey) + continue + } + if publicKey, err := parseRSAPublicKey(block.Bytes); err == nil { + keys = append(keys, publicKey) + continue + } + if privateKey, err := parseECPrivateKey(block.Bytes); err == nil { + keys = append(keys, &privateKey.PublicKey) + continue + } + if publicKey, err := parseECPublicKey(block.Bytes); err == nil { + keys = append(keys, publicKey) + continue + } + + // tolerate non-key PEM blocks for backwards compatibility + // originally, only the first PEM block was parsed and expected to be a key block + } + + if len(keys) == 0 { + return nil, fmt.Errorf("data does not contain any valid RSA or ECDSA public keys") + } + return keys, nil +} + +// 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 != CertificateBlockType || 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("data does not contain any valid RSA or ECDSA certificates") + } + return certs, nil +} + +// parseRSAPublicKey parses a single RSA public key from the provided data +func parseRSAPublicKey(data []byte) (*rsa.PublicKey, error) { + var err error + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKIXPublicKey(data); err != nil { + if cert, err := x509.ParseCertificate(data); err == nil { + parsedKey = cert.PublicKey + } else { + return nil, err + } + } + + // Test if parsed key is an RSA Public Key + var pubKey *rsa.PublicKey + var ok bool + if pubKey, ok = parsedKey.(*rsa.PublicKey); !ok { + return nil, fmt.Errorf("data doesn't contain valid RSA Public Key") + } + + return pubKey, nil +} + +// parseRSAPrivateKey parses a single RSA private key from the provided data +func parseRSAPrivateKey(data []byte) (*rsa.PrivateKey, error) { + var err error + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKCS1PrivateKey(data); err != nil { + if parsedKey, err = x509.ParsePKCS8PrivateKey(data); err != nil { + return nil, err + } + } + + // Test if parsed key is an RSA Private Key + var privKey *rsa.PrivateKey + var ok bool + if privKey, ok = parsedKey.(*rsa.PrivateKey); !ok { + return nil, fmt.Errorf("data doesn't contain valid RSA Private Key") + } + + return privKey, nil +} + +// parseECPublicKey parses a single ECDSA public key from the provided data +func parseECPublicKey(data []byte) (*ecdsa.PublicKey, error) { + var err error + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKIXPublicKey(data); err != nil { + if cert, err := x509.ParseCertificate(data); err == nil { + parsedKey = cert.PublicKey + } else { + return nil, err + } + } + + // Test if parsed key is an ECDSA Public Key + var pubKey *ecdsa.PublicKey + var ok bool + if pubKey, ok = parsedKey.(*ecdsa.PublicKey); !ok { + return nil, fmt.Errorf("data doesn't contain valid ECDSA Public Key") + } + + return pubKey, nil +} + +// parseECPrivateKey parses a single ECDSA private key from the provided data +func parseECPrivateKey(data []byte) (*ecdsa.PrivateKey, error) { + var err error + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParseECPrivateKey(data); err != nil { + return nil, err + } + + // Test if parsed key is an ECDSA Private Key + var privKey *ecdsa.PrivateKey + var ok bool + if privKey, ok = parsedKey.(*ecdsa.PrivateKey); !ok { + return nil, fmt.Errorf("data doesn't contain valid ECDSA Private Key") + } + + return privKey, nil +} diff --git a/pki/deploy.go b/pki/deploy.go index 6ee69aa2d..fbf95ed9d 100644 --- a/pki/deploy.go +++ b/pki/deploy.go @@ -14,9 +14,9 @@ import ( "github.com/rancher/rke/docker" "github.com/rancher/rke/hosts" "github.com/rancher/rke/log" + "github.com/rancher/rke/pki/cert" v3 "github.com/rancher/types/apis/management.cattle.io/v3" "github.com/sirupsen/logrus" - "k8s.io/client-go/util/cert" ) const ( diff --git a/pki/services.go b/pki/services.go index d0cb44853..e9e5f208f 100644 --- a/pki/services.go +++ b/pki/services.go @@ -9,8 +9,8 @@ import ( "github.com/rancher/rke/hosts" "github.com/rancher/rke/log" + "github.com/rancher/rke/pki/cert" v3 "github.com/rancher/types/apis/management.cattle.io/v3" - "k8s.io/client-go/util/cert" ) func GenerateKubeAPICertificate(ctx context.Context, certs map[string]CertificatePKI, rkeConfig v3.RancherKubernetesEngineConfig, configPath, configDir string, rotate bool) error { diff --git a/pki/util.go b/pki/util.go index 350d67700..e54a3dfe8 100644 --- a/pki/util.go +++ b/pki/util.go @@ -21,9 +21,9 @@ import ( "time" "github.com/rancher/rke/hosts" + "github.com/rancher/rke/pki/cert" v3 "github.com/rancher/types/apis/management.cattle.io/v3" "github.com/sirupsen/logrus" - "k8s.io/client-go/util/cert" ) var ( diff --git a/services/etcd.go b/services/etcd.go index 030923ad0..99b45ddcd 100644 --- a/services/etcd.go +++ b/services/etcd.go @@ -1,6 +1,7 @@ package services import ( + "context" "encoding/base64" "fmt" "path" @@ -8,8 +9,6 @@ import ( "strings" "time" - "context" - etcdclient "github.com/coreos/etcd/client" "github.com/docker/docker/api/types/container" "github.com/pkg/errors" @@ -17,11 +16,11 @@ import ( "github.com/rancher/rke/hosts" "github.com/rancher/rke/log" "github.com/rancher/rke/pki" + "github.com/rancher/rke/pki/cert" "github.com/rancher/rke/util" v3 "github.com/rancher/types/apis/management.cattle.io/v3" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" - "k8s.io/client-go/util/cert" ) const ( diff --git a/services/healthcheck.go b/services/healthcheck.go index ad6f0c61e..f9a27974f 100644 --- a/services/healthcheck.go +++ b/services/healthcheck.go @@ -14,8 +14,8 @@ import ( "github.com/rancher/rke/hosts" "github.com/rancher/rke/log" "github.com/rancher/rke/pki" + "github.com/rancher/rke/pki/cert" "github.com/sirupsen/logrus" - "k8s.io/client-go/util/cert" ) const (