now inferring the service accounts from the network policy config.

This commit is contained in:
Erik Brakkee 2025-01-12 22:08:33 +01:00
parent b3c24048d6
commit 95e7106dba
3 changed files with 112 additions and 1 deletions

View File

@ -31,6 +31,7 @@ func (c *CIDR) UnmarshalYAML(value []byte) error {
return err return err
} }
*c = CIDR(s) *c = CIDR(s)
return nil return nil
} }
@ -65,6 +66,7 @@ type Application struct {
MatchLabels map[string]string `yaml:"matchLabels"` MatchLabels map[string]string `yaml:"matchLabels"`
//MatchExpressions []MatchExpression `yaml:"matchExpressions" validate:"omitempty,dive"` //MatchExpressions []MatchExpression `yaml:"matchExpressions" validate:"omitempty,dive"`
MatchExpressions []metav1.LabelSelectorRequirement `yaml:"matchExpressions" validate:"omitempty,dive"` MatchExpressions []metav1.LabelSelectorRequirement `yaml:"matchExpressions" validate:"omitempty,dive"`
ServiceAccounts []string `yaml:"serviceAccounts,omitempty"`
Namespace *Namespace `yaml:"-" validate:"-"` Namespace *Namespace `yaml:"-" validate:"-"`
} }
@ -171,6 +173,16 @@ func (c Config) GetApplication(name string) (*Application, *Network, string) {
return nil, nil, "" return nil, nil, ""
} }
func (c *Config) Infer(resolver func(application *Application) []string) {
for _, ns := range c.Namespaces {
for _, app := range ns.Applications {
if len(app.ServiceAccounts) == 0 {
app.ServiceAccounts = resolver(app)
}
}
}
}
func LoadConfig(file string) (*Config, error) { func LoadConfig(file string) (*Config, error) {
fmt.Fprintf(os.Stderr, "Reading config %s\n", file) fmt.Fprintf(os.Stderr, "Reading config %s\n", file)
yamlFile, err := os.ReadFile(file) yamlFile, err := os.ReadFile(file)

View File

@ -10,6 +10,7 @@ import (
"log" "log"
"maps" "maps"
"os" "os"
"slices"
"strconv" "strconv"
) )
@ -38,11 +39,28 @@ func MapKeys[K comparable, V any](m map[K]V) []K {
} }
func validate(files []string, options *Options) error { func validate(files []string, options *Options) error {
clientset, _ := GetKubernetesConnection()
config, err := readConfig(files) config, err := readConfig(files)
if err != nil { if err != nil {
return err return err
} }
clientset, _ := GetKubernetesConnection() config.Infer(func(application *Application) []string {
pods := FindPods(application, clientset)
var res []string
for _, pod := range pods {
if !slices.Contains(res, pod.Spec.ServiceAccountName) {
res = append(res, pod.Spec.ServiceAccountName)
}
}
log.Printf("Inferred service accounts: %s/%s: %v", application.Namespace.Name, application.Name,
res)
return res
})
// map applname1 -> appname2 where appname1 is in an open namespace and app2 is in a closed namespace.
openToClosedAccess := make(map[string]string)
applicationPods := make(map[string][]v1.Pod)
for _, ns := range config.Namespaces { for _, ns := range config.Namespaces {
namespace := ns.Name namespace := ns.Name
_, err = clientset.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) _, err = clientset.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{})
@ -50,8 +68,14 @@ func validate(files []string, options *Options) error {
LogValidationMsg(Error, "ERROR: namespace not found: %s", namespace) LogValidationMsg(Error, "ERROR: namespace not found: %s", namespace)
continue continue
} }
// checking for service accounts shared by applications
// map of namespace/sa -> []applicationname
serviceAccountMap := make(map[string][]string)
for _, application := range ns.Applications { for _, application := range ns.Applications {
pods := FindPods(application, clientset) pods := FindPods(application, clientset)
applicationPods[application.Name] = pods
//log.Printf(namespace + "/" + application.Name) //log.Printf(namespace + "/" + application.Name)
if len(pods) == 0 { if len(pods) == 0 {
LogValidationMsg(Error, "application %s: no running pods found", application.Name) LogValidationMsg(Error, "application %s: no running pods found", application.Name)
@ -76,9 +100,47 @@ func validate(files []string, options *Options) error {
} }
} }
// Check service accounts // Check service accounts
applicationServiceAccounts := make(map[string]bool)
for _, sa := range application.ServiceAccounts {
applicationServiceAccounts[sa] = true
}
for _, pod := range pods {
delete(applicationServiceAccounts, pod.Spec.ServiceAccountName)
}
if len(applicationServiceAccounts) > 0 {
LogValidationMsg(Error, "application %s: service accounts %v configured but not used by workloads",
application.Name, MapKeys(applicationServiceAccounts))
}
for _, pod := range pods {
sa := pod.Namespace + "/" + pod.Spec.ServiceAccountName
serviceAccountMap[sa] = append(serviceAccountMap[sa],
application.Name)
if pod.Spec.ServiceAccountName == "default" {
LogValidationMsg(Warning, "Pod %s/%s: running with default service account",
pod.Namespace, pod.Name)
if application.ServiceAccounts != nil && !slices.Contains(application.ServiceAccounts, "default") {
LogValidationMsg(Warning, "application %s: Pod %s/%s: running with 'default' service account but configured with %v",
application.Name, pod.Namespace, pod.Name, application.ServiceAccounts)
}
} else {
if !slices.Contains(application.ServiceAccounts, pod.Spec.ServiceAccountName) {
LogValidationMsg(Warning, "application %s: Pod %s/%s: running with service account '%s' but configured with %v",
application.Name, pod.Namespace, pod.Name, pod.Spec.ServiceAccountName, application.ServiceAccounts)
} }
} }
}
}
// service accounts shared by multiple applications.
for sa, applist := range serviceAccountMap {
if len(applist) == 1 {
continue
}
LogValidationMsg(Error, "service account %s: shared by multiple applications %v", sa, applist)
}
}
for _, communication := range config.Communications { for _, communication := range config.Communications {
if len(communication.Ports) == 0 { if len(communication.Ports) == 0 {
continue continue
@ -99,7 +161,42 @@ func validate(files []string, options *Options) error {
} }
} }
for _, communication := range config.Communications {
for _, applicationName := range communication.To {
application, _, _ := config.GetApplication(applicationName)
if application == nil {
continue
} }
// capability linkerd must exist on target namespace
if !slices.Contains(application.Namespace.Capabilities, "linkerd") {
continue
}
// annotation linkerd.io/inject must not be disabled
pods := applicationPods[applicationName]
for _, pod := range pods {
if pod.Annotations["linkerd.io/inject"] == "disabled" {
continue
}
}
if !application.Namespace.Open {
for _, applicationNameFrom := range communication.From {
applicationFrom, _, _ := config.GetApplication(applicationNameFrom)
if applicationFrom != nil && !applicationFrom.Namespace.Open {
continue
}
openToClosedAccess[applicationNameFrom] = applicationName
}
}
}
}
}
for appFrom, appTo := range openToClosedAccess {
LogValidationMsg(Error, "Access from 'open' application '%s' to 'closed' application '%s'",
appFrom, appTo)
}
return nil return nil
} }

View File

@ -27,6 +27,8 @@ namespaces:
- port: 8081 - port: 8081
- port: 8082 - port: 8082
protocol: UDP protocol: UDP
serviceAccounts:
- jantje
matchLabels: matchLabels:
app: nexus-server app: nexus-server
#matchExpressions: #matchExpressions: