policy-generator/cmd/policygen/config.go
2025-01-25 12:01:51 +01:00

253 lines
7.2 KiB
Go

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,hostname"`
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,hostname"`
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
for i, _ := range app.Ports {
if app.Ports[i].Protocol == "" {
app.Ports[i].Protocol = "TCP"
}
}
}
}
return &config, nil
}