tls.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package grpc
  14. import (
  15. "context"
  16. "crypto/tls"
  17. "crypto/x509"
  18. "fmt"
  19. "net"
  20. "strings"
  21. corev1 "k8s.io/api/core/v1"
  22. "k8s.io/apimachinery/pkg/types"
  23. ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
  24. )
  25. // TLSConfig holds TLS configuration for connecting to a provider.
  26. type TLSConfig struct {
  27. CACert []byte
  28. ClientCert []byte
  29. ClientKey []byte
  30. // ServerName is optional. If empty, hostname verification is skipped
  31. // but certificate chain validation is still performed (safe for mTLS).
  32. ServerName string
  33. }
  34. // LoadClientTLSConfig loads TLS configuration for connecting to a provider.
  35. // It fetches the provider's certificate secret from Kubernetes.
  36. func LoadClientTLSConfig(
  37. ctx context.Context,
  38. k8sClient ctrlclient.Client,
  39. address string,
  40. namespace string,
  41. ) (*TLSConfig, error) {
  42. // parse the address to get the hostname, it is a url
  43. hostname, _, err := net.SplitHostPort(address)
  44. if err != nil {
  45. hostname = address
  46. }
  47. secretName := "external-secrets-provider-tls"
  48. // Fetch secret
  49. var secret corev1.Secret
  50. key := types.NamespacedName{
  51. Name: secretName,
  52. Namespace: namespace,
  53. }
  54. if err := k8sClient.Get(ctx, key, &secret); err != nil {
  55. return nil, fmt.Errorf("failed to get provider TLS secret %s: %w", secretName, err)
  56. }
  57. // Validate secret data
  58. caCert, ok := secret.Data["ca.crt"]
  59. if !ok || len(caCert) == 0 {
  60. return nil, fmt.Errorf("ca.crt not found or empty in secret %s", secretName)
  61. }
  62. clientCert, ok := secret.Data["client.crt"]
  63. if !ok || len(clientCert) == 0 {
  64. return nil, fmt.Errorf("client.crt not found or empty in secret %s", secretName)
  65. }
  66. clientKey, ok := secret.Data["client.key"]
  67. if !ok || len(clientKey) == 0 {
  68. return nil, fmt.Errorf("client.key not found or empty in secret %s", secretName)
  69. }
  70. return &TLSConfig{
  71. CACert: caCert,
  72. ClientCert: clientCert,
  73. ClientKey: clientKey,
  74. ServerName: hostname,
  75. }, nil
  76. }
  77. // NamespaceFromAddress extracts the service namespace from a Kubernetes service DNS address.
  78. // Expected address formats include:
  79. // - "<service>.<namespace>.svc:port"
  80. // - "<service>.<namespace>.svc.cluster.local:port"
  81. // If parsing fails, fallbackNamespace is returned.
  82. func NamespaceFromAddress(address, fallbackNamespace string) string {
  83. host := address
  84. if parsedHost, _, err := net.SplitHostPort(address); err == nil {
  85. host = parsedHost
  86. }
  87. parts := strings.Split(host, ".")
  88. if len(parts) >= 3 && parts[2] == "svc" && parts[1] != "" {
  89. return parts[1]
  90. }
  91. return fallbackNamespace
  92. }
  93. // ResolveTLSSecretNamespace determines the namespace of the TLS secret used to connect
  94. // to a provider. Service DNS addresses win, otherwise the namespace precedence is:
  95. // auth namespace, resource namespace, providerRef namespace.
  96. func ResolveTLSSecretNamespace(address, authNamespace, resourceNamespace, providerRefNamespace string) string {
  97. fallbackNamespace := authNamespace
  98. if fallbackNamespace == "" {
  99. fallbackNamespace = resourceNamespace
  100. }
  101. if fallbackNamespace == "" {
  102. fallbackNamespace = providerRefNamespace
  103. }
  104. return NamespaceFromAddress(address, fallbackNamespace)
  105. }
  106. // ToGRPCTLSConfig converts TLSConfig to a *tls.Config suitable for gRPC.
  107. func (t *TLSConfig) ToGRPCTLSConfig() (*tls.Config, error) {
  108. // Load client certificate
  109. cert, err := tls.X509KeyPair(t.ClientCert, t.ClientKey)
  110. if err != nil {
  111. return nil, fmt.Errorf("failed to load client certificate: %w", err)
  112. }
  113. // Load CA certificate
  114. caCertPool := x509.NewCertPool()
  115. if !caCertPool.AppendCertsFromPEM(t.CACert) {
  116. return nil, fmt.Errorf("failed to parse CA certificate")
  117. }
  118. tlsConfig := &tls.Config{
  119. Certificates: []tls.Certificate{cert},
  120. RootCAs: caCertPool,
  121. MinVersion: tls.VersionTLS12, // TLS 1.2 minimum for compatibility
  122. ServerName: t.ServerName,
  123. }
  124. return tlsConfig, nil
  125. }