Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --experimental-issue-pkcs12 flag to enable PKCS12 bundle generation #2643

Merged
merged 1 commit into from Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 36 additions & 0 deletions LICENSES
Expand Up @@ -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
================================================================================

23 changes: 23 additions & 0 deletions cmd/controller/app/options/options.go
Expand Up @@ -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 (
Expand Down Expand Up @@ -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 {
Expand All @@ -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 change: 1 addition & 0 deletions go.mod
Expand Up @@ -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 change: 1 addition & 0 deletions go.sum
Expand Up @@ -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=
2 changes: 2 additions & 0 deletions pkg/controller/certificates/BUILD.bazel
Expand Up @@ -5,6 +5,7 @@ go_library(
srcs = [
"checks.go",
"controller.go",
"keystore.go",
"sync.go",
"util.go",
],
Expand All @@ -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",
Expand Down
20 changes: 20 additions & 0 deletions pkg/controller/certificates/controller.go
Expand Up @@ -19,6 +19,7 @@ package certificates
import (
"context"
"crypto/x509"
"fmt"
"time"

"k8s.io/client-go/kubernetes"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
71 changes: 71 additions & 0 deletions pkg/controller/certificates/keystore.go
@@ -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
}
23 changes: 20 additions & 3 deletions pkg/controller/certificates/sync.go
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package certificates

import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/rsa"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions pkg/controller/context.go
Expand Up @@ -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 {
Expand Down