Skip to content

Commit

Permalink
Optimization of SGs (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
YairSlobodin1 authored Dec 18, 2024
1 parent 0b37979 commit e6a3bfc
Show file tree
Hide file tree
Showing 52 changed files with 9,312 additions and 1,101 deletions.
62 changes: 44 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,69 @@
# vpc-network-config-synthesis

## About vpc-network-config-synthesis
Tool for automatic synthesis of VPC network configurations, namely Network ACLs and Security Groups.
Tool for automatic synthesis and optimization of VPC network configurations, namely Network ACLs and Security Groups.


## Usage
Use the `vpcgen` CLI tool with one of the following commands to specify the type of network resources to generate.
Use the `vpcgen` CLI tool with one of the following commands:
* `vpcgen synth sg` - generate Security Groups.
* `vpcgen synth acl` - generate an nACL for each subnet separately.
* `vpcgen synth acl --single` - generate a single nACL for all subnets in the same VPC.
* `vpcgen optimize sg` - optimize SGs.
* `vpcgen optimize acl` - optimize nACLs (In progress).

### nACLs Generation
Specifying the `--single` flag results in generating a single nACL for all subnets in the same VPC. Otherwise, an nACL is generated for each subnet separately.
**Note**: A required connection between NIFs/VSIs/VPEs implies connectivity will be allowed between the subnets they are contained in.
## Synthesis
#### nACLs Generation
A required connection between NIFs/VSIs/VPEs implies connectivity will be allowed between the subnets they are contained in.

### SGs Generation
**Note**: A Security Group, generated for a specific VSI (or for one of its NIFs), will be applied to all the NIFs of the VSI. The same goes for Reserved IPs of a VPE.
#### SGs Generation
A Security Group, generated for a specific VSI (or for one of its NIFs), will be applied to all the NIFs of the VSI. The same goes for Reserved IPs of a VPE.

### Supported types
#### Supported types
The input supports subnets, subnet segments, CIDR segments, NIFs, NIF segments, instances (VSIs), instance segments, VPEs, VPE segments and externals.
**Note**: Segments should be defined in the spec file.

### Output
1. If the `output-dir` flag is used, the specified folder will be created, containing one file per VPC. Each generated file will contain the network resources (Security Groups or Network ACLs) relevant to its VPC. File names are set as `prefix_vpc`, where prefix is ​​the value received in the `prefix` flag. If the `prefix` flag is omitted, file names will match VPC names.
2. If the `output-file` flag is used, all generated resources will be written to the specified file.
3. if both `output-file` and `output-dir` flags are not used, the collection will be written to stdout.
#### Options
```commandline
Flags:
-s, --spec string JSON file containing spec file
```

## Optimization
#### SG optimization
SG optimizatin attempts to reduce the number of security group rules in a SG without changing the semantic.
Specifying the `-n` flag results in optimizing only one given SG. Otherwise, all SGs will be optimized.
```
Flags:
-n, --sg-name string which security group to optimize
```

### Global options
#### nACL optimization (in progress)
nACL optimizatin attempts to reduce the number of nACL rules in an nACL without changing the semantic.
Specifying the `-n` flag results in optimizing only one given nACL. Otherwise, all nACLs will be optimized.
```
Flags:
-n, --acl-name string which nACL to optimize
```


## Global options
```commandline
Flags:
-c, --config string JSON file containing a configuration object of existing resources
-f, --format string Output format; must be one of [tf, csv, md, json]
-h, --help help for vpc-synthesis
-h, --help help for vpcgen
-l, --locals whether to generate a locals.tf file (only possible when the output format is tf)
-d, --output-dir string Write generated resources to files in the specified directory, one file per VPC.
-o, --output-file string Write all generated resources to the specified file.
-o, --output-file string Write all generated resources to the specified file
-p, --prefix string The prefix of the files that will be created.
-s, --spec string JSON file containing spec file
```
**Note 1**: The infrastructure configuration must always be provided using the `--config` flag.
**Note 2**: Multi-vpc input is supported.
**Note**: The infrastructure configuration must always be provided using the `--config` flag.

## Output
1. If the `output-dir` flag is used, the specified folder will be created, containing one file per VPC. Each generated file will contain the network resources (Security Groups or Network ACLs) relevant to its VPC. File names are set as `prefix_vpc`, where prefix is ​​the value received in the `prefix` flag. If the `prefix` flag is omitted, file names will match VPC names.
2. If the `output-file` flag is used, all generated resources will be written to the specified file.
3. if both `output-file` and `output-dir` flags are not used, the collection will be written to stdout.

## Build the project
Make sure you have golang 1.23+ on your platform.
Expand All @@ -60,6 +84,8 @@ make build
bin/vpcgen synth acl -c test/data/acl_testing5/config_object.json -s test/data/acl_testing5/conn_spec.json
bin/vpcgen synth sg -c test/data/sg_testing3/config_object.json -s test/data/sg_testing3/conn_spec.json
bin/vpcgen optimize sg -c test/data/optimize_sg_redundant/config_object.json
```

**Note**: Windows environment users should replace all `/` with `\`.
31 changes: 26 additions & 5 deletions cmd/subcmds/optimize.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,38 @@ SPDX-License-Identifier: Apache-2.0

package subcmds

import "github.com/spf13/cobra"
import (
"fmt"

func NewOptimizeCommand(args *inArgs) *cobra.Command {
"github.com/spf13/cobra"

"github.com/np-guard/vpc-network-config-synthesis/pkg/ir"
"github.com/np-guard/vpc-network-config-synthesis/pkg/optimize"
)

func newOptimizeCommand(args *inArgs) *cobra.Command {
cmd := &cobra.Command{
Use: "optimize",
Short: "Optimize is not supported yet",
Long: `Optimize is not supported yet`,
Short: "optimization of existing SG (nACLS are not supported yet)",
Long: `optimization of existing SG (nACLS are not supported yet)`,
}

cmd.AddCommand(newOptimizeACLCommand(args))
// sub cmds
cmd.AddCommand(newOptimizeSGCommand(args))

return cmd
}

func optimization(cmd *cobra.Command, args *inArgs, newOptimizer func(ir.Collection, string) optimize.Optimizer, isSG bool) error {
cmd.SilenceUsage = true // if we got this far, flags are syntactically correct, so no need to print usage
collection, err := parseCollection(args, isSG)
if err != nil {
return fmt.Errorf("could not parse config file %v: %w", args.configFile, err)
}
optimizer := newOptimizer(collection, args.firewallName)
optimizedCollection, err := optimizer.Optimize()
if err != nil {
return err
}
return writeOutput(args, optimizedCollection, collection.VpcNames(), false)
}
4 changes: 2 additions & 2 deletions cmd/subcmds/optimizeACL.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ package subcmds

import "github.com/spf13/cobra"

func newOptimizeACLCommand(_ *inArgs) *cobra.Command {
// temporarily exported and currently unused
func NewOptimizeACLCommand(_ *inArgs) *cobra.Command {
cmd := &cobra.Command{
Use: "acl",
Short: "OptimizeACL is not supported yet",
Expand All @@ -17,6 +18,5 @@ func newOptimizeACLCommand(_ *inArgs) *cobra.Command {
return nil
},
}

return cmd
}
19 changes: 14 additions & 5 deletions cmd/subcmds/optimizeSG.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,27 @@ SPDX-License-Identifier: Apache-2.0

package subcmds

import "github.com/spf13/cobra"
import (
"github.com/spf13/cobra"

func newOptimizeSGCommand(_ *inArgs) *cobra.Command {
sgoptimizer "github.com/np-guard/vpc-network-config-synthesis/pkg/optimize/sg"
)

const sgNameFlag = "sg-name"

func newOptimizeSGCommand(args *inArgs) *cobra.Command {
cmd := &cobra.Command{
Use: "sg",
Short: "OptimizeSG is not supported yet",
Long: `OptimizeSG is not supported yet`,
Short: "OptimizeSG attempts to reduce the number of security group rules in a SG without changing the semantic.",
Long: `OptimizeSG attempts to reduce the number of security group rules in a SG without changing the semantic.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
return nil
return optimization(cmd, args, sgoptimizer.NewSGOptimizer, true)
},
}

