package main import ( "bytes" "errors" "fmt" yaml "github.com/goccy/go-yaml" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net" "os" "slices" "strings" ) var PREDEFINED_APPS = []string{"apiserver"} func validateCIDR(cidr string) error { _, _, err := net.ParseCIDR(cidr) return err } type CIDR string // UnmarshalYAML implements the yaml.Unmarshaler interface func (c *CIDR) UnmarshalYAML(value []byte) error { // Get the string value from the node var s string if err := yaml.Unmarshal(value, &s); err != nil { return err } if err := validateCIDR(s); err != nil { return err } *c = CIDR(s) return nil } // MarshalYAML implements the yaml.Marshaler interface func (c CIDR) MarshalYAML() ([]byte, error) { // Do any custom processing here before marshalling return []byte(string(c)), nil } type Port struct { Port string `yaml:"port"` Protocol string `yaml:"protocol,omitempty" validate:"omitempty,oneof=TCP UDP"` } // Network represents each network entry in the YAML type Network struct { Name string `yaml:"name" validate:"required,applicationName"` CIDR CIDR `yaml:"cidr"` Except []CIDR `yaml:"except,omitempty" validate:"dive,required"` Ports []Port `yaml:"ports,omitempty" validate:"dive,required"` } type MatchExpression struct { Key string `json:"key" yaml:"key" validate:"required"` Operator string `json:"operator" yaml:"operator" validate:"oneof=In NotIn Exists DoesNotExist"` Values []string `json:"values" yaml:"values"` } type Application struct { Name string `yaml:"name" validate:"required,applicationName"` 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"` ServiceAccounts []string `yaml:"serviceAccounts,omitempty"` Namespace *Namespace `yaml:"-" validate:"-"` } func (a Application) Selector() *metav1.LabelSelector { return &metav1.LabelSelector{ MatchLabels: a.MatchLabels, MatchExpressions: a.MatchExpressions, } } type Namespace struct { Name string `yaml:"name"` // Open closed for network policies Open bool `yaml:"open"` // service mesh. When open = true following will happen // * when linkerd is enabled on the namespace, a netwoakauth to allow all traffic is // generated // * when not, then nothing is generated // When open = false, the following will happen // * when unauthorized = true the same will happen as with open = true // * else the appropriate rules will be generated to specify detailed traffic using // service accounts and networks as configured. Unauthorized bool `yaml:"unauthorized"` Capabilities []string `yaml:"capabilities"` Applications []*Application `yaml:"applications" validate:"dive,required"` } type Communication struct { From []string `yaml:"from" validate:"dive,required"` To []string `yaml:"to" validate:"dive,required"` Ports []Port `yaml:"ports" validate:"dive,required"` } // Config represents the top-level YAML structure type Config struct { Networks []*Network `yaml:"networks,omitempty" validate:"dive,required"` Namespaces []*Namespace `yaml:"namespaces,omitempty" validate:"dive,required"` Communications []*Communication `yaml:"communications,omitempty" validate:"dive,required"` } func (c *Config) Update(config *Config) { c.Namespaces = append(c.Namespaces, config.Namespaces...) c.Networks = append(c.Networks, config.Networks...) c.Communications = append(c.Communications, config.Communications...) } func (c Config) Validate() error { errs := make([]error, 0) // namesapaces must be unique ns := make(map[string]bool) for _, namespace := range c.Namespaces { if ns[namespace.Name] { errs = append(errs, fmt.Errorf("Duplicate namespace name %s", namespace.Name)) } ns[namespace.Name] = true } // network names mus tbe unique networks := make(map[string]bool) for _, network := range c.Networks { if networks[network.Name] { errs = append(errs, fmt.Errorf("Duplicate network name %s", network.Name)) } networks[network.Name] = true } // application names must be unique and may not conflict with predefined applications apps := make(map[string]bool) for _, predefined := range PREDEFINED_APPS { apps[predefined] = true } // application names may also not conflict with network names. for _, network := range c.Networks { apps[network.Name] = true } for _, namespace := range c.Namespaces { for _, app := range namespace.Applications { if apps[app.Name] { errs = append(errs, fmt.Errorf("Duplicate application %s (%s)", app.Name, namespace.Name)) } apps[app.Name] = true } } // applications mentioned in a communication must exist for _, communication := range c.Communications { for _, from := range communication.From { if !apps[from] { errs = append(errs, fmt.Errorf("Application does not exist: '%s' referenced in a communication (%+v)", from, communication)) } } for _, to := range communication.To { if !apps[to] { errs = append(errs, fmt.Errorf("Application does not exist: '%s' referenced in a communication (%+v)", to, communication)) } } } return errors.Join(errs...) } func (c Config) GetApplication(name string) (*Application, *Network, string) { if slices.Contains(PREDEFINED_APPS, name) { return nil, nil, name } for _, network := range c.Networks { if name == network.Name { return nil, network, "" } } for _, ns := range c.Namespaces { for _, app := range ns.Applications { if app.Name == name { return app, nil, "" } } } return nil, nil, "" } func (c *Config) Infer(resolver Resolver) { for _, ns := range c.Namespaces { for _, app := range ns.Applications { if len(app.ServiceAccounts) == 0 { app.ServiceAccounts = resolver.ServiceAccounts(app) fmt.Fprintf(os.Stderr, "Inferred service accounts: %s/%s: %v\n", app.Namespace.Name, app.Name, app.ServiceAccounts) } if len(app.Ports) == 0 && !strings.HasPrefix(ns.Name, "linkerd") { app.Ports = resolver.PortNumbers(app) if len(app.Ports) > 0 { fmt.Fprintf(os.Stderr, "Inferred ports: %s/%s: %v\n", app.Namespace.Name, app.Name, app.Ports) } } } } } func LoadConfig(file string) (*Config, error) { fmt.Fprintf(os.Stderr, "Reading config %s\n", file) yamlFile, err := os.ReadFile(file) if err != nil { return nil, fmt.Errorf("Error reading YAML file: %v", err) } validator, err := NewValidator() if err != nil { return nil, err } // Parse the YAML content dec := yaml.NewDecoder(bytes.NewReader(yamlFile), yaml.UseJSONUnmarshaler(), yaml.DisallowUnknownField(), yaml.UseOrderedMap(), yaml.Validator(validator), yaml.Strict(), ) var config Config err = dec.Decode(&config) if err != nil { return nil, fmt.Errorf("Error parsing YAML: %v", err) } // every application must have its namespace field set for _, ns := range config.Namespaces { for _, app := range ns.Applications { app.Namespace = ns } } return &config, nil }