mirror of https://github.com/kubernetes/kops.git
Merge pull request #9130 from johngmyers/pki-refactor
Refactor cert issuance code
This commit is contained in:
commit
d18e97140e
|
|
@ -35,7 +35,6 @@ go_library(
|
|||
"import.go",
|
||||
"import_cluster.go",
|
||||
"main.go",
|
||||
"pkix.go",
|
||||
"replace.go",
|
||||
"rollingupdate.go",
|
||||
"rollingupdatecluster.go",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/pkg/pki"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
|
|
@ -176,8 +177,8 @@ func describeKeypair(keyStore fi.CAStore, item *fi.KeystoreItem, w *bytes.Buffer
|
|||
}
|
||||
|
||||
if cert != nil {
|
||||
fmt.Fprintf(w, "Subject:\t%s\n", pkixNameToString(&cert.Certificate.Subject))
|
||||
fmt.Fprintf(w, "Issuer:\t%s\n", pkixNameToString(&cert.Certificate.Issuer))
|
||||
fmt.Fprintf(w, "Subject:\t%s\n", pki.PkixNameToString(&cert.Certificate.Subject))
|
||||
fmt.Fprintf(w, "Issuer:\t%s\n", pki.PkixNameToString(&cert.Certificate.Issuer))
|
||||
fmt.Fprintf(w, "AlternateNames:\t%s\n", strings.Join(alternateNames, ", "))
|
||||
fmt.Fprintf(w, "CA:\t%v\n", cert.IsCA)
|
||||
fmt.Fprintf(w, "NotAfter:\t%s\n", cert.Certificate.NotAfter)
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func pkixNameToString(name *pkix.Name) string {
|
||||
seq := name.ToRDNSequence()
|
||||
var s bytes.Buffer
|
||||
for _, rdnSet := range seq {
|
||||
for _, rdn := range rdnSet {
|
||||
if s.Len() != 0 {
|
||||
s.WriteString(",")
|
||||
}
|
||||
key := ""
|
||||
t := rdn.Type
|
||||
if len(t) == 4 && t[0] == 2 && t[1] == 5 && t[2] == 4 {
|
||||
switch t[3] {
|
||||
case 3:
|
||||
key = "cn"
|
||||
case 5:
|
||||
key = "serial"
|
||||
case 6:
|
||||
key = "c"
|
||||
case 7:
|
||||
key = "l"
|
||||
case 10:
|
||||
key = "o"
|
||||
case 11:
|
||||
key = "ou"
|
||||
}
|
||||
}
|
||||
if key == "" {
|
||||
key = t.String()
|
||||
}
|
||||
s.WriteString(fmt.Sprintf("%v=%v", key, rdn.Value))
|
||||
}
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
|
@ -17,12 +17,10 @@ limitations under the License.
|
|||
package model
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
"k8s.io/kops/pkg/pki"
|
||||
|
|
@ -78,26 +76,22 @@ func (b *EtcdManagerTLSBuilder) Build(ctx *fi.ModelBuilderContext) error {
|
|||
}
|
||||
|
||||
func (b *EtcdManagerTLSBuilder) buildKubeAPIServerKeypair() error {
|
||||
etcdClientsCACertificate, err := b.KeyStore.FindCert("etcd-clients-ca")
|
||||
if err != nil {
|
||||
return err
|
||||
req := &pki.IssueCertRequest{
|
||||
Signer: "etcd-clients-ca",
|
||||
Type: "client",
|
||||
Subject: pkix.Name{
|
||||
CommonName: "kube-apiserver",
|
||||
},
|
||||
MinValidDays: 455,
|
||||
}
|
||||
|
||||
etcdClientsCAPrivateKey, err := b.KeyStore.FindPrivateKey("etcd-clients-ca")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if etcdClientsCACertificate == nil {
|
||||
klog.Errorf("unable to find etcd-clients-ca certificate, won't build key for apiserver")
|
||||
return nil
|
||||
}
|
||||
if etcdClientsCAPrivateKey == nil {
|
||||
klog.Errorf("unable to find etcd-clients-ca private key, won't build key for apiserver")
|
||||
return nil
|
||||
}
|
||||
|
||||
dir := "/etc/kubernetes/pki/kube-apiserver"
|
||||
name := "etcd-client"
|
||||
humanName := dir + "/" + name
|
||||
klog.Infof("signing certificate for %q", humanName)
|
||||
cert, privateKey, etcdClientsCACertificate, err := pki.IssueCert(req, b.KeyStore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error signing certificate for %q: %v", humanName, err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("error creating directories %q: %v", dir, err)
|
||||
|
|
@ -110,39 +104,12 @@ func (b *EtcdManagerTLSBuilder) buildKubeAPIServerKeypair() error {
|
|||
}
|
||||
}
|
||||
|
||||
name := "etcd-client"
|
||||
|
||||
humanName := dir + "/" + name
|
||||
privateKey, err := pki.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create private key %q: %v", humanName, err)
|
||||
}
|
||||
|
||||
certTmpl := &x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "kube-apiserver",
|
||||
},
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365).UTC(),
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
klog.Infof("signing certificate for %q", humanName)
|
||||
cert, err := pki.SignNewCertificate(privateKey, certTmpl, etcdClientsCACertificate.Certificate, etcdClientsCAPrivateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error signing certificate for %q: %v", humanName, err)
|
||||
}
|
||||
|
||||
p := filepath.Join(dir, name)
|
||||
{
|
||||
if err := cert.WriteToFile(p+".crt", 0644); err != nil {
|
||||
return fmt.Errorf("error writing certificate key file %q: %v", p+".crt", err)
|
||||
}
|
||||
if err := cert.WriteToFile(p+".crt", 0644); err != nil {
|
||||
return fmt.Errorf("error writing certificate key file %q: %v", p+".crt", err)
|
||||
}
|
||||
|
||||
{
|
||||
if err := privateKey.WriteToFile(p+".key", 0600); err != nil {
|
||||
return fmt.Errorf("error writing private key file %q: %v", p+".key", err)
|
||||
}
|
||||
if err := privateKey.WriteToFile(p+".key", 0600); err != nil {
|
||||
return fmt.Errorf("error writing private key file %q: %v", p+".key", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
package model
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
|
@ -86,7 +85,17 @@ func (b *KubeAPIServerBuilder) addHealthcheckSidecarTasks(c *fi.ModelBuilderCont
|
|||
})
|
||||
}
|
||||
|
||||
clientKey, clientCert, err := b.buildClientKeypair(id)
|
||||
req := &pki.IssueCertRequest{
|
||||
Signer: fi.CertificateId_CA,
|
||||
Type: "client",
|
||||
Subject: pkix.Name{
|
||||
CommonName: id,
|
||||
},
|
||||
MinValidDays: 455,
|
||||
}
|
||||
|
||||
klog.Infof("signing certificate for %q", id)
|
||||
clientCert, clientKey, _, err := pki.IssueCert(req, b.KeyStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -136,68 +145,3 @@ func (b *KubeAPIServerBuilder) addHealthcheckSidecarTasks(c *fi.ModelBuilderCont
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *KubeAPIServerBuilder) buildClientKeypair(commonName string) (*pki.PrivateKey, *pki.Certificate, error) {
|
||||
signerID := fi.CertificateId_CA
|
||||
|
||||
var signerKey *pki.PrivateKey
|
||||
{
|
||||
k, err := b.KeyStore.FindPrivateKey(signerID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if k == nil {
|
||||
return nil, nil, fmt.Errorf("private key %q not found", signerID)
|
||||
}
|
||||
signerKey = k
|
||||
}
|
||||
|
||||
var signerCertificate *pki.Certificate
|
||||
{
|
||||
cert, err := b.KeyStore.FindCert(signerID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if cert == nil {
|
||||
return nil, nil, fmt.Errorf("certificate %q not found", signerID)
|
||||
}
|
||||
|
||||
signerCertificate = cert
|
||||
}
|
||||
|
||||
privateKey, err := pki.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
|
||||
Subject: pkix.Name{
|
||||
CommonName: commonName,
|
||||
},
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc5280#section-4.2.1.3
|
||||
//
|
||||
// Digital signature allows the certificate to be used to verify
|
||||
// digital signatures used during TLS negotiation.
|
||||
template.KeyUsage = template.KeyUsage | x509.KeyUsageDigitalSignature
|
||||
// KeyEncipherment allows the cert/key pair to be used to encrypt
|
||||
// keys, including the symmetric keys negotiated during TLS setup
|
||||
// and used for data transfer.
|
||||
template.KeyUsage = template.KeyUsage | x509.KeyUsageKeyEncipherment
|
||||
// ClientAuth allows the cert to be used by a TLS client to
|
||||
// authenticate itself to the TLS server.
|
||||
template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
|
||||
|
||||
klog.Infof("signing certificate for %q", commonName)
|
||||
cert, err := pki.SignNewCertificate(privateKey, template, signerCertificate.Certificate, signerKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error signing certificate for %q: %v", commonName, err)
|
||||
}
|
||||
|
||||
return privateKey, cert, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package model
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -74,10 +73,6 @@ func (k fakeKeyStore) FindKeypair(name string) (*pki.Certificate, *pki.PrivateKe
|
|||
panic("implement me")
|
||||
}
|
||||
|
||||
func (k fakeKeyStore) CreateKeypair(signer string, name string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (k fakeKeyStore) StoreKeypair(id string, cert *pki.Certificate, privateKey *pki.PrivateKey) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,13 +17,11 @@ limitations under the License.
|
|||
package model
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/kops/pkg/model/components"
|
||||
|
||||
|
|
@ -553,54 +551,17 @@ func (b *KubeletBuilder) buildMasterKubeletKubeconfig() (*nodetasks.File, error)
|
|||
return nil, fmt.Errorf("error getting NodeName: %v", err)
|
||||
}
|
||||
|
||||
caCert, err := b.KeyStore.FindCert(fi.CertificateId_CA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching CA certificate from keystore: %v", err)
|
||||
}
|
||||
if caCert == nil {
|
||||
return nil, fmt.Errorf("unable to find CA certificate %q in keystore", fi.CertificateId_CA)
|
||||
req := &pki.IssueCertRequest{
|
||||
Signer: fi.CertificateId_CA,
|
||||
Type: "client",
|
||||
Subject: pkix.Name{
|
||||
CommonName: fmt.Sprintf("system:node:%s", nodeName),
|
||||
Organization: []string{rbac.NodesGroup},
|
||||
},
|
||||
MinValidDays: 455,
|
||||
}
|
||||
|
||||
caKey, err := b.KeyStore.FindPrivateKey(fi.CertificateId_CA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching CA certificate from keystore: %v", err)
|
||||
}
|
||||
if caKey == nil {
|
||||
return nil, fmt.Errorf("unable to find CA key %q in keystore", fi.CertificateId_CA)
|
||||
}
|
||||
|
||||
privateKey, err := pki.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
}
|
||||
|
||||
template.Subject = pkix.Name{
|
||||
CommonName: fmt.Sprintf("system:node:%s", nodeName),
|
||||
Organization: []string{rbac.NodesGroup},
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc5280#section-4.2.1.3
|
||||
//
|
||||
// Digital signature allows the certificate to be used to verify
|
||||
// digital signatures used during TLS negotiation.
|
||||
template.KeyUsage = template.KeyUsage | x509.KeyUsageDigitalSignature
|
||||
// KeyEncipherment allows the cert/key pair to be used to encrypt
|
||||
// keys, including the symmetric keys negotiated during TLS setup
|
||||
// and used for data transfer.
|
||||
template.KeyUsage = template.KeyUsage | x509.KeyUsageKeyEncipherment
|
||||
// ClientAuth allows the cert to be used by a TLS client to
|
||||
// authenticate itself to the TLS server.
|
||||
template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
|
||||
|
||||
t := time.Now().UnixNano()
|
||||
template.SerialNumber = pki.BuildPKISerial(t)
|
||||
|
||||
certificate, err := pki.SignNewCertificate(privateKey, template, caCert.Certificate, caKey)
|
||||
certificate, privateKey, caCert, err := pki.IssueCert(req, b.KeyStore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error signing certificate for master kubelet: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,10 @@ limitations under the License.
|
|||
package networking
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"k8s.io/kops/nodeup/pkg/model"
|
||||
|
||||
|
|
@ -129,17 +127,22 @@ func (b *CiliumBuilder) buildCiliumEtcdSecrets(c *fi.ModelBuilderContext) error
|
|||
}
|
||||
}
|
||||
|
||||
etcdClientsCACertificate, err := b.KeyStore.FindCert("etcd-clients-ca-cilium")
|
||||
if err != nil {
|
||||
return err
|
||||
req := &pki.IssueCertRequest{
|
||||
Signer: "etcd-clients-ca-cilium",
|
||||
Type: "client",
|
||||
Subject: pkix.Name{
|
||||
CommonName: "cilium",
|
||||
},
|
||||
MinValidDays: 455,
|
||||
}
|
||||
|
||||
etcdClientsCAPrivateKey, err := b.KeyStore.FindPrivateKey("etcd-clients-ca-cilium")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir := "/etc/kubernetes/pki/cilium"
|
||||
name := "etcd-client-cilium"
|
||||
humanName := dir + "/" + name
|
||||
klog.Infof("signing certificate for %q", humanName)
|
||||
cert, privateKey, etcdClientsCACertificate, err := pki.IssueCert(req, b.KeyStore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error signing certificate for %q: %v", humanName, err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("error creating directories %q: %v", dir, err)
|
||||
|
|
@ -152,39 +155,12 @@ func (b *CiliumBuilder) buildCiliumEtcdSecrets(c *fi.ModelBuilderContext) error
|
|||
}
|
||||
}
|
||||
|
||||
name := "etcd-client"
|
||||
|
||||
humanName := dir + "/" + name
|
||||
privateKey, err := pki.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create private key %q: %v", humanName, err)
|
||||
}
|
||||
|
||||
certTmpl := &x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "cilium",
|
||||
},
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365).UTC(),
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
klog.Infof("signing certificate for %q", humanName)
|
||||
cert, err := pki.SignNewCertificate(privateKey, certTmpl, etcdClientsCACertificate.Certificate, etcdClientsCAPrivateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error signing certificate for %q: %v", humanName, err)
|
||||
}
|
||||
|
||||
p := filepath.Join(dir, name)
|
||||
{
|
||||
if err := cert.WriteToFile(p+".crt", 0644); err != nil {
|
||||
return fmt.Errorf("error writing certificate key file %q: %v", p+".crt", err)
|
||||
}
|
||||
if err := cert.WriteToFile(p+".crt", 0644); err != nil {
|
||||
return fmt.Errorf("error writing certificate key file %q: %v", p+".crt", err)
|
||||
}
|
||||
|
||||
{
|
||||
if err := privateKey.WriteToFile(p+".key", 0600); err != nil {
|
||||
return fmt.Errorf("error writing private key file %q: %v", p+".key", err)
|
||||
}
|
||||
if err := privateKey.WriteToFile(p+".key", 0600); err != nil {
|
||||
return fmt.Errorf("error writing private key file %q: %v", p+".key", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"crypto/x509"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/pkg/pki"
|
||||
|
|
@ -50,8 +48,6 @@ func (f fakeStatusStore) GetApiIngressStatus(cluster *kops.Cluster) ([]kops.ApiI
|
|||
type fakeKeyStore struct {
|
||||
FindKeypairFn func(name string) (*pki.Certificate, *pki.PrivateKey, bool, error)
|
||||
|
||||
CreateKeypairFn func(signer string, name string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error)
|
||||
|
||||
// StoreKeypair writes the keypair to the store
|
||||
StoreKeypairFn func(id string, cert *pki.Certificate, privateKey *pki.PrivateKey) error
|
||||
|
||||
|
|
@ -63,10 +59,6 @@ func (f fakeKeyStore) FindKeypair(name string) (*pki.Certificate, *pki.PrivateKe
|
|||
return f.FindKeypairFn(name)
|
||||
}
|
||||
|
||||
func (f fakeKeyStore) CreateKeypair(signer string, name string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
|
||||
return f.CreateKeypairFn(signer, name, template, privateKey)
|
||||
}
|
||||
|
||||
func (f fakeKeyStore) StoreKeypair(id string, cert *pki.Certificate, privateKey *pki.PrivateKey) error {
|
||||
return f.StoreKeypairFn(id, cert, privateKey)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cert_utils.go",
|
||||
"certificate.go",
|
||||
"csr.go",
|
||||
"issue.go",
|
||||
"privatekey.go",
|
||||
"sshkey.go",
|
||||
],
|
||||
|
|
@ -20,6 +22,7 @@ go_test(
|
|||
name = "go_default_test",
|
||||
srcs = [
|
||||
"certificate_test.go",
|
||||
"issue_test.go",
|
||||
"privatekey_test.go",
|
||||
"sshkey_test.go",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -14,19 +14,20 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fitasks
|
||||
package pki
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
func pkixNameToString(name *pkix.Name) string {
|
||||
func PkixNameToString(name *pkix.Name) string {
|
||||
seq := name.ToRDNSequence()
|
||||
var s bytes.Buffer
|
||||
for _, rdnSet := range seq {
|
||||
|
|
@ -61,32 +62,6 @@ func pkixNameToString(name *pkix.Name) string {
|
|||
return s.String()
|
||||
}
|
||||
|
||||
func parsePkixName(s string) (*pkix.Name, error) {
|
||||
name := new(pkix.Name)
|
||||
|
||||
tokens := strings.Split(s, ",")
|
||||
for _, token := range tokens {
|
||||
token = strings.TrimSpace(token)
|
||||
kv := strings.SplitN(token, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("unrecognized token (expected k=v): %q", token)
|
||||
}
|
||||
k := strings.ToLower(kv[0])
|
||||
v := kv[1]
|
||||
|
||||
switch k {
|
||||
case "cn":
|
||||
name.CommonName = v
|
||||
case "o":
|
||||
name.Organization = append(name.Organization, v)
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized key %q in token %q", k, token)
|
||||
}
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
var keyUsageStrings = map[x509.KeyUsage]string{
|
||||
x509.KeyUsageDigitalSignature: "KeyUsageDigitalSignature",
|
||||
x509.KeyUsageContentCommitment: "KeyUsageContentCommitment",
|
||||
|
|
@ -153,3 +128,29 @@ func parseExtKeyUsage(s string) (x509.ExtKeyUsage, bool) {
|
|||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// BuildTypeDescription extracts the type based on the certificate extensions
|
||||
func BuildTypeDescription(cert *x509.Certificate) string {
|
||||
var options []string
|
||||
|
||||
if cert.IsCA {
|
||||
options = append(options, "CA")
|
||||
}
|
||||
|
||||
options = append(options, keyUsageToString(cert.KeyUsage)...)
|
||||
|
||||
for _, extKeyUsage := range cert.ExtKeyUsage {
|
||||
options = append(options, extKeyUsageToString(extKeyUsage))
|
||||
}
|
||||
|
||||
sort.Strings(options)
|
||||
s := strings.Join(options, ",")
|
||||
|
||||
for k, v := range wellKnownCertificateTypes {
|
||||
if v == s {
|
||||
s = k
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
|
@ -97,8 +97,8 @@ func TestGenerateCertificate(t *testing.T) {
|
|||
CommonName: tc.name,
|
||||
}
|
||||
|
||||
cert, err := SignNewCertificate(key, &tc.template, tc.signer, tc.signerKey)
|
||||
require.NoError(t, err, "SignNewCertificate")
|
||||
cert, err := signNewCertificate(key, &tc.template, tc.signer, tc.signerKey)
|
||||
require.NoError(t, err, "signNewCertificate")
|
||||
|
||||
{
|
||||
subject := cert.Certificate.Subject
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func BuildPKISerial(timestamp int64) *big.Int {
|
|||
return serial
|
||||
}
|
||||
|
||||
func SignNewCertificate(privateKey *PrivateKey, template *x509.Certificate, signer *x509.Certificate, signerPrivateKey *PrivateKey) (*Certificate, error) {
|
||||
func signNewCertificate(privateKey *PrivateKey, template *x509.Certificate, signer *x509.Certificate, signerPrivateKey *PrivateKey) (*Certificate, error) {
|
||||
if template.PublicKey == nil {
|
||||
rsaPrivateKey, ok := privateKey.Key.(*rsa.PrivateKey)
|
||||
if ok {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
Copyright 2020 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 pki
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"math/big"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
var wellKnownCertificateTypes = map[string]string{
|
||||
"ca": "CA,KeyUsageCRLSign,KeyUsageCertSign",
|
||||
"client": "ExtKeyUsageClientAuth,KeyUsageDigitalSignature",
|
||||
"clientServer": "ExtKeyUsageClientAuth,ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
|
||||
"server": "ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
|
||||
}
|
||||
|
||||
type IssueCertRequest struct {
|
||||
// Signer is the keypair to use to sign. Ignored if Type is "CA", in which case the cert will be self-signed.
|
||||
Signer string
|
||||
// Type is the type of certificate i.e. CA, server, client etc.
|
||||
Type string
|
||||
// Subject is the certificate subject.
|
||||
Subject pkix.Name
|
||||
// AlternateNames is a list of alternative names for this certificate.
|
||||
AlternateNames []string
|
||||
|
||||
// PrivateKey is the private key for this certificate. If nil, a new private key will be generated.
|
||||
PrivateKey *PrivateKey
|
||||
// MinValidDays is the lower bound on the certificate validity, in days. If specified, up to 30 days
|
||||
// will be added so that certificate generated at the same time on different hosts will be unlikely to
|
||||
// expire at the same time. The default is 10 years (without the up to 30 day skew).
|
||||
MinValidDays int
|
||||
|
||||
// Serial is the certificate serial number. If nil, a random number will be generated.
|
||||
Serial *big.Int
|
||||
}
|
||||
|
||||
type Keystore interface {
|
||||
// FindKeypair finds a cert & private key, returning nil where either is not found
|
||||
// (if the certificate is found but not keypair, that is not an error: only the cert will be returned).
|
||||
// This func returns a cert, private key and a bool. The bool value is whether the keypair is stored
|
||||
// in a legacy format. This bool is used by a keypair
|
||||
// task to convert a Legacy Keypair to the new Keypair API format.
|
||||
FindKeypair(name string) (*Certificate, *PrivateKey, bool, error)
|
||||
}
|
||||
|
||||
// IssueCert issues a certificate, either a self-signed CA or from a CA in a keystore.
|
||||
func IssueCert(request *IssueCertRequest, keystore Keystore) (issuedCertificate *Certificate, issuedKey *PrivateKey, caCertificate *Certificate, err error) {
|
||||
certificateType := request.Type
|
||||
if expanded, found := wellKnownCertificateTypes[certificateType]; found {
|
||||
certificateType = expanded
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
SerialNumber: request.Serial,
|
||||
}
|
||||
|
||||
tokens := strings.Split(certificateType, ",")
|
||||
for _, t := range tokens {
|
||||
if strings.HasPrefix(t, "KeyUsage") {
|
||||
ku, found := parseKeyUsage(t)
|
||||
if !found {
|
||||
return nil, nil, nil, fmt.Errorf("unrecognized certificate option: %v", t)
|
||||
}
|
||||
template.KeyUsage |= ku
|
||||
} else if strings.HasPrefix(t, "ExtKeyUsage") {
|
||||
ku, found := parseExtKeyUsage(t)
|
||||
if !found {
|
||||
return nil, nil, nil, fmt.Errorf("unrecognized certificate option: %v", t)
|
||||
}
|
||||
template.ExtKeyUsage = append(template.ExtKeyUsage, ku)
|
||||
} else if t == "CA" {
|
||||
template.IsCA = true
|
||||
} else {
|
||||
return nil, nil, nil, fmt.Errorf("unrecognized certificate option: %q", t)
|
||||
}
|
||||
}
|
||||
|
||||
template.Subject = request.Subject
|
||||
|
||||
var alternateNames []string
|
||||
alternateNames = append(alternateNames, request.AlternateNames...)
|
||||
|
||||
for _, san := range alternateNames {
|
||||
san = strings.TrimSpace(san)
|
||||
if san == "" {
|
||||
continue
|
||||
}
|
||||
if ip := net.ParseIP(san); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, san)
|
||||
}
|
||||
}
|
||||
|
||||
var caPrivateKey *PrivateKey
|
||||
var signer *x509.Certificate
|
||||
if !template.IsCA {
|
||||
var err error
|
||||
caCertificate, caPrivateKey, _, err = keystore.FindKeypair(request.Signer)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if caPrivateKey == nil {
|
||||
return nil, nil, nil, fmt.Errorf("ca key for %q was not found; cannot issue certificates", request.Signer)
|
||||
}
|
||||
if caCertificate == nil {
|
||||
return nil, nil, nil, fmt.Errorf("ca certificate for %q was not found; cannot issue certificates", request.Signer)
|
||||
}
|
||||
signer = caCertificate.Certificate
|
||||
}
|
||||
|
||||
privateKey := request.PrivateKey
|
||||
if privateKey == nil {
|
||||
var err error
|
||||
privateKey, err = GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Skew the certificate lifetime by up to 30 days based on information about the generating node.
|
||||
// This is so that different nodes created at the same time have the certificates they generated
|
||||
// expire at different times, but all certificates on a given node expire around the same time.
|
||||
if request.MinValidDays != 0 {
|
||||
hash := fnv.New32()
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
sort.Slice(addrs, func(i, j int) bool {
|
||||
return addrs[i].String() < addrs[j].String()
|
||||
})
|
||||
if err == nil {
|
||||
for _, addr := range addrs {
|
||||
_, _ = hash.Write([]byte(addr.String()))
|
||||
}
|
||||
} else {
|
||||
klog.Warningf("cannot skew certificate lifetime: failed to get interface addresses: %v", err)
|
||||
}
|
||||
template.NotAfter = time.Now().Add(time.Hour * 24 * time.Duration(request.MinValidDays)).Add(time.Hour * time.Duration(hash.Sum32()%(30*24))).UTC()
|
||||
}
|
||||
|
||||
certificate, err := signNewCertificate(privateKey, template, signer, caPrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
if signer == nil {
|
||||
caCertificate = certificate
|
||||
}
|
||||
|
||||
return certificate, privateKey, caCertificate, err
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
Copyright 2020 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 pki
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockKeystore struct {
|
||||
t *testing.T
|
||||
signer string
|
||||
cert *Certificate
|
||||
key *PrivateKey
|
||||
|
||||
invoked bool
|
||||
}
|
||||
|
||||
func (m *mockKeystore) FindKeypair(name string) (*Certificate, *PrivateKey, bool, error) {
|
||||
assert.False(m.t, m.invoked, "invoked already")
|
||||
m.invoked = true
|
||||
assert.Equal(m.t, m.signer, name, "name argument")
|
||||
return m.cert, m.key, false, nil
|
||||
}
|
||||
|
||||
func TestIssueCert(t *testing.T) {
|
||||
caCertificate, err := ParsePEMCertificate([]byte("-----BEGIN CERTIFICATE-----\nMIIBRjCB8aADAgECAhAzhRMOcwfggPtgZNIOFU19MA0GCSqGSIb3DQEBCwUAMBIx\nEDAOBgNVBAMTB1Rlc3QgQ0EwHhcNMjAwNTE1MDIzNjI0WhcNMzAwNTE1MDIzNjI0\nWjASMRAwDgYDVQQDEwdUZXN0IENBMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM/S\ncagGaiDA3jJWBXUr8rM19TWLA65jK/iA05FCsmQbyvETs5gbJdBfnhQp8wkKFlkt\nKxZ34k3wQUzoB1lv8/kCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADQQCDOxvs58AVAWgWLtD3Obvy7XXsKx6d\nMzg9epbiQchLE4G/jlbgVu7vwh8l5XFNfQooG6stCU7pmLFXkXzkJQxr\n-----END CERTIFICATE-----\n"))
|
||||
require.NoError(t, err)
|
||||
caPrivateKey, err := ParsePEMPrivateKey([]byte("-----BEGIN RSA PRIVATE KEY-----\nMIIBPAIBAAJBAM/ScagGaiDA3jJWBXUr8rM19TWLA65jK/iA05FCsmQbyvETs5gb\nJdBfnhQp8wkKFlktKxZ34k3wQUzoB1lv8/kCAwEAAQJBAJzXQZeBX87gP9DVQsEv\nLbc6XZjPFTQi/ChLcWALaf5J7drFJHUcWbKIHzOmM3fm3lQlb/1IcwOBU5cTY0e9\nBVECIQD73kxOWWAIzKqMOvFZ9s79Et7G1HUMnVAVKJ1NS1uvYwIhANM7LULdi0YD\nbcHvDl3+Msj4cPH7CXAJFyPWaQZPlXPzAiEAhDg6jpbUl0n57guzT6sFFk2lrXMy\nzyB2PeVITp9UzkkCIEpcF7flQ+U2ycmuvVELbpdfFmupIw5ktNex4DEPjR5PAiEA\n68vR1L1Kaja/GzU76qAQaYA/V1Ag4sPmOQdEaVZKu78=\n-----END RSA PRIVATE KEY-----\n"))
|
||||
require.NoError(t, err)
|
||||
privateKey, err := ParsePEMPrivateKey([]byte("-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBANgL5cR2cLOB7oZZTiuiUmMwQRBaia8yLULt+XtBtDHf0lPOrn78\nvLPh7P7zRBgHczbTddcsg68g9vAfb9TC5M8CAwEAAQJAJytxCv+WS1VhU4ZZf9u8\nKDOVeEuR7uuf/SR8OPaenvPqONpYbZSVjnWnRBRHvg3HaHchQqH32UljZUojs9z4\nEQIhAO/yoqCFckfqswOGwWyYX1oNOtU8w9ulXlZqAtZieavVAiEA5n/tKHoZyx3U\nbZcks/wns1WqhAoSmDJpMyVXOVrUlBMCIDGnalQBiYasYOMn7bsFRSYjertJ2dYI\nQJ9tTK0Er90JAiAmpVQx8SbZ80pmhWzV8HUHkFligf3UHr+cn6ocJ6p0mQIgB728\npdvrS5zRPoUN8BHfWOZcPrElKTuJjP2kH6eNPvI=\n-----END RSA PRIVATE KEY-----"))
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
req IssueCertRequest
|
||||
expectedKeyUsage x509.KeyUsage
|
||||
expectedExtKeyUsage []x509.ExtKeyUsage
|
||||
expectedSubject pkix.Name
|
||||
expectedDNSNames []string
|
||||
expectedIPAddresses []net.IP
|
||||
}{
|
||||
{
|
||||
name: "ca",
|
||||
req: IssueCertRequest{
|
||||
Type: "ca",
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Test CA",
|
||||
},
|
||||
},
|
||||
expectedKeyUsage: x509.KeyUsageCRLSign | x509.KeyUsageCertSign,
|
||||
expectedSubject: pkix.Name{CommonName: "Test CA"},
|
||||
},
|
||||
{
|
||||
name: "client",
|
||||
req: IssueCertRequest{
|
||||
Type: "client",
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Test client",
|
||||
Organization: []string{"system:masters"},
|
||||
},
|
||||
Serial: BuildPKISerial(123456),
|
||||
},
|
||||
expectedKeyUsage: x509.KeyUsageDigitalSignature,
|
||||
expectedExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
expectedSubject: pkix.Name{CommonName: "Test client", Organization: []string{"system:masters"}},
|
||||
},
|
||||
{
|
||||
name: "clientOneYear",
|
||||
req: IssueCertRequest{
|
||||
Type: "client",
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Test client",
|
||||
},
|
||||
MinValidDays: 365,
|
||||
},
|
||||
expectedKeyUsage: x509.KeyUsageDigitalSignature,
|
||||
expectedExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
expectedSubject: pkix.Name{CommonName: "Test client"},
|
||||
},
|
||||
{
|
||||
req: IssueCertRequest{
|
||||
Type: "clientServer",
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Test client/server",
|
||||
},
|
||||
AlternateNames: []string{"*.internal.test.cluster.local", "localhost", "127.0.0.1"},
|
||||
PrivateKey: privateKey,
|
||||
},
|
||||
expectedKeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
expectedExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||
expectedSubject: pkix.Name{CommonName: "Test client/server"},
|
||||
expectedDNSNames: []string{"*.internal.test.cluster.local", "localhost"},
|
||||
expectedIPAddresses: []net.IP{net.ParseIP("127.0.0.1").To4()},
|
||||
},
|
||||
{
|
||||
name: "server",
|
||||
req: IssueCertRequest{
|
||||
Type: "server",
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Test server",
|
||||
},
|
||||
AlternateNames: []string{"*.internal.test.cluster.local", "localhost", "127.0.0.1"},
|
||||
PrivateKey: privateKey,
|
||||
},
|
||||
expectedKeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
expectedExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
expectedSubject: pkix.Name{CommonName: "Test server"},
|
||||
expectedDNSNames: []string{"*.internal.test.cluster.local", "localhost"},
|
||||
expectedIPAddresses: []net.IP{net.ParseIP("127.0.0.1").To4()},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var minExpectedValidity int64
|
||||
if tc.req.MinValidDays == 0 {
|
||||
minExpectedValidity = time.Now().Add(time.Hour * 10 * 365 * 24).Unix()
|
||||
} else {
|
||||
minExpectedValidity = time.Now().Add(time.Hour * 24 * time.Duration(tc.req.MinValidDays)).Unix()
|
||||
}
|
||||
|
||||
var keystore Keystore
|
||||
if tc.req.Type != "ca" {
|
||||
tc.req.Signer = tc.name + "-signer"
|
||||
keystore = &mockKeystore{
|
||||
t: t,
|
||||
signer: tc.req.Signer,
|
||||
cert: caCertificate,
|
||||
key: caPrivateKey,
|
||||
}
|
||||
}
|
||||
certificate, key, caCert, err := IssueCert(&tc.req, keystore)
|
||||
require.NoError(t, err)
|
||||
|
||||
cert := certificate.Certificate
|
||||
if tc.req.Signer == "" {
|
||||
assert.Equal(t, cert.Issuer, cert.Subject, "self-signed")
|
||||
assert.NoError(t, cert.CheckSignatureFrom(cert), "check signature")
|
||||
assert.Equal(t, certificate, caCert, "returned CA cert")
|
||||
} else {
|
||||
assert.Equal(t, cert.Issuer, caCertificate.Certificate.Subject, "cert signer")
|
||||
assert.NoError(t, cert.CheckSignatureFrom(caCertificate.Certificate), "check signature")
|
||||
assert.Equal(t, caCertificate, caCert, "returned CA cert")
|
||||
}
|
||||
|
||||
// type
|
||||
assert.Equal(t, certificate.IsCA, cert.IsCA, "IsCA matches")
|
||||
assert.Equal(t, tc.req.Type == "ca", cert.IsCA, "IsCA")
|
||||
assert.True(t, cert.BasicConstraintsValid, "BasicConstraintsValid")
|
||||
assert.Equal(t, tc.expectedKeyUsage, cert.KeyUsage, "KeyUsage")
|
||||
assert.ElementsMatch(t, tc.expectedExtKeyUsage, cert.ExtKeyUsage)
|
||||
assert.Nil(t, cert.ExtraExtensions, "ExtraExtensions")
|
||||
|
||||
// subject
|
||||
assert.Equal(t, certificate.Subject, cert.Subject, "Subject matches")
|
||||
actualName := cert.Subject
|
||||
actualName.Names = nil
|
||||
assert.Equal(t, tc.expectedSubject, actualName, "Subject")
|
||||
|
||||
// alternateNames
|
||||
assert.Equal(t, tc.expectedDNSNames, cert.DNSNames, "DNSNames")
|
||||
assert.Equal(t, tc.expectedIPAddresses, cert.IPAddresses, "IPAddresses")
|
||||
assert.Empty(t, cert.EmailAddresses, "EmailAddresses")
|
||||
|
||||
// privateKey
|
||||
rsaPrivateKey, ok := key.Key.(*rsa.PrivateKey)
|
||||
require.True(t, ok, "private key is RSA")
|
||||
if tc.req.PrivateKey == nil {
|
||||
assert.Equal(t, 2048, rsaPrivateKey.N.BitLen(), "Private key length")
|
||||
} else {
|
||||
assert.Equal(t, tc.req.PrivateKey, key, "Private key")
|
||||
}
|
||||
assert.Equal(t, &rsaPrivateKey.PublicKey, cert.PublicKey, "certificate public key matches private key")
|
||||
assert.Equal(t, certificate.PublicKey, cert.PublicKey, "PublicKey")
|
||||
|
||||
// serial
|
||||
if tc.req.Serial != nil {
|
||||
assert.Equal(t, cert.SerialNumber, tc.req.Serial, "SerialNumber")
|
||||
} else {
|
||||
assert.Greater(t, cert.SerialNumber.BitLen(), 110, "SerialNumber bit length")
|
||||
}
|
||||
|
||||
// validity
|
||||
var maxExpectedValidity int64
|
||||
if tc.req.MinValidDays == 0 {
|
||||
maxExpectedValidity = time.Now().Add(time.Hour * 10 * 365 * 24).Unix()
|
||||
} else {
|
||||
maxExpectedValidity = time.Now().Add(time.Hour * 24 * time.Duration(tc.req.MinValidDays+30)).Unix()
|
||||
}
|
||||
assert.Less(t, cert.NotBefore.Unix(), time.Now().Add(time.Hour*-47).Unix(), "NotBefore")
|
||||
assert.GreaterOrEqual(t, cert.NotAfter.Unix(), minExpectedValidity, "NotAfter")
|
||||
assert.LessOrEqual(t, cert.NotAfter.Unix(), maxExpectedValidity, "NotAfter")
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ package fi
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kops/pkg/apis/kops"
|
||||
|
|
@ -53,8 +52,6 @@ type Keystore interface {
|
|||
// task to convert a Legacy Keypair to the new Keypair API format.
|
||||
FindKeypair(name string) (*pki.Certificate, *pki.PrivateKey, bool, error)
|
||||
|
||||
CreateKeypair(signer string, name string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error)
|
||||
|
||||
// StoreKeypair writes the keypair to the store
|
||||
StoreKeypair(id string, cert *pki.Certificate, privateKey *pki.PrivateKey) error
|
||||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,8 @@ package fi
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
|
|
@ -40,9 +37,6 @@ type ClientsetCAStore struct {
|
|||
cluster *kops.Cluster
|
||||
namespace string
|
||||
clientset kopsinternalversion.KopsInterface
|
||||
|
||||
mutex sync.Mutex
|
||||
cachedCaKeysets map[string]*keyset
|
||||
}
|
||||
|
||||
var _ CAStore = &ClientsetCAStore{}
|
||||
|
|
@ -51,10 +45,9 @@ var _ SSHCredentialStore = &ClientsetCAStore{}
|
|||
// NewClientsetCAStore is the constructor for ClientsetCAStore
|
||||
func NewClientsetCAStore(cluster *kops.Cluster, clientset kopsinternalversion.KopsInterface, namespace string) CAStore {
|
||||
c := &ClientsetCAStore{
|
||||
cluster: cluster,
|
||||
clientset: clientset,
|
||||
namespace: namespace,
|
||||
cachedCaKeysets: make(map[string]*keyset),
|
||||
cluster: cluster,
|
||||
clientset: clientset,
|
||||
namespace: namespace,
|
||||
}
|
||||
|
||||
return c
|
||||
|
|
@ -64,38 +57,14 @@ func NewClientsetCAStore(cluster *kops.Cluster, clientset kopsinternalversion.Ko
|
|||
func NewClientsetSSHCredentialStore(cluster *kops.Cluster, clientset kopsinternalversion.KopsInterface, namespace string) SSHCredentialStore {
|
||||
// Note: currently identical to NewClientsetCAStore
|
||||
c := &ClientsetCAStore{
|
||||
cluster: cluster,
|
||||
clientset: clientset,
|
||||
namespace: namespace,
|
||||
cachedCaKeysets: make(map[string]*keyset),
|
||||
cluster: cluster,
|
||||
clientset: clientset,
|
||||
namespace: namespace,
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// readCAKeypairs retrieves the CA keypair.
|
||||
// (No longer generates a keypair if not found.)
|
||||
func (c *ClientsetCAStore) readCAKeypairs(ctx context.Context, id string) (*keyset, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
cached := c.cachedCaKeysets[id]
|
||||
if cached != nil {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
keyset, err := c.loadKeyset(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if keyset == nil {
|
||||
return nil, nil
|
||||
}
|
||||
c.cachedCaKeysets[id] = keyset
|
||||
return keyset, nil
|
||||
}
|
||||
|
||||
// keyset is a parsed Keyset
|
||||
type keyset struct {
|
||||
legacyFormat bool
|
||||
|
|
@ -319,67 +288,6 @@ func (c *ClientsetCAStore) ListSSHCredentials() ([]*kops.SSHCredential, error) {
|
|||
return items, nil
|
||||
}
|
||||
|
||||
func (c *ClientsetCAStore) issueCert(signer string, name string, serial *big.Int, privateKey *pki.PrivateKey, template *x509.Certificate) (*pki.Certificate, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
klog.Infof("Issuing new certificate: %q", name)
|
||||
|
||||
template.SerialNumber = serial
|
||||
|
||||
caKeyset, err := c.readCAKeypairs(ctx, signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if caKeyset == nil {
|
||||
return nil, fmt.Errorf("ca keyset was not found; cannot issue certificates")
|
||||
}
|
||||
if caKeyset.primary == nil {
|
||||
return nil, fmt.Errorf("ca keyset did not have any key data; cannot issue certificates")
|
||||
}
|
||||
if caKeyset.primary.certificate == nil {
|
||||
return nil, fmt.Errorf("ca certificate was not found; cannot issue certificates")
|
||||
}
|
||||
if caKeyset.primary.privateKey == nil {
|
||||
return nil, fmt.Errorf("ca privateKey was not found; cannot issue certificates")
|
||||
}
|
||||
cert, err := pki.SignNewCertificate(privateKey, template, caKeyset.primary.certificate.Certificate, caKeyset.primary.privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := c.storeAndVerifyKeypair(ctx, name, cert, privateKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// storeAndVerifyKeypair writes the keypair, also re-reading it to double-check it
|
||||
func (c *ClientsetCAStore) storeAndVerifyKeypair(ctx context.Context, name string, cert *pki.Certificate, privateKey *pki.PrivateKey) (*keyset, error) {
|
||||
id := cert.Certificate.SerialNumber.String()
|
||||
if err := c.storeKeypair(ctx, name, id, cert, privateKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make double-sure it round-trips
|
||||
keyset, err := c.loadKeyset(ctx, name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching stored certificate: %v", err)
|
||||
}
|
||||
|
||||
if keyset == nil {
|
||||
return nil, fmt.Errorf("stored certificate not found: %v", err)
|
||||
}
|
||||
if keyset.primary == nil {
|
||||
return nil, fmt.Errorf("stored certificate did not have data: %v", err)
|
||||
}
|
||||
if keyset.primary.id != id {
|
||||
return nil, fmt.Errorf("stored certificate changed concurrently (id mismatch)")
|
||||
}
|
||||
return keyset, nil
|
||||
}
|
||||
|
||||
// StoreKeypair implements CAStore::StoreKeypair
|
||||
func (c *ClientsetCAStore) StoreKeypair(name string, cert *pki.Certificate, privateKey *pki.PrivateKey) error {
|
||||
ctx := context.TODO()
|
||||
|
|
@ -429,18 +337,6 @@ func (c *ClientsetCAStore) FindPrivateKeyset(name string) (*kops.Keyset, error)
|
|||
return o, nil
|
||||
}
|
||||
|
||||
// CreateKeypair implements CAStore::CreateKeypair
|
||||
func (c *ClientsetCAStore) CreateKeypair(signer string, id string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
|
||||
serial := c.buildSerial()
|
||||
|
||||
cert, err := c.issueCert(signer, id, serial, privateKey, template)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// addKey saves the specified key to the registry
|
||||
func (c *ClientsetCAStore) addKey(ctx context.Context, name string, keysetType kops.KeysetType, item *kops.KeysetItem) error {
|
||||
create := false
|
||||
|
|
@ -573,12 +469,6 @@ func (c *ClientsetCAStore) storeKeypair(ctx context.Context, name string, id str
|
|||
return c.addKey(ctx, name, kops.SecretTypeKeypair, item)
|
||||
}
|
||||
|
||||
// buildSerial returns a serial for use when issuing certificates
|
||||
func (c *ClientsetCAStore) buildSerial() *big.Int {
|
||||
t := time.Now().UnixNano()
|
||||
return pki.BuildPKISerial(t)
|
||||
}
|
||||
|
||||
// AddSSHPublicKey implements CAStore::AddSSHPublicKey
|
||||
func (c *ClientsetCAStore) AddSSHPublicKey(name string, pubkey []byte) error {
|
||||
ctx := context.TODO()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cert_utils.go",
|
||||
"keypair.go",
|
||||
"keypair_fitask.go",
|
||||
"managedfile.go",
|
||||
|
|
|
|||
|
|
@ -17,24 +17,17 @@ limitations under the License.
|
|||
package fitasks
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
"k8s.io/kops/pkg/pki"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
)
|
||||
|
||||
var wellKnownCertificateTypes = map[string]string{
|
||||
"ca": "CA,KeyUsageCRLSign,KeyUsageCertSign",
|
||||
"client": "ExtKeyUsageClientAuth,KeyUsageDigitalSignature",
|
||||
"clientServer": "ExtKeyUsageClientAuth,ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
|
||||
"server": "ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
|
||||
}
|
||||
|
||||
//go:generate fitask -type=Keypair
|
||||
type Keypair struct {
|
||||
// Name is the name of the keypair
|
||||
|
|
@ -97,12 +90,12 @@ func (e *Keypair) Find(c *fi.Context) (*Keypair, error) {
|
|||
actual := &Keypair{
|
||||
Name: &name,
|
||||
AlternateNames: alternateNames,
|
||||
Subject: pkixNameToString(&cert.Subject),
|
||||
Type: buildTypeDescription(cert.Certificate),
|
||||
Subject: pki.PkixNameToString(&cert.Subject),
|
||||
Type: pki.BuildTypeDescription(cert.Certificate),
|
||||
LegacyFormat: legacyFormat,
|
||||
}
|
||||
|
||||
actual.Signer = &Keypair{Subject: pkixNameToString(&cert.Certificate.Issuer)}
|
||||
actual.Signer = &Keypair{Subject: pki.PkixNameToString(&cert.Certificate.Issuer)}
|
||||
|
||||
// Avoid spurious changes
|
||||
actual.Lifecycle = e.Lifecycle
|
||||
|
|
@ -168,11 +161,6 @@ func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
|
|||
return fi.RequiredField("Name")
|
||||
}
|
||||
|
||||
template, err := e.BuildCertificateTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changeStoredFormat := false
|
||||
createCertificate := false
|
||||
if a == nil {
|
||||
|
|
@ -209,11 +197,6 @@ func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
|
|||
// TODO: Eventually rotate keys / don't always reuse?
|
||||
if privateKey == nil {
|
||||
klog.V(2).Infof("Creating privateKey %q", name)
|
||||
|
||||
privateKey, err = pki.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
signer := fi.CertificateId_CA
|
||||
|
|
@ -221,7 +204,38 @@ func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
|
|||
signer = fi.StringValue(e.Signer.Name)
|
||||
}
|
||||
|
||||
cert, err := c.Keystore.CreateKeypair(signer, name, template, privateKey)
|
||||
klog.Infof("Issuing new certificate: %q", *e.Name)
|
||||
|
||||
serial := pki.BuildPKISerial(time.Now().UnixNano())
|
||||
|
||||
subjectPkix, err := parsePkixName(e.Subject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing Subject: %v", err)
|
||||
}
|
||||
|
||||
if len(subjectPkix.ToRDNSequence()) == 0 {
|
||||
return fmt.Errorf("subject name was empty for SSL keypair %q", *e.Name)
|
||||
}
|
||||
|
||||
req := pki.IssueCertRequest{
|
||||
Signer: signer,
|
||||
Type: e.Type,
|
||||
Subject: *subjectPkix,
|
||||
AlternateNames: e.AlternateNames,
|
||||
PrivateKey: privateKey,
|
||||
Serial: serial,
|
||||
}
|
||||
cert, privateKey, _, err := pki.IssueCert(&req, c.Keystore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Keystore.StoreKeypair(name, cert, privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make double-sure it round-trips
|
||||
_, _, _, err = c.Keystore.FindKeypair(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -249,98 +263,28 @@ func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// BuildCertificateTemplate is responsible for constructing a certificate template
|
||||
func (e *Keypair) BuildCertificateTemplate() (*x509.Certificate, error) {
|
||||
template, err := buildCertificateTemplateForType(e.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func parsePkixName(s string) (*pkix.Name, error) {
|
||||
name := new(pkix.Name)
|
||||
|
||||
subjectPkix, err := parsePkixName(e.Subject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing Subject: %v", err)
|
||||
}
|
||||
|
||||
if len(subjectPkix.ToRDNSequence()) == 0 {
|
||||
return nil, fmt.Errorf("Subject name was empty for SSL keypair %q", *e.Name)
|
||||
}
|
||||
|
||||
template.Subject = *subjectPkix
|
||||
|
||||
var alternateNames []string
|
||||
alternateNames = append(alternateNames, e.AlternateNames...)
|
||||
|
||||
for _, san := range alternateNames {
|
||||
san = strings.TrimSpace(san)
|
||||
if san == "" {
|
||||
continue
|
||||
tokens := strings.Split(s, ",")
|
||||
for _, token := range tokens {
|
||||
token = strings.TrimSpace(token)
|
||||
kv := strings.SplitN(token, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("unrecognized token (expected k=v): %q", token)
|
||||
}
|
||||
if ip := net.ParseIP(san); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, san)
|
||||
k := strings.ToLower(kv[0])
|
||||
v := kv[1]
|
||||
|
||||
switch k {
|
||||
case "cn":
|
||||
name.CommonName = v
|
||||
case "o":
|
||||
name.Organization = append(name.Organization, v)
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized key %q in token %q", k, token)
|
||||
}
|
||||
}
|
||||
|
||||
return template, nil
|
||||
}
|
||||
|
||||
func buildCertificateTemplateForType(certificateType string) (*x509.Certificate, error) {
|
||||
if expanded, found := wellKnownCertificateTypes[certificateType]; found {
|
||||
certificateType = expanded
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
}
|
||||
|
||||
tokens := strings.Split(certificateType, ",")
|
||||
for _, t := range tokens {
|
||||
if strings.HasPrefix(t, "KeyUsage") {
|
||||
ku, found := parseKeyUsage(t)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("unrecognized certificate option: %v", t)
|
||||
}
|
||||
template.KeyUsage |= ku
|
||||
} else if strings.HasPrefix(t, "ExtKeyUsage") {
|
||||
ku, found := parseExtKeyUsage(t)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("unrecognized certificate option: %v", t)
|
||||
}
|
||||
template.ExtKeyUsage = append(template.ExtKeyUsage, ku)
|
||||
} else if t == "CA" {
|
||||
template.IsCA = true
|
||||
} else {
|
||||
return nil, fmt.Errorf("unrecognized certificate option: %q", t)
|
||||
}
|
||||
}
|
||||
|
||||
return template, nil
|
||||
}
|
||||
|
||||
// buildTypeDescription extracts the type based on the certificate extensions
|
||||
func buildTypeDescription(cert *x509.Certificate) string {
|
||||
var options []string
|
||||
|
||||
if cert.IsCA {
|
||||
options = append(options, "CA")
|
||||
}
|
||||
|
||||
options = append(options, keyUsageToString(cert.KeyUsage)...)
|
||||
|
||||
for _, extKeyUsage := range cert.ExtKeyUsage {
|
||||
options = append(options, extKeyUsageToString(extKeyUsage))
|
||||
}
|
||||
|
||||
sort.Strings(options)
|
||||
s := strings.Join(options, ",")
|
||||
|
||||
for k, v := range wellKnownCertificateTypes {
|
||||
if v == s {
|
||||
s = k
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
return name, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,13 +18,11 @@ package fi
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog"
|
||||
|
|
@ -41,17 +39,8 @@ type VFSCAStore struct {
|
|||
basedir vfs.Path
|
||||
cluster *kops.Cluster
|
||||
|
||||
mutex sync.Mutex
|
||||
cachedCAs map[string]*cachedEntry
|
||||
|
||||
// SerialGenerator is the function for generating certificate serial numbers
|
||||
// It can be replaced for testing purposes.
|
||||
SerialGenerator func() *big.Int
|
||||
}
|
||||
|
||||
type cachedEntry struct {
|
||||
certificates *keyset
|
||||
privateKeys *keyset
|
||||
mutex sync.Mutex
|
||||
cachedCA *keyset
|
||||
}
|
||||
|
||||
var _ CAStore = &VFSCAStore{}
|
||||
|
|
@ -59,14 +48,8 @@ var _ SSHCredentialStore = &VFSCAStore{}
|
|||
|
||||
func NewVFSCAStore(cluster *kops.Cluster, basedir vfs.Path) *VFSCAStore {
|
||||
c := &VFSCAStore{
|
||||
basedir: basedir,
|
||||
cluster: cluster,
|
||||
cachedCAs: make(map[string]*cachedEntry),
|
||||
}
|
||||
|
||||
c.SerialGenerator = func() *big.Int {
|
||||
t := time.Now().UnixNano()
|
||||
return pki.BuildPKISerial(t)
|
||||
basedir: basedir,
|
||||
cluster: cluster,
|
||||
}
|
||||
|
||||
return c
|
||||
|
|
@ -76,9 +59,8 @@ func NewVFSCAStore(cluster *kops.Cluster, basedir vfs.Path) *VFSCAStore {
|
|||
func NewVFSSSHCredentialStore(cluster *kops.Cluster, basedir vfs.Path) SSHCredentialStore {
|
||||
// Note currently identical to NewVFSCAStore
|
||||
c := &VFSCAStore{
|
||||
basedir: basedir,
|
||||
cluster: cluster,
|
||||
cachedCAs: make(map[string]*cachedEntry),
|
||||
basedir: basedir,
|
||||
cluster: cluster,
|
||||
}
|
||||
|
||||
return c
|
||||
|
|
@ -88,47 +70,6 @@ func (s *VFSCAStore) VFSPath() vfs.Path {
|
|||
return s.basedir
|
||||
}
|
||||
|
||||
// Retrieves the CA keypair. No longer generates keypairs if not found.
|
||||
func (s *VFSCAStore) readCAKeypairs(id string) (*keyset, *keyset, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
cached := s.cachedCAs[id]
|
||||
if cached != nil {
|
||||
return cached.certificates, cached.privateKeys, nil
|
||||
}
|
||||
|
||||
caCertificates, err := s.loadCertificates(s.buildCertificatePoolPath(id))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var caPrivateKeys *keyset
|
||||
|
||||
if caCertificates != nil {
|
||||
caPrivateKeys, err = s.loadPrivateKeys(s.buildPrivateKeyPoolPath(id))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if caPrivateKeys == nil {
|
||||
klog.Warningf("CA private key was not found")
|
||||
//return nil, fmt.Errorf("error loading CA private key - key not found")
|
||||
}
|
||||
}
|
||||
|
||||
if caPrivateKeys == nil {
|
||||
// We no longer generate CA certificates automatically - too race-prone
|
||||
return caCertificates, caPrivateKeys, nil
|
||||
}
|
||||
|
||||
cached = &cachedEntry{certificates: caCertificates, privateKeys: caPrivateKeys}
|
||||
s.cachedCAs[id] = cached
|
||||
|
||||
return cached.certificates, cached.privateKeys, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) buildCertificatePoolPath(name string) vfs.Path {
|
||||
return c.basedir.Join("issued", name)
|
||||
}
|
||||
|
|
@ -586,46 +527,6 @@ func mirrorSSHCredential(cluster *kops.Cluster, basedir vfs.Path, sshCredential
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) issueCert(signer string, id string, serial *big.Int, privateKey *pki.PrivateKey, template *x509.Certificate) (*pki.Certificate, error) {
|
||||
klog.Infof("Issuing new certificate: %q", id)
|
||||
|
||||
template.SerialNumber = serial
|
||||
|
||||
var cert *pki.Certificate
|
||||
if template.IsCA {
|
||||
var err error
|
||||
cert, err = pki.SignNewCertificate(privateKey, template, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
caCertificates, caPrivateKeys, err := c.readCAKeypairs(signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if caPrivateKeys == nil || caPrivateKeys.primary == nil || caPrivateKeys.primary.privateKey == nil {
|
||||
return nil, fmt.Errorf("ca key for %q was not found; cannot issue certificates", signer)
|
||||
}
|
||||
if caCertificates == nil || caCertificates.primary == nil || caCertificates.primary.certificate == nil {
|
||||
return nil, fmt.Errorf("ca certificate for %q was not found; cannot issue certificates", signer)
|
||||
}
|
||||
cert, err = pki.SignNewCertificate(privateKey, template, caCertificates.primary.certificate.Certificate, caPrivateKeys.primary.privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err := c.StoreKeypair(id, cert, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make double-sure it round-trips
|
||||
p := c.buildCertificatePath(id, serial.String())
|
||||
return c.loadOneCertificate(p)
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) StoreKeypair(name string, cert *pki.Certificate, privateKey *pki.PrivateKey) error {
|
||||
serial := cert.Certificate.SerialNumber.String()
|
||||
|
||||
|
|
@ -684,14 +585,28 @@ func (c *VFSCAStore) loadPrivateKeys(p vfs.Path) (*keyset, error) {
|
|||
|
||||
func (c *VFSCAStore) findPrivateKeyset(id string) (*keyset, error) {
|
||||
var keys *keyset
|
||||
var err error
|
||||
if id == CertificateId_CA {
|
||||
_, caPrivateKeys, err := c.readCAKeypairs(id)
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
cached := c.cachedCA
|
||||
if cached != nil {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
keys, err = c.loadPrivateKeys(c.buildPrivateKeyPoolPath(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys = caPrivateKeys
|
||||
|
||||
if keys == nil {
|
||||
klog.Warningf("CA private key was not found")
|
||||
// We no longer generate CA certificates automatically - too race-prone
|
||||
} else {
|
||||
c.cachedCA = keys
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
p := c.buildPrivateKeyPoolPath(id)
|
||||
keys, err = c.loadPrivateKeys(p)
|
||||
if err != nil {
|
||||
|
|
@ -729,17 +644,6 @@ func (c *VFSCAStore) FindPrivateKeyset(name string) (*kops.Keyset, error) {
|
|||
return o, nil
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) CreateKeypair(signer string, id string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
|
||||
serial := c.SerialGenerator()
|
||||
|
||||
cert, err := c.issueCert(signer, id, serial, privateKey, template)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) storePrivateKey(name string, ki *keysetItem) error {
|
||||
if ki.privateKey == nil {
|
||||
return fmt.Errorf("privateKey not provided to storeCertificate")
|
||||
|
|
|
|||
|
|
@ -55,8 +55,7 @@ func TestVFSCAStoreRoundTrip(t *testing.T) {
|
|||
}
|
||||
|
||||
s := &VFSCAStore{
|
||||
basedir: basePath,
|
||||
cachedCAs: make(map[string]*cachedEntry),
|
||||
basedir: basePath,
|
||||
}
|
||||
|
||||
privateKeyData := "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA4JwpEprZ5n8RIEt6jT2lAh+UDgRgx/4px21gjgywQivYHVxH\nAZexVb/E9pBa9Q2G9B1Q7TCO7YsUVRQy4JMDZVt+McFnWVwexnqBYFNcVjkEmDgA\ngvCYGE0P9d/RwRL4KuLHo+u6fv7P0jXMN+CpOxyLhYZZNa0ZOZDHsSiJSQSj9WGF\nGHrbCf0KVDpKieR1uBqHrRO+mLR5zkX2L58m74kjK4dsBhmjeq/7OAoTmiG2QgJ/\nP2IjyhiA2mRqY+hl55lwEUV/0yHYEkJC8LdGkwwZz2eF77aSPGmi/A2CSKgMwDTx\n9m+P7jcpWreYw6NG9BueGoDIve/tgFKwvVFF6QIDAQABAoIBAA0ktjaTfyrAxsTI\nBezb7Zr5NBW55dvuII299cd6MJo+rI/TRYhvUv48kY8IFXp/hyUjzgeDLunxmIf9\n/Zgsoic9Ol44/g45mMduhcGYPzAAeCdcJ5OB9rR9VfDCXyjYLlN8H8iU0734tTqM\n0V13tQ9zdSqkGPZOIcq/kR/pylbOZaQMe97BTlsAnOMSMKDgnftY4122Lq3GYy+t\nvpr+bKVaQZwvkLoSU3rECCaKaghgwCyX7jft9aEkhdJv+KlwbsGY6WErvxOaLWHd\ncuMQjGapY1Fa/4UD00mvrA260NyKfzrp6+P46RrVMwEYRJMIQ8YBAk6N6Hh7dc0G\n8Z6i1m0CgYEA9HeCJR0TSwbIQ1bDXUrzpftHuidG5BnSBtax/ND9qIPhR/FBW5nj\n22nwLc48KkyirlfIULd0ae4qVXJn7wfYcuX/cJMLDmSVtlM5Dzmi/91xRiFgIzx1\nAsbBzaFjISP2HpSgL+e9FtSXaaqeZVrflitVhYKUpI/AKV31qGHf04sCgYEA6zTV\n99Sb49Wdlns5IgsfnXl6ToRttB18lfEKcVfjAM4frnkk06JpFAZeR+9GGKUXZHqs\nz2qcplw4d/moCC6p3rYPBMLXsrGNEUFZqBlgz72QA6BBq3X0Cg1Bc2ZbK5VIzwkg\nST2SSux6ccROfgULmN5ZiLOtdUKNEZpFF3i3qtsCgYADT/s7dYFlatobz3kmMnXK\nsfTu2MllHdRys0YGHu7Q8biDuQkhrJwhxPW0KS83g4JQym+0aEfzh36bWcl+u6R7\nKhKj+9oSf9pndgk345gJz35RbPJYh+EuAHNvzdgCAvK6x1jETWeKf6btj5pF1U1i\nQ4QNIw/QiwIXjWZeubTGsQKBgQCbduLu2rLnlyyAaJZM8DlHZyH2gAXbBZpxqU8T\nt9mtkJDUS/KRiEoYGFV9CqS0aXrayVMsDfXY6B/S/UuZjO5u7LtklDzqOf1aKG3Q\ndGXPKibknqqJYH+bnUNjuYYNerETV57lijMGHuSYCf8vwLn3oxBfERRX61M/DU8Z\nworz/QKBgQDCTJI2+jdXg26XuYUmM4XXfnocfzAXhXBULt1nENcogNf1fcptAVtu\nBAiz4/HipQKqoWVUYmxfgbbLRKKLK0s0lOWKbYdVjhEm/m2ZU8wtXTagNwkIGoyq\nY/C1Lox4f1ROJnCjc/hfcOjcxX5M8A8peecHWlVtUPKTJgxQ7oMKcw==\n-----END RSA PRIVATE KEY-----\n"
|
||||
|
|
|
|||
Loading…
Reference in New Issue