// flags
cmd.PersistentFlags().StringVarP(&args.firewallName, sgNameFlag, "n", "", "which security group to optimize")

return cmd
}
63 changes: 28 additions & 35 deletions cmd/subcmds/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,27 @@ import (
"github.com/np-guard/vpc-network-config-synthesis/pkg/io/csvio"
"github.com/np-guard/vpc-network-config-synthesis/pkg/io/mdio"
"github.com/np-guard/vpc-network-config-synthesis/pkg/io/tfio"

"github.com/np-guard/vpc-network-config-synthesis/pkg/ir"
)

const defaultFilePermission = 0o644
const defaultDirectoryPermission = 0o755

func writeOutput(args *inArgs, collection ir.Collection, vpcNames []ir.ID) error {
if err := updateOutputFormat(args); err != nil {
return err
}
if args.outputDir != "" && args.outputFmt == apiOutputFormat {
return fmt.Errorf("-d cannot be used with format json")
}
func writeOutput(args *inArgs, collection ir.Collection, vpcNames []string, isSynth bool) error {
if args.outputDir != "" { // create the directory if needed
if err := os.MkdirAll(args.outputDir, defaultDirectoryPermission); err != nil {
return err
}
}
if err := writeLocals(args, collection, vpcNames); err != nil {

if err := writeLocals(args, vpcNames, collection); err != nil {
return err
}

var data *bytes.Buffer
var err error
if args.outputDir == "" {
if data, err = writeCollection(args, collection, ""); err != nil {
if data, err = writeCollection(args, collection, "", isSynth); err != nil {
return err
}
return writeToFile(args.outputFile, data)
Expand All @@ -55,7 +49,7 @@ func writeOutput(args *inArgs, collection ir.Collection, vpcNames []ir.ID) error
if args.prefix != "" {
args.outputFile = args.outputDir + "/" + args.prefix + "_" + suffix
}
if data, err = writeCollection(args, collection, vpc); err != nil {
if data, err = writeCollection(args, collection, vpc, isSynth); err != nil {
return err
}
if err := writeToFile(args.outputFile, data); err != nil {
Expand All @@ -65,9 +59,9 @@ func writeOutput(args *inArgs, collection ir.Collection, vpcNames []ir.ID) error
return nil
}

func writeCollection(args *inArgs, collection ir.Collection, vpc string) (*bytes.Buffer, error) {
func writeCollection(args *inArgs, collection ir.Collection, vpc string, isSynth bool) (*bytes.Buffer, error) {
var data bytes.Buffer
writer, err := pickWriter(args, &data)
writer, err := pickWriter(args, &data, isSynth)
if err != nil {
return nil, err
}
Expand All @@ -77,15 +71,7 @@ func writeCollection(args *inArgs, collection ir.Collection, vpc string) (*bytes
return &data, nil
}

func writeToFile(outputFile string, data *bytes.Buffer) error {
if outputFile == "" {
fmt.Println(data.String())
return nil
}
return os.WriteFile(outputFile, data.Bytes(), defaultFilePermission)
}

func pickWriter(args *inArgs, data *bytes.Buffer) (ir.Writer, error) {
func pickWriter(args *inArgs, data *bytes.Buffer, isSynth bool) (ir.Writer, error) {
w := bufio.NewWriter(data)
switch args.outputFmt {
case tfOutputFormat:
Expand All @@ -94,34 +80,41 @@ func pickWriter(args *inArgs, data *bytes.Buffer) (ir.Writer, error) {
return csvio.NewWriter(w), nil
case mdOutputFormat:
return mdio.NewWriter(w), nil
case apiOutputFormat:
return confio.NewWriter(w, args.configFile)
default:
return nil, fmt.Errorf("bad output format: %q", args.outputFmt)
case jsonOutputFormat:
if isSynth {
return confio.NewWriter(w, args.configFile)
}
}
return nil, fmt.Errorf("bad output format: %q", args.outputFmt)
}

func writeLocals(args *inArgs, collection ir.Collection, vpcNames []ir.ID) error {
if !args.locals {
func writeToFile(outputFile string, data *bytes.Buffer) error {
if outputFile == "" {
fmt.Println(data.String())
return nil
}
if args.outputFmt != tfOutputFormat {
return fmt.Errorf("--locals flag requires setting the output format to tf")
}
return os.WriteFile(outputFile, data.Bytes(), defaultFilePermission)
}

_, isACLCollection := collection.(*ir.ACLCollection)
func writeLocals(args *inArgs, vpcNames []ir.ID, collection ir.Collection) error {
if !args.locals {
return nil
}
var data *bytes.Buffer
var err error

_, isACLCollection := collection.(*ir.ACLCollection)

if data, err = tfio.WriteLocals(vpcNames, isACLCollection); err != nil {
return err
}

outputFile := ""
suffix := "/locals.tf"
pathSuffix := "/locals.tf"
if args.outputDir != "" {
outputFile = args.outputDir + suffix
outputFile = args.outputDir + pathSuffix
} else if args.outputFile != "" {
outputFile = filepath.Dir(args.outputFile) + suffix
outputFile = filepath.Dir(args.outputFile) + pathSuffix
}
return writeToFile(outputFile, data)
}
6 changes: 3 additions & 3 deletions cmd/subcmds/outputFormat.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ const (
tfOutputFormat = "tf"
csvOutputFormat = "csv"
mdOutputFormat = "md"
apiOutputFormat = "json"
jsonOutputFormat = "json"
defaultOutputFormat = csvOutputFormat
)

var outputFormats = []string{tfOutputFormat, csvOutputFormat, mdOutputFormat, apiOutputFormat}
var outputFormats = []string{tfOutputFormat, csvOutputFormat, mdOutputFormat, jsonOutputFormat}

func updateOutputFormat(args *inArgs) error {
var err error
Expand All @@ -40,7 +40,7 @@ func inferFormatUsingFilename(filename string) (string, error) {
case strings.HasSuffix(filename, ".md"):
return mdOutputFormat, nil
case strings.HasSuffix(filename, ".json"):
return apiOutputFormat, nil
return jsonOutputFormat, nil
default:
return "", fmt.Errorf("bad output format")
}
Expand Down
Loading

0 comments on commit e6a3bfc

Please sign in to comment.