Skip to content

Commit

Permalink
Semantic diff cmd (#190)
Browse files Browse the repository at this point in the history
* copying changes from branch diff_new_part1

* copying diff_new_part_2

* copy from diff_new_part_3

* handle disjoint ip-blocks grouping + update documentation (#198)

Signed-off-by: adisos <[email protected]>

* Diff with added/removed peers info (#202)

* diff_test with k8s-ingress :
-> new deployment (and its service)
-> new ingress backend rule;  ingress-controller connection to the new deployment
-> new netpols -> to limit conns. between/from/to the "old" pods

* diff with acs-security-demos:
-> removed deployment payment/mastercard-processor
-> blocked ingress conns to frontend/asset-cache
-> added new port to backend/catalog (changed some conns to it)
-> added new external/unicorn deployment (all UDP5353 conns automatically appeared)
-> removed conns between two deployments (frontend/webapp to backend/shipping)

* move formats to common

* update connlistAnalyzer with diff flags

* logger.info on diff/no-diff + keep output empty if no diff

* revert changes to StopOnError

* Diff code updates (#195)

* move formats to common

* update connlistAnalyzer with diff flags

* logger.info on diff/no-diff + keep output empty if no diff

* revert changes to StopOnError

* Update pkg/netpol/common/outputFormats.go

Co-authored-by: Adi Sosnovich <[email protected]>

---------

Co-authored-by: Adi Sosnovich <[email protected]>

* diff fatal errors examples - wrong format / fatal errors returned from ca

* append connlistAnalyzer.Errors() to diff_errors

* tests capturing connlist warnings

* one more test

* no tests with disjoint/group IP

* fix after merge

* moving ingress analyzer diffs to the end of the output

* adding test descriptions

* more diff with ingress objects tests

* update output with info for connections with added/removed workloads

* fixes

* testing with and wo stopOnError

* remove ingress-controller from added/removed info

* update syntax

* changing ConnlistAnalyzer interface, returning list of peersNames fom ConnlistFromDirPath

* adding test with a peer exists in both dirs but appears only in dir2 conns

* code simplifications

* another code simplification

* fixes

* diff fatal errors examples  (#196)

* fixes

* revert diff_test change

* fix

* comparing peerStirng with exact

* fix

* fix2

---------

Co-authored-by: Adi Sosnovich <[email protected]>

* merge fix

---------

Signed-off-by: adisos <[email protected]>
Co-authored-by: shireenf-ibm <[email protected]>
Co-authored-by: shireenf-ibm <[email protected]>
  • Loading branch information
3 people committed Aug 6, 2023
1 parent 84f9070 commit 23a7dcb
Show file tree
Hide file tree
Showing 107 changed files with 9,770 additions and 2,515 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,26 @@ Global Flags:
-v, --verbose Runs with more informative messages printed to log
```

### Diff command
```
Reports all differences in allowed connections between two different directories of YAML manifests.
Usage:
k8snetpolicy diff [flags]
Examples:
# Get list of different allowed connections between two resources dir paths
k8snetpolicy diff --dir1 ./resources_dir/ --dir2 ./other_resources_dir/
Flags:
--dir1 string First resources dir path
--dir2 string Second resources dir path to be compared with the first dir path
-o, --output string Required output format (txt, csv, md) (default "txt")
-h, --help help for diff
```

### Example outputs:

```
$ k8snetpolicy eval --dirpath tests/onlineboutique -s adservice-77d5cd745d-t8mx4 -d emailservice-54c7c5d9d-vp27n -p 80
Expand Down Expand Up @@ -101,6 +118,20 @@ default/loadgenerator[Deployment] => default/frontend[Deployment] : TCP 8080
default/recommendationservice[Deployment] => default/productcatalogservice[Deployment] : TCP 3550
default/redis-cart[Deployment] => 0.0.0.0-255.255.255.255 : All Connections
$ ./bin/k8snetpolicy diff --dir1 tests/onlineboutique_workloads --dir2 tests/onlineboutique_workloads_changed_netpols
Connectivity diff:
source: default/checkoutservice[Deployment], destination: default/cartservice[Deployment], dir1: TCP 7070, dir2: TCP 8000, diff-type: changed
source: default/checkoutservice[Deployment], destination: default/emailservice[Deployment], dir1: TCP 8080, dir2: TCP 8080,9555, diff-type: changed
source: default/cartservice[Deployment], destination: default/emailservice[Deployment], dir1: No Connections, dir2: TCP 9555, diff-type: added
source: default/checkoutservice[Deployment], destination: default/adservice[Deployment], dir1: No Connections, dir2: TCP 9555, diff-type: added
source: 128.0.0.0-255.255.255.255, destination: default/redis-cart[Deployment], dir1: All Connections, dir2: No Connections, diff-type: removed
source: default/checkoutservice[Deployment], destination: default/currencyservice[Deployment], dir1: TCP 7000, dir2: No Connections, diff-type: removed
source: default/frontend[Deployment], destination: default/adservice[Deployment], dir1: TCP 9555, dir2: No Connections, diff-type: removed
source: default/redis-cart[Deployment], destination: 0.0.0.0-255.255.255.255, dir1: All Connections, dir2: No Connections, diff-type: removed
```

Additional details about the connectivity analysis and its output is specified [here](docs/connlist_output.md).
Expand Down
89 changes: 88 additions & 1 deletion cmd/netpolicy/cmd/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ type cmdTest struct {
isErr bool
}

func TestCommannds(t *testing.T) {
func TestCommands(t *testing.T) {
tests := []cmdTest{
{
name: "test_illegal_command",
Expand All @@ -105,6 +105,34 @@ func TestCommannds(t *testing.T) {
isErr: true,
},

{
name: "test_illegal_diff_no_args",
args: []string{"diff"},
expectedOutput: "both directory paths dir1 and dir2 are required",
containment: true,
isErr: true,
},
{
name: "test_illegal_diff_unsupported_args",
args: []string{"diff", "--dirpath", filepath.Join(getTestsDir(), "onlineboutique")},
expectedOutput: "dirpath flag is not used with diff command",
containment: true,
isErr: true,
},
{
name: "test_illegal_diff_output_format",
args: []string{
"diff",
"--dir1",
filepath.Join(getTestsDir(), "onlineboutique_workloads"),
"--dir2",
filepath.Join(getTestsDir(), "onlineboutique_workloads_changed_workloads"),
"-o",
"png"},
expectedOutput: "png output format is not supported.",
containment: true,
isErr: true,
},
{
name: "test_illegal_eval_peer_not_found",
args: []string{
Expand Down Expand Up @@ -268,6 +296,65 @@ func TestCommannds(t *testing.T) {
containment: true,
isErr: true,
},
{
name: "test_legal_diff_txt_output",
args: []string{
"diff",
"--dir1",
filepath.Join(getTestsDir(), "onlineboutique_workloads"),
"--dir2",
filepath.Join(getTestsDir(), "onlineboutique_workloads_changed_workloads"),
"--output",
"txt",
},
// expected first 3 rows
expectedOutput: "Connectivity diff:\n" +
"source: 0.0.0.0-255.255.255.255, destination: default/unicorn[Deployment], " +
"dir1: No Connections, dir2: All Connections, diff-type: added (workload default/unicorn[Deployment] added)\n" +
"source: default/redis-cart[Deployment], destination: default/unicorn[Deployment], " +
"dir1: No Connections, dir2: All Connections, diff-type: added (workload default/unicorn[Deployment] added)",
containment: true,
isErr: false,
},
{
name: "test_legal_diff_csv_output",
args: []string{
"diff",
"--dir1",
filepath.Join(getTestsDir(), "onlineboutique_workloads"),
"--dir2",
filepath.Join(getTestsDir(), "onlineboutique_workloads_changed_workloads"),
"--output",
"csv",
},
// expected first 3 rows
expectedOutput: "source,destination,dir1,dir2,diff-type\n" +
"0.0.0.0-255.255.255.255,default/unicorn[Deployment],No Connections," +
"All Connections,added (workload default/unicorn[Deployment] added)\n" +
"default/redis-cart[Deployment],default/unicorn[Deployment],No Connections,All Connections," +
"added (workload default/unicorn[Deployment] added)",
containment: true,
isErr: false,
},
{
name: "test_legal_diff_md_output",
args: []string{
"diff",
"--dir1",
filepath.Join(getTestsDir(), "onlineboutique_workloads"),
"--dir2",
filepath.Join(getTestsDir(), "onlineboutique_workloads_changed_workloads"),
"--output",
"md",
},
// expected first 3 rows
expectedOutput: "| source | destination | dir1 | dir2 | diff-type |\n" +
"|--------|-------------|------|------|-----------|\n" +
"| 0.0.0.0-255.255.255.255 | default/unicorn[Deployment] | No Connections | All Connections |" +
" added (workload default/unicorn[Deployment] added) |",
containment: true,
isErr: false,
},
}

for _, test := range tests {
Expand Down
88 changes: 88 additions & 0 deletions cmd/netpolicy/cmd/diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2022
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd

import (
"errors"
"fmt"
"strings"

"github.com/spf13/cobra"

"github.com/np-guard/netpol-analyzer/pkg/netpol/common"

"github.com/np-guard/netpol-analyzer/pkg/netpol/diff"
)

var (
dir1 string
dir2 string
outFormat string
)

func runDiffCommand() error {
var connsDiff diff.ConnectivityDiff
var err error

diffAnalyzer := diff.NewDiffAnalyzer(diff.WithOutputFormat(outFormat))

connsDiff, err = diffAnalyzer.ConnDiffFromDirPaths(dir1, dir2)
if err != nil {
return err
}
out, err := diffAnalyzer.ConnectivityDiffToString(connsDiff)
if err != nil {
return err
}
fmt.Printf("%s", out)
return nil
}

func newCommandDiff() *cobra.Command {
c := &cobra.Command{
Use: "diff",
Short: "Reports semantic-diff of allowed connectivity ",
Long: `Reports all differences in allowed connections between two different directories of YAML manifests.`,
Example: ` # Get list of different allowed connections between two resources dir paths
k8snetpolicy diff --dir1 ./resources_dir/ --dir2 ./other_resources_dir/`,

PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if dirPath != "" {
return errors.New("dirpath flag is not used with diff command")
}
if dir1 == "" || dir2 == "" {
return errors.New("both directory paths dir1 and dir2 are required")
}
if err := diff.ValidateDiffOutputFormat(outFormat); err != nil {
return err
}
return nil
},

RunE: func(cmd *cobra.Command, args []string) error {
if err := runDiffCommand(); err != nil {
return err
}
return nil
},
}

// define any flags and configuration settings.
c.Flags().StringVarP(&dir1, "dir1", "", "", "Original Resources path to be compared")
c.Flags().StringVarP(&dir2, "dir2", "", "", "New Resources path to compare with original resources path")
supportedDiffFormats := strings.Join(diff.ValidDiffFormats, ",")
c.Flags().StringVarP(&outFormat, "output", "o", common.DefaultFormat, "Required output format ("+supportedDiffFormats+")")

return c
}
8 changes: 5 additions & 3 deletions cmd/netpolicy/cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (

"github.com/spf13/cobra"

"github.com/np-guard/netpol-analyzer/pkg/netpol/common"

"github.com/np-guard/netpol-analyzer/pkg/netpol/connlist"
"github.com/np-guard/netpol-analyzer/pkg/netpol/logger"
)
Expand All @@ -38,9 +40,9 @@ func runListCommand() error {
connlist.WithOutputFormat(output))

if dirPath != "" {
conns, err = analyzer.ConnlistFromDirPath(dirPath)
conns, _, err = analyzer.ConnlistFromDirPath(dirPath)
} else {
conns, err = analyzer.ConnlistFromK8sCluster(clientset)
conns, _, err = analyzer.ConnlistFromK8sCluster(clientset)
}
if err != nil {
return err
Expand Down Expand Up @@ -96,7 +98,7 @@ defined`,
c.Flags().StringVarP(&focusWorkload, "focusworkload", "", "", "Focus connections of specified workload name in the output")
// output format - default txt
supportedFormats := strings.Join(connlist.ValidFormats, ",")
c.Flags().StringVarP(&output, "output", "o", connlist.DefaultFormat, "Required output format ("+supportedFormats+")")
c.Flags().StringVarP(&output, "output", "o", common.DefaultFormat, "Required output format ("+supportedFormats+")")

return c
}
1 change: 1 addition & 0 deletions cmd/netpolicy/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func newCommandRoot() *cobra.Command {
// add sub-commands
c.AddCommand(newCommandEvaluate())
c.AddCommand(newCommandList())
c.AddCommand(newCommandDiff())

return c
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package k8s
package common

import (
"encoding/binary"
Expand All @@ -7,8 +7,6 @@ import (
"sort"
"strconv"
"strings"

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

const (
Expand All @@ -22,7 +20,7 @@ const (

// IPBlock captures a set of ip ranges
type IPBlock struct {
ipRange common.CanonicalIntervalSet
ipRange CanonicalIntervalSet
}

// ToIPRanges returns a string of the ip ranges in the current IPBlock object
Expand Down Expand Up @@ -61,12 +59,12 @@ func (b *IPBlock) ipCount() int {
return res
}

// split returns a set of IpBlock objects, each with a single range of ips
func (b *IPBlock) split() []*IPBlock {
// Split returns a set of IpBlock objects, each with a single range of ips
func (b *IPBlock) Split() []*IPBlock {
res := make([]*IPBlock, len(b.ipRange.IntervalSet))
for index, ipr := range b.ipRange.IntervalSet {
newBlock := IPBlock{}
newBlock.ipRange.IntervalSet = append(newBlock.ipRange.IntervalSet, common.Interval{Start: ipr.Start, End: ipr.End})
newBlock.ipRange.IntervalSet = append(newBlock.ipRange.IntervalSet, Interval{Start: ipr.Start, End: ipr.End})
res[index] = &newBlock
}
return res
Expand Down Expand Up @@ -127,14 +125,14 @@ func addIntervalToList(ipbNew *IPBlock, ipbList []*IPBlock) []*IPBlock {
break
}
}
ipbList = append(ipbList, ipbNew.split()...)
ipbList = append(ipbList, ipbNew.Split()...)
ipbList = append(ipbList, toAdd...)
return ipbList
}

// NewIPBlock returns an IPBlock object from input cidr str an exceptions cidr str
func NewIPBlock(cidr string, exceptions []string) (*IPBlock, error) {
res := IPBlock{ipRange: common.CanonicalIntervalSet{}}
res := IPBlock{ipRange: CanonicalIntervalSet{}}
interval, err := cidrToInterval(cidr)
if err != nil {
return nil, err
Expand Down Expand Up @@ -172,10 +170,25 @@ func cidrToIPRange(cidr string) (beginning, end int64, err error) {
return int64(start), int64(finish), nil
}

func cidrToInterval(cidr string) (*common.Interval, error) {
func cidrToInterval(cidr string) (*Interval, error) {
start, end, err := cidrToIPRange(cidr)
if err != nil {
return nil, err
}
return &common.Interval{Start: start, End: end}, nil
return &Interval{Start: start, End: end}, nil
}

func (b *IPBlock) ContainedIn(other *IPBlock) bool {
return b.ipRange.ContainedIn(other.ipRange)
}

func MergeIPBlocksList(inputList []*IPBlock) []*IPBlock {
if len(inputList) == 0 {
return inputList
}
union := inputList[0].Copy()
for i := 1; i < len(inputList); i++ {
union.ipRange.Union(inputList[i].ipRange)
}
return union.Split()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package k8s
package common

import (
"fmt"
Expand Down
11 changes: 11 additions & 0 deletions pkg/netpol/common/outputFormats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package common

// formats supported for output of various commands
const (
DefaultFormat = "txt"
TextFormat = "txt"
JSONFormat = "json"
DOTFormat = "dot"
CSVFormat = "csv"
MDFormat = "md"
)
Loading

0 comments on commit 23a7dcb

Please sign in to comment.