generating policies first version.

Still includes linkerd ports.
This commit is contained in:
Erik Brakkee 2025-01-25 12:01:51 +01:00
parent 496e58347c
commit 108f21ea58
10 changed files with 209 additions and 85 deletions

View File

@ -240,6 +240,11 @@ func LoadConfig(file string) (*Config, error) {
for _, ns := range config.Namespaces { for _, ns := range config.Namespaces {
for _, app := range ns.Applications { for _, app := range ns.Applications {
app.Namespace = ns app.Namespace = ns
for i, _ := range app.Ports {
if app.Ports[i].Protocol == "" {
app.Ports[i].Protocol = "TCP"
}
}
} }
} }

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"iter" "iter"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"maps"
"os" "os"
"slices" "slices"
"strconv" "strconv"
@ -35,13 +34,6 @@ func IterToSlice[K any](i iter.Seq[K]) []K {
return res return res
} }
func MapKeys[K comparable, V any](m map[K]V) []K {
return IterToSlice(maps.Keys(m))
}
func MapValues[K comparable, V any](m map[K]V) []V {
return IterToSlice(maps.Values(m))
}
func validate(files []string, options *Options) error { func validate(files []string, options *Options) error {
clientset, _ := GetKubernetesConnection() clientset, _ := GetKubernetesConnection()
config, err := readConfig(files) config, err := readConfig(files)

View File

@ -6,9 +6,10 @@ import (
) )
type Generator interface { type Generator interface {
Init(write io.Writer) error Init(writer io.Writer) error
GenerateNamespace(writer io.Writer, namespace *Namespace) error GenerateNamespace(writer io.Writer, namespace *Namespace) error
GenerateCommunicationRule(writer io.Writer, app *Application, ingress *Ingress, egress *Egress) error GenerateCommunicationRule(writer io.Writer, app *Application, ingress *Ingress, egress *Egress) error
Finalize(writer io.Writer) error
} }
type ApplicationPeer struct { type ApplicationPeer struct {
@ -172,5 +173,5 @@ func Generate(writer io.Writer, generator Generator, config *Config) error {
} }
} }
return nil return generator.Finalize(writer)
} }

View File

