first commit

This commit is contained in:
Erik Brakkee 2025-01-02 11:37:20 +01:00
commit 4d56d8ea21
12 changed files with 475 additions and 0 deletions

134
DESIGN.md Normal file
View File

@ -0,0 +1,134 @@
Network policy and (later) linkerd policy generator.
Basic idea:
1. The kubernetes clusters hosts applications
2. Applications communicate to other applications
Allowed communication betwen applications is configured as follows:
communication:
- from: app1
to: app2
ports:
- 80
- linkerd-admin
Ports are optional. When omitted all ports are intended
There are pre-defined applications such as api-server.
Beyond that thera are two types of applications:
- cidr: a cidr with possible except clauses following netpol syntax
- app: regular app based on matchLabels, together with namespace name.
Application names must be unique.
There are also standard capablities for an application such as:
* linkerd: addes egress to linkerd-jaeger, egress to linkerd, ingress from
linkerd-viz
capablities can also be defined at the namespace level, which means they
apply to each pod in the namespace.
networks:
- name: internet
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
namespaces:
- namespace: wamblee-org
capabilities:
- linkerd
applications:
- name: nexus-server
# ports when specified at the application level are used when
# not explicitly mentioned when a link is made
ports:
- 8081
- 8082
matchLabels:
app: nexus-server
- namespace: exposure
applications:
- name: httpd-wamblee-org
matchLabels:
app: wamblee-org
communications:
- from: # can we support both string and list of strings?
- httpd-wamblee-org
to:
- nexus-server
- wamblee-static
- wamblee-safe
# or limiting ports further
- from:
- httpd-wamblee-org
to:
- nexus-server:8081,8082
Handling of capabilities:
1. capabilities at namespace level apply to each individual pod in the
namespace
2. a capability is a list of templates.
Ingress template
from:
- linkerd-viz
to:
- {{ application }}
egress template
from:
- {{ application }}
to:
- linkerd-jaeger
- linkerd
The templates are evaluated for an application and then parsed, and added
to the allowed communications.
Linkerd extension:
* for each application an optional service account is defined, when not
defined, 'default' is assumed.
* together with the communication links this determines the authorization
policies.
Technical realization could be in the form of go templates where the input
the template is:
1. map of app name to definition with for each app also a list of
capabilities.
2. a single app config with for each app
* all ingresses
* all egresses
Similar and easier technical solution via interface with a network policy
implementation and a linkerd implementation.
Then we can have:
1. networkpolicy templates
2. server, meshtls, networkauthentication, authorization policy
* when linkerd capability for both pods, the tool can generated a
meshtlsauthentication, when the target pods does not have linkerd
capability, it can be ignored.

29
Makefile Normal file
View File

@ -0,0 +1,29 @@
.DEFAULT_GOAL := all
# seems superfluous
#.PHONY: fmt vet build clean
tools:
GOPROXY=direct go install git.wamblee.org/public/gotools/cmd/go2junit@main
fmt:
go fmt ./...
vet: fmt
go vet ./...
build: vet
mkdir -p bin
go build -o bin ./cmd/...
install:
go install ./...
test: build
go test -count=1 -coverprofile=testout/coverage.out -json ${TESTFLAGS} ./... | go2junit testout
clean:
rm -rf bin
all: build

45
cmd/policygen/config.go Normal file
View File

@ -0,0 +1,45 @@
package main
import (
"github.com/goccy/go-yaml"
"net"
)
func validateCIDR(cidr string) error {
_, _, err := net.ParseCIDR(cidr)
return err
}
type CIDR string
// UnmarshalYAML implements the yaml.Unmarshaler interface
func (c *CIDR) UnmarshalYAML(value []byte) error {
// Get the string value from the node
var s string
if err := yaml.Unmarshal(value, &s); err != nil {
return err
}
if err := validateCIDR(s); err != nil {
return err
}
*c = CIDR(s)
return nil
}
// MarshalYAML implements the yaml.Marshaler interface
func (c CIDR) MarshalYAML() ([]byte, error) {
// Do any custom processing here before marshalling
return []byte(string(c)), nil
}
// CIDRS represents each network entry in the YAML
type CIDRS struct {
Name string `yaml:"name"`
CIDR CIDR `yaml:"cidr"`
Except []CIDR `yaml:"except,omitempty"`
}
// Config represents the top-level YAML structure
type Config struct {
Networks []CIDRS `yaml:"networks"`
}

50
cmd/policygen/main.go Normal file
View File

@ -0,0 +1,50 @@
package main
import (
"bytes"
"fmt"
"github.com/goccy/go-yaml"
"github.com/spf13/cobra"
"os"
)
type Options struct {
}
func execute(files []string, options *Options) error {
if len(files) != 1 {
return fmt.Errorf("File expected")
}
yamlFile, err := os.ReadFile(files[0])
if err != nil {
return fmt.Errorf("Error reading YAML file: %v", err)
}
// Parse the YAML content
dec := yaml.NewDecoder(bytes.NewReader(yamlFile),
yaml.UseJSONUnmarshaler(),
yaml.DisallowUnknownField(),
)
var config Config
err = dec.Decode(&config)
if err != nil {
return fmt.Errorf("Error parsing YAML: %v", err)
}
fmt.Printf("PARSED %+v\n", config)
return nil
}
func main() {
options := Options{}
cmd := &cobra.Command{
Use: "policygen",
Short: "Generate network policies",
Long: "Generated policies based on a more compact representation of topology",
RunE: func(cmd *cobra.Command, args []string) error {
return execute(args, &options)
},
}
cmd.Execute()
}

View File

@ -0,0 +1,94 @@
package main
import (
"fmt"
"html/template"
"io/fs"
"os"
"strings"
)
import "embed"
import sprig "github.com/Masterminds/sprig/v3" // This provides most Helm functions
import "github.com/goccy/go-yaml"
// This provides most Helm functions
//go:embed templates/*
var templateFS embed.FS
func NewTemplate() *template.Template {
tmpl := template.New("")
// Combine sprig functions with custom functions
funcMap := template.FuncMap{}
// Add all sprig functions
for name, fn := range sprig.FuncMap() {
funcMap[name] = fn
}
// Add any custom functions you want
customFuncs := template.FuncMap{
"toYaml": func(v interface{}) string {
data, err := yaml.Marshal(v)
if err != nil {
return ""
}
return string(data)
},
}
// Merge custom functions
for name, fn := range customFuncs {
funcMap[name] = fn
}
// Add the function map to the template
tmpl = tmpl.Funcs(funcMap)
return tmpl
}
func showContents(files fs.FS) {
entries, err := fs.ReadDir(files, ".")
if err != nil {
panic(err)
}
for _, entry := range entries {
fmt.Printf("entry %s %s\n", entry.Name(), entry.Type())
if entry.Type().IsDir() {
subdir, err := fs.Sub(files, entry.Name())
if err != nil {
panic(err)
}
showContents(subdir)
}
}
}
func loadTemplates() (*template.Template, error) {
showContents(templateFS)
// Parse all templates at once from the embedded FS
tmpl := NewTemplate()
err := loadTemplatesGlob(tmpl)
if err != nil {
return nil, err
}
return tmpl, err
}
func loadTemplatesGlob(tmpl *template.Template) error {
return fs.WalkDir(templateFS, ".", func(path string, d os.DirEntry, err error) error {
if strings.HasSuffix(path, ".yaml") {
data, err := fs.ReadFile(templateFS, path)
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "Loading template %s\n", path)
tmpl.New(path).Parse(string(data))
}
return nil
})
}

View File

@ -0,0 +1,15 @@
kind: CiliumNetworkPolicy
apiVersion: cilium.io/v2
metadata:
name: {{.name}}
namespace: {{.namespace}}
spec:
endpointSelector:
{{ .selector }}
egress:
- toEntities:
- kube-apiserver
- toPorts:
- ports:
- port: "6443"
protocol: TCP

View File

@ -0,0 +1,13 @@
kind: CiliumNetworkPolicy
apiVersion: cilium.io/v2
metadata:
name: {{.name}}
namespace: {{.namespace}}
spec:
endpointSelector:
{{ .selector }}
ingress:
- fromEntities:
- kube-apiserver
# See https://github.com/cilium/cilium/issues/35401
- remote-node

View File

@ -0,0 +1,14 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: "{{.name}}"
namespace: "{{.namespace}}"
spec:
policyTypes:
- Egress
podSelector:
{{.selector}}
egress:
{{- range $from := .from }}
- {{ $from | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,14 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: "{{.name}}"
namespace: "{{.namespace}}"
spec:
policyTypes:
- Ingress
podSelector:
{{.selector}}
ingress:
{{- range $from := .from }}
- {{ $from | nindent 4 }}
{{- end }}

12
example/config.yaml Normal file
View File

@ -0,0 +1,12 @@
networks:
- name: internet
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16

21
go.mod Normal file
View File

@ -0,0 +1,21 @@
module git.wamblee.org/public/policy-generator
go 1.23.4
require (
dario.cat/mergo v1.0.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/goccy/go-yaml v1.15.13 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.26.0 // indirect
)

34
go.sum Normal file
View File

@ -0,0 +1,34 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/goccy/go-yaml v1.15.13 h1:Xd87Yddmr2rC1SLLTm2MNDcTjeO/GYo0JGiww6gSTDg=
github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=