Merge pull request #2643 from munnerz/keystore-mvp

Add --experimental-issue-pkcs12 flag to enable PKCS12 bundle generation
This commit is contained in:
jetstack-bot 2020-03-04 10:30:58 +00:00 committed by GitHub
commit 02da8fbd6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 186 additions and 3 deletions

View File

@ -17898,3 +17898,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
= vendor/sigs.k8s.io/yaml/LICENSE 0ceb9ff3b27d3a8cf451ca3785d73c71
================================================================================
================================================================================
= vendor/software.sslmate.com/src/go-pkcs12 licensed under: =
Copyright (c) 2015, 2018 Opsmate, Inc.
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
= vendor/software.sslmate.com/src/go-pkcs12/LICENSE 59ab963efbc183656060b23893ea5745
================================================================================

View File

@ -94,6 +94,18 @@ type ControllerOptions struct {
// DNSNames are the dns names that should be set on the serving certificate
WebhookDNSNames []string
// ExperimentalIssuePKCS12, if true, will make the certificates controller
// create a `keystore.p12` in the Secret resource for each Certificate.
// This can only be toggled globally, and the keystore will be encrypted
// with the supplied ExperimentalPKCS12KeystorePassword.
// This flag is likely to be removed in future in favour of native PKCS12
// keystore bundle support.
ExperimentalIssuePKCS12 bool
// ExperimentalPKCS12KeystorePassword is the password used to encrypt and
// decrypt PKCS#12 bundles stored in Secret resources.
// This option only has any affect is ExperimentalIssuePKCS12 is true.
ExperimentalPKCS12KeystorePassword string
}
const (
@ -289,6 +301,12 @@ func (s *ControllerOptions) AddFlags(fs *pflag.FlagSet) {
"serving certificate.")
fs.StringSliceVar(&s.WebhookDNSNames, "webhook-dns-names", defaultWebhookDNSNames, "Comma-separated list of DNS names that should be present on "+
"the webhook's serving certificate.")
fs.BoolVar(&s.ExperimentalIssuePKCS12, "experimental-issue-pkcs12", false, "If true, the certificate controller will create 'keystore.p12' files in Secret resources it "+
"manages, containing a copy of the certificate data encrypted using the provided --experimental-pkcs12-keystore-password. "+
"If true, --experimental-pkcs12-keystore-password must be provided.")
fs.StringVar(&s.ExperimentalPKCS12KeystorePassword, "experimental-pkcs12-keystore-password", "", "The password used to encrypt and decrypt PKCS#12 "+
"bundles stored in Secret resources. This field is required if --experimental-issue-pkcs12 is enabled.")
}
func (o *ControllerOptions) Validate() error {
@ -306,5 +324,10 @@ func (o *ControllerOptions) Validate() error {
return fmt.Errorf("invalid DNS server (%v): %v", err, server)
}
}
if o.ExperimentalIssuePKCS12 && len(o.ExperimentalPKCS12KeystorePassword) == 0 {
return fmt.Errorf("--experimental-pkcs12-keystore-password must be specified if --experimental-issue-pkcs12 is enabled")
}
return nil
}

1
go.mod
View File

@ -55,4 +55,5 @@ require (
sigs.k8s.io/controller-runtime v0.4.0
sigs.k8s.io/controller-tools v0.2.5
sigs.k8s.io/testing_frameworks v0.1.2
software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237
)

1
go.sum
View File

@ -744,4 +744,5 @@ sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH
sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237 h1:iAEkCBPbRaflBgZ7o9gjVUuWuvWeV4sytFWg9o+Pj2k=
software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237/go.mod h1:/xvNRWUqm0+/ZMiF4EX00vrSCMsE4/NHb+Pt3freEeQ=

View File

@ -5,6 +5,7 @@ go_library(
srcs = [
"checks.go",
"controller.go",
"keystore.go",
"sync.go",
"util.go",
],
@ -26,6 +27,7 @@ go_library(
"//pkg/util/pki:go_default_library",
"@com_github_go_logr_logr//:go_default_library",
"@com_github_kr_pretty//:go_default_library",
"@com_sslmate_software_src_go_pkcs12//:go_default_library",
"@io_k8s_api//core/v1:go_default_library",
"@io_k8s_apimachinery//pkg/api/errors:go_default_library",
"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",

View File

@ -19,6 +19,7 @@ package certificates
import (
"context"
"crypto/x509"
"fmt"
"time"
"k8s.io/client-go/kubernetes"
@ -82,6 +83,18 @@ type certificateRequestManager struct {
// Secret resource will be automatically deleted.
// This option is disabled by default.
enableSecretOwnerReferences bool
// experimentalIssuePKCS12, if true, will make the certificates controller
// create a `keystore.p12` in the Secret resource for each Certificate.
// This can only be toggled globally, and the keystore will be encrypted
// with the supplied ExperimentalPKCS12KeystorePassword.
// This flag is likely to be removed in future in favour of native PKCS12
// keystore bundle support.
experimentalIssuePKCS12 bool
// ExperimentalPKCS12KeystorePassword is the password used to encrypt and
// decrypt PKCS#12 bundles stored in Secret resources.
// This option only has any affect is ExperimentalIssuePKCS12 is true.
experimentalPKCS12KeystorePassword string
}
type localTemporarySignerFn func(crt *cmapi.Certificate, pk []byte) ([]byte, error)
@ -139,6 +152,13 @@ func (c *certificateRequestManager) Register(ctx *controllerpkg.Context) (workqu
c.localTemporarySigner = generateLocallySignedTemporaryCertificate
c.enableSecretOwnerReferences = ctx.CertificateOptions.EnableOwnerRef
// Experimental PKCS12 handling options
c.experimentalIssuePKCS12 = ctx.CertificateOptions.ExperimentalIssuePKCS12
c.experimentalPKCS12KeystorePassword = ctx.CertificateOptions.ExperimentalPKCS12KeystorePassword
if c.experimentalIssuePKCS12 && len(c.experimentalPKCS12KeystorePassword) == 0 {
return nil, nil, nil, fmt.Errorf("if experimental pkcs12 issuance is enabled, a keystore password must be provided")
}
c.cmClient = ctx.CMClient
c.kubeClient = ctx.Client

View File

@ -0,0 +1,71 @@
/*
Copyright 2020 The Jetstack cert-manager contributors.
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.
*/
// This file defines methods used for PKCS#12 support.
// This is an experimental feature and the contents of this file are intended
// to be absorbed into a more fully fledged implementing ahead of the v0.15
// release.
// This should hopefully not exist by the next time you come to read this :)
package certificates
import (
"crypto/rand"
"crypto/x509"
"software.sslmate.com/src/go-pkcs12"
"github.com/jetstack/cert-manager/pkg/util/pki"
)
const (
// pkcs12SecretKey is the name of the data entry in the Secret resource
// used to store the p12 file.
pkcs12SecretKey = "keystore.p12"
)
// encodePKCS12Keystore will encode a PKCS12 keystore using the password provided.
// The key, certificate and CA data must be provided in PKCS1 or PKCS8 PEM format.
// If the certificate data contains multiple certificates, the first will be used
// as the keystores 'certificate' and the remaining certificates will be prepended
// to the list of CAs in the resulting keystore.
func encodePKCS12Keystore(password string, rawKey []byte, certPem []byte, caPem []byte) ([]byte, error) {
key, err := pki.DecodePrivateKeyBytes(rawKey)
if err != nil {
return nil, err
}
certs, err := pki.DecodeX509CertificateChainBytes(certPem)
if err != nil {
return nil, err
}
var cas []*x509.Certificate
if len(caPem) > 0 {
cas, err = pki.DecodeX509CertificateChainBytes(caPem)
if err != nil {
return nil, err
}
// prepend the certificate chain to the list of certificates as the PKCS12
// library only allows setting a single certificate.
if len(certs) > 1 {
cas = append(certs[1:], cas...)
}
}
keystoreData, err := pkcs12.Encode(rand.Reader, key, certs[0], cas, password)
if err != nil {
return nil, err
}
return keystoreData, nil
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package certificates
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/rsa"
@ -536,7 +537,7 @@ func (c *certificateRequestManager) updateSecretData(ctx context.Context, crt *c
}
newSecret := s.DeepCopy()
err := setSecretValues(ctx, crt, newSecret, secretData{pk: data.pk, cert: data.cert, ca: data.ca})
err := c.setSecretValues(ctx, crt, newSecret, secretData{pk: data.pk, cert: data.cert, ca: data.ca})
if err != nil {
return false, err
}
@ -582,7 +583,7 @@ func (c *certificateRequestManager) issueTemporaryCertificate(ctx context.Contex
}
newSecret := secret.DeepCopy()
err = setSecretValues(ctx, crt, newSecret, secretData{pk: key, cert: tempCertData})
err = c.setSecretValues(ctx, crt, newSecret, secretData{pk: key, cert: tempCertData})
if err != nil {
return err
}
@ -808,12 +809,28 @@ type secretData struct {
// If updating an existing Secret resource returned by an api client 'lister',
// make sure to DeepCopy the object first to avoid modifying data in-cache.
// It will also update depreciated issuer name and kind annotations if they exist.
func setSecretValues(ctx context.Context, crt *cmapi.Certificate, s *corev1.Secret, data secretData) error {
func (c *certificateRequestManager) setSecretValues(ctx context.Context, crt *cmapi.Certificate, s *corev1.Secret, data secretData) error {
// initialize the `Data` field if it is nil
if s.Data == nil {
s.Data = make(map[string][]byte)
}
// Handle the experimental PKCS12 support
if c.experimentalIssuePKCS12 {
// Only write a new PKCS12 file if any of the private key/certificate/CA data has
// actually changed.
if data.pk != nil && data.cert != nil &&
(!bytes.Equal(s.Data[corev1.TLSPrivateKeyKey], data.pk) ||
!bytes.Equal(s.Data[corev1.TLSCertKey], data.cert) ||
!bytes.Equal(s.Data[cmmeta.TLSCAKey], data.ca)) {
keystoreData, err := encodePKCS12Keystore(c.experimentalPKCS12KeystorePassword, data.pk, data.cert, data.ca)
if err != nil {
return fmt.Errorf("error encoding PKCS12 bundle: %w", err)
}
// always overwrite the keystore entry for now
s.Data[pkcs12SecretKey] = keystoreData
}
}
s.Data[corev1.TLSPrivateKeyKey] = data.pk
s.Data[corev1.TLSCertKey] = data.cert
s.Data[cmmeta.TLSCAKey] = data.ca

View File

@ -132,6 +132,18 @@ type CertificateOptions struct {
// EnableOwnerRef controls whether the certificate is configured as an owner of
// secret where the effective TLS certificate is stored.
EnableOwnerRef bool
// ExperimentalIssuePKCS12, if true, will make the certificates controller
// create a `keystore.p12` in the Secret resource for each Certificate.
// This can only be toggled globally, and the keystore will be encrypted
// with the supplied ExperimentalPKCS12KeystorePassword.
// This flag is likely to be removed in future in favour of native PKCS12
// keystore bundle support.
ExperimentalIssuePKCS12 bool
// ExperimentalPKCS12KeystorePassword is the password used to encrypt and
// decrypt PKCS#12 bundles stored in Secret resources.
// This option only has any affect is ExperimentalIssuePKCS12 is true.
ExperimentalPKCS12KeystorePassword string
}
type SchedulerOptions struct {