policy-generator/cmd/policygen/linkerd_generator.go
Erik Brakkee cd4023f5ce emojivoto is working.
But... need to do major rework

only a single networkauthentication may be set
the required authenticationRefs in the authorization policy are anded
together so we should use a separate authorization policy for each
communication link
2025-01-25 12:44:01 +01:00

172 lines
4.9 KiB
Go

package main
import (
"fmt"
"io"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"os"
"slices"
"strconv"
"strings"
)
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 only 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) []v1.ServiceAccount {
serviceAccounts := []v1.ServiceAccount{}
for _, peer := range peers {
for _, sa := range peer.Application.ServiceAccounts {
serviceAccounts = append(serviceAccounts, v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: sa,
Namespace: peer.Application.Namespace.Name,
},
})
}
}
slices.SortFunc(serviceAccounts, func(s1 v1.ServiceAccount, s2 v1.ServiceAccount) int {
return strings.Compare(s1.Namespace+"/"+s1.Name, s2.Namespace+"/"+s2.Name)
})
return slices.CompactFunc(serviceAccounts, func(s1, s2 v1.ServiceAccount) bool {
return s1.Namespace == s2.Namespace && s1.Name == s2.Name
})
}
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
}