package main import ( "bytes" "errors" "fmt" "github.com/goccy/go-yaml" "net" "os" "slices" ) 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"` } // Network represents each network entry in the YAML type Network struct { Name string `yaml:"name"` CIDR CIDR `yaml:"cidr"` Except []CIDR `yaml:"except,omitempty"` Ports []Port `yaml:"ports,omitempty"` } type Application struct { Name string `yaml:"name"` Ports []Port `yaml:"ports,omitempty"` MatchLabels map[string]string `yaml:"matchLabels"` Namespace string `yaml:"-"` } type Namespace struct { Name string `yaml:"name"` Open bool `yaml:"open"` Capabilities []string `yaml:"capabilities"` Applications []*Application `yaml:"applications"` } type Communication struct { From []string `yaml:"from"` To []string `yaml:"to"` Ports []string `yaml:"ports"` } // Config represents the top-level YAML structure type Config struct { Networks []*Network `yaml:"networks,omitempty"` Namespaces []*Namespace `yaml:"namespaces,omitempty"` Communications []*Communication `yaml:"communications,omitempty"` } 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 := map[string]bool{ "apiserver": 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 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) } // Parse the YAML content dec := yaml.NewDecoder(bytes.NewReader(yamlFile), yaml.UseJSONUnmarshaler(), yaml.DisallowUnknownField(), ) var config Config err = dec.Decode(&config) if err != nil { return nil, fmt.Errorf("Error parsing YAML: %v", err) } err = config.Validate() if err != nil { return nil, err } // every application must have its namespace field set for _, ns := range config.Namespaces { for _, app := range ns.Applications { app.Namespace = ns.Name } } return &config, nil }