| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- /*
- Copyright © The ESO 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
- https://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 grpc
- import (
- "context"
- "crypto/tls"
- "crypto/x509"
- "fmt"
- "net"
- "strings"
- corev1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/types"
- ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
- )
- // TLSConfig holds TLS configuration for connecting to a provider.
- type TLSConfig struct {
- CACert []byte
- ClientCert []byte
- ClientKey []byte
- // ServerName is optional. If empty, hostname verification is skipped
- // but certificate chain validation is still performed (safe for mTLS).
- ServerName string
- }
- // LoadClientTLSConfig loads TLS configuration for connecting to a provider.
- // It fetches the provider's certificate secret from Kubernetes.
- func LoadClientTLSConfig(
- ctx context.Context,
- k8sClient ctrlclient.Client,
- address string,
- namespace string,
- ) (*TLSConfig, error) {
- // parse the address to get the hostname, it is a url
- hostname, _, err := net.SplitHostPort(address)
- if err != nil {
- hostname = address
- }
- secretName := "external-secrets-provider-tls"
- // Fetch secret
- var secret corev1.Secret
- key := types.NamespacedName{
- Name: secretName,
- Namespace: namespace,
- }
- if err := k8sClient.Get(ctx, key, &secret); err != nil {
- return nil, fmt.Errorf("failed to get provider TLS secret %s: %w", secretName, err)
- }
- // Validate secret data
- caCert, ok := secret.Data["ca.crt"]
- if !ok || len(caCert) == 0 {
- return nil, fmt.Errorf("ca.crt not found or empty in secret %s", secretName)
- }
- clientCert, ok := secret.Data["client.crt"]
- if !ok || len(clientCert) == 0 {
- return nil, fmt.Errorf("client.crt not found or empty in secret %s", secretName)
- }
- clientKey, ok := secret.Data["client.key"]
- if !ok || len(clientKey) == 0 {
- return nil, fmt.Errorf("client.key not found or empty in secret %s", secretName)
- }
- return &TLSConfig{
- CACert: caCert,
- ClientCert: clientCert,
- ClientKey: clientKey,
- ServerName: hostname,
- }, nil
- }
- // NamespaceFromAddress extracts the service namespace from a Kubernetes service DNS address.
- // Expected address formats include:
- // - "<service>.<namespace>.svc:port"
- // - "<service>.<namespace>.svc.cluster.local:port"
- // If parsing fails, fallbackNamespace is returned.
- func NamespaceFromAddress(address, fallbackNamespace string) string {
- host := address
- if parsedHost, _, err := net.SplitHostPort(address); err == nil {
- host = parsedHost
- }
- parts := strings.Split(host, ".")
- if len(parts) >= 3 && parts[2] == "svc" && parts[1] != "" {
- return parts[1]
- }
- return fallbackNamespace
- }
- // ResolveTLSSecretNamespace determines the namespace of the TLS secret used to connect
- // to a provider. Service DNS addresses win, otherwise the namespace precedence is:
- // auth namespace, resource namespace, providerRef namespace.
- func ResolveTLSSecretNamespace(address, authNamespace, resourceNamespace, providerRefNamespace string) string {
- fallbackNamespace := authNamespace
- if fallbackNamespace == "" {
- fallbackNamespace = resourceNamespace
- }
- if fallbackNamespace == "" {
- fallbackNamespace = providerRefNamespace
- }
- return NamespaceFromAddress(address, fallbackNamespace)
- }
- // ToGRPCTLSConfig converts TLSConfig to a *tls.Config suitable for gRPC.
- func (t *TLSConfig) ToGRPCTLSConfig() (*tls.Config, error) {
- // Load client certificate
- cert, err := tls.X509KeyPair(t.ClientCert, t.ClientKey)
- if err != nil {
- return nil, fmt.Errorf("failed to load client certificate: %w", err)
- }
- // Load CA certificate
- caCertPool := x509.NewCertPool()
- if !caCertPool.AppendCertsFromPEM(t.CACert) {
- return nil, fmt.Errorf("failed to parse CA certificate")
- }
- tlsConfig := &tls.Config{
- Certificates: []tls.Certificate{cert},
- RootCAs: caCertPool,
- MinVersion: tls.VersionTLS12, // TLS 1.2 minimum for compatibility
- ServerName: t.ServerName,
- }
- return tlsConfig, nil
- }
|