policy-generator/cmd/policygen/config.go

189 lines
4.5 KiB
Go

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 *Namespace `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
}
}
return &config, nil
}