Skip to content

Commit

Permalink
Allow CA dirs to be specified beyond /custom/ca/ (#5859)
Browse files Browse the repository at this point in the history
Signed-off-by: Joel Smith <joelsmith@redhat.com>
  • Loading branch information
joelsmith committed Jun 18, 2024
1 parent 6ccfad9 commit 00a11d2
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
### New

- TODO ([#XXX](https://github.com/kedacore/keda/issues/XXX))
- **General**: Add --ca-dir flag to KEDA operator to specify directories with CA certificates for scalers to authenticate TLS connections (defaults to /custom/ca) ([#5860](https://github.com/kedacore/keda/issues/5860))
- **General**: Declarative parsing of scaler config ([#5037](https://github.com/kedacore/keda/issues/5037)|[#5797](https://github.com/kedacore/keda/issues/5797))
- **General**: Support for Kubernetes v1.30 ([#5828](https://github.com/kedacore/keda/issues/5828))

Expand Down
4 changes: 4 additions & 0 deletions cmd/operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func main() {
var k8sClusterDomain string
var enableCertRotation bool
var validatingWebhookName string
var caDirs []string
pflag.BoolVar(&enablePrometheusMetrics, "enable-prometheus-metrics", true, "Enable the prometheus metric of keda-operator.")
pflag.BoolVar(&enableOpenTelemetryMetrics, "enable-opentelemetry-metrics", false, "Enable the opentelemetry metric of keda-operator.")
pflag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the prometheus metric endpoint binds to.")
Expand All @@ -106,6 +107,7 @@ func main() {
pflag.StringVar(&k8sClusterDomain, "k8s-cluster-domain", "cluster.local", "Kubernetes cluster domain. Defaults to cluster.local")
pflag.BoolVar(&enableCertRotation, "enable-cert-rotation", false, "enable automatic generation and rotation of TLS certificates/keys")
pflag.StringVar(&validatingWebhookName, "validating-webhook-name", "keda-admission", "ValidatingWebhookConfiguration name. Defaults to keda-admission")
pflag.StringArrayVar(&caDirs, "ca-dir", []string{"/custom/ca"}, "Directory with CA certificates for scalers to authenticate TLS connections. Can be specified multiple times. Defaults to /custom/ca")
opts := zap.Options{}
opts.BindFlags(flag.CommandLine)
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
Expand Down Expand Up @@ -300,6 +302,8 @@ func main() {
close(certReady)
}

kedautil.SetCACertDirs(caDirs)

grpcServer := metricsservice.NewGrpcServer(&scaledHandler, metricsServiceAddr, certDir, certReady)
if err := mgr.Add(&grpcServer); err != nil {
setupLog.Error(err, "unable to set up Metrics Service gRPC server")
Expand Down
63 changes: 36 additions & 27 deletions pkg/util/certificates.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,23 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

const customCAPath = "/custom/ca"
const defaultCustomCAPath = "/custom/ca"

var logger = logf.Log.WithName("certificates")

var (
rootCAs *x509.CertPool
rootCAsLock sync.Mutex
rootCAs *x509.CertPool
rootCAsLock sync.Mutex
customCAPaths = []string{defaultCustomCAPath}
)

// SetCACertDirs sets location(s) containing CA certificates which should be trusted for
// all future calls to CreateTLSClientConfig
func SetCACertDirs(caCertDirs []string) {
customCAPaths = caCertDirs
rootCAs = nil // force a reload on the next call to getRootCAs()
}

func getRootCAs() *x509.CertPool {
rootCAsLock.Lock()
defer rootCAsLock.Unlock()
Expand All @@ -56,37 +64,38 @@ func getRootCAs() *x509.CertPool {
logger.V(1).Info("system cert pool not available, using new cert pool instead")
}
}
if _, err := os.Stat(customCAPath); errors.Is(err, fs.ErrNotExist) {
logger.V(1).Info(fmt.Sprintf("the path %s doesn't exist, skipping custom CA registrations", customCAPath))
return rootCAs
}

files, err := os.ReadDir(customCAPath)
if err != nil {
logger.Error(err, fmt.Sprintf("unable to read %s", customCAPath))
return rootCAs
}

for _, file := range files {
filename := file.Name()
if file.IsDir() || strings.HasPrefix(filename, "..") {
logger.V(1).Info(fmt.Sprintf("%s isn't a valid certificate", filename))
continue // Skip directories and special files
for _, customCAPath := range customCAPaths {
if _, err := os.Stat(customCAPath); errors.Is(err, fs.ErrNotExist) {
logger.V(1).Info(fmt.Sprintf("the path %s doesn't exist, skipping custom CA registrations", customCAPath))
continue
}

filePath := filepath.Join(customCAPath, filename)
certs, err := os.ReadFile(filePath)
files, err := os.ReadDir(customCAPath)
if err != nil {
logger.Error(err, fmt.Sprintf("error reading %q", filename))
logger.Error(err, fmt.Sprintf("unable to read %s", customCAPath))
continue
}

if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
logger.Error(fmt.Errorf("no certs appended"), "filename", filename)
continue
for _, file := range files {
filename := file.Name()
if file.IsDir() || strings.HasPrefix(filename, "..") {
logger.V(1).Info(fmt.Sprintf("%s isn't a valid certificate", filename))
continue // Skip directories and special files
}

filePath := filepath.Join(customCAPath, filename)
certs, err := os.ReadFile(filePath)
if err != nil {
logger.Error(err, fmt.Sprintf("error reading %q", filename))
continue
}

if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
logger.Error(fmt.Errorf("no certs appended"), "filename", filename)
continue
}
logger.V(1).Info(fmt.Sprintf("the certificate %s has been added to the pool", filename))
}
logger.V(1).Info(fmt.Sprintf("the certificate %s has been added to the pool", filename))
}

return rootCAs
}
54 changes: 46 additions & 8 deletions pkg/util/certificates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,24 @@ import (
)

var (
caCrtPath = path.Join(customCAPath, "ca.crt")
certCommonName = "test-cert"
certCommonName = "test-cert"
certCommonName2 = "test-cert2"
)

func TestCustomCAsAreRegistered(t *testing.T) {
defer os.Remove(caCrtPath)
generateCA(t)
customCAPath, err := os.MkdirTemp("", "test-ca-certdir-")
require.NoErrorf(t, err, "error creating temporary certs dir - %s", err)
defer os.RemoveAll(customCAPath)

generateCA(t, certCommonName, customCAPath)

SetCACertDirs([]string{customCAPath})

rootCAs := getRootCAs()
//nolint:staticcheck // func (s *CertPool) Subjects was deprecated if s was returned by SystemCertPool, Subjects
subjects := rootCAs.Subjects()
var rdnSequence pkix.RDNSequence
_, err := asn1.Unmarshal(subjects[len(subjects)-1], &rdnSequence)
_, err = asn1.Unmarshal(subjects[len(subjects)-1], &rdnSequence)
if err != nil {
t.Fatal("could not unmarshal der formatted subject")
}
Expand All @@ -56,8 +61,41 @@ func TestCustomCAsAreRegistered(t *testing.T) {
assert.Equal(t, certCommonName, name.CommonName, "certificate not found")
}

func generateCA(t *testing.T) {
err := os.MkdirAll(customCAPath, os.ModePerm)
func TestMultipleCustomCADirs(t *testing.T) {
customCAPath, err := os.MkdirTemp("", "test-ca-certdir-")
require.NoErrorf(t, err, "error creating temporary certs dir - %s", err)
defer os.RemoveAll(customCAPath)
customCAPath2, err := os.MkdirTemp("", "test-ca-certdir2-")
require.NoErrorf(t, err, "error creating temporary certs dir - %s", err)
defer os.RemoveAll(customCAPath2)

generateCA(t, certCommonName, customCAPath)
generateCA(t, certCommonName2, customCAPath2)
SetCACertDirs([]string{customCAPath, customCAPath2})

rootCAs := getRootCAs()
//nolint:staticcheck // func (s *CertPool) Subjects was deprecated if s was returned by SystemCertPool, Subjects
subjects := rootCAs.Subjects()
var rdnSequence pkix.RDNSequence
for i := 0; i < 2; i++ {
_, err = asn1.Unmarshal(subjects[len(subjects)-1-i], &rdnSequence)
if err != nil {
t.Fatal("could not unmarshal der formatted subject")
}
var name pkix.Name
name.FillFromRDNSequence(&rdnSequence)

if i == 1 {
assert.Equal(t, certCommonName, name.CommonName, "certificate not found")
} else {
assert.Equal(t, certCommonName2, name.CommonName, "certificate not found")
}
}
}

func generateCA(t *testing.T, cn, dir string) {
err := os.MkdirAll(dir, os.ModePerm)
caCrtPath := path.Join(dir, "ca.crt")
require.NoErrorf(t, err, "error generating the custom ca folder - %s", err)

ca := &x509.Certificate{
Expand All @@ -69,7 +107,7 @@ func generateCA(t *testing.T) {
Locality: []string{"San Francisco"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
CommonName: certCommonName,
CommonName: cn,
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
Expand Down

0 comments on commit 00a11d2

Please sign in to comment.