mirror of
https://github.com/kubevirt/containerized-data-importer.git
synced 2025-06-03 06:30:22 +00:00
231 lines
6.6 KiB
Go
231 lines
6.6 KiB
Go
/*
|
|
Copyright 2018 The CDI 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 controller
|
|
|
|
import (
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"io/ioutil"
|
|
"strings"
|
|
|
|
"github.com/golang/glog"
|
|
"github.com/pkg/errors"
|
|
"k8s.io/api/core/v1"
|
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/util/cert"
|
|
"k8s.io/client-go/util/cert/triple"
|
|
)
|
|
|
|
const (
|
|
keyStoreTLSKeyFile = "tls.key"
|
|
keyStoreTLSCertFile = "tls.cert"
|
|
keyStoreTLSCAFile = "ca.cert"
|
|
)
|
|
|
|
// KeyPairAndCert holds a KeyPair and optional CA
|
|
// In the case of a server key pair, the CA is the CA that signed client certs
|
|
// In the case of a client key pair, the CA is the CA that signed the server cert
|
|
type KeyPairAndCert struct {
|
|
KeyPair triple.KeyPair
|
|
CACert *x509.Certificate
|
|
}
|
|
|
|
// GetOrCreateCA will get the CA KeyPair, creating it if necessary
|
|
func GetOrCreateCA(client kubernetes.Interface, namespace, secretName, caName string) (*triple.KeyPair, error) {
|
|
keyPairAndCert, err := GetKeyPairAndCert(client, namespace, secretName)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error getting CA")
|
|
}
|
|
|
|
if keyPairAndCert != nil {
|
|
glog.Infof("Retrieved CA key/cert %s from kubernetes", caName)
|
|
return &keyPairAndCert.KeyPair, nil
|
|
}
|
|
|
|
glog.Infof("Recreating CA %s", caName)
|
|
|
|
keyPair, err := triple.NewCA(caName)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error creating CA")
|
|
}
|
|
|
|
exists, err := SaveKeyPairAndCert(client, namespace, secretName, KeyPairAndCert{*keyPair, nil}, nil)
|
|
if !exists && err != nil {
|
|
return nil, errors.Wrap(err, "Error saving CA")
|
|
}
|
|
|
|
// do another get
|
|
// this should be very unlikely to hit code path
|
|
if exists {
|
|
keyPairAndCert, err = GetKeyPairAndCert(client, namespace, secretName)
|
|
if keyPairAndCert == nil || err != nil {
|
|
return nil, errors.Wrap(err, "Error getting CA second time around")
|
|
}
|
|
keyPair = &keyPairAndCert.KeyPair
|
|
}
|
|
|
|
return keyPair, nil
|
|
}
|
|
|
|
// CreateServerKeyPairAndCert creates secret for an upload server
|
|
func CreateServerKeyPairAndCert(client kubernetes.Interface,
|
|
namespace,
|
|
secretName string,
|
|
caKeyPair *triple.KeyPair,
|
|
clientCACert *x509.Certificate,
|
|
commonName string,
|
|
serviceName string,
|
|
failIfExists bool,
|
|
owner *metav1.OwnerReference) error {
|
|
keyPair, err := triple.NewServerKeyPair(caKeyPair, commonName, serviceName, namespace, "cluster.local", []string{}, []string{})
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error creating server key pair")
|
|
}
|
|
|
|
exists, err := SaveKeyPairAndCert(client, namespace, secretName, KeyPairAndCert{*keyPair, clientCACert}, owner)
|
|
if exists && !failIfExists {
|
|
return nil
|
|
}
|
|
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error saving server key pair")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateClientKeyPairAndCert creates a secret for upload proxy
|
|
func CreateClientKeyPairAndCert(client kubernetes.Interface,
|
|
namespace, secretName string,
|
|
caKeyPair *triple.KeyPair,
|
|
serverCACert *x509.Certificate,
|
|
commonName string,
|
|
organizations []string,
|
|
failIfExists bool,
|
|
owner *metav1.OwnerReference) error {
|
|
keyPair, err := triple.NewClientKeyPair(caKeyPair, commonName, organizations)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error creating client key pair")
|
|
}
|
|
|
|
exists, err := SaveKeyPairAndCert(client, namespace, secretName, KeyPairAndCert{*keyPair, serverCACert}, owner)
|
|
if exists && !failIfExists {
|
|
return nil
|
|
}
|
|
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error saving client key pair")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetKeyPairAndCert will return the secret data if it exists
|
|
func GetKeyPairAndCert(client kubernetes.Interface, namespace, secretName string) (*KeyPairAndCert, error) {
|
|
var keyPairAndCert KeyPairAndCert
|
|
|
|
secret, err := client.CoreV1().Secrets(namespace).Get(secretName, metav1.GetOptions{})
|
|
if err != nil {
|
|
if k8serrors.IsNotFound(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, errors.Wrap(err, "Error getting secret")
|
|
}
|
|
|
|
if bytes, ok := secret.Data[keyStoreTLSKeyFile]; ok {
|
|
obj, err := cert.ParsePrivateKeyPEM(bytes)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error parsing secret")
|
|
}
|
|
|
|
key, ok := obj.(*rsa.PrivateKey)
|
|
if !ok {
|
|
return nil, errors.New("Invalid pem format")
|
|
}
|
|
|
|
keyPairAndCert.KeyPair.Key = key
|
|
} else {
|
|
return nil, errors.Errorf("Private key missing from secret")
|
|
}
|
|
|
|
if bytes, ok := secret.Data[keyStoreTLSCertFile]; ok {
|
|
certs, err := cert.ParseCertsPEM(bytes)
|
|
if err != nil || len(certs) != 1 {
|
|
return nil, errors.Errorf("Cert parse error %s, %d", err, len(certs))
|
|
}
|
|
keyPairAndCert.KeyPair.Cert = certs[0]
|
|
} else {
|
|
return nil, errors.Errorf("Cert missing from secret")
|
|
}
|
|
|
|
// okay if this doesn't exist
|
|
if bytes, ok := secret.Data[keyStoreTLSCAFile]; ok {
|
|
certs, err := cert.ParseCertsPEM(bytes)
|
|
if err != nil || len(certs) != 1 {
|
|
return nil, errors.Errorf("CA cert parse error %s, %d", err, len(certs))
|
|
}
|
|
keyPairAndCert.CACert = certs[0]
|
|
}
|
|
|
|
return &keyPairAndCert, nil
|
|
}
|
|
|
|
// SaveKeyPairAndCert saves a private key, cert, and maybe a ca cert to kubernetes
|
|
func SaveKeyPairAndCert(client kubernetes.Interface, namespace, secretName string, keyPairAntCA KeyPairAndCert, owner *metav1.OwnerReference) (bool, error) {
|
|
privateKeyBytes := cert.EncodePrivateKeyPEM(keyPairAntCA.KeyPair.Key)
|
|
certBytes := cert.EncodeCertPEM(keyPairAntCA.KeyPair.Cert)
|
|
secret := &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: secretName,
|
|
Namespace: namespace,
|
|
},
|
|
Type: "Opaque",
|
|
Data: map[string][]byte{
|
|
keyStoreTLSKeyFile: privateKeyBytes,
|
|
keyStoreTLSCertFile: certBytes,
|
|
},
|
|
}
|
|
|
|
if owner != nil {
|
|
secret.OwnerReferences = []metav1.OwnerReference{*owner}
|
|
}
|
|
|
|
if keyPairAntCA.CACert != nil {
|
|
caCertBytes := cert.EncodeCertPEM(keyPairAntCA.CACert)
|
|
secret.Data[keyStoreTLSCAFile] = caCertBytes
|
|
}
|
|
|
|
_, err := client.CoreV1().Secrets(namespace).Create(secret)
|
|
if err != nil {
|
|
return k8serrors.IsAlreadyExists(err), errors.Wrap(err, "Error creating cert")
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// GetNamespace returns the namespace the pod is executing in
|
|
func GetNamespace() string {
|
|
if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
|
|
if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
|
|
return ns
|
|
}
|
|
}
|
|
return metav1.NamespaceSystem
|
|
}
|