From 0109788ccc440736fae22c2731a38c1bf101e96b Mon Sep 17 00:00:00 2001 From: stefanprodan Date: Fri, 25 Jan 2019 13:20:46 +0200 Subject: [PATCH] Discover config maps and secrets - scan target deployment volumes and containers for configmaps and secrets --- pkg/controller/tracker.go | 174 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 pkg/controller/tracker.go diff --git a/pkg/controller/tracker.go b/pkg/controller/tracker.go new file mode 100644 index 00000000..c24fdf64 --- /dev/null +++ b/pkg/controller/tracker.go @@ -0,0 +1,174 @@ +package controller + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha3" + clientset "github.com/stefanprodan/flagger/pkg/client/clientset/versioned" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +// ConfigTracker is managing the operations for Kubernetes configmaps and secrets +type ConfigTracker struct { + kubeClient kubernetes.Interface + flaggerClient clientset.Interface + logger *zap.SugaredLogger +} + +type ConfigRefType string + +const ( + ConfigRefMap ConfigRefType = "configmap" + ConfigRefSecret ConfigRefType = "secret" +) + +// ConfigRef holds the reference to a tracked configmap or secrets +type ConfigRef struct { + Name string + Type ConfigRefType + Checksum string +} + +func (c *ConfigRef) GetName() string { + return fmt.Sprintf("%s/%s", c.Type, c.Name) +} + +func checksum(data interface{}) string { + jsonBytes, _ := json.Marshal(data) + hashBytes := sha256.Sum256(jsonBytes) + return fmt.Sprintf("%x", hashBytes[:8]) +} + +func (ct *ConfigTracker) getTargetConfigs(cd *flaggerv1.Canary) (map[string]ConfigRef, error) { + res := make(map[string]ConfigRef) + targetName := cd.Spec.TargetRef.Name + targetDep, err := ct.kubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return res, fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace) + } + return res, fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err) + } + + // scan volumes + for _, volume := range targetDep.Spec.Template.Spec.Volumes { + if cmv := volume.ConfigMap; cmv != nil { + config, err := ct.getRefFromConfigMap(cmv.Name, cd.Namespace) + if err != nil { + ct.logger.Errorf("configMap %s.%s query error %v", cmv.Name, cd.Namespace, err) + continue + } + if config != nil { + res[config.GetName()] = *config + } + } + + if sv := volume.Secret; sv != nil { + secret, err := ct.getRefFromSecret(sv.SecretName, cd.Namespace) + if err != nil { + ct.logger.Errorf("secret %s.%s query error %v", sv.SecretName, cd.Namespace, err) + continue + } + if secret != nil { + res[secret.GetName()] = *secret + } + } + } + + // scan containers + for _, container := range targetDep.Spec.Template.Spec.Containers { + // scan env + for _, env := range container.Env { + if env.ValueFrom != nil { + switch { + case env.ValueFrom.ConfigMapKeyRef != nil: + name := env.ValueFrom.ConfigMapKeyRef.LocalObjectReference.Name + config, err := ct.getRefFromConfigMap(name, cd.Namespace) + if err != nil { + ct.logger.Errorf("configMap %s.%s query error %v", name, cd.Namespace, err) + continue + } + if config != nil { + res[config.GetName()] = *config + } + case env.ValueFrom.SecretKeyRef != nil: + name := env.ValueFrom.SecretKeyRef.LocalObjectReference.Name + secret, err := ct.getRefFromSecret(name, cd.Namespace) + if err != nil { + ct.logger.Errorf("secret %s.%s query error %v", name, cd.Namespace, err) + continue + } + if secret != nil { + res[secret.GetName()] = *secret + } + } + } + } + // scan envFrom + for _, envFrom := range container.EnvFrom { + switch { + case envFrom.ConfigMapRef != nil: + name := envFrom.ConfigMapRef.LocalObjectReference.Name + config, err := ct.getRefFromConfigMap(name, cd.Namespace) + if err != nil { + ct.logger.Errorf("configMap %s.%s query error %v", name, cd.Namespace, err) + continue + } + if config != nil { + res[config.GetName()] = *config + } + case envFrom.SecretRef != nil: + name := envFrom.SecretRef.LocalObjectReference.Name + secret, err := ct.getRefFromSecret(name, cd.Namespace) + if err != nil { + ct.logger.Errorf("secret %s.%s query error %v", name, cd.Namespace, err) + continue + } + if secret != nil { + res[secret.GetName()] = *secret + } + } + } + } + + return res, nil +} + +func (ct *ConfigTracker) getRefFromConfigMap(name string, namespace string) (*ConfigRef, error) { + config, err := ct.kubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + return &ConfigRef{ + Name: config.Name, + Type: ConfigRefMap, + Checksum: checksum(config.Data), + }, nil +} + +func (ct *ConfigTracker) getRefFromSecret(name string, namespace string) (*ConfigRef, error) { + secret, err := ct.kubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + if secret.Type != corev1.SecretTypeOpaque && + secret.Type != corev1.SecretTypeBasicAuth && + secret.Type != corev1.SecretTypeSSHAuth && + secret.Type != corev1.SecretTypeTLS { + ct.logger.Debugf("ignoring secret %s.%s type not supported %v", name, namespace, secret.Type) + return nil, nil + } + + return &ConfigRef{ + Name: secret.Name, + Type: ConfigRefSecret, + Checksum: checksum(secret.Data), + }, nil +}