Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

255 remove stateful #48

Merged
merged 9 commits into from
Jun 18, 2024
8 changes: 0 additions & 8 deletions pkg/connection/connectionset.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ func entireDimension(dim Dimension) *interval.CanonicalSet {

type Set struct {
connectionProperties *hypercube.CanonicalSet
IsStateful StatefulState
}

func None() *Set {
Expand Down Expand Up @@ -104,7 +103,6 @@ func (c *Set) Equal(other *Set) bool {
func (c *Set) Copy() *Set {
return &Set{
connectionProperties: c.connectionProperties.Copy(),
IsStateful: c.IsStateful,
}
}

Expand All @@ -128,12 +126,6 @@ func (c *Set) Union(other *Set) *Set {
}
}

// Subtract
// ToDo: Subtract seems to ignore IsStateful (see https://github.com/np-guard/vpc-network-config-analyzer/issues/199):
// 1. is the delta connection stateful
// 2. connectionProperties is identical but c stateful while other is not
// the 2nd item can be computed here, with enhancement to relevant structure
// the 1st can not since we do not know where exactly the statefulness came from
func (c *Set) Subtract(other *Set) *Set {
if c.IsEmpty() {
return None()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
"github.com/np-guard/models/pkg/netp"
)

func newTCPConn(t *testing.T, srcMinP, srcMaxP, dstMinP, dstMaxP int64) *connection.Set {
// todo: move to analyzer

func NewTCPConn(t *testing.T, srcMinP, srcMaxP, dstMinP, dstMaxP int64) *connection.Set {
t.Helper()
return connection.TCPorUDPConnection(netp.ProtocolStringTCP, srcMinP, srcMaxP, dstMinP, dstMaxP)
}
Expand All @@ -38,95 +40,79 @@ func newTCPUDPSet(t *testing.T, p netp.ProtocolString) *connection.Set {
connection.MinPort, connection.MaxPort)
}

type statefulnessTest struct {
type responsiveTest struct {
name string
srcToDst *connection.Set
dstToSrc *connection.Set
// expectedIsStateful represents the expected IsStateful computed value for srcToDst,
// which should be either StatefulTrue or StatefulFalse, given the input dstToSrc connection.
// the computation applies only to the TCP protocol within those connections.
expectedIsStateful connection.StatefulState
// expectedStatefulConn represents the subset from srcToDst which is not related to the "non-stateful" mark (*) on the srcToDst connection,
// the stateless part for TCP is srcToDst.Subtract(statefulConn)
expectedStatefulConn *connection.Set
}

func (tt statefulnessTest) runTest(t *testing.T) {
func (tt responsiveTest) runTest(t *testing.T) {
t.Helper()
statefulConn := tt.srcToDst.WithStatefulness(tt.dstToSrc)
require.Equal(t, tt.expectedIsStateful, tt.srcToDst.IsStateful)
statefulConn := tt.srcToDst.GetResponsiveConn(tt.dstToSrc)
require.True(t, tt.expectedStatefulConn.Equal(statefulConn))
}

func TestAll(t *testing.T) {
var testCasesStatefulness = []statefulnessTest{
var testCasesStatefulness = []responsiveTest{
{
name: "tcp_all_ports_on_both_directions",
srcToDst: newTCPUDPSet(t, netp.ProtocolStringTCP), // TCP all ports
dstToSrc: newTCPUDPSet(t, netp.ProtocolStringTCP), // TCP all ports
expectedIsStateful: connection.StatefulTrue,
expectedStatefulConn: newTCPUDPSet(t, netp.ProtocolStringTCP), // TCP all ports
},
{
name: "first_all_cons_second_tcp_with_ports",
srcToDst: connection.All(), // all connections
dstToSrc: newTCPConn(t, 80, 80, connection.MinPort, connection.MaxPort), // TCP , src-ports: 80, dst-ports: all

// there is a subset of the tcp connection which is not stateful
expectedIsStateful: connection.StatefulFalse,
dstToSrc: NewTCPConn(t, 80, 80, connection.MinPort, connection.MaxPort), // TCP , src-ports: 80, dst-ports: all

// TCP src-ports: all, dst-port: 80 , union: all non-TCP conns
expectedStatefulConn: connection.All().Subtract(newTCPUDPSet(t, netp.ProtocolStringTCP)).Union(
newTCPConn(t, connection.MinPort, connection.MaxPort, 80, 80)),
NewTCPConn(t, connection.MinPort, connection.MaxPort, 80, 80)),
},
{
name: "first_all_conns_second_no_tcp",
srcToDst: connection.All(), // all connections
dstToSrc: newICMPconn(t), // ICMP
expectedIsStateful: connection.StatefulFalse,
name: "first_all_conns_second_no_tcp",
srcToDst: connection.All(), // all connections
dstToSrc: newICMPconn(t), // ICMP
// UDP, ICMP (all TCP is considered stateless here)
expectedStatefulConn: connection.All().Subtract(newTCPUDPSet(t, netp.ProtocolStringTCP)),
},
{
name: "tcp_with_ports_both_directions_exact_match",
srcToDst: newTCPConn(t, 80, 80, 443, 443),
dstToSrc: newTCPConn(t, 443, 443, 80, 80),
expectedIsStateful: connection.StatefulTrue,
expectedStatefulConn: newTCPConn(t, 80, 80, 443, 443),
srcToDst: NewTCPConn(t, 80, 80, 443, 443),
dstToSrc: NewTCPConn(t, 443, 443, 80, 80),
expectedStatefulConn: NewTCPConn(t, 80, 80, 443, 443),
},
{
name: "tcp_with_ports_both_directions_partial_match",
srcToDst: newTCPConn(t, 80, 100, 443, 443),
dstToSrc: newTCPConn(t, 443, 443, 80, 80),
expectedIsStateful: connection.StatefulFalse,
expectedStatefulConn: newTCPConn(t, 80, 80, 443, 443),
srcToDst: NewTCPConn(t, 80, 100, 443, 443),
dstToSrc: NewTCPConn(t, 443, 443, 80, 80),
expectedStatefulConn: NewTCPConn(t, 80, 80, 443, 443),
},
{
name: "tcp_with_ports_both_directions_no_match",
srcToDst: newTCPConn(t, 80, 100, 443, 443),
dstToSrc: newTCPConn(t, 80, 80, 80, 80),
expectedIsStateful: connection.StatefulFalse,
srcToDst: NewTCPConn(t, 80, 100, 443, 443),
dstToSrc: NewTCPConn(t, 80, 80, 80, 80),
expectedStatefulConn: connection.None(),
},
{
name: "udp_and_tcp_with_ports_both_directions_no_match",
srcToDst: newTCPConn(t, 80, 100, 443, 443).Union(newUDPConn(t, 80, 100, 443, 443)),
dstToSrc: newTCPConn(t, 80, 80, 80, 80).Union(newUDPConn(t, 80, 80, 80, 80)),
expectedIsStateful: connection.StatefulFalse,
srcToDst: NewTCPConn(t, 80, 100, 443, 443).Union(newUDPConn(t, 80, 100, 443, 443)),
dstToSrc: NewTCPConn(t, 80, 80, 80, 80).Union(newUDPConn(t, 80, 80, 80, 80)),
expectedStatefulConn: newUDPConn(t, 80, 100, 443, 443),
},
{
name: "no_tcp_in_first_direction",
srcToDst: newUDPConn(t, 70, 100, 443, 443),
dstToSrc: newTCPConn(t, 70, 80, 80, 80).Union(newUDPConn(t, 70, 80, 80, 80)),
expectedIsStateful: connection.StatefulTrue,
dstToSrc: NewTCPConn(t, 70, 80, 80, 80).Union(newUDPConn(t, 70, 80, 80, 80)),
expectedStatefulConn: newUDPConn(t, 70, 100, 443, 443),
},
{
name: "empty_conn_in_first_direction",
srcToDst: connection.None(),
dstToSrc: newTCPConn(t, 80, 80, 80, 80).Union(newTCPUDPSet(t, netp.ProtocolStringUDP)),
expectedIsStateful: connection.StatefulTrue,
dstToSrc: NewTCPConn(t, 80, 80, 80, 80).Union(newTCPUDPSet(t, netp.ProtocolStringUDP)),
expectedStatefulConn: connection.None(),
},
{
Expand All @@ -135,7 +121,6 @@ func TestAll(t *testing.T) {
dstToSrc: connection.None(),
// stateful analysis does not apply to udp/icmp, thus considered in the result as "stateful"
// (to avoid marking it as stateless in the output)
expectedIsStateful: connection.StatefulTrue,
expectedStatefulConn: newTCPUDPSet(t, netp.ProtocolStringUDP).Union(newICMPconn(t)),
},
}
Expand Down
69 changes: 20 additions & 49 deletions pkg/connection/statefulness.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,66 +11,37 @@ import (
"github.com/np-guard/models/pkg/netp"
)

// default is StatefulUnknown
type StatefulState int

const (
// StatefulUnknown is the default value for a Set object,
StatefulUnknown StatefulState = 0
// StatefulTrue represents a connection object for which any allowed TCP (on all allowed src/dst ports)
// has an allowed response connection
StatefulTrue StatefulState = 1
// StatefulFalse represents a connection object for which there exists some allowed TCP
// (on any allowed subset from the allowed src/dst ports) that does not have an allowed response connection
StatefulFalse StatefulState = 2
)

// EnhancedString returns a connection string with possibly added asterisk for stateless connection
func (c *Set) EnhancedString() string {
if c.IsStateful == StatefulFalse {
return c.String() + " *"
}
return c.String()
func NewTCPSet() *Set {
return TCPorUDPConnection(netp.ProtocolStringTCP, MinPort, MaxPort, MinPort, MaxPort)
}

func newTCPSet() *Set {
return TCPorUDPConnection(netp.ProtocolStringTCP, MinPort, MaxPort, MinPort, MaxPort)
// PartitionTCPNonTCP given a connection returns its TCP and non-TCP sub-connections
func PartitionTCPNonTCP(conn *Set) (tcp, nonTCP *Set) {
tcpFractionOfConn := NewTCPSet().Intersect(conn)
nonTCPFractionOfConn := conn.Subtract(tcpFractionOfConn)
return tcpFractionOfConn, nonTCPFractionOfConn
}

// WithStatefulness updates `c` object with `IsStateful` property, based on input `secondDirectionConn`.
// GetResponsiveConn returns connection object with the exact the responsive part within TCP
// and with the original connections on other protocols.
// `c` represents a src-to-dst connection, and `secondDirectionConn` represents dst-to-src connection.
// The property `IsStateful` of `c` is set as `StatefulFalse` if there is at least some subset within TCP from `c`
// which is not stateful (such that the response direction for this subset is not enabled).
// This function also returns a connection object with the exact subset of the stateful part (within TCP)
// from the entire connection `c`, and with the original connections on other protocols.
func (c *Set) WithStatefulness(secondDirectionConn *Set) *Set {
connTCP := c.Intersect(newTCPSet())
// todo: move to analyzer
func (c *Set) GetResponsiveConn(secondDirectionConn *Set) *Set {
connTCP := c.Intersect(NewTCPSet())
if connTCP.IsEmpty() {
c.IsStateful = StatefulTrue
return c
}
statefulCombinedConnTCP := connTCP.connTCPWithStatefulness(secondDirectionConn.Intersect(newTCPSet()))
c.IsStateful = connTCP.IsStateful
return c.Subtract(connTCP).Union(statefulCombinedConnTCP)
}

// connTCPWithStatefulness assumes that both `c` and `secondDirectionConn` are within TCP.
// it assigns IsStateful a value within `c`, and returns the subset from `c` which is stateful.
func (c *Set) connTCPWithStatefulness(secondDirectionConn *Set) *Set {
tcpSecondDirection := secondDirectionConn.Intersect(NewTCPSet())
// flip src/dst ports before intersection
statefulCombinedConn := c.Intersect(secondDirectionConn.switchSrcDstPortsOnTCP())
if c.Equal(statefulCombinedConn) {
c.IsStateful = StatefulTrue
} else {
c.IsStateful = StatefulFalse
}
return statefulCombinedConn
tcpSecondDirectionFlipped := tcpSecondDirection.SwitchSrcDstPorts()
// tcp connection stateful subset
statefulCombinedConnTCP := connTCP.Intersect(tcpSecondDirectionFlipped)
return c.Subtract(connTCP).Union(statefulCombinedConnTCP)
}

// switchSrcDstPortsOnTCP returns a new Set object, built from the input Set object.
// It assumes the input connection object is only within TCP protocol.
// For TCP the src and dst ports on relevant cubes are being switched.
func (c *Set) switchSrcDstPortsOnTCP() *Set {
// SwitchSrcDstPorts returns a new Set object, built from the input Set object.
// The src and dst ports on relevant cubes are being switched.
func (c *Set) SwitchSrcDstPorts() *Set {
if c.IsAll() {
return c.Copy()
}
Expand Down