@ -4,71 +4,154 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"slices"
"strconv"
) )
type LinkerdPolicyGenerator struct { type LinkerdPolicyGenerator struct {
config *Config config *Config
policyTemplates *PolicyTemplates policyTemplates *PolicyTemplates
networks map[string]*Network
} }
func (g LinkerdPolicyGenerator) Init(writer io.Writer) error { func NewLinkerdPolicyGenerator(config *Config, templates *PolicyTemplates) *LinkerdPolicyGenerator {
// start by generating network authentications return &LinkerdPolicyGenerator{
for _, network := range g.config.Networks { config: config,
fmt.Fprintf(os.Stderr, "NetworkAuthentication default/%s\n", network.Name) policyTemplates: templates,
template := g.policyTemplates.PredefineApplicationPolicyTemplate("linkerd", "networkauthentication") networks: make(map[string]*Network),
if template == nil {
return fmt.Errorf("Linkerd template for network authentication not found")
}
err := template.Execute(writer, network)
if err != nil {
return fmt.Errorf("Error executing network authentication template for %s", network.Name)
}
} }
}
func (g *LinkerdPolicyGenerator) Init(writer io.Writer) error {
return nil return nil
} }
func (g LinkerdPolicyGenerator) GenerateNamespace(writer io.Writer, namespace *Namespace) error { func (g *LinkerdPolicyGenerator) GenerateNamespace(writer io.Writer, namespace *Namespace) error {
return nil return nil
} }
func (g LinkerdPolicyGenerator) GenerateCommunicationRule( func (g *LinkerdPolicyGenerator) GenerateCommunicationRule(
writer io.Writer, writer io.Writer,
app *Application, app *Application,
ingress *Ingress, ingress *Ingress,
egress *Egress) error { egress *Egress) error {
// and then the meshTLSAuthentications if app.Namespace.Unauthorized {
fmt.Fprintf(os.Stderr, "MeshTLSAuthentication %s/%s %v\n", fmt.Fprintf(os.Stderr, "UNAUTHORIZED %s\n", app.Name)
app.Namespace.Name, app.Name, app.ServiceAccounts) return nil
template := g.policyTemplates.PredefineApplicationPolicyTemplate("linkerd", "meshtlsauthentication")
if template == nil {
return fmt.Errorf("Could not find meshtlsauthentication template")
}
err := template.Execute(writer, app)
if err != nil {
return err
} }
// and the server resources // and the server resources
fmt.Fprintf(os.Stderr, "Server %s/%s\n", fmt.Fprintf(os.Stderr, "Server %s/%s\n",
app.Namespace.Name, app.Name) app.Namespace.Name, app.Name)
template = g.policyTemplates.PredefineApplicationPolicyTemplate("linkerd", "server") err := g.policyTemplates.Execute("linkerd", "server", writer, app)
if template == nil {
return fmt.Errorf("Could not find meshtlsauthentication template")
}
err = template.Execute(writer, app)
if err != nil { if err != nil {
return err return err
} }
if len(ingress.Applications)+ if len(ingress.Applications)+
len(ingress.Networks)+ len(ingress.Networks) > 0 {
len(egress.Applications)+ ports := make(map[int]bool)
len(egress.Networks) > 0 { //
// non-trivial regular network policy clientApplications := make(map[int][]*ApplicationPeer)
for _, ingress := range ingress.Applications {
for _, port := range ingress.Ports {
if port.Protocol == "TCP" {
portno, err := strconv.Atoi(port.Port)
if err != nil {
return fmt.Errorf("Error convert port '%s' of ingress '%s' of app '%s': %w",
port.Port, ingress.Application.Name, app.Name, err)
}
ports[portno] = true
clientApplications[portno] = append(clientApplications[portno], ingress)
}
}
}
// TODO clientNetworks := make(map[int][]*NetworkPeer)
for _, ingress := range ingress.Networks {
for _, port := range ingress.Ports {
if port.Protocol == "TCP" {
portno, err := strconv.Atoi(port.Port)
if err != nil {
return fmt.Errorf("Error convert port '%s' of ingress '%s' of app '%s': %w",
port.Port, ingress.Network.Name, app.Name, err)
}
ports[portno] = true
clientNetworks[portno] = append(clientNetworks[portno], ingress)
}
}
}
for port, _ := range ports {
fmt.Fprintf(os.Stderr, "Generating authorization policy: %v %v -> %v : %v\n",
Map(clientApplications[port], func(peer *ApplicationPeer) string {
return peer.Application.Name
}),
Map(clientNetworks[port], func(peer *NetworkPeer) string {
return peer.Network.Name
}),
app.Name,
port)
// Optimization: keep track of the references clientApps and
// client Networks and only generate authentications for those instead of for
// all apps and networks.
for _, clientNetwork := range clientNetworks[port] {
g.networks[clientNetwork.Network.Name] = clientNetwork.Network
}
// linkerd rules
// 1. an authpolicy may contain only one meshtlsauthentication rule
// 2. an authpolicy may contain only one service account .
// 3. an authpolicy may contain more than one networkauthentication
//
// Should generate here a methtlsautheorization for every port
// and pass in a list of service accounts instead of a list of apps.
serviceAccounts := g.serviceAccounts(clientApplications[port])
if len(serviceAccounts) > 0 {
err = g.policyTemplates.Execute("linkerd", "meshtlsauthentication",
writer,
map[string]any{
"app": app,
"port": port,
"serviceAccounts": serviceAccounts,
})
if err != nil {
return err
}
}
err = g.policyTemplates.Execute("linkerd", "authorizationpolicy",
writer,
map[string]any{
"app": app,
"port": port,
"clientNetworks": clientNetworks[port],
})
if err != nil {
return err
}
}
}
return nil
}
func (g *LinkerdPolicyGenerator) serviceAccounts(peers []*ApplicationPeer) []string {
serviceAccounts := []string{}
for _, peer := range peers {
serviceAccounts = append(serviceAccounts, peer.Application.ServiceAccounts...)
}
slices.Sort(serviceAccounts)
return slices.Compact(serviceAccounts)
}
func (g *LinkerdPolicyGenerator) Finalize(writer io.Writer) error {
for _, network := range g.networks {
fmt.Fprintf(os.Stderr, "NetworkAuthentication default/%s\n", network.Name)
err := g.policyTemplates.Execute("linkerd", "networkauthentication", writer, network)
if err != nil {
return err
}
} }
return nil return nil
} }

View File

@ -42,10 +42,7 @@ func generateNetworkPolicy(files []string, options *Options) error {
return err return err
} }
var generator Generator var generator Generator
generator = NetworkPolicyGenerator{ generator = NewNetworkPolicyGenerator(config, policyTemplates)
config: config,
policyTemplates: policyTemplates,
}
err = Generate(os.Stdout, generator, config) err = Generate(os.Stdout, generator, config)
if err != nil { if err != nil {
return err return err
@ -74,10 +71,7 @@ func generateLinkerdPolicies(files []string, options *Options) error {
return err return err
} }
var generator Generator var generator Generator
generator = LinkerdPolicyGenerator{ generator = NewLinkerdPolicyGenerator(config, policyTemplates)
config: config,
policyTemplates: policyTemplates,
}
err = Generate(os.Stdout, generator, config) err = Generate(os.Stdout, generator, config)
if err != nil { if err != nil {
return err return err

View File

@ -12,11 +12,18 @@ type NetworkPolicyGenerator struct {
policyTemplates *PolicyTemplates policyTemplates *PolicyTemplates
} }
func (g NetworkPolicyGenerator) Init(writer io.Writer) error { func NewNetworkPolicyGenerator(config *Config, templates *PolicyTemplates) *NetworkPolicyGenerator {
return &NetworkPolicyGenerator{
config: config,
policyTemplates: templates,
}
}
func (g *NetworkPolicyGenerator) Init(writer io.Writer) error {
return nil return nil
} }
func (g NetworkPolicyGenerator) GenerateNamespace(writer io.Writer, namespace *Namespace) error { func (g *NetworkPolicyGenerator) GenerateNamespace(writer io.Writer, namespace *Namespace) error {
fmt.Fprintf(os.Stderr, "Namespace %s\n", namespace.Name) fmt.Fprintf(os.Stderr, "Namespace %s\n", namespace.Name)
templates := g.policyTemplates.NamespaceTemplates("netpol", namespace.Capabilities) templates := g.policyTemplates.NamespaceTemplates("netpol", namespace.Capabilities)
@ -30,7 +37,7 @@ func (g NetworkPolicyGenerator) GenerateNamespace(writer io.Writer, namespace *N
return nil return nil
} }
func (g NetworkPolicyGenerator) GenerateCommunicationRule( func (g *NetworkPolicyGenerator) GenerateCommunicationRule(
writer io.Writer, writer io.Writer,
app *Application, app *Application,
ingress *Ingress, ingress *Ingress,
@ -42,11 +49,7 @@ func (g NetworkPolicyGenerator) GenerateCommunicationRule(
len(egress.Networks) > 0 { len(egress.Networks) > 0 {
// non-trivial regular network policy // non-trivial regular network policy
tmpl := g.policyTemplates.ApplicationTemplate("netpol") err := g.policyTemplates.Execute("netpol", "pod", writer, map[string]any{
if tmpl == nil {
return fmt.Errorf("Could not find policy template for 'netpol'")
}
err := tmpl.Execute(writer, map[string]any{
"app": app, "app": app,
"ingress": ingress, "ingress": ingress,
"egress": egress, "egress": egress,
@ -68,18 +71,15 @@ func (g NetworkPolicyGenerator) GenerateCommunicationRule(
} }
for predefined, _ := range allPredefined { for predefined, _ := range allPredefined {
tmpl := g.policyTemplates.PredefineApplicationPolicyTemplate("netpol", predefined) err := g.policyTemplates.Execute("netpol", predefined,
if tmpl == nil { writer, map[string]any{
return fmt.Errorf("Could not find predefined template for netpol/%s", predefined) "app": app,
} "ingress": slices.Contains(ingress.Predefined, predefined),
err := tmpl.Execute(writer, map[string]any{ "egress": slices.Contains(egress.Predefined, predefined),
"app": app, "labels": map[string]string{
"ingress": slices.Contains(ingress.Predefined, predefined), "policy-generator": "1",
"egress": slices.Contains(egress.Predefined, predefined), },
"labels": map[string]string{ })
"policy-generator": "1",
},
})
if err != nil { if err != nil {
return err return err
} }
@ -87,3 +87,7 @@ func (g NetworkPolicyGenerator) GenerateCommunicationRule(
return nil return nil
} }
func (g *NetworkPolicyGenerator) Finalize(writer io.Writer) error {
return nil
}

View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"io"
"io/fs" "io/fs"
"log" "log"
"os" "os"
@ -119,12 +120,21 @@ func (t *PolicyTemplates) NamespaceTemplates(policyType string, capabilities []s
return res return res
} }
func (t *PolicyTemplates) ApplicationTemplate(policyType string) *template.Template {
tmpl := t.templates.Lookup(fmt.Sprintf("templates/%s/application/pod.yaml", policyType))
return tmpl
}
func (t *PolicyTemplates) PredefineApplicationPolicyTemplate(policyType string, predefined string) *template.Template { func (t *PolicyTemplates) PredefineApplicationPolicyTemplate(policyType string, predefined string) *template.Template {
tmpl := t.templates.Lookup(fmt.Sprintf("templates/%s/application/%s.yaml", policyType, predefined)) tmpl := t.templates.Lookup(fmt.Sprintf("templates/%s/application/%s.yaml", policyType, predefined))
return tmpl return tmpl
} }
func (t *PolicyTemplates) Execute(policyType string, predefined string, writer io.Writer, data any) error {
tmpl := t.PredefineApplicationPolicyTemplate(policyType, predefined)
if tmpl == nil {
return fmt.Errorf("Could not find template %s for policy type %s", predefined, policyType)
}
err := tmpl.Execute(writer, data)
if err != nil {
return fmt.Errorf("Error rendering template %s for policy type %s: %w", predefined, policyType, err)
}
writer.Write([]byte("\n"))
return nil
}

View File

@ -0,0 +1,22 @@
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: {{ .app.Name }}-p{{ .port }}
namespace: {{ .app.Namespace.Name}}
spec:
targetRef:
group: policy.linkerd.io
kind: Server
name: {{ .app.Name }}-p{{ .port }}
requiredAuthenticationRefs:
- name: {{ .app.Name }}-p{{ .port }}
namespace: {{ .app.Namespace.Name }}
kind: MeshTLSAuthentication
group: policy.linkerd.io
{{- range $net := .clientNetworks }}
- name: {{ $net.Network.Name }}
namespace: default
kind: NetworkAuthentication
group: policy.linkerd.io
{{- end }}

View File

@ -2,16 +2,12 @@
apiVersion: policy.linkerd.io/v1alpha1 apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication kind: MeshTLSAuthentication
metadata: metadata:
name: {{ .Name }} name: {{ .app.Name }}-{{.port}}
namespace: {{ .Namespace.Name }} namespace: {{ .app.Namespace.Name }}
spec: spec:
{{- if .ServiceAccounts }}
identityRefs: identityRefs:
{{- range $sa := .ServiceAccounts }} {{- range $sa := .serviceAccounts }}
- kind: ServiceAccount - kind: ServiceAccount
name: {{ $sa }} name: {{ $sa }}
{{- end }} {{- end }}
{{- else }}
fail (printf "no service accounts defined for app %s" .Name )
{{- end}}

17
cmd/policygen/util.go Normal file
View File

@ -0,0 +1,17 @@
package main
import "maps"
func MapKeys[K comparable, V any](m map[K]V) []K {
return IterToSlice(maps.Keys(m))
}
func MapValues[K comparable, V any](m map[K]V) []V {
return IterToSlice(maps.Values(m))
}
func Map[K any, V any](s []K, mapper func(K) V) []V {
res := make([]V, len(s))
for i, _ := range s {
res[i] = mapper(s[i])
}
return res
}