diff --git a/cmd/policygen/config.go b/cmd/policygen/config.go index 736f019..6fe62ab 100644 --- a/cmd/policygen/config.go +++ b/cmd/policygen/config.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "github.com/goccy/go-yaml" - "log" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net" "os" "slices" @@ -60,11 +60,12 @@ type MatchExpression struct { } type Application struct { - Name string `yaml:"name"` - Ports []Port `yaml:"ports,omitempty"` - MatchLabels map[string]string `yaml:"matchLabels"` - MatchExpressions []MatchExpression `yaml:"matchExpressions" validate:"omitempty,dive"` - Namespace *Namespace `yaml:"-" validate:"-"` + Name string `yaml:"name"` + Ports []Port `yaml:"ports,omitempty"` + MatchLabels map[string]string `yaml:"matchLabels"` + //MatchExpressions []MatchExpression `yaml:"matchExpressions" validate:"omitempty,dive"` + MatchExpressions []metav1.LabelSelectorRequirement `yaml:"matchExpressions" validate:"omitempty,dive"` + Namespace *Namespace `yaml:"-" validate:"-"` } type Namespace struct { @@ -108,8 +109,7 @@ func (c Config) Validate() error { // network names mus tbe unique networks := make(map[string]bool) - for nn, network := range c.Networks { - log.Printf("Network %+v %v %v\n", network, nn, c.Networks) + for _, network := range c.Networks { if networks[network.Name] { errs = append(errs, fmt.Errorf("Duplicate network name %s", network.Name)) } diff --git a/cmd/policygen/configvalidator.go b/cmd/policygen/configvalidator.go new file mode 100644 index 0000000..c5004c3 --- /dev/null +++ b/cmd/policygen/configvalidator.go @@ -0,0 +1,140 @@ +package main + +import ( + "context" + "fmt" + "iter" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "log" + "maps" + "os" + "strconv" +) + +type ValidationLevel int + +const ( + Info ValidationLevel = iota + Warning + Error +) + +func LogValidationMsg(level ValidationLevel, msg string, v ...any) { + fmt.Fprintf(os.Stderr, msg+"\n", v...) +} + +func IterToSlice[K any](i iter.Seq[K]) []K { + res := make([]K, 0) + for v := range i { + res = append(res, v) + } + return res +} + +func MapKeys[K comparable, V any](m map[K]V) []K { + return IterToSlice(maps.Keys(m)) +} + +func validate(files []string, options *Options) error { + config, err := readConfig(files) + if err != nil { + return err + } + clientset, _ := GetKubernetesConnection() + for _, ns := range config.Namespaces { + namespace := ns.Name + _, err = clientset.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) + if err != nil { + LogValidationMsg(Error, "ERROR: namespace not found: %s", namespace) + continue + } + for _, application := range ns.Applications { + pods := FindPods(application, clientset) + log.Printf(namespace + "/" + application.Name) + if len(pods) == 0 { + LogValidationMsg(Error, "application %s: no running pods found", application.Name) + } + ownerReferences := make(map[string]bool) + for _, pod := range pods { + log.Printf(" %s %v", pod.Name, pod.OwnerReferences) + for _, ownerReference := range pod.OwnerReferences { + ownerReferences[ownerReference.Kind+"/"+ownerReference.Name] = true + } + } + if len(ownerReferences) > 1 { + LogValidationMsg(Error, "Application %s: multiple owners found: %v", application.Name, MapKeys(ownerReferences)) + } + // check ports + for _, port := range application.Ports { + for _, pod := range pods { + if !HasPort(pod, port) { + LogValidationMsg(Error, "application %s: port %v not found in pod %s/%s", + application.Name, port, pod.Namespace, pod.Name) + } + } + } + } + + for _, communication := range config.Communications { + if len(communication.Ports) == 0 { + continue + } + for _, applicationName := range communication.To { + application, _, _ := config.GetApplication(applicationName) + if application == nil { + continue + } + for _, port := range communication.Ports { + pods := FindPods(application, clientset) + for _, pod := range pods { + if !HasPort(pod, port) { + LogValidationMsg(Error, "communication %v -> %v: port %v is not configured in pod %s/%s", + communication.From, communication.To, port, pod.Namespace, pod.Name) + } + } + + } + } + + } + + } + return nil +} + +func FindPods(application *Application, clientset *kubernetes.Clientset) []v1.Pod { + labelSelector := &metav1.LabelSelector{ + MatchLabels: application.MatchLabels, + MatchExpressions: application.MatchExpressions, + } + selector, err := metav1.LabelSelectorAsSelector(labelSelector) + if err != nil { + log.Fatalf("Error creating selector: %v", err) + } + pods, err := clientset.CoreV1().Pods(application.Namespace.Name).List(context.TODO(), metav1.ListOptions{ + LabelSelector: selector.String(), + }) + if err != nil { + log.Fatalf("Error listing pods: %v", err) + } + return pods.Items +} + +func HasPort(pod v1.Pod, port Port) bool { + if port.Protocol == "" { + port.Protocol = "TCP" + } + for _, container := range pod.Spec.Containers { + for _, kport := range container.Ports { + if kport.Protocol == "" { + kport.Protocol = "TCP" + } + if port.Protocol == string(kport.Protocol) && (port.Port == kport.Name || port.Port == strconv.Itoa(int(kport.ContainerPort))) { + return true + } + } + } + return false +} diff --git a/cmd/policygen/main.go b/cmd/policygen/main.go index 2ec8f29..f0ea6bc 100644 --- a/cmd/policygen/main.go +++ b/cmd/policygen/main.go @@ -55,10 +55,6 @@ func generate(files []string, options *Options) error { return nil } -func validate(files []string, options *Options) error { - return nil -} - func main() { options := Options{ diff --git a/example/config.yaml b/example/config.yaml index 2ef0168..6a1f24a 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -10,7 +10,7 @@ networks: - 192.168.0.0/16 ports: - port: 2303 - protocol: UDPx + protocol: UDP namespaces: @@ -27,11 +27,11 @@ namespaces: - port: 8081 - port: 8082 protocol: UDP - matchLabels: + matchLabels: app: nexus-server - matchExpressions: - - key: jenkins/label - operator: Exists + #matchExpressions: + # - key: jenkins/label + # operator: Exists - name: exposure @@ -39,9 +39,9 @@ namespaces: applications: - name: httpd-wamblee-org matchLabels: - app: wamblee-org + app: httpd-wamblee-org ports: - - port: 1000 + - port: 80 - port: 1001 protocol: UDP @@ -54,6 +54,7 @@ communications: - nexus-server - internet ports: + - port: 8084 - port: 53 protocol: UDP