diff --git a/cmd/policygen/config.go b/cmd/policygen/config.go index e8a3494..793255c 100644 --- a/cmd/policygen/config.go +++ b/cmd/policygen/config.go @@ -240,6 +240,11 @@ func LoadConfig(file string) (*Config, error) { 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" + } + } } } diff --git a/cmd/policygen/configvalidator.go b/cmd/policygen/configvalidator.go index fc89a58..f6f6c43 100644 --- a/cmd/policygen/configvalidator.go +++ b/cmd/policygen/configvalidator.go @@ -4,7 +4,6 @@ import ( "fmt" "iter" "k8s.io/api/core/v1" - "maps" "os" "slices" "strconv" @@ -35,13 +34,6 @@ func IterToSlice[K any](i iter.Seq[K]) []K { 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 { clientset, _ := GetKubernetesConnection() config, err := readConfig(files) diff --git a/cmd/policygen/generator.go b/cmd/policygen/generator.go index 3b0da7a..fa4c329 100644 --- a/cmd/policygen/generator.go +++ b/cmd/policygen/generator.go @@ -6,9 +6,10 @@ import ( ) type Generator interface { - Init(write io.Writer) error + Init(writer io.Writer) error GenerateNamespace(writer io.Writer, namespace *Namespace) error GenerateCommunicationRule(writer io.Writer, app *Application, ingress *Ingress, egress *Egress) error + Finalize(writer io.Writer) error } type ApplicationPeer struct { @@ -172,5 +173,5 @@ func Generate(writer io.Writer, generator Generator, config *Config) error { } } - return nil + return generator.Finalize(writer) } diff --git a/cmd/policygen/linkerd_generator.go b/cmd/policygen/linkerd_generator.go index 615f78d..c9252a6 100644 --- a/cmd/policygen/linkerd_generator.go +++ b/cmd/policygen/linkerd_generator.go @@ -4,71 +4,154 @@ import ( "fmt" "io" "os" + "slices" + "strconv" ) type LinkerdPolicyGenerator struct { config *Config policyTemplates *PolicyTemplates + + networks map[string]*Network } -func (g LinkerdPolicyGenerator) Init(writer io.Writer) error { - // start by generating network authentications - for _, network := range g.config.Networks { - fmt.Fprintf(os.Stderr, "NetworkAuthentication default/%s\n", network.Name) - template := g.policyTemplates.PredefineApplicationPolicyTemplate("linkerd", "networkauthentication") - 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 NewLinkerdPolicyGenerator(config *Config, templates *PolicyTemplates) *LinkerdPolicyGenerator { + return &LinkerdPolicyGenerator{ + config: config, + policyTemplates: templates, + networks: make(map[string]*Network), } +} + +func (g *LinkerdPolicyGenerator) Init(writer io.Writer) error { return nil } -func (g LinkerdPolicyGenerator) GenerateNamespace(writer io.Writer, namespace *Namespace) error { +func (g *LinkerdPolicyGenerator) GenerateNamespace(writer io.Writer, namespace *Namespace) error { return nil } -func (g LinkerdPolicyGenerator) GenerateCommunicationRule( +func (g *LinkerdPolicyGenerator) GenerateCommunicationRule( writer io.Writer, app *Application, ingress *Ingress, egress *Egress) error { - // and then the meshTLSAuthentications - fmt.Fprintf(os.Stderr, "MeshTLSAuthentication %s/%s %v\n", - app.Namespace.Name, app.Name, app.ServiceAccounts) - 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 + if app.Namespace.Unauthorized { + fmt.Fprintf(os.Stderr, "UNAUTHORIZED %s\n", app.Name) + return nil } // and the server resources fmt.Fprintf(os.Stderr, "Server %s/%s\n", app.Namespace.Name, app.Name) - template = g.policyTemplates.PredefineApplicationPolicyTemplate("linkerd", "server") - if template == nil { - return fmt.Errorf("Could not find meshtlsauthentication template") - } - err = template.Execute(writer, app) + err := g.policyTemplates.Execute("linkerd", "server", writer, app) if err != nil { return err } if len(ingress.Applications)+ - len(ingress.Networks)+ - len(egress.Applications)+ - len(egress.Networks) > 0 { - // non-trivial regular network policy + len(ingress.Networks) > 0 { + ports := make(map[int]bool) + // + 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 } diff --git a/cmd/policygen/main.go b/cmd/policygen/main.go index 1696f91..0c35fac 100644 --- a/cmd/policygen/main.go +++ b/cmd/policygen/main.go @@ -42,10 +42,7 @@ func generateNetworkPolicy(files []string, options *Options) error { return err } var generator Generator - generator = NetworkPolicyGenerator{ - config: config, - policyTemplates: policyTemplates, - } + generator = NewNetworkPolicyGenerator(config, policyTemplates) err = Generate(os.Stdout, generator, config) if err != nil { return err @@ -74,10 +71,7 @@ func generateLinkerdPolicies(files []string, options *Options) error { return err } var generator Generator - generator = LinkerdPolicyGenerator{ - config: config, - policyTemplates: policyTemplates, - } + generator = NewLinkerdPolicyGenerator(config, policyTemplates) err = Generate(os.Stdout, generator, config) if err != nil { return err diff --git a/cmd/policygen/netpol_generator.go b/cmd/policygen/netpol_generator.go index 67543d4..46ecb9a 100644 --- a/cmd/policygen/netpol_generator.go +++ b/cmd/policygen/netpol_generator.go @@ -12,11 +12,18 @@ type NetworkPolicyGenerator struct { 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 } -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) templates := g.policyTemplates.NamespaceTemplates("netpol", namespace.Capabilities) @@ -30,7 +37,7 @@ func (g NetworkPolicyGenerator) GenerateNamespace(writer io.Writer, namespace *N return nil } -func (g NetworkPolicyGenerator) GenerateCommunicationRule( +func (g *NetworkPolicyGenerator) GenerateCommunicationRule( writer io.Writer, app *Application, ingress *Ingress, @@ -42,11 +49,7 @@ func (g NetworkPolicyGenerator) GenerateCommunicationRule( len(egress.Networks) > 0 { // non-trivial regular network policy - tmpl := g.policyTemplates.ApplicationTemplate("netpol") - if tmpl == nil { - return fmt.Errorf("Could not find policy template for 'netpol'") - } - err := tmpl.Execute(writer, map[string]any{ + err := g.policyTemplates.Execute("netpol", "pod", writer, map[string]any{ "app": app, "ingress": ingress, "egress": egress, @@ -68,18 +71,15 @@ func (g NetworkPolicyGenerator) GenerateCommunicationRule( } for predefined, _ := range allPredefined { - tmpl := g.policyTemplates.PredefineApplicationPolicyTemplate("netpol", predefined) - if tmpl == nil { - return fmt.Errorf("Could not find predefined template for netpol/%s", predefined) - } - err := tmpl.Execute(writer, map[string]any{ - "app": app, - "ingress": slices.Contains(ingress.Predefined, predefined), - "egress": slices.Contains(egress.Predefined, predefined), - "labels": map[string]string{ - "policy-generator": "1", - }, - }) + err := g.policyTemplates.Execute("netpol", predefined, + writer, map[string]any{ + "app": app, + "ingress": slices.Contains(ingress.Predefined, predefined), + "egress": slices.Contains(egress.Predefined, predefined), + "labels": map[string]string{ + "policy-generator": "1", + }, + }) if err != nil { return err } @@ -87,3 +87,7 @@ func (g NetworkPolicyGenerator) GenerateCommunicationRule( return nil } + +func (g *NetworkPolicyGenerator) Finalize(writer io.Writer) error { + return nil +} diff --git a/cmd/policygen/templates.go b/cmd/policygen/templates.go index a492f5f..bb349b9 100644 --- a/cmd/policygen/templates.go +++ b/cmd/policygen/templates.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "io" "io/fs" "log" "os" @@ -119,12 +120,21 @@ func (t *PolicyTemplates) NamespaceTemplates(policyType string, capabilities []s 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 { tmpl := t.templates.Lookup(fmt.Sprintf("templates/%s/application/%s.yaml", policyType, predefined)) 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 + +} diff --git a/cmd/policygen/templates/linkerd/application/authorizationpolicy.yaml b/cmd/policygen/templates/linkerd/application/authorizationpolicy.yaml new file mode 100644 index 0000000..1059cc4 --- /dev/null +++ b/cmd/policygen/templates/linkerd/application/authorizationpolicy.yaml @@ -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 }} \ No newline at end of file diff --git a/cmd/policygen/templates/linkerd/application/meshtlsauthentication.yaml b/cmd/policygen/templates/linkerd/application/meshtlsauthentication.yaml index b16b44c..68af912 100644 --- a/cmd/policygen/templates/linkerd/application/meshtlsauthentication.yaml +++ b/cmd/policygen/templates/linkerd/application/meshtlsauthentication.yaml @@ -2,16 +2,12 @@ apiVersion: policy.linkerd.io/v1alpha1 kind: MeshTLSAuthentication metadata: - name: {{ .Name }} - namespace: {{ .Namespace.Name }} + name: {{ .app.Name }}-{{.port}} + namespace: {{ .app.Namespace.Name }} spec: - {{- if .ServiceAccounts }} identityRefs: - {{- range $sa := .ServiceAccounts }} + {{- range $sa := .serviceAccounts }} - kind: ServiceAccount name: {{ $sa }} {{- end }} - {{- else }} - fail (printf "no service accounts defined for app %s" .Name ) - {{- end}} diff --git a/cmd/policygen/util.go b/cmd/policygen/util.go new file mode 100644 index 0000000..a897bae --- /dev/null +++ b/cmd/policygen/util.go @@ -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 +}