package main import ( "fmt" "io" "os" "slices" "strconv" ) type LinkerdPolicyGenerator struct { config *Config policyTemplates *PolicyTemplates networks map[string]*Network } 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 { return nil } func (g *LinkerdPolicyGenerator) GenerateCommunicationRule( writer io.Writer, app *Application, ingress *Ingress, egress *Egress) error { 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) err := g.policyTemplates.Execute("linkerd", "server", writer, app) if err != nil { return err } if len(ingress.Applications)+ 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) } } } 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 }