<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2019 Red Hat, Inc.
 *
 */

package webhooks

import (
	"crypto/x509"
	"fmt"
	"sync"

	k8sv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/cert"

	"kubevirt.io/kubevirt/pkg/virt-operator/creation/components"

	"kubevirt.io/kubevirt/pkg/util"
)

type ClientCAManager interface {
	GetCurrent() (*x509.CertPool, error)
}

type manager struct {
	store        cache.Store
	lock         *sync.Mutex
	lastRevision string
	namespace    string
	name         string
	secretKey    string

	lastPool *x509.CertPool
}

func NewKubernetesClientCAManager(configMapCache cache.Store) ClientCAManager {
	return &manager{
		store:     configMapCache,
		lock:      &sync.Mutex{},
		namespace: metav1.NamespaceSystem,
		name:      util.ExtensionAPIServerAuthenticationConfigMap,
		secretKey: util.RequestHeaderClientCAFileKey,
	}
}

func NewCAManager(configMapCache cache.Store, namespace string, configMapName string) ClientCAManager {
	return &manager{
		store:     configMapCache,
		lock:      &sync.Mutex{},
		namespace: namespace,
		name:      configMapName,
		secretKey: components.CABundleKey,
	}
}

func (m *manager) GetCurrent() (*x509.CertPool, error) {
	m.lock.Lock()
	defer m.lock.Unlock()

	obj, exists, err := m.store.GetByKey(m.namespace + "/" + m.name)

	if err != nil {
		return nil, err
	} else if !exists {
		if m.lastPool != nil {
			return m.lastPool, nil
		}

		return nil, fmt.Errorf("configmap %s not found. Unable to detect request header CA", m.name)
	}

	configMap := obj.(*k8sv1.ConfigMap)

	// no change detected.
	if m.lastRevision == configMap.ResourceVersion {
		return m.lastPool, nil
	}

	requestHeaderClientCA, ok := configMap.Data[m.secretKey]
	if !ok {
		return nil, fmt.Errorf("requestheader-client-ca-file not found in extension-apiserver-authentication ConfigMap")
	}

	certs, err := cert.ParseCertsPEM([]byte(requestHeaderClientCA))
	if err != nil {
		return nil, err
	}
	pool := x509.NewCertPool()
	for _, cert := range certs {
		pool.AddCert(cert)
	}

	m.lastRevision = configMap.ResourceVersion<commit_msg>Add a log message if we pick up a new CA bundle<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2019 Red Hat, Inc.
 *
 */

package webhooks

import (
	"crypto/x509"
	"fmt"
	"sync"

	k8sv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/cert"

	"kubevirt.io/client-go/log"
	"kubevirt.io/kubevirt/pkg/util"
	"kubevirt.io/kubevirt/pkg/virt-operator/creation/components"
)

type ClientCAManager interface {
	GetCurrent() (*x509.CertPool, error)
}

type manager struct {
	store        cache.Store
	lock         *sync.Mutex
	lastRevision string
	namespace    string
	name         string
	secretKey    string

	lastPool *x509.CertPool
}

func NewKubernetesClientCAManager(configMapCache cache.Store) ClientCAManager {
	return &manager{
		store:        configMapCache,
		lock:         &sync.Mutex{},
		namespace:    metav1.NamespaceSystem,
		name:         util.ExtensionAPIServerAuthenticationConfigMap,
		secretKey:    util.RequestHeaderClientCAFileKey,
		lastRevision: "-1",
	}
}

func NewCAManager(configMapCache cache.Store, namespace string, configMapName string) ClientCAManager {
	return &manager{
		store:        configMapCache,
		lock:         &sync.Mutex{},
		namespace:    namespace,
		name:         configMapName,
		secretKey:    components.CABundleKey,
		lastRevision: "-1",
	}
}

func (m *manager) GetCurrent() (*x509.CertPool, error) {
	m.lock.Lock()
	defer m.lock.Unlock()

	obj, exists, err := m.store.GetByKey(m.namespace + "/" + m.name)

	if err != nil {
		return nil, err
	} else if !exists {
		if m.lastPool != nil {
			return m.lastPool, nil
		}

		return nil, fmt.Errorf("configmap %s not found. Unable to detect request header CA", m.name)
	}

	configMap := obj.(*k8sv1.ConfigMap)

	// no change detected.
	if m.lastRevision == configMap.ResourceVersion {
		return m.lastPool, nil
	}
	log.DefaultLogger().Infof("CA update in configmap %s/%s detected. Updating from resource version %v to %v", m.namespace, m.name, m.lastRevision, configMap.ResourceVersion)

	requestHeaderClientCA, ok := configMap.Data[m.secretKey]
	if !ok {
		return nil, fmt.Errorf("requestheader-client-ca-file not found in configmap %s/%s", m.namespace, m.name)
	}

	certs, err := cert.ParseCertsPEM([]byte(requestHeaderClientCA))
	if err != nil {
		return nil, err
	}
	pool := x509.NewCertPool()
	for _, cert := range certs {
		pool.AddCert(cert)
	}

	m.lastRevision = configMap.ResourceVersion
{'fraction_non_alphanumeric': 0.10969079288305407, 'fraction_numerical': 0.012437381240283296, 'mean_word_length': 3.6468699839486356, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4915479', 'n_tokens_mistral': 2017, 'n_tokens_neox': 1924, 'n_words': 532}