Skip to content

Commit

Permalink
Fix certmonitor labelselector (#1998)
Browse files Browse the repository at this point in the history
* Fix certMonitor labelSelector

* Fix certMonitor labelSelector
  • Loading branch information
ludydoo authored Aug 21, 2024
1 parent a0b3024 commit 15703b3
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 68 deletions.
9 changes: 4 additions & 5 deletions fleetshard/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,11 @@ func main() {
}

k8sInterface := k8s.CreateInterfaceOrDie()
informedFactory := informers.NewSharedInformerFactory(k8sInterface, time.Minute)
secretInformer := informedFactory.Core().V1().Secrets().Informer()
namespaceLister := informedFactory.Core().V1().Namespaces().Lister()

monitor := certmonitor.NewCertMonitor(certmonitorConfig, informedFactory, secretInformer, namespaceLister)
informerFactory := informers.NewSharedInformerFactory(k8sInterface, time.Minute)
secretInformer := informerFactory.Core().V1().Secrets().Informer()
namespaceLister := informerFactory.Core().V1().Namespaces().Lister()

monitor := certmonitor.NewCertMonitor(certmonitorConfig, informerFactory, secretInformer, namespaceLister)
if err := monitor.Start(); err != nil {
glog.Fatalf("Error starting certmonitor: %v", err)
}
Expand Down
83 changes: 72 additions & 11 deletions internal/certmonitor/certmonitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ package certmonitor

import (
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"github.com/golang/glog"
"github.com/pkg/errors"
"github.com/stackrox/acs-fleet-manager/fleetshard/pkg/fleetshardmetrics"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache"
Expand Down Expand Up @@ -55,15 +55,13 @@ func (c *certMonitor) Start() error {
return errors.New("already started")
}
c.stopCh = make(chan struct{})

c.secretInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: c.handleSecretCreation,
UpdateFunc: c.handleSecretUpdate,
DeleteFunc: c.handleSecretDeletion,
})
c.informerfactory.Start(c.stopCh)

if !cache.WaitForCacheSync(c.stopCh, c.secretInformer.HasSynced) {
if !cache.WaitForCacheSync(c.stopCh) {
return fmt.Errorf("timed out waiting for caches to sync")
}
return nil
Expand Down Expand Up @@ -91,20 +89,17 @@ func NewCertMonitor(config *Config, informerFactory informers.SharedInformerFact
// processSecret extracts, decodes, parses certificates from a secret, and populates prometheus metrics
func (c *certMonitor) processSecret(secret *corev1.Secret) {
for dataKey, dataCert := range secret.Data {
certConv, err := base64.StdEncoding.DecodeString(string(dataCert))
if err != nil {
continue
}

pparse, _ := pem.Decode(certConv)
pparse, _ := pem.Decode(dataCert)
if pparse == nil {
continue
}

certss, err := x509.ParseCertificate(pparse.Bytes)
if err != nil {
glog.Errorf("Failed to parse certificate %s: %v", dataKey, err)
continue
}

expiryTime := float64(certss.NotAfter.Unix())
c.metrics.SetCertKeyExpiryMetric(secret.Namespace, secret.Name, dataKey, expiryTime)
}
Expand All @@ -116,6 +111,9 @@ func (c *certMonitor) handleSecretCreation(obj interface{}) {
if !ok {
return
}
if !c.shouldProcessSecret(secret) {
return
}
c.processSecret(secret)
}

Expand All @@ -140,6 +138,11 @@ func (c *certMonitor) handleSecretUpdate(oldObj, newObj interface{}) {
c.metrics.DeleteKeyCertMetric(newSecret.Namespace, newSecret.Name, oldKey)
}
}

if !c.shouldProcessSecret(newSecret) {
return
}

c.processSecret(newSecret)
}

Expand All @@ -149,9 +152,67 @@ func (c *certMonitor) handleSecretDeletion(obj interface{}) {
if !ok {
return
}
if !c.shouldProcessSecret(secret) {
return
}
c.metrics.DeleteCertMetric(secret.Namespace, secret.Name)
}

func (c *certMonitor) shouldProcessSecret(s *corev1.Secret) bool {
for _, monitor := range c.config.Monitors {
if c.secretMatches(s, monitor) {
return true
}
}
return false
}

// secretMatches checks if a secret matches a monitor config
func (c *certMonitor) secretMatches(s *corev1.Secret, monitor MonitorConfig) bool {
if s == nil {
return false
}
if len(monitor.Secret.Name) > 0 && s.Name != monitor.Secret.Name {
return false
}
if len(monitor.Namespace.Name) > 0 && s.Namespace != monitor.Namespace.Name {
return false
}
if monitor.Secret.LabelSelector != nil && !objectMatchesSelector(s, monitor.Secret.LabelSelector) {
return false
}

if monitor.Namespace.LabelSelector != nil {
ns, err := c.namespaceGetter.Get(s.Namespace)
if err != nil {
return false
}
if !objectMatchesSelector(ns, monitor.Namespace.LabelSelector) {
return false
}
}
return true
}

// objectMatchesSelector checks if object matches given label selector
func objectMatchesSelector(obj runtime.Object, selector *metav1.LabelSelector) bool {
if selector == nil {
return true
}
labelselector, err := metav1.LabelSelectorAsSelector(selector)
if err != nil {
return false
}

metaObj, ok := obj.(metav1.Object)
if !ok {
return false
}

return labelselector.Matches(labels.Set(metaObj.GetLabels()))

}

// ValidateConfig checks the validity of Config
func ValidateConfig(config Config) (errs field.ErrorList) {
errs = append(errs, validateMonitors(field.NewPath("monitors"), config.Monitors)...)
Expand Down
66 changes: 14 additions & 52 deletions internal/certmonitor/certmonitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"github.com/prometheus/client_golang/prometheus/testutil"
Expand All @@ -14,8 +13,6 @@ import (
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"math/big"
"testing"
"time"
Expand Down Expand Up @@ -44,52 +41,6 @@ func newFakeNamespaceGetter(namespaces []v1.Namespace) *fakeNamespaceGetter {
return &f
}

// objectMatchesSelector checks if object matches given label selector
func objectMatchesSelector(obj runtime.Object, selector *metav1.LabelSelector) bool {
if selector == nil {
return true
}
labelselector, err := metav1.LabelSelectorAsSelector(selector)
if err != nil {
return false
}

metaObj, ok := obj.(metav1.Object)
if !ok {
return false
}

return labelselector.Matches(labels.Set(metaObj.GetLabels()))

}

// secretMatches checks if a secret matches a monitor config
func (c *certMonitor) secretMatches(s *v1.Secret, monitor MonitorConfig) bool {
if s == nil {
return false
}
if len(monitor.Secret.Name) > 0 && s.Name != monitor.Secret.Name {
return false
}
if len(monitor.Namespace.Name) > 0 && s.Namespace != monitor.Namespace.Name {
return false
}
if monitor.Secret.LabelSelector != nil && !objectMatchesSelector(s, monitor.Secret.LabelSelector) {
return false
}

if monitor.Namespace.LabelSelector != nil {
ns, err := c.namespaceGetter.Get(s.Namespace)
if err != nil {
return false
}
if !objectMatchesSelector(ns, monitor.Secret.LabelSelector) {
return false
}
}
return true
}

// TestCertMonitor_secretMatches func tests the secretMatches method in certmonitor.go
func TestCertMonitor_secretMatches(t *testing.T) {
tests := []struct {
Expand Down Expand Up @@ -355,6 +306,18 @@ func TestCertMonitor(t *testing.T) {
certMonitor := &certMonitor{
namespaceGetter: newFakeNamespaceGetter(namespaces),
metrics: fleetshardmetrics.MetricsInstance(),
config: &Config{
Monitors: []MonitorConfig{
{
Namespace: SelectorConfig{
Name: "namespace-1",
},
Secret: SelectorConfig{ // pragma: allowlist secret
Name: "secret-1",
},
},
},
},
}
now1 := time.Now().UTC()
expirytime := now1.Add(1 * time.Hour)
Expand Down Expand Up @@ -383,7 +346,7 @@ func TestCertMonitor(t *testing.T) {

}

// generateCertWithExpiration func generates a base64encoded certificate
// generateCertWithExpiration func generates a pem-encoded certificate
func generateCertWithExpiration(t *testing.T, expiry time.Time) []byte {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
Expand All @@ -397,8 +360,7 @@ func generateCertWithExpiration(t *testing.T, expiry time.Time) []byte {
certBytesDER, err := x509.CreateCertificate(rand.Reader, cert, cert, &privateKey.PublicKey, privateKey)
require.NoError(t, err)
pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytesDER})
base64Encoded := base64.StdEncoding.EncodeToString(pemCert)
return []byte(base64Encoded)
return []byte(pemCert)
}

// verifyPrometheusMetric func verifies if the promethues metric matches the expected value (create + update handle)
Expand Down

0 comments on commit 15703b3

Please sign in to comment.