work in progress. working on pod templatest.

This commit is contained in:
Erik Brakkee 2025-01-02 17:14:06 +01:00
parent 8c5a099082
commit 207043d38f
15 changed files with 420 additions and 92 deletions

View File

@ -81,9 +81,7 @@ communications:
Handling of capabilities:
1. capabilities at namespace level apply to each individual pod in the
namespace
2. a capability is a list of templates.
1. capabilities at namespace level is defined a template that gets the namespace name.
Ingress template

View File

@ -1,12 +1,17 @@
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
@ -36,21 +41,24 @@ func (c CIDR) MarshalYAML() ([]byte, error) {
// Network represents each network entry in the YAML
type Network struct {
Name string `yaml:"name"`
CIDR CIDR `yaml:"cidr"`
Except []CIDR `yaml:"except,omitempty"`
Name string `yaml:"name"`
CIDR CIDR `yaml:"cidr"`
Except []CIDR `yaml:"except,omitempty"`
Ports []string `yaml:"ports,omitempty"`
}
type Application struct {
Name string `yaml:"name"`
Ports []string `yaml:"ports,omitempty"`
MatchLabels map[string]string `yaml:"matchLabels"`
Namespace string `yaml:"-"`
}
type Namespace struct {
Name string `yaml:"name"`
Capabilities []string `yaml:"capabilities"`
Applications []Application `yaml:"applications"`
Name string `yaml:"name"`
Open bool `yaml:"open"`
Capabilities []string `yaml:"capabilities"`
Applications []*Application `yaml:"applications"`
}
type Communication struct {
@ -61,9 +69,9 @@ type Communication struct {
// 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"`
Networks []*Network `yaml:"networks,omitempty"`
Namespaces []*Namespace `yaml:"namespaces,omitempty"`
Communications []*Communication `yaml:"communications,omitempty"`
}
func (c Config) Validate() error {
@ -88,8 +96,15 @@ func (c Config) Validate() error {
networks[network.Name] = true
}
// application names must be unique
apps := make(map[string]bool)
// 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] {
@ -115,3 +130,54 @@ func (c Config) Validate() error {
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
}

115
cmd/policygen/generator.go Normal file
View File

@ -0,0 +1,115 @@
package main
import (
"fmt"
"io"
"log"
"os"
)
type Generator interface {
GenerateNamespace(writer io.Writer, namespace *Namespace) error
GenerateCommunicationRule(writer io.Writer, app *Application, ingress *Ingress, egress *Egress) error
}
type Peer struct {
Applications []*Application
Networks []*Network
Predefined []string
}
func (p *Peer) append(app *Application, network *Network, predefined string) {
if app != nil {
p.Applications = append(p.Applications, app)
}
if network != nil {
p.Networks = append(p.Networks, network)
}
if predefined != "" {
p.Predefined = append(p.Predefined, predefined)
}
}
func (p *Peer) Empty() bool {
return len(p.Applications)+len(p.Networks)+len(p.Predefined) == 0
}
func (p Peer) String() string {
res := ""
for _, app := range p.Applications {
res += "app:" + app.Name + " "
}
for _, net := range p.Networks {
res += "net:" + net.Name + " "
}
for _, pre := range p.Predefined {
res += "pre:" + pre + " "
}
return res
}
type Ingress struct {
Peer
}
type Egress struct {
Peer
}
func Generate(writer io.Writer, generator Generator, config *Config) error {
log.Printf("CONFIG %+v", config)
for _, ns := range config.Namespaces {
err := generator.GenerateNamespace(os.Stdout, ns)
if err != nil {
return err
}
}
// Loop over all applications and gather the ingress and egress for each application
var applications = make(map[string]*Application)
var ingresses = make(map[string]*Ingress)
var egresses = make(map[string]*Egress)
for _, ns := range config.Namespaces {
for _, app := range ns.Applications {
applications[app.Name] = app
if ingresses[app.Name] == nil {
ingresses[app.Name] = &Ingress{}
}
if egresses[app.Name] == nil {
egresses[app.Name] = &Egress{}
}
}
}
for _, communication := range config.Communications {
for _, from := range communication.From {
appFrom, networkFrom, predefinedFrom := config.GetApplication(from)
for _, to := range communication.To {
appTo, networkTo, predefinedTo := config.GetApplication(to)
if appFrom != nil {
// we have an egress
egress := egresses[from]
egress.append(appTo, networkTo, predefinedTo)
}
if appTo != nil {
// we have an ingress
ingress := ingresses[to]
ingress.append(appFrom, networkFrom, predefinedFrom)
}
}
}
}
// loop over all apps and configure them
for app, ingress := range ingresses {
egress := egresses[app]
if !ingress.Empty() || !egress.Empty() {
fmt.Fprintf(os.Stderr, "RULE %s\n", app)
fmt.Fprintf(os.Stderr, " IN %s\n", ingress)
fmt.Fprintf(os.Stderr, " OUT %s\n", egress)
generator.GenerateCommunicationRule(writer, applications[app], ingress, egress)
}
}
return nil
}

View File

@ -1,10 +1,10 @@
package main
import (
"bytes"
"fmt"
"github.com/goccy/go-yaml"
"github.com/spf13/cobra"
"log"
"os"
)
@ -18,32 +18,39 @@ func execute(files []string, options *Options) error {
return fmt.Errorf("File expected")
}
for _, file := range files {
fmt.Fprintf(os.Stderr, "Reading config %s\n", file)
yamlFile, err := os.ReadFile(file)
if err != nil {
return 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 fmt.Errorf("Error parsing YAML: %v", err)
}
err = config.Validate()
config, err := LoadConfig(file)
if err != nil {
return err
}
fmt.Printf("PARSED %+v\n", config)
policyTemplates, err := NewPolicyTemplates()
if err != nil {
return err
}
var generator Generator
generator = NetworkPolicyGenerrator{
config: config,
policyTemplates: policyTemplates,
}
Generate(os.Stdout, generator, config)
}
return nil
}
func main() {
val := map[string]string{
"abc": "1",
}
data, err := yaml.Marshal(val)
if err != nil {
panic(err)
}
log.Printf("val %s", string(data))
//os.Exit(1)
options := Options{
cni: "cilium",
policyType: "netpol",

View File

@ -0,0 +1,61 @@
package main
import (
"fmt"
"io"
"log"
"os"
)
type NetworkPolicyGenerrator struct {
config *Config
policyTemplates *PolicyTemplates
}
func (g NetworkPolicyGenerrator) GenerateNamespace(writer io.Writer, namespace *Namespace) error {
fmt.Fprintf(os.Stderr, "Namespace %s\n", namespace.Name)
templates := g.policyTemplates.NamespaceTemplates("netpol", namespace.Capabilities)
log.Printf("Got %d templates", len(templates))
for _, template := range templates {
err := template.Execute(writer, &namespace)
if err != nil {
return fmt.Errorf("Error using template %s: %w", template.Name(), err)
}
}
return nil
}
func (g NetworkPolicyGenerrator) GenerateCommunicationRule(
writer io.Writer,
app *Application,
ingress *Ingress,
egress *Egress) error {
if len(ingress.Applications)+
len(ingress.Networks)+
len(egress.Applications)+
len(egress.Networks) > 0 {
// non-trivial regular network policy
tmpl := g.policyTemplates.ApplicationTemplate("netpol")
log.Printf("Found template %v for pod %s", tmpl, app.Name)
if tmpl != nil {
log.Printf("EXECUTING %s", app.Namespace)
err := tmpl.Execute(writer, map[string]any{
"app": app,
"ingress": ingress,
"egress": egress,
"labels": map[string]string{
"policy-generator": "1",
},
})
if err != nil {
return err
}
}
}
return nil
}

View File

@ -2,10 +2,10 @@ package main
import (
"fmt"
"html/template"
"io/fs"
"os"
"strings"
"text/template"
)
import "embed"
import sprig "github.com/Masterminds/sprig/v3" // This provides most Helm functions
@ -66,20 +66,24 @@ func showContents(files fs.FS) {
}
}
func loadTemplates() (*template.Template, error) {
type PolicyTemplates struct {
templates *template.Template
}
func NewPolicyTemplates() (*PolicyTemplates, error) {
showContents(templateFS)
// Parse all templates at once from the embedded FS
tmpl := NewTemplate()
err := loadTemplatesGlob(tmpl)
err := loadTemplatesAll(tmpl)
if err != nil {
return nil, err
}
return tmpl, err
return &PolicyTemplates{templates: tmpl}, err
}
func loadTemplatesGlob(tmpl *template.Template) error {
func loadTemplatesAll(tmpl *template.Template) error {
return fs.WalkDir(templateFS, ".", func(path string, d os.DirEntry, err error) error {
if strings.HasSuffix(path, ".yaml") {
data, err := fs.ReadFile(templateFS, path)
@ -87,8 +91,36 @@ func loadTemplatesGlob(tmpl *template.Template) error {
return err
}
fmt.Fprintf(os.Stderr, "Loading template %s\n", path)
tmpl.New(path).Parse(string(data))
_, err = tmpl.New(path).Option("missingkey=error").Parse(string(data))
if err != nil {
return err
}
}
return nil
})
}
func (t *PolicyTemplates) NamespaceTemplates(policyType string, capabilities []string) []*template.Template {
res := make([]*template.Template, 0)
tmpl := t.templates.Lookup(fmt.Sprintf("templates/%s/namespace/namespace.yaml", policyType))
if tmpl != nil {
res = append(res, tmpl)
}
for _, capability := range capabilities {
tmpl := t.templates.Lookup(fmt.Sprintf("templates/%s/namespace/%s.yaml", policyType, capability))
if tmpl != nil {
res = append(res, tmpl)
}
}
return res
}
func (t *PolicyTemplates) ApplicationTemplate(policyType string) *template.Template {
tmpl := t.templates.Lookup(fmt.Sprintf("templates/%s/pod/pod.yaml", policyType))
return tmpl
}
func (t *PolicyTemplates) PredefineApplicationPolicyTemplate(policyType string, predefined string) *template.Template {
tmpl := t.templates.Lookup(fmt.Sprintf("templates/pod/%s/%s.yaml", policyType, predefined))
return tmpl
}

View File

@ -1,15 +0,0 @@
kind: CiliumNetworkPolicy
apiVersion: cilium.io/v2
metadata:
name: {{.name}}
namespace: {{.namespace}}
spec:
endpointSelector:
{{ .selector }}
egress:
- toEntities:
- kube-apiserver
- toPorts:
- ports:
- port: "6443"
protocol: TCP

View File

@ -1,14 +0,0 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: "{{.name}}"
namespace: "{{.namespace}}"
spec:
policyTypes:
- Egress
podSelector:
{{.selector}}
egress:
{{- range $from := .from }}
- {{ $from | nindent 4 }}
{{- end }}

View File

@ -1,14 +0,0 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: "{{.name}}"
namespace: "{{.namespace}}"
spec:
policyTypes:
- Ingress
podSelector:
{{.selector}}
ingress:
{{- range $from := .from }}
- {{ $from | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,6 @@
---
####################################################################################
# LINKERD NETPOL TBD
####################################################################################

View File

@ -0,0 +1,14 @@
{{- if not .Open }}
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: default-allow-nothing
namespace: "{{.Name}}"
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
{{ end }}

View File

@ -3,11 +3,24 @@ apiVersion: cilium.io/v2
metadata:
name: {{.name}}
namespace: {{.namespace}}
labels: "{{ .labels | toYaml | nindent 4 }}"
spec:
endpointSelector:
{{ .selector }}
{{- if .from }}
ingress:
- fromEntities:
- kube-apiserver
# See https://github.com/cilium/cilium/issues/35401
- remote-node
{{- end }}
{{- if .to }}
egress:
- toEntities:
- kube-apiserver
- toPorts:
- ports:
- port: "6443"
protocol: TCP
{{- end }}

View File

@ -0,0 +1,56 @@
---
{{- define "peer" }}
- podSelector:
matchLabels: {{ .MatchLabels | toYaml | nindent 12 }}
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ .Namespace }}
{{- if .Ports }}
ports:
# TODO: add protocol
{{- range $port := .Ports }}
- port: {{ $port }}
{{- end }}
{{- end }}
{{- end }}
-
{{- define "ports" }}
{{- range $port := . }}
PORT {{ $port }}
{{- end }}
{{- end }}
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: "{{.app.Name}}"
namespace: "{{.app.Namespace}}"
labels: {{ .labels | toYaml | nindent 4 }}
spec:
podSelector: {{ .app.MatchLabels | toYaml | nindent 4 }}
policyTypes:
{{- if or .ingress.Applications .ingress.Networks }}
- Ingress
{{- end }}
{{- if or .egress.Applications .egress.Networks }}
- Egress
{{- end }}
{{- if or .ingress.Applications .ingress.Networks }}
ingress:
from:
{{- range $ingress := .ingress.Applications }}
{{- template "peer" $ingress }}
{{- template "ports" $ingress.Ports }}
{{- end }}
{{- range $ingress := .ingress.Networks }}
- ipBlock:
cidr: {{ $ingress.CIDR}}
except:
{{- range $except := $ingress.Except }}
- {{ $except }}
{{- end }}
{{- end }}
{{- end }}
THEEND

View File

@ -12,6 +12,7 @@ networks:
namespaces:
- name: wamblee-org
open: true
capabilities:
- linkerd
applications:
@ -29,22 +30,24 @@ namespaces:
- name: httpd-wamblee-org
matchLabels:
app: wamblee-org
ports:
- 1000
communications:
- from: # can we support both string and list of strings?
- httpd-wamblee-org
- internet
to:
- nexus-server
- wamblee-static
- wamblee-safe
# or limiting ports further
- from:
- httpd-wamblee-org
to:
- nexus-server
ports:
- 8081
- 8082
# # or limiting ports further
# - from:
# - httpd-wamblee-org
# to:
# - nexus-server
# ports:
# - 8081
# - 8082