Merge pull request #3679 from justinsb/support_api_aggregation

Automatic merge from submit-queue.

Initial aggregation work

Create the keypairs, which are supposed to be signed by a different CA.
    
Set the `--requestheader-...` flags on apiserver.
    
Fix #3152
Fix #2691
This commit is contained in:
Kubernetes Submit Queue 2017-10-24 12:08:27 -07:00 committed by GitHub
commit 8df13bd468
19 changed files with 351 additions and 63 deletions

View File

@ -116,7 +116,7 @@ func (c *NodeupModelContext) CNIConfDir() string {
// buildPKIKubeconfig generates a kubeconfig
func (c *NodeupModelContext) buildPKIKubeconfig(id string) (string, error) {
caCertificate, err := c.KeyStore.Cert(fi.CertificateId_CA, false)
caCertificate, err := c.KeyStore.FindCert(fi.CertificateId_CA)
if err != nil {
return "", fmt.Errorf("error fetching CA certificate from keystore: %v", err)
}

View File

@ -184,12 +184,25 @@ func (b *KubeAPIServerBuilder) buildPod() (*v1.Pod, error) {
}
if b.IsKubernetesGTE("1.7") {
certPath := filepath.Join(b.PathSrvKubernetes(), "proxy-client.cert")
certPath := filepath.Join(b.PathSrvKubernetes(), "apiserver-aggregator.cert")
kubeAPIServer.ProxyClientCertFile = &certPath
keyPath := filepath.Join(b.PathSrvKubernetes(), "proxy-client.key")
keyPath := filepath.Join(b.PathSrvKubernetes(), "apiserver-aggregator.key")
kubeAPIServer.ProxyClientKeyFile = &keyPath
}
// APIServer aggregation options
if b.IsKubernetesGTE("1.7") {
cert, err := b.KeyStore.FindCert("apiserver-aggregator-ca")
if err != nil {
return nil, fmt.Errorf("apiserver aggregator CA cert lookup failed: %v", err.Error())
}
if cert != nil {
certPath := filepath.Join(b.PathSrvKubernetes(), "apiserver-aggregator-ca.cert")
kubeAPIServer.RequestheaderClientCAFile = certPath
}
}
// build the kube-apiserver flags for the service
flags, err := flagbuilder.BuildFlagsList(b.Cluster.Spec.KubeAPIServer)
if err != nil {

View File

@ -21,6 +21,7 @@ import (
"path/filepath"
"strings"
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"
)
@ -117,7 +118,7 @@ func (b *SecretBuilder) Build(c *fi.ModelBuilderContext) error {
}
if b.IsKubernetesGTE("1.7") {
// TODO: Remove - we use the apiserver-aggregator keypair instead (which is signed by a different CA)
cert, err := b.KeyStore.Cert("apiserver-proxy-client", false)
if err != nil {
return fmt.Errorf("apiserver proxy client cert lookup failed: %v", err.Error())
@ -153,6 +154,22 @@ func (b *SecretBuilder) Build(c *fi.ModelBuilderContext) error {
c.AddTask(t)
}
if b.IsKubernetesGTE("1.7") {
if err := b.writeCertificate(c, "apiserver-aggregator"); err != nil {
return err
}
if err := b.writePrivateKey(c, "apiserver-aggregator"); err != nil {
return err
}
}
if b.IsKubernetesGTE("1.7") {
if err := b.writeCertificate(c, "apiserver-aggregator-ca"); err != nil {
return err
}
}
if b.SecretStore != nil {
key := "kube"
token, err := b.SecretStore.FindSecret(key)
@ -200,6 +217,55 @@ func (b *SecretBuilder) Build(c *fi.ModelBuilderContext) error {
return nil
}
// writeCertificate writes the specified certificate to the local filesystem, under PathSrvKubernetes()
func (b *SecretBuilder) writeCertificate(c *fi.ModelBuilderContext, id string) error {
cert, err := b.KeyStore.FindCert(id)
if err != nil {
return fmt.Errorf("cert lookup failed for %q: %v", id, err)
}
if cert != nil {
serialized, err := cert.AsString()
if err != nil {
return err
}
t := &nodetasks.File{
Path: filepath.Join(b.PathSrvKubernetes(), id+".cert"),
Contents: fi.NewStringResource(serialized),
Type: nodetasks.FileType_File,
}
c.AddTask(t)
} else {
// TODO: Make this an error?
glog.Warningf("certificate %q not found", id)
}
return nil
}
// writePrivateKey writes the specified private key to the local filesystem, under PathSrvKubernetes()
func (b *SecretBuilder) writePrivateKey(c *fi.ModelBuilderContext, id string) error {
key, err := b.KeyStore.FindPrivateKey(id)
if err != nil {
return fmt.Errorf("private key lookup failed for %q: %v", id, err)
}
serialized, err := key.AsString()
if err != nil {
return err
}
t := &nodetasks.File{
Path: filepath.Join(b.PathSrvKubernetes(), id+".key"),
Contents: fi.NewStringResource(serialized),
Type: nodetasks.FileType_File,
}
c.AddTask(t)
return nil
}
// allTokens returns a map of all tokens
func (b *SecretBuilder) allTokens() (map[string]string, error) {
tokens := make(map[string]string)

View File

@ -258,6 +258,17 @@ type KubeAPIServerConfig struct {
AuthorizationRBACSuperUser *string `json:"authorizationRbacSuperUser,omitempty" flag:"authorization-rbac-super-user"`
// ExperimentalEncryptionProviderConfig enables encryption at rest for secrets.
ExperimentalEncryptionProviderConfig *string `json:"experimentalEncryptionProviderConfig,omitempty" flag:"experimental-encryption-provider-config"`
// List of request headers to inspect for usernames. X-Remote-User is common.
RequestheaderUsernameHeaders []string `json:"requestheaderUsernameHeaders,omitempty" flag:"requestheader-username-headers"`
// List of request headers to inspect for groups. X-Remote-Group is suggested.
RequestheaderGroupHeaders []string `json:"requestheaderGroupHeaders,omitempty" flag:"requestheader-group-headers"`
// List of request header prefixes to inspect. X-Remote-Extra- is suggested.
RequestheaderExtraHeaderPrefixes []string `json:"requestheaderExtraHeaderPrefixes,omitempty" flag:"requestheader-extra-headers-prefix"`
//Root certificate bundle to use to verify client certificates on incoming requests before trusting usernames in headers specified by --requestheader-username-headers
RequestheaderClientCAFile string `json:"requestheaderClientCAFile,omitempty" flag:"requestheader-client-ca-file"`
// List of client certificate common names to allow to provide usernames in headers specified by --requestheader-username-headers. If empty, any client certificate validated by the authorities in --requestheader-client-ca-file is allowed.
RequestheaderAllowedNames []string `json:"requestheaderAllowedNames,omitempty" flag:"requestheader-allowed-names"`
}
// KubeControllerManagerConfig is the configuration for the controller

View File

@ -258,6 +258,17 @@ type KubeAPIServerConfig struct {
AuthorizationRBACSuperUser *string `json:"authorizationRbacSuperUser,omitempty" flag:"authorization-rbac-super-user"`
// ExperimentalEncryptionProviderConfig enables encryption at rest for secrets.
ExperimentalEncryptionProviderConfig *string `json:"experimentalEncryptionProviderConfig,omitempty" flag:"experimental-encryption-provider-config"`
// List of request headers to inspect for usernames. X-Remote-User is common.
RequestheaderUsernameHeaders []string `json:"requestheaderUsernameHeaders,omitempty" flag:"requestheader-username-headers"`
// List of request headers to inspect for groups. X-Remote-Group is suggested.
RequestheaderGroupHeaders []string `json:"requestheaderGroupHeaders,omitempty" flag:"requestheader-group-headers"`
// List of request header prefixes to inspect. X-Remote-Extra- is suggested.
RequestheaderExtraHeaderPrefixes []string `json:"requestheaderExtraHeaderPrefixes,omitempty" flag:"requestheader-extra-headers-prefix"`
//Root certificate bundle to use to verify client certificates on incoming requests before trusting usernames in headers specified by --requestheader-username-headers
RequestheaderClientCAFile string `json:"requestheaderClientCAFile,omitempty" flag:"requestheader-client-ca-file"`
// List of client certificate common names to allow to provide usernames in headers specified by --requestheader-username-headers. If empty, any client certificate validated by the authorities in --requestheader-client-ca-file is allowed.
RequestheaderAllowedNames []string `json:"requestheaderAllowedNames,omitempty" flag:"requestheader-allowed-names"`
}
// KubeControllerManagerConfig is the configuration for the controller

View File

@ -1816,6 +1816,11 @@ func autoConvert_v1alpha1_KubeAPIServerConfig_To_kops_KubeAPIServerConfig(in *Ku
out.AuthorizationMode = in.AuthorizationMode
out.AuthorizationRBACSuperUser = in.AuthorizationRBACSuperUser
out.ExperimentalEncryptionProviderConfig = in.ExperimentalEncryptionProviderConfig
out.RequestheaderUsernameHeaders = in.RequestheaderUsernameHeaders
out.RequestheaderGroupHeaders = in.RequestheaderGroupHeaders
out.RequestheaderExtraHeaderPrefixes = in.RequestheaderExtraHeaderPrefixes
out.RequestheaderClientCAFile = in.RequestheaderClientCAFile
out.RequestheaderAllowedNames = in.RequestheaderAllowedNames
return nil
}
@ -1868,6 +1873,11 @@ func autoConvert_kops_KubeAPIServerConfig_To_v1alpha1_KubeAPIServerConfig(in *ko
out.AuthorizationMode = in.AuthorizationMode
out.AuthorizationRBACSuperUser = in.AuthorizationRBACSuperUser
out.ExperimentalEncryptionProviderConfig = in.ExperimentalEncryptionProviderConfig
out.RequestheaderUsernameHeaders = in.RequestheaderUsernameHeaders
out.RequestheaderGroupHeaders = in.RequestheaderGroupHeaders
out.RequestheaderExtraHeaderPrefixes = in.RequestheaderExtraHeaderPrefixes
out.RequestheaderClientCAFile = in.RequestheaderClientCAFile
out.RequestheaderAllowedNames = in.RequestheaderAllowedNames
return nil
}

View File

@ -2035,6 +2035,26 @@ func (in *KubeAPIServerConfig) DeepCopyInto(out *KubeAPIServerConfig) {
**out = **in
}
}
if in.RequestheaderUsernameHeaders != nil {
in, out := &in.RequestheaderUsernameHeaders, &out.RequestheaderUsernameHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RequestheaderGroupHeaders != nil {
in, out := &in.RequestheaderGroupHeaders, &out.RequestheaderGroupHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RequestheaderExtraHeaderPrefixes != nil {
in, out := &in.RequestheaderExtraHeaderPrefixes, &out.RequestheaderExtraHeaderPrefixes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RequestheaderAllowedNames != nil {
in, out := &in.RequestheaderAllowedNames, &out.RequestheaderAllowedNames
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}

View File

@ -258,6 +258,17 @@ type KubeAPIServerConfig struct {
AuthorizationRBACSuperUser *string `json:"authorizationRbacSuperUser,omitempty" flag:"authorization-rbac-super-user"`
// ExperimentalEncryptionProviderConfig enables encryption at rest for secrets.
ExperimentalEncryptionProviderConfig *string `json:"experimentalEncryptionProviderConfig,omitempty" flag:"experimental-encryption-provider-config"`
// List of request headers to inspect for usernames. X-Remote-User is common.
RequestheaderUsernameHeaders []string `json:"requestheaderUsernameHeaders,omitempty" flag:"requestheader-username-headers"`
// List of request headers to inspect for groups. X-Remote-Group is suggested.
RequestheaderGroupHeaders []string `json:"requestheaderGroupHeaders,omitempty" flag:"requestheader-group-headers"`
// List of request header prefixes to inspect. X-Remote-Extra- is suggested.
RequestheaderExtraHeaderPrefixes []string `json:"requestheaderExtraHeaderPrefixes,omitempty" flag:"requestheader-extra-headers-prefix"`
//Root certificate bundle to use to verify client certificates on incoming requests before trusting usernames in headers specified by --requestheader-username-headers
RequestheaderClientCAFile string `json:"requestheaderClientCAFile,omitempty" flag:"requestheader-client-ca-file"`
// List of client certificate common names to allow to provide usernames in headers specified by --requestheader-username-headers. If empty, any client certificate validated by the authorities in --requestheader-client-ca-file is allowed.
RequestheaderAllowedNames []string `json:"requestheaderAllowedNames,omitempty" flag:"requestheader-allowed-names"`
}
// KubeControllerManagerConfig is the configuration for the controller

View File

@ -2078,6 +2078,11 @@ func autoConvert_v1alpha2_KubeAPIServerConfig_To_kops_KubeAPIServerConfig(in *Ku
out.AuthorizationMode = in.AuthorizationMode
out.AuthorizationRBACSuperUser = in.AuthorizationRBACSuperUser
out.ExperimentalEncryptionProviderConfig = in.ExperimentalEncryptionProviderConfig
out.RequestheaderUsernameHeaders = in.RequestheaderUsernameHeaders
out.RequestheaderGroupHeaders = in.RequestheaderGroupHeaders
out.RequestheaderExtraHeaderPrefixes = in.RequestheaderExtraHeaderPrefixes
out.RequestheaderClientCAFile = in.RequestheaderClientCAFile
out.RequestheaderAllowedNames = in.RequestheaderAllowedNames
return nil
}
@ -2130,6 +2135,11 @@ func autoConvert_kops_KubeAPIServerConfig_To_v1alpha2_KubeAPIServerConfig(in *ko
out.AuthorizationMode = in.AuthorizationMode
out.AuthorizationRBACSuperUser = in.AuthorizationRBACSuperUser
out.ExperimentalEncryptionProviderConfig = in.ExperimentalEncryptionProviderConfig
out.RequestheaderUsernameHeaders = in.RequestheaderUsernameHeaders
out.RequestheaderGroupHeaders = in.RequestheaderGroupHeaders
out.RequestheaderExtraHeaderPrefixes = in.RequestheaderExtraHeaderPrefixes
out.RequestheaderClientCAFile = in.RequestheaderClientCAFile
out.RequestheaderAllowedNames = in.RequestheaderAllowedNames
return nil
}

View File

@ -2161,6 +2161,26 @@ func (in *KubeAPIServerConfig) DeepCopyInto(out *KubeAPIServerConfig) {
**out = **in
}
}
if in.RequestheaderUsernameHeaders != nil {
in, out := &in.RequestheaderUsernameHeaders, &out.RequestheaderUsernameHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RequestheaderGroupHeaders != nil {
in, out := &in.RequestheaderGroupHeaders, &out.RequestheaderGroupHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RequestheaderExtraHeaderPrefixes != nil {
in, out := &in.RequestheaderExtraHeaderPrefixes, &out.RequestheaderExtraHeaderPrefixes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RequestheaderAllowedNames != nil {
in, out := &in.RequestheaderAllowedNames, &out.RequestheaderAllowedNames
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}

View File

@ -2380,6 +2380,26 @@ func (in *KubeAPIServerConfig) DeepCopyInto(out *KubeAPIServerConfig) {
**out = **in
}
}
if in.RequestheaderUsernameHeaders != nil {
in, out := &in.RequestheaderUsernameHeaders, &out.RequestheaderUsernameHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RequestheaderGroupHeaders != nil {
in, out := &in.RequestheaderGroupHeaders, &out.RequestheaderGroupHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RequestheaderExtraHeaderPrefixes != nil {
in, out := &in.RequestheaderExtraHeaderPrefixes, &out.RequestheaderExtraHeaderPrefixes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RequestheaderAllowedNames != nil {
in, out := &in.RequestheaderAllowedNames, &out.RequestheaderAllowedNames
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}

View File

@ -94,6 +94,10 @@ func (b *KubeAPIServerOptionsBuilder) BuildOptions(o interface{}) error {
clusterSpec.KubeAPIServer.AuthorizationMode = fi.String("RBAC")
}
if err := b.configureAggregation(clusterSpec); err != nil {
return nil
}
image, err := Image("kube-apiserver", clusterSpec, b.AssetBuilder)
if err != nil {
return err
@ -247,3 +251,15 @@ func (b *KubeAPIServerOptionsBuilder) buildAPIServerCount(clusterSpec *kops.Clus
return count
}
// configureAggregation sets up the aggregation options
func (b *KubeAPIServerOptionsBuilder) configureAggregation(clusterSpec *kops.ClusterSpec) error {
if b.IsKubernetesGTE("1.7") {
clusterSpec.KubeAPIServer.RequestheaderAllowedNames = []string{"aggregator"}
clusterSpec.KubeAPIServer.RequestheaderExtraHeaderPrefixes = []string{"X-Remote-Extra-"}
clusterSpec.KubeAPIServer.RequestheaderGroupHeaders = []string{"X-Remote-Group"}
clusterSpec.KubeAPIServer.RequestheaderUsernameHeaders = []string{"X-Remote-User"}
}
return nil
}

View File

@ -35,13 +35,25 @@ var _ fi.ModelBuilder = &PKIModelBuilder{}
// Build is responsible for generating the various pki assets
func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
// TODO: Only create the CA via this task
defaultCA := &fitasks.Keypair{
Name: fi.String(fi.CertificateId_CA),
Lifecycle: b.Lifecycle,
Subject: "cn=kubernetes",
Type: "ca",
}
c.AddTask(defaultCA)
{
t := &fitasks.Keypair{
Name: fi.String("kubelet"),
Lifecycle: b.Lifecycle,
Subject: "o=" + user.NodesGroup + ",cn=kubelet",
Type: "client",
Signer: defaultCA,
}
c.AddTask(t)
}
@ -54,6 +66,7 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
Lifecycle: b.Lifecycle,
Subject: "cn=kubelet-api",
Type: "client",
Signer: defaultCA,
})
}
{
@ -62,6 +75,7 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
Lifecycle: b.Lifecycle,
Subject: "cn=" + user.KubeScheduler,
Type: "client",
Signer: defaultCA,
}
c.AddTask(t)
}
@ -72,6 +86,7 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
Lifecycle: b.Lifecycle,
Subject: "cn=" + user.KubeProxy,
Type: "client",
Signer: defaultCA,
}
c.AddTask(t)
}
@ -82,6 +97,7 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
Lifecycle: b.Lifecycle,
Subject: "cn=" + user.KubeControllerManager,
Type: "client",
Signer: defaultCA,
}
c.AddTask(t)
}
@ -101,6 +117,7 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
Name: fi.String("etcd"),
Subject: "cn=etcd",
Type: "server",
Signer: defaultCA,
})
}
{
@ -109,6 +126,7 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
Lifecycle: b.Lifecycle,
Subject: "cn=etcd-client",
Type: "client",
Signer: defaultCA,
})
}
}
@ -118,6 +136,7 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
Name: fi.String("kube-router"),
Subject: "cn=" + "system:kube-router",
Type: "client",
Signer: defaultCA,
}
c.AddTask(t)
}
@ -128,6 +147,7 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
Lifecycle: b.Lifecycle,
Subject: "o=" + user.SystemPrivilegedGroup + ",cn=kubecfg",
Type: "client",
Signer: defaultCA,
}
c.AddTask(t)
}
@ -138,16 +158,38 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
Lifecycle: b.Lifecycle,
Subject: "cn=apiserver-proxy-client",
Type: "client",
Signer: defaultCA,
}
c.AddTask(t)
}
{
aggregatorCA := &fitasks.Keypair{
Name: fi.String("apiserver-aggregator-ca"),
Lifecycle: b.Lifecycle,
Subject: "cn=apiserver-aggregator-ca",
Type: "ca",
}
c.AddTask(aggregatorCA)
aggregator := &fitasks.Keypair{
Name: fi.String("apiserver-aggregator"),
Lifecycle: b.Lifecycle,
// Must match RequestheaderAllowedNames
Subject: "cn=aggregator",
Type: "client",
Signer: aggregatorCA,
}
c.AddTask(aggregator)
}
{
t := &fitasks.Keypair{
Name: fi.String("kops"),
Lifecycle: b.Lifecycle,
Subject: "o=" + user.SystemPrivilegedGroup + ",cn=kops",
Type: "client",
Signer: defaultCA,
}
c.AddTask(t)
}
@ -183,6 +225,7 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
Subject: "cn=kubernetes-master",
Type: "server",
AlternateNames: alternateNames,
Signer: defaultCA,
}
c.AddTask(t)
}

View File

@ -85,7 +85,7 @@ func SignNewCertificate(privateKey *PrivateKey, template *x509.Certificate, sign
template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
}
if template.ExtKeyUsage == nil {
if template.ExtKeyUsage == nil && !template.IsCA {
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
}
//c.SignatureAlgorithm = do we want to overrride?

View File

@ -49,7 +49,7 @@ type Keystore interface {
// (if the certificate is found but not keypair, that is not an error: only the cert will be returned)
FindKeypair(name string) (*pki.Certificate, *pki.PrivateKey, error)
CreateKeypair(name string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, 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
@ -67,11 +67,13 @@ type CAStore interface {
Keystore
// Cert returns the primary specified certificate
// For createIfMissing=false, using FindCert is preferred
Cert(name string, createIfMissing bool) (*pki.Certificate, error)
// CertificatePool returns all active certificates with the specified id
CertificatePool(name string, createIfMissing bool) (*CertificatePool, error)
PrivateKey(name string, createIfMissing bool) (*pki.PrivateKey, error)
// FindCert returns the specified certificate, if it exists, or nil if not found
FindCert(name string) (*pki.Certificate, error)
FindPrivateKey(name string) (*pki.PrivateKey, error)

View File

@ -43,7 +43,7 @@ type ClientsetCAStore struct {
clientset kopsinternalversion.KopsInterface
mutex sync.Mutex
cacheCaKeyset *keyset
cachedCaKeysets map[string]*keyset
}
var _ CAStore = &ClientsetCAStore{}
@ -53,40 +53,42 @@ func NewClientsetCAStore(clientset kopsinternalversion.KopsInterface, namespace
c := &ClientsetCAStore{
clientset: clientset,
namespace: namespace,
cachedCaKeysets: make(map[string]*keyset),
}
return c
}
// readCAKeypairs retrieves the CA keypair, generating a new keypair if not found
func (c *ClientsetCAStore) readCAKeypairs() (*keyset, error) {
func (c *ClientsetCAStore) readCAKeypairs(id string) (*keyset, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.cacheCaKeyset != nil {
return c.cacheCaKeyset, nil
cached := c.cachedCaKeysets[id]
if cached != nil {
return cached, nil
}
keyset, err := c.loadKeyset(CertificateId_CA)
keyset, err := c.loadKeyset(id)
if err != nil {
return nil, err
}
if keyset == nil {
keyset, err = c.generateCACertificate()
keyset, err = c.generateCACertificate(id)
if err != nil {
return nil, err
}
}
c.cacheCaKeyset = keyset
c.cachedCaKeysets[id] = keyset
return keyset, nil
}
// generateCACertificate creates and stores a CA keypair
// Should be called with the mutex held, to prevent concurrent creation of different keys
func (c *ClientsetCAStore) generateCACertificate() (*keyset, error) {
func (c *ClientsetCAStore) generateCACertificate(id string) (*keyset, error) {
template := BuildCAX509Template()
caRsaKey, err := rsa.GenerateKey(crypto_rand.Reader, 2048)
@ -104,7 +106,7 @@ func (c *ClientsetCAStore) generateCACertificate() (*keyset, error) {
return nil, err
}
return c.storeAndVerifyKeypair(CertificateId_CA, caCertificate, caPrivateKey)
return c.storeAndVerifyKeypair(id, caCertificate, caPrivateKey)
}
// keyset is a parsed Keyset
@ -310,12 +312,12 @@ func (c *ClientsetCAStore) List() ([]*KeystoreItem, error) {
}
// IssueCert implements CAStore::IssueCert
func (c *ClientsetCAStore) IssueCert(name string, serial *big.Int, privateKey *pki.PrivateKey, template *x509.Certificate) (*pki.Certificate, error) {
func (c *ClientsetCAStore) IssueCert(signer string, name string, serial *big.Int, privateKey *pki.PrivateKey, template *x509.Certificate) (*pki.Certificate, error) {
glog.Infof("Issuing new certificate: %q", name)
template.SerialNumber = serial
caKeyset, err := c.readCAKeypairs()
caKeyset, err := c.readCAKeypairs(signer)
if err != nil {
return nil, err
}
@ -416,10 +418,10 @@ func (c *ClientsetCAStore) PrivateKey(name string, createIfMissing bool) (*pki.P
}
// CreateKeypair implements CAStore::CreateKeypair
func (c *ClientsetCAStore) CreateKeypair(id string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
func (c *ClientsetCAStore) CreateKeypair(signer string, id string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
serial := c.buildSerial()
cert, err := c.IssueCert(id, serial, privateKey, template)
cert, err := c.IssueCert(signer, id, serial, privateKey, template)
if err != nil {
return nil, err
}

View File

@ -31,6 +31,7 @@ import (
var wellKnownCertificateTypes = map[string]string{
"client": "ExtKeyUsageClientAuth,KeyUsageDigitalSignature",
"server": "ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
"ca": "CA,KeyUsageCRLSign,KeyUsageCertSign",
}
//go:generate fitask -type=Keypair
@ -41,6 +42,9 @@ type Keypair struct {
Type string `json:"type"`
AlternateNames []string `json:"alternateNames"`
AlternateNameTasks []fi.Task `json:"alternateNameTasks"`
// Signer is the keypair to use to sign, for when we want to use an alternative CA
Signer *Keypair
}
var _ fi.HasCheckExisting = &Keypair{}
@ -51,6 +55,12 @@ func (e *Keypair) CheckExisting(c *fi.Context) bool {
return true
}
var _ fi.CompareWithID = &Keypair{}
func (e *Keypair) CompareWithID() *string {
return &e.Subject
}
func (e *Keypair) Find(c *fi.Context) (*Keypair, error) {
name := fi.StringValue(e.Name)
if name == "" {
@ -84,6 +94,8 @@ func (e *Keypair) Find(c *fi.Context) (*Keypair, error) {
Type: buildTypeDescription(cert.Certificate),
}
actual.Signer = &Keypair{Subject: pkixNameToString(&cert.Certificate.Issuer)}
// Avoid spurious changes
actual.Lifecycle = e.Lifecycle
@ -133,7 +145,7 @@ func (e *Keypair) normalize(c *fi.Context) error {
return nil
}
func (s *Keypair) CheckChanges(a, e, changes *Keypair) error {
func (_ *Keypair) CheckChanges(a, e, changes *Keypair) error {
if a != nil {
if changes.Name != nil {
return fi.CannotChangeField("Name")
@ -184,7 +196,11 @@ func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
}
}
cert, err = c.Keystore.CreateKeypair(name, template, privateKey)
signer := fi.CertificateId_CA
if e.Signer != nil {
signer = fi.StringValue(e.Signer.Name)
}
cert, err = c.Keystore.CreateKeypair(signer, name, template, privateKey)
if err != nil {
return err
}
@ -256,8 +272,10 @@ func buildCertificateTemplateForType(certificateType string) (*x509.Certificate,
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: %v", t)
return nil, fmt.Errorf("unrecognized certificate option: %q", t)
}
}

View File

@ -51,12 +51,12 @@ func NewKubernetesKeystore(client kubernetes.Interface, namespace string) fi.Key
return c
}
func (c *KubernetesKeystore) issueCert(id string, serial *big.Int, privateKey *pki.PrivateKey, template *x509.Certificate) (*pki.Certificate, error) {
func (c *KubernetesKeystore) issueCert(signer string, id string, serial *big.Int, privateKey *pki.PrivateKey, template *x509.Certificate) (*pki.Certificate, error) {
glog.Infof("Issuing new certificate: %q", id)
template.SerialNumber = serial
caCert, caKey, err := c.FindKeypair(fi.CertificateId_CA)
caCert, caKey, err := c.FindKeypair(signer)
if err != nil {
return nil, err
}
@ -107,11 +107,11 @@ func (c *KubernetesKeystore) FindKeypair(id string) (*pki.Certificate, *pki.Priv
return keypair.Certificate, keypair.PrivateKey, nil
}
func (c *KubernetesKeystore) CreateKeypair(id string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
func (c *KubernetesKeystore) CreateKeypair(signer string, id string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
t := time.Now().UnixNano()
serial := pki.BuildPKISerial(t)
cert, err := c.issueCert(id, serial, privateKey, template)
cert, err := c.issueCert(signer, id, serial, privateKey, template)
if err != nil {
return nil, err
}

View File

@ -41,8 +41,12 @@ type VFSCAStore struct {
basedir vfs.Path
mutex sync.Mutex
cacheCaCertificates *certificates
cacheCaPrivateKeys *privateKeys
cachedCAs map[string]*cachedEntry
}
type cachedEntry struct {
certificates *certificates
privateKeys *privateKeys
}
var _ CAStore = &VFSCAStore{}
@ -50,6 +54,7 @@ var _ CAStore = &VFSCAStore{}
func NewVFSCAStore(basedir vfs.Path) CAStore {
c := &VFSCAStore{
basedir: basedir,
cachedCAs: make(map[string]*cachedEntry),
}
return c
@ -60,15 +65,16 @@ func (s *VFSCAStore) VFSPath() vfs.Path {
}
// Retrieves the CA keypair, generating a new keypair if not found
func (s *VFSCAStore) readCAKeypairs() (*certificates, *privateKeys, error) {
func (s *VFSCAStore) readCAKeypairs(id string) (*certificates, *privateKeys, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.cacheCaPrivateKeys != nil {
return s.cacheCaCertificates, s.cacheCaPrivateKeys, nil
cached := s.cachedCAs[id]
if cached != nil {
return cached.certificates, cached.privateKeys, nil
}
caCertificates, err := s.loadCertificates(s.buildCertificatePoolPath(CertificateId_CA))
caCertificates, err := s.loadCertificates(s.buildCertificatePoolPath(id))
if err != nil {
return nil, nil, err
}
@ -76,7 +82,7 @@ func (s *VFSCAStore) readCAKeypairs() (*certificates, *privateKeys, error) {
var caPrivateKeys *privateKeys
if caCertificates != nil {
caPrivateKeys, err = s.loadPrivateKeys(s.buildPrivateKeyPoolPath(CertificateId_CA))
caPrivateKeys, err = s.loadPrivateKeys(s.buildPrivateKeyPoolPath(id))
if err != nil {
return nil, nil, err
}
@ -88,16 +94,16 @@ func (s *VFSCAStore) readCAKeypairs() (*certificates, *privateKeys, error) {
}
if caPrivateKeys == nil {
caCertificates, caPrivateKeys, err = s.generateCACertificate()
caCertificates, caPrivateKeys, err = s.generateCACertificate(id)
if err != nil {
return nil, nil, err
}
}
s.cacheCaCertificates = caCertificates
s.cacheCaPrivateKeys = caPrivateKeys
cached = &cachedEntry{certificates: caCertificates, privateKeys: caPrivateKeys}
s.cachedCAs[id] = cached
return s.cacheCaCertificates, s.cacheCaPrivateKeys, nil
return cached.certificates, cached.privateKeys, nil
}
func BuildCAX509Template() *x509.Certificate {
@ -117,7 +123,7 @@ func BuildCAX509Template() *x509.Certificate {
// Creates and stores CA keypair
// Should be called with the mutex held, to prevent concurrent creation of different keys
func (c *VFSCAStore) generateCACertificate() (*certificates, *privateKeys, error) {
func (c *VFSCAStore) generateCACertificate(id string) (*certificates, *privateKeys, error) {
template := BuildCAX509Template()
caRsaKey, err := rsa.GenerateKey(crypto_rand.Reader, 2048)
@ -135,14 +141,14 @@ func (c *VFSCAStore) generateCACertificate() (*certificates, *privateKeys, error
t := time.Now().UnixNano()
serial := pki.BuildPKISerial(t)
keyPath := c.buildPrivateKeyPath(CertificateId_CA, serial)
keyPath := c.buildPrivateKeyPath(id, serial)
err = c.storePrivateKey(caPrivateKey, keyPath)
if err != nil {
return nil, nil, err
}
// Make double-sure it round-trips
privateKeys, err := c.loadPrivateKeys(c.buildPrivateKeyPoolPath(CertificateId_CA))
privateKeys, err := c.loadPrivateKeys(c.buildPrivateKeyPoolPath(id))
if err != nil {
return nil, nil, err
}
@ -150,14 +156,14 @@ func (c *VFSCAStore) generateCACertificate() (*certificates, *privateKeys, error
return nil, nil, fmt.Errorf("failed to round-trip CA private key")
}
certPath := c.buildCertificatePath(CertificateId_CA, serial)
certPath := c.buildCertificatePath(id, serial)
err = c.storeCertificate(caCertificate, certPath)
if err != nil {
return nil, nil, err
}
// Make double-sure it round-trips
certificates, err := c.loadCertificates(c.buildCertificatePoolPath(CertificateId_CA))
certificates, err := c.loadCertificates(c.buildCertificatePoolPath(id))
if err != nil {
return nil, nil, err
}
@ -414,25 +420,34 @@ func (c *VFSCAStore) MirrorTo(basedir vfs.Path) error {
return vfs.CopyTree(c.basedir, basedir)
}
func (c *VFSCAStore) IssueCert(id string, serial *big.Int, privateKey *pki.PrivateKey, template *x509.Certificate) (*pki.Certificate, error) {
func (c *VFSCAStore) IssueCert(signer string, id string, serial *big.Int, privateKey *pki.PrivateKey, template *x509.Certificate) (*pki.Certificate, error) {
glog.Infof("Issuing new certificate: %q", id)
template.SerialNumber = serial
caCertificates, caPrivateKeys, err := c.readCAKeypairs()
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 {
return nil, fmt.Errorf("ca.key was not found; cannot issue certificates")
return nil, fmt.Errorf("ca key for %q was not found; cannot issue certificates", signer)
}
cert, err := pki.SignNewCertificate(privateKey, template, caCertificates.Primary().Certificate, caPrivateKeys.Primary())
cert, err = pki.SignNewCertificate(privateKey, template, caCertificates.Primary().Certificate, caPrivateKeys.Primary())
if err != nil {
return nil, err
}
}
err = c.StoreKeypair(id, cert, privateKey)
err := c.StoreKeypair(id, cert, privateKey)
if err != nil {
return nil, err
}
@ -557,7 +572,7 @@ func (c *VFSCAStore) loadOnePrivateKey(p vfs.Path) (*pki.PrivateKey, error) {
func (c *VFSCAStore) FindPrivateKey(id string) (*pki.PrivateKey, error) {
var keys *privateKeys
if id == CertificateId_CA {
_, caPrivateKeys, err := c.readCAKeypairs()
_, caPrivateKeys, err := c.readCAKeypairs(id)
if err != nil {
return nil, err
}
@ -592,10 +607,10 @@ func (c *VFSCAStore) PrivateKey(id string, createIfMissing bool) (*pki.PrivateKe
}
func (c *VFSCAStore) CreateKeypair(id string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
func (c *VFSCAStore) CreateKeypair(signer string, id string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
serial := c.buildSerial()
cert, err := c.IssueCert(id, serial, privateKey, template)
cert, err := c.IssueCert(signer, id, serial, privateKey, template)
if err != nil {
return nil, err
}