Skip to content

Commit

Permalink
create k8s resources (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
haim-kermany authored Jan 13, 2025
1 parent c3c29e5 commit 7ffdeaf
Show file tree
Hide file tree
Showing 29 changed files with 759 additions and 216 deletions.
55 changes: 53 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,66 @@ module github.com/np-guard/vmware-analyzer
go 1.23.0

require (
github.com/np-guard/models v0.5.5
github.com/np-guard/models v0.5.4
github.com/np-guard/netpol-analyzer v1.2.2-0.20250107062546-e33b8e3cd83b
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.30.3
k8s.io/apimachinery v0.30.3
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openshift/api v0.0.0-20230502160752-c71432710382 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.12.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/cli-runtime v0.30.3 // indirect
k8s.io/client-go v0.30.3 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
sigs.k8s.io/kustomize/kyaml v0.16.0 // indirect
sigs.k8s.io/network-policy-api v0.1.5 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
244 changes: 241 additions & 3 deletions go.sum

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions pkg/common/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ SPDX-License-Identifier: Apache-2.0
package common

import (
"encoding/json"
"os"
"path"
"strings"

"gopkg.in/yaml.v3"
)

const (
Expand All @@ -22,3 +26,31 @@ func WriteToFile(file, content string) error {
}
return os.WriteFile(file, []byte(content), writeFileMode)
}

func WriteYamlUsingJSON[A any](content []A, file string) error {
outs := make([]string, len(content))
for i := range content {
buf, err := marshalYamlUsingJSON(content[i])
if err != nil {
return err
}
outs[i] = string(buf)
}
return WriteToFile(file, strings.Join(outs, "---\n"))
}

func marshalYamlUsingJSON(content interface{}) ([]byte, error) {
// Directly marshaling content into YAML, results in malformed Kubernetes resources.
// This is because K8s NetworkPolicy struct has json field tags, but no yaml field tags (also true for other resources).
// The (somewhat ugly) solution is to first marshal content to json, unmarshal to an interface{} var and marshal to yaml
buf, err := json.Marshal(content)
if err != nil {
return nil, err
}
var contentFromJSON interface{}
err = json.Unmarshal(buf, &contentFromJSON)
if err != nil {
return nil, err
}
return yaml.Marshal(contentFromJSON)
}
33 changes: 21 additions & 12 deletions pkg/symbolicexpr/atomic.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
package symbolicexpr

import (
"fmt"
"slices"

"github.com/np-guard/vmware-analyzer/pkg/collector"
"github.com/np-guard/vmware-analyzer/pkg/model/endpoints"
)

func (term atomicTerm) string() string {
equalSign := " = "
if term.neg {
equalSign = " != "
}
labelType := ""
func (term atomicTerm) labelKey() string {
switch term.property.(type) {
case *collector.Segment:
labelType = "segment "
return "segment"
case *endpoints.VM:
labelType = "virtual machine "
return "virtual machine"
case *collector.Tag:
labelType = "tag " + term.property.Name()
return "tag " + term.property.Name()
// includes atomic NSX groups; e.g., groups defined over other entities (such as tags) are not included
case *collector.Group:
labelType = "group "
return "group"
default: // for structs used for testing
labelType = term.property.Name()
return term.property.Name()
}
}

func (term atomicTerm) string() string {
equalSign := " = "
if term.neg {
equalSign = " != "
}
return labelType + equalSign + term.toVal
return term.labelKey() + equalSign + term.toVal
}
func (term atomicTerm) AsSelector() (string, bool) {
return fmt.Sprintf("%s__%s", term.labelKey(), term.toVal), term.neg
}

func NewAtomicTerm(label vmProperty, toVal string, neg bool) *atomicTerm {
Expand Down Expand Up @@ -110,6 +116,9 @@ func (tautology) isTautology() bool {
func (tautology) isNegateOf(atomic) bool {
return false
}
func (tautology) AsSelector() (string, bool) {
return "", false
}

// tautology is not disjoint to any atomic term
func (tautology) disjoint(atomic, *Hints) bool {
Expand Down
1 change: 1 addition & 0 deletions pkg/symbolicexpr/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type atomic interface {
isNegation() bool
isTautology() bool
isNegateOf(atomic) bool
AsSelector() (string, bool)
disjoint(atomic, *Hints) bool // based on hints
supersetOf(atomic, *Hints) bool // based on hints
}
Expand Down
195 changes: 195 additions & 0 deletions pkg/synthesis/createK8sResources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package synthesis

import (
"fmt"
"path"
"slices"

core "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"

"github.com/np-guard/models/pkg/netp"
"github.com/np-guard/models/pkg/netset"

"github.com/np-guard/netpol-analyzer/pkg/netpol/connlist"
"github.com/np-guard/vmware-analyzer/pkg/common"
"github.com/np-guard/vmware-analyzer/pkg/symbolicexpr"
)

const k8sAPIVersion = "networking.k8s.io/v1"

func CreateK8sResources(model *AbstractModelSyn, outDir string) error {
policies := toNetworkPolicies(model)
policiesFileName := path.Join(outDir, "policies.yaml")
if err := common.WriteYamlUsingJSON(policies, policiesFileName); err != nil {
return err
}
pods := toPods(model)
podsFileName := path.Join(outDir, "pods.yaml")
if err := common.WriteYamlUsingJSON(pods, podsFileName); err != nil {
return err
}
for _, format := range []string{"txt", "dot"} {
out, err := k8sAnalyzer(outDir, format)
if err != nil {
return err
}
err = common.WriteToFile(path.Join(outDir, "k8s_connectivity."+format), out)
if err != nil {
return err
}
}
return nil
}

func toNetworkPolicies(model *AbstractModelSyn) []*networking.NetworkPolicy {
policies := []*networking.NetworkPolicy{}
addNewPolicy := func(description string) *networking.NetworkPolicy {
pol := newNetworkPolicy(fmt.Sprintf("policy_%d", len(policies)), description)
policies = append(policies, pol)
return pol
}
for _, p := range model.policy {
for _, ob := range p.outbound {
for _, p := range ob.allowOnlyRulePaths {
srcSelector, dstSelector, ports, empty := toSelectorsAndPorts(p)
if empty {
continue
}
to := []networking.NetworkPolicyPeer{{PodSelector: dstSelector}}
rules := []networking.NetworkPolicyEgressRule{{To: to, Ports: ports}}
pol := addNewPolicy(p.String())
pol.Spec.Egress = rules
pol.Spec.PolicyTypes = []networking.PolicyType{"Egress"}
pol.Spec.PodSelector = *srcSelector
}
}
for _, ib := range p.inbound {
for _, p := range ib.allowOnlyRulePaths {
srcSelector, dstSelector, ports, empty := toSelectorsAndPorts(p)
if empty {
continue
}
from := []networking.NetworkPolicyPeer{{PodSelector: srcSelector}}
rules := []networking.NetworkPolicyIngressRule{{From: from, Ports: ports}}
pol := addNewPolicy(p.String())
pol.Spec.Ingress = rules
pol.Spec.PolicyTypes = []networking.PolicyType{"Ingress"}
pol.Spec.PodSelector = *dstSelector
}
}
}
return policies
}

func newNetworkPolicy(name, description string) *networking.NetworkPolicy {
pol := &networking.NetworkPolicy{}
pol.TypeMeta.Kind = "NetworkPolicy"
pol.TypeMeta.APIVersion = k8sAPIVersion
pol.ObjectMeta.Name = name
pol.ObjectMeta.Labels = map[string]string{"description": description}
return pol
}

func toSelectorsAndPorts(p *symbolicexpr.SymbolicPath) (srcSelector, dstSelector *meta.LabelSelector,
ports []networking.NetworkPolicyPort, empty bool) {
srcSelector = toSelector(p.Src)
dstSelector = toSelector(p.Dst)
ports, empty = toPolicyPorts(p.Conn)
return
}

var codeToProtocol = map[int]core.Protocol{netset.UDPCode: core.ProtocolUDP, netset.TCPCode: core.ProtocolTCP}
var boolToOperator = map[bool]meta.LabelSelectorOperator{false: meta.LabelSelectorOpExists, true: meta.LabelSelectorOpDoesNotExist}

func pointerTo[T any](t T) *T {
return &t
}

func toSelector(con symbolicexpr.Conjunction) *meta.LabelSelector {
selector := &meta.LabelSelector{}
for _, a := range con {
label, notIn := a.AsSelector()
if label != "" { // not tautology
req := meta.LabelSelectorRequirement{Key: label, Operator: boolToOperator[notIn]}
selector.MatchExpressions = append(selector.MatchExpressions, req)
}
}
return selector
}

func toPolicyPorts(conn *netset.TransportSet) ([]networking.NetworkPolicyPort, bool) {
ports := []networking.NetworkPolicyPort{}
tcpUDPSet := conn.TCPUDPSet()
if tcpUDPSet.IsEmpty() {
return nil, true
}
if tcpUDPSet.IsAll() {
return nil, false
}
partitions := tcpUDPSet.Partitions()
for _, partition := range partitions {
protocols := partition.S1.Elements()
portRanges := partition.S3
for _, portRange := range portRanges.Intervals() {
var portPointer *intstr.IntOrString
var endPortPointer *int32
if portRange.Start() != netp.MinPort || portRange.End() != netp.MaxPort {
port := intstr.FromInt(int(portRange.Start()))
portPointer = &port
if portRange.End() != portRange.Start() {
//nolint:gosec // port should fit int32:
endPort := int32(portRange.End())
endPortPointer = &endPort
}
}
for _, protocolCode := range protocols {
ports = append(ports, networking.NetworkPolicyPort{
Protocol: pointerTo(codeToProtocol[int(protocolCode)]),
Port: portPointer,
EndPort: endPortPointer})
}
if slices.Contains(protocols, netset.TCPCode) && slices.Contains(protocols, netset.UDPCode) {
ports = append(ports, networking.NetworkPolicyPort{Protocol: pointerTo(core.ProtocolSCTP), Port: portPointer, EndPort: endPortPointer})
}
}
}
return ports, false
}

// ///////////////////////////////////////////////////////////////////////////////
func toPods(model *AbstractModelSyn) []*core.Pod {
pods := []*core.Pod{}
for _, vm := range model.vms {
pod := &core.Pod{}
pod.TypeMeta.Kind = "Pod"
pod.TypeMeta.APIVersion = k8sAPIVersion
pod.ObjectMeta.Name = vm.Name()
pod.ObjectMeta.UID = types.UID(vm.ID())
if len(model.epToGroups[vm]) == 0 {
continue
}
pod.ObjectMeta.Labels = map[string]string{}
for _, group := range model.epToGroups[vm] {
label, _ := symbolicexpr.NewAtomicTerm(group, group.Name(), false).AsSelector()
pod.ObjectMeta.Labels[label] = label
}
pods = append(pods, pod)
}
return pods
}

///////////////////////////////////////////////////////////////////////////

func k8sAnalyzer(outDir, format string) (string, error) {
analyzer := connlist.NewConnlistAnalyzer(connlist.WithOutputFormat(format))

conns, _, err := analyzer.ConnlistFromDirPath(outDir)
if err != nil {
return "", err
}
return analyzer.ConnectionsListToString(conns)
}
4 changes: 2 additions & 2 deletions pkg/synthesis/synthesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func NSXToAbstractModelSynthesis(recourses *collector.ResourcesContainerModel,
hints *symbolicexpr.Hints) (*symbolicPolicy, error) {
hints *symbolicexpr.Hints) (*AbstractModelSyn, error) {
parser := model.NewNSXConfigParserFromResourcesContainer(recourses)
err := parser.RunParser()
if err != nil {
Expand All @@ -23,5 +23,5 @@ func NSXToAbstractModelSynthesis(recourses *collector.ResourcesContainerModel,
abstractModel.epToGroups = parser.GetConfig().GroupsPerVM
abstractModel.vms = parser.VMs()
abstractModel.policy = append(abstractModel.policy, &allowOnlyPolicy)
return &allowOnlyPolicy, nil
return abstractModel, nil
}
Loading

0 comments on commit 7ffdeaf

Please sign in to comment.