From 55788106b9fca177d82d3d2662216a8e51e61aff Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Sun, 17 Mar 2024 14:08:57 +0200 Subject: [PATCH] add data model from synthesis spec (#22) Signed-off-by: Elazar Gershuni --- Makefile | 9 + pkg/model/data_model.go | 403 ++++++++++++++++++++++++++++++++++++++++ spec_schema.json | 241 ++++++++++++++++++++++++ 3 files changed, 653 insertions(+) create mode 100644 pkg/model/data_model.go create mode 100644 spec_schema.json diff --git a/Makefile b/Makefile index 7f6abe2..c02dc03 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ REPOSITORY := github.com/np-guard/models +JSON_PACKAGE_NAME := model mod: go.mod @echo -- $@ -- @@ -19,3 +20,11 @@ precommit: mod fmt lint test: @echo -- $@ -- go test ./... -v -cover -coverprofile models.coverprofile + +pkg/${JSON_PACKAGE_NAME}/data_model.go: spec_schema.json + @echo -- generate -- + # Install https://github.com/atombender/go-jsonschema + go-jsonschema spec_schema.json --package ${JSON_PACKAGE_NAME} --struct-name-from-title --tags json --output $@ + goimports -local $(REPOSITORY) -w $@ + +generate: pkg/${JSON_PACKAGE_NAME}/data_model.go diff --git a/pkg/model/data_model.go b/pkg/model/data_model.go new file mode 100644 index 0000000..fcb56cd --- /dev/null +++ b/pkg/model/data_model.go @@ -0,0 +1,403 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package model + +import ( + "encoding/json" + "fmt" + "reflect" +) + +type AnyProtocol struct { + // Necessarily ANY + Protocol AnyProtocolProtocol `json:"protocol"` +} + +type AnyProtocolProtocol string + +const AnyProtocolProtocolANY AnyProtocolProtocol = "ANY" + +type Icmp struct { + // ICMP code allowed. If omitted, any code is allowed + Code *int `json:"code,omitempty"` + + // Necessarily ICMP + Protocol IcmpProtocol `json:"protocol"` + + // ICMP type allowed. If omitted, any type is allowed + Type *int `json:"type,omitempty"` +} + +type IcmpProtocol string + +const IcmpProtocolICMP IcmpProtocol = "ICMP" + +type Protocol interface{} + +type ProtocolList []interface{} + +type Resource struct { + // Name of resource + Name string `json:"name"` + + // Type of resource + Type ResourceType `json:"type"` +} + +type ResourceType string + +const ResourceTypeCidr ResourceType = "cidr" +const ResourceTypeExternal ResourceType = "external" +const ResourceTypeInstance ResourceType = "instance" +const ResourceTypeNif ResourceType = "nif" +const ResourceTypeSegment ResourceType = "segment" +const ResourceTypeSubnet ResourceType = "subnet" +const ResourceTypeVpe ResourceType = "vpe" + +type Spec struct { + // Externals are a way for users to name CIDRs external to the VPC. These are + // later used in src/dst definitions + Externals SpecExternals `json:"externals,omitempty"` + + // Lightweight way to define instance as a list of interfaces. + Instances SpecInstances `json:"instances,omitempty"` + + // Lightweight way to define network interfaces. + Nifs SpecNifs `json:"nifs,omitempty"` + + // A list of required connections + RequiredConnections []SpecRequiredConnectionsElem `json:"required-connections"` + + // Segments are a way for users to create aggregations. These can later be used in + // src/dst fields + Segments SpecSegments `json:"segments,omitempty"` + + // Lightweight way to define subnets. + Subnets SpecSubnets `json:"subnets,omitempty"` +} + +// Externals are a way for users to name CIDRs external to the VPC. These are later +// used in src/dst definitions +type SpecExternals map[string]string + +// Lightweight way to define instance as a list of interfaces. +type SpecInstances map[string][]string + +// Lightweight way to define network interfaces. +type SpecNifs map[string]string + +type SpecRequiredConnectionsElem struct { + // List of allowed protocols + AllowedProtocols ProtocolList `json:"allowed-protocols,omitempty"` + + // If true, allow both connections from src to dst and connections from dst to src + Bidirectional bool `json:"bidirectional,omitempty"` + + // In unidirectional connection, this is the ingress resource + Dst Resource `json:"dst"` + + // In unidirectional connection, this is the egress resource + Src Resource `json:"src"` +} + +// Segments are a way for users to create aggregations. These can later be used in +// src/dst fields +type SpecSegments map[string]struct { + // All items are of the type specified in the type property, identified by name + Items []string `json:"items"` + + // The type of the elements inside the segment + Type Type `json:"type"` +} + +// Lightweight way to define subnets. +type SpecSubnets map[string]string + +type TcpUdp struct { + // Maximal destination port; default is 65535 + MaxDestinationPort int `json:"max_destination_port,omitempty"` + + // Maximal source port; default is 65535. Unsupported in vpc synthesis + MaxSourcePort int `json:"max_source_port,omitempty"` + + // Minimal destination port; default is 1 + MinDestinationPort int `json:"min_destination_port,omitempty"` + + // Minimal source port; default is 1. Unsupported in vpc synthesis + MinSourcePort int `json:"min_source_port,omitempty"` + + // Is it TCP or UDP + Protocol TcpUdpProtocol `json:"protocol"` +} + +type TcpUdpProtocol string + +const TcpUdpProtocolTCP TcpUdpProtocol = "TCP" + +// UnmarshalJSON implements json.Unmarshaler. +func (j *SpecRequiredConnectionsElem) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["dst"]; !ok || v == nil { + return fmt.Errorf("field dst in SpecRequiredConnectionsElem: required") + } + if v, ok := raw["src"]; !ok || v == nil { + return fmt.Errorf("field src in SpecRequiredConnectionsElem: required") + } + type Plain SpecRequiredConnectionsElem + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if v, ok := raw["bidirectional"]; !ok || v == nil { + plain.Bidirectional = false + } + *j = SpecRequiredConnectionsElem(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *TcpUdpProtocol) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_TcpUdpProtocol { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_TcpUdpProtocol, v) + } + *j = TcpUdpProtocol(v) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *IcmpProtocol) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_IcmpProtocol { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_IcmpProtocol, v) + } + *j = IcmpProtocol(v) + return nil +} + +const TcpUdpProtocolUDP TcpUdpProtocol = "UDP" + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Resource) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in Resource: required") + } + if v, ok := raw["type"]; !ok || v == nil { + return fmt.Errorf("field type in Resource: required") + } + type Plain Resource + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Resource(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *TcpUdp) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["protocol"]; !ok || v == nil { + return fmt.Errorf("field protocol in TcpUdp: required") + } + type Plain TcpUdp + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if v, ok := raw["max_destination_port"]; !ok || v == nil { + plain.MaxDestinationPort = 65535.0 + } + if v, ok := raw["max_source_port"]; !ok || v == nil { + plain.MaxSourcePort = 65535.0 + } + if v, ok := raw["min_destination_port"]; !ok || v == nil { + plain.MinDestinationPort = 1.0 + } + if v, ok := raw["min_source_port"]; !ok || v == nil { + plain.MinSourcePort = 1.0 + } + *j = TcpUdp(plain) + return nil +} + +var enumValues_AnyProtocolProtocol = []interface{}{ + "ANY", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ResourceType) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_ResourceType { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_ResourceType, v) + } + *j = ResourceType(v) + return nil +} + +var enumValues_ResourceType = []interface{}{ + "external", + "segment", + "subnet", + "instance", + "nif", + "cidr", + "vpe", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *AnyProtocolProtocol) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_AnyProtocolProtocol { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_AnyProtocolProtocol, v) + } + *j = AnyProtocolProtocol(v) + return nil +} + +var enumValues_TcpUdpProtocol = []interface{}{ + "TCP", + "UDP", +} + +type Type string + +var enumValues_Type = []interface{}{ + "subnet", + "cidr", + "instance", + "nif", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Type) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_Type { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_Type, v) + } + *j = Type(v) + return nil +} + +const TypeSubnet Type = "subnet" +const TypeCidr Type = "cidr" +const TypeInstance Type = "instance" +const TypeNif Type = "nif" + +// UnmarshalJSON implements json.Unmarshaler. +func (j *AnyProtocol) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["protocol"]; !ok || v == nil { + return fmt.Errorf("field protocol in AnyProtocol: required") + } + type Plain AnyProtocol + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = AnyProtocol(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Icmp) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["protocol"]; !ok || v == nil { + return fmt.Errorf("field protocol in Icmp: required") + } + type Plain Icmp + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Icmp(plain) + return nil +} + +var enumValues_IcmpProtocol = []interface{}{ + "ICMP", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Spec) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["required-connections"]; !ok || v == nil { + return fmt.Errorf("field required-connections in Spec: required") + } + type Plain Spec + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Spec(plain) + return nil +} diff --git a/spec_schema.json b/spec_schema.json new file mode 100644 index 0000000..a09a237 --- /dev/null +++ b/spec_schema.json @@ -0,0 +1,241 @@ +{ + "title": "Spec", + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "https://github.com/np-guard/vpc-network-synthesis/v0.1", + "$defs": { + "any-protocol": { + "type": "object", + "properties": { + "protocol": { + "description": "Necessarily ANY", + "enum": [ + "ANY" + ] + } + }, + "required": [ + "protocol" + ], + "additionalProperties": false + }, + "tcp-udp": { + "type": "object", + "properties": { + "protocol": { + "description": "Is it TCP or UDP", + "enum": [ + "TCP", + "UDP" + ] + }, + "min_destination_port": { + "description": "Minimal destination port; default is 1", + "type": "integer", + "minimum": 1, + "maximum": 65535, + "default": 1 + }, + "max_destination_port": { + "description": "Maximal destination port; default is 65535", + "type": "integer", + "minimum": 1, + "maximum": 65535, + "default": 65535 + }, + "min_source_port": { + "description": "Minimal source port; default is 1. Unsupported in vpc synthesis", + "type": "integer", + "minimum": 1, + "maximum": 65535, + "default": 1 + }, + "max_source_port": { + "description": "Maximal source port; default is 65535. Unsupported in vpc synthesis", + "type": "integer", + "minimum": 1, + "maximum": 65535, + "default": 65535 + } + }, + "required": [ + "protocol" + ], + "additionalProperties": false + }, + "icmp": { + "type": "object", + "properties": { + "protocol": { + "description": "Necessarily ICMP", + "enum": [ + "ICMP" + ] + }, + "type": { + "description": "ICMP type allowed. If omitted, any type is allowed", + "type": "integer", + "minimum": 0, + "maximum": 16 + }, + "code": { + "description": "ICMP code allowed. If omitted, any code is allowed", + "type": "integer", + "minimum": 0, + "maximum": 5 + } + }, + "required": [ + "protocol" + ], + "additionalProperties": false + }, + "protocol": { + "oneOf": [ + { + "$ref": "#/$defs/tcp-udp" + }, + { + "$ref": "#/$defs/icmp" + }, + { + "$ref": "#/$defs/any-protocol" + } + ] + }, + "protocol-list": { + "type": "array", + "items": { + "$ref": "#/$defs/protocol" + } + }, + "resource": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "description": "Name of resource", + "type": "string" + }, + "type": { + "description": "Type of resource", + "enum": [ + "external", + "segment", + "subnet", + "instance", + "nif", + "cidr", + "vpe" + ] + } + }, + "required": [ + "name", + "type" + ] + } + }, + "type": "object", + "additionalProperties": false, + "properties": { + "segments": { + "description": "Segments are a way for users to create aggregations. These can later be used in src/dst fields", + "type": "object", + "additionalProperties": { + "description": "A segment is a named collection of resources of the same type (subnet, cidr, instance, or nif)", + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "description": "The type of the elements inside the segment", + "enum": [ + "subnet", + "cidr", + "instance", + "nif" + ] + }, + "items": { + "description": "All items are of the type specified in the type property, identified by name", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "subnets": { + "type": "object", + "description": "Lightweight way to define subnets.", + "additionalProperties": { + "type": "string", + "pattern": "^\\d{1,3}(\\.\\d{1,3}){3}/([1-2]?[0-9]|3[0-2])$" + } + }, + "nifs": { + "type": "object", + "description": "Lightweight way to define network interfaces.", + "additionalProperties": { + "type": "string", + "pattern": "^\\d{1,3}(\\.\\d{1,3}){3}$" + } + }, + "instances": { + "type": "object", + "description": "Lightweight way to define instance as a list of interfaces.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "externals": { + "type": "object", + "description": "Externals are a way for users to name CIDRs external to the VPC. These are later used in src/dst definitions", + "additionalProperties": { + "type": "string", + "pattern": "^\\d{1,3}(\\.\\d{1,3}){3}(/([1-2]?[0-9]|3[0-2]))?$" + } + }, + "required-connections": { + "type": "array", + "description": "A list of required connections", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "src", + "dst" + ], + "properties": { + "src": { + "description": "In unidirectional connection, this is the egress resource", + "$ref": "#/$defs/resource" + }, + "dst": { + "description": "In unidirectional connection, this is the ingress resource", + "$ref": "#/$defs/resource" + }, + "bidirectional": { + "description": "If true, allow both connections from src to dst and connections from dst to src", + "type": "boolean", + "default": false + }, + "allowed-protocols": { + "description": "List of allowed protocols", + "$ref": "#/$defs/protocol-list" + } + } + } + } + }, + "required": [ + "required-connections" + ] +}