From fb099ae3856750ddad24a8cc5742b515d033c75d Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Mon, 7 Nov 2016 10:25:45 -0800 Subject: [PATCH] certificates: support allowed usage --- pkg/apis/certificates/types.go | 37 +++++++ pkg/apis/certificates/v1alpha1/types.go | 37 +++++++ pkg/controller/certificates/BUILD | 1 + .../certificates/certificate_controller.go | 23 ++--- pkg/controller/certificates/cfssl_signer.go | 99 +++++++++++++++++++ 5 files changed, 182 insertions(+), 15 deletions(-) create mode 100644 pkg/controller/certificates/cfssl_signer.go diff --git a/pkg/apis/certificates/types.go b/pkg/apis/certificates/types.go index 71bc07c21ac..02e2135973b 100644 --- a/pkg/apis/certificates/types.go +++ b/pkg/apis/certificates/types.go @@ -46,6 +46,12 @@ type CertificateSigningRequestSpec struct { // Base64-encoded PKCS#10 CSR data Request []byte + // usages specifies a set of usage contexts the key will be + // valid for. + // See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 + // https://tools.ietf.org/html/rfc5280#section-4.2.1.12 + Usages []KeyUsage + // Information about the requesting user (if relevant) // See user.Info interface for details // +optional @@ -96,3 +102,34 @@ type CertificateSigningRequestList struct { // +optional Items []CertificateSigningRequest } + +// KeyUsages specifies valid usage contexts for keys. +// See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 +// https://tools.ietf.org/html/rfc5280#section-4.2.1.12 +type KeyUsage string + +const ( + UsageSigning KeyUsage = "signing" + UsageDigitalSignature KeyUsage = "digital signature" + UsageContentCommittment KeyUsage = "content committment" + UsageKeyEncipherment KeyUsage = "key encipherment" + UsageKeyAgreement KeyUsage = "key agreement" + UsageDataEncipherment KeyUsage = "data encipherment" + UsageCertSign KeyUsage = "cert sign" + UsageCRLSign KeyUsage = "crl sign" + UsageEncipherOnly KeyUsage = "encipher only" + UsageDecipherOnly KeyUsage = "decipher only" + UsageAny KeyUsage = "any" + UsageServerAuth KeyUsage = "server auth" + UsageClientAuth KeyUsage = "client auth" + UsageCodeSigning KeyUsage = "code signing" + UsageEmailProtection KeyUsage = "email protection" + UsageSMIME KeyUsage = "s/mime" + UsageIPsecEndSystem KeyUsage = "ipsec end system" + UsageIPsecTunnel KeyUsage = "ipsec tunnel" + UsageIPsecUser KeyUsage = "ipsec user" + UsageTimestamping KeyUsage = "timestamping" + UsageOCSPSigning KeyUsage = "ocsp signing" + UsageMicrosoftSGC KeyUsage = "microsoft sgc" + UsageNetscapSGC KeyUsage = "netscape sgc" +) diff --git a/pkg/apis/certificates/v1alpha1/types.go b/pkg/apis/certificates/v1alpha1/types.go index cf9e4f09022..a5e70bfcff9 100644 --- a/pkg/apis/certificates/v1alpha1/types.go +++ b/pkg/apis/certificates/v1alpha1/types.go @@ -46,6 +46,12 @@ type CertificateSigningRequestSpec struct { // Base64-encoded PKCS#10 CSR data Request []byte `json:"request" protobuf:"bytes,1,opt,name=request"` + // allowedUsages specifies a set of usage contexts the key will be + // valid for. + // See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 + // https://tools.ietf.org/html/rfc5280#section-4.2.1.12 + Usages []KeyUsage `json:"usages,omitempty" protobuf:"bytes,5,opt,name=keyUsage"` + // Information about the requesting user (if relevant) // See user.Info interface for details // +optional @@ -95,3 +101,34 @@ type CertificateSigningRequestList struct { Items []CertificateSigningRequest `json:"items" protobuf:"bytes,2,rep,name=items"` } + +// KeyUsages specifies valid usage contexts for keys. +// See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 +// https://tools.ietf.org/html/rfc5280#section-4.2.1.12 +type KeyUsage string + +const ( + UsageSigning KeyUsage = "signing" + UsageDigitalSignature KeyUsage = "digital signature" + UsageContentCommittment KeyUsage = "content committment" + UsageKeyEncipherment KeyUsage = "key encipherment" + UsageKeyAgreement KeyUsage = "key agreement" + UsageDataEncipherment KeyUsage = "data encipherment" + UsageCertSign KeyUsage = "cert sign" + UsageCRLSign KeyUsage = "crl sign" + UsageEncipherOnly KeyUsage = "encipher only" + UsageDecipherOnly KeyUsage = "decipher only" + UsageAny KeyUsage = "any" + UsageServerAuth KeyUsage = "server auth" + UsageClientAuth KeyUsage = "client auth" + UsageCodeSigning KeyUsage = "code signing" + UsageEmailProtection KeyUsage = "email protection" + UsageSMIME KeyUsage = "s/mime" + UsageIPsecEndSystem KeyUsage = "ipsec end system" + UsageIPsecTunnel KeyUsage = "ipsec tunnel" + UsageIPsecUser KeyUsage = "ipsec user" + UsageTimestamping KeyUsage = "timestamping" + UsageOCSPSigning KeyUsage = "ocsp signing" + UsageMicrosoftSGC KeyUsage = "microsoft sgc" + UsageNetscapSGC KeyUsage = "netscape sgc" +) diff --git a/pkg/controller/certificates/BUILD b/pkg/controller/certificates/BUILD index bdfb182744c..9540ee798f4 100644 --- a/pkg/controller/certificates/BUILD +++ b/pkg/controller/certificates/BUILD @@ -32,6 +32,7 @@ go_library( "//pkg/util/workqueue:go_default_library", "//pkg/watch:go_default_library", "//vendor:github.com/cloudflare/cfssl/config", + "//vendor:github.com/cloudflare/cfssl/helpers", "//vendor:github.com/cloudflare/cfssl/signer", "//vendor:github.com/cloudflare/cfssl/signer/local", "//vendor:github.com/golang/glog", diff --git a/pkg/controller/certificates/certificate_controller.go b/pkg/controller/certificates/certificate_controller.go index 81339c27c63..c073481a069 100644 --- a/pkg/controller/certificates/certificate_controller.go +++ b/pkg/controller/certificates/certificate_controller.go @@ -33,9 +33,6 @@ import ( "k8s.io/kubernetes/pkg/util/workqueue" "k8s.io/kubernetes/pkg/watch" - "github.com/cloudflare/cfssl/config" - "github.com/cloudflare/cfssl/signer" - "github.com/cloudflare/cfssl/signer/local" "github.com/golang/glog" ) @@ -43,6 +40,10 @@ type AutoApprover interface { AutoApprove(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, error) } +type Signer interface { + Sign(csr *certificates.CertificateSigningRequest) ([]byte, error) +} + type CertificateController struct { kubeClient clientset.Interface @@ -53,8 +54,7 @@ type CertificateController struct { syncHandler func(csrKey string) error approver AutoApprover - - signer *local.Signer + signer Signer queue workqueue.RateLimitingInterface } @@ -65,12 +65,7 @@ func NewCertificateController(kubeClient clientset.Interface, syncPeriod time.Du eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeClient.Core().Events("")}) - // Configure cfssl signer - // TODO: support non-default policy and remote/pkcs11 signing - policy := &config.Signing{ - Default: config.DefaultConfig(), - } - ca, err := local.NewSignerFromFile(caCertFile, caKeyFile, policy) + s, err := NewCFSSLSigner(caCertFile, caKeyFile) if err != nil { return nil, err } @@ -78,7 +73,7 @@ func NewCertificateController(kubeClient clientset.Interface, syncPeriod time.Du cc := &CertificateController{ kubeClient: kubeClient, queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "certificate"), - signer: ca, + signer: s, approver: approver, } @@ -209,9 +204,7 @@ func (cc *CertificateController) maybeSignCertificate(key string) error { // 3. Update the Status subresource if csr.Status.Certificate == nil && IsCertificateRequestApproved(csr) { - pemBytes := csr.Spec.Request - req := signer.SignRequest{Request: string(pemBytes)} - certBytes, err := cc.signer.Sign(req) + certBytes, err := cc.signer.Sign(csr) if err != nil { return err } diff --git a/pkg/controller/certificates/cfssl_signer.go b/pkg/controller/certificates/cfssl_signer.go new file mode 100644 index 00000000000..a6f50e2f542 --- /dev/null +++ b/pkg/controller/certificates/cfssl_signer.go @@ -0,0 +1,99 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certificates + +import ( + "crypto" + "crypto/x509" + "fmt" + "io/ioutil" + "os" + + certificates "k8s.io/kubernetes/pkg/apis/certificates/v1alpha1" + + "github.com/cloudflare/cfssl/config" + "github.com/cloudflare/cfssl/helpers" + "github.com/cloudflare/cfssl/signer" + "github.com/cloudflare/cfssl/signer/local" +) + +var onlySigningPolicy = &config.Signing{ + Default: &config.SigningProfile{ + Usage: []string{"signing"}, + Expiry: helpers.OneYear, + ExpiryString: "8760h", + }, +} + +type CFSSLSigner struct { + ca *x509.Certificate + priv crypto.Signer + sigAlgo x509.SignatureAlgorithm +} + +func NewCFSSLSigner(caFile, caKeyFile string) (*CFSSLSigner, error) { + ca, err := ioutil.ReadFile(caFile) + if err != nil { + return nil, err + } + cakey, err := ioutil.ReadFile(caKeyFile) + if err != nil { + return nil, err + } + + parsedCa, err := helpers.ParseCertificatePEM(ca) + if err != nil { + return nil, err + } + + strPassword := os.Getenv("CFSSL_CA_PK_PASSWORD") + password := []byte(strPassword) + if strPassword == "" { + password = nil + } + + priv, err := helpers.ParsePrivateKeyPEMWithPassword(cakey, password) + if err != nil { + return nil, fmt.Errorf("Malformed private key %v", err) + } + return &CFSSLSigner{ + priv: priv, + ca: parsedCa, + sigAlgo: signer.DefaultSigAlgo(priv), + }, nil +} + +func (cs *CFSSLSigner) Sign(csr *certificates.CertificateSigningRequest) ([]byte, error) { + var usages []string + for _, usage := range csr.Spec.Usages { + usages = append(usages, string(usage)) + } + policy := &config.Signing{ + Default: &config.SigningProfile{ + Usage: usages, + Expiry: helpers.OneYear, + ExpiryString: "8760h", + }, + } + s, err := local.NewSigner(cs.priv, cs.ca, cs.sigAlgo, policy) + if err != nil { + return nil, err + } + return s.Sign(signer.SignRequest{ + Request: string(csr.Spec.Request), + }) +}