/* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at http://www.apache.org/licenses/LICENSE-2.0 or in the "license" file accompanying this file. This file 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 cert import ( "context" "crypto/tls" "crypto/x509" "fmt" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/util/certificate" "k8s.io/klog/v2" ) // Compile time check that secretCertStore implements the certificate.Store interface var _ certificate.Store = &secretCertStore{} type secretCertStore struct { namespace string secretName string clientset clientset.Interface } // NewSecretCertStore returns a certificate.Store that keeps TLS secrets in a Kubernetes secret object func NewSecretCertStore(namespace, secretName string, clientset clientset.Interface) certificate.Store { return &secretCertStore{ namespace: namespace, secretName: secretName, clientset: clientset, } } func (s *secretCertStore) Current() (*tls.Certificate, error) { secret, err := s.clientset.CoreV1().Secrets(s.namespace).Get( context.TODO(), s.secretName, metav1.GetOptions{}, ) noKeyErr := certificate.NoCertKeyError( fmt.Sprintf("no cert/key files read at secret %s/%s", s.namespace, s.secretName)) if err != nil { klog.Errorf("Error fetching secret: %v", err.Error()) return nil, &noKeyErr } klog.Infof("Fetched secret: %s/%s", s.namespace, s.secretName) keyBytes, ok := secret.Data[v1.TLSPrivateKeyKey] if !ok { return nil, &noKeyErr } certBytes, ok := secret.Data[v1.TLSCertKey] if !ok { return nil, &noKeyErr } return loadX509KeyPairData(certBytes, keyBytes) } func (s *secretCertStore) Update(cert, key []byte) (*tls.Certificate, error) { var secret *v1.Secret var err error secret, err = s.clientset.CoreV1().Secrets(s.namespace).Get( context.TODO(), s.secretName, metav1.GetOptions{}, ) if err != nil { secret = &v1.Secret{} secret.Name = s.secretName secret.Namespace = s.namespace secret.Data = map[string][]byte{ v1.TLSCertKey: cert, v1.TLSPrivateKeyKey: key, } secret.Type = v1.SecretTypeTLS _, err = s.clientset.CoreV1().Secrets(s.namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) if err != nil { klog.Errorf("Error creating secret: %v", err.Error()) return nil, err } return loadX509KeyPairData(cert, key) } secret.Data = map[string][]byte{ v1.TLSCertKey: cert, v1.TLSPrivateKeyKey: key, } _, err = s.clientset.CoreV1().Secrets(s.namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) if err != nil { klog.Errorf("Error updating secret: %v", err.Error()) return nil, err } return loadX509KeyPairData(cert, key) } func loadX509KeyPairData(cert, key []byte) (*tls.Certificate, error) { tlsCert, err := tls.X509KeyPair(cert, key) if err != nil { klog.Errorf("Error parsing bytes: %v", err.Error()) return nil, err } certs, err := x509.ParseCertificates(tlsCert.Certificate[0]) if err != nil { return nil, fmt.Errorf("unable to parse certificate data: %v", err) } tlsCert.Leaf = certs[0] return &tlsCert, nil }