Skip to content

Commit

Permalink
Randomise outgoing port for connections in the SNAT iptables rule. (#246
Browse files Browse the repository at this point in the history
)

* Added logic to randomize the SNAT rule using --random
* added PRNG/--random-fully functionality as well 
* Switched to case and implemented iptables version aware code.
* Completed go-iptables interface
  • Loading branch information
taylorb-syd authored and mogren committed Jan 9, 2019
1 parent 9365868 commit 5b560fb
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 5 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ Default: `false`
Specifies whether an external NAT gateway should be used to provide SNAT of secondary ENI IP addresses\. If set to `true`, the SNAT `iptables` rule and off\-VPC IP rule are not applied, and these rules are removed if they have already been applied\.
Disable SNAT if you need to allow inbound communication to your pods from external VPNs, direct connections, and external VPCs, and your pods do not need to access the Internet directly via an Internet Gateway\. However, your nodes must be running in a private subnet and connected to the internet through an AWS NAT Gateway or another external NAT device\.

`AWS_VPC_K8S_CNI_RANDOMIZESNAT`
Type: String
Default: `hashrandom`
Valid Values: `hashrandom`, `prng`, `none`
Specifies weather the SNAT `iptables` rule should randomize the outgoing ports for connections. When enabled (`hashrandom`) the `--random` flag will be added to the SNAT `iptables` rule.
To use pseudo random number generation rather than hash based (i.e. `--random-fully`) use `prng` for the environment variable. For old versions of `iptables` that do not support `--random-fully` this option will fall back to `--random`.
Disable (`none`) this functionality if you rely on sequential port allocation for outgoing connections.

`WARM_ENI_TARGET`
Type: Integer
Default: `1`
Expand Down
71 changes: 66 additions & 5 deletions pkg/networkutils/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ const (
// be installed and will be removed if they are already installed. Defaults to false.
envExternalSNAT = "AWS_VPC_K8S_CNI_EXTERNALSNAT"

// This environment is used to specify weather the SNAT rule added to iptables should randomize port
// allocation for outgoing connections. If set to "hashrandom" the SNAT iptables rule will have the "--random" flag
// added to it. Set it to "prng" if you want to use a pseudo random numbers, i.e. "--random-fully".
// Defaults to hashrandom.
envRandomizeSNAT = "AWS_VPC_K8S_CNI_RANDOMIZESNAT"

// envNodePortSupport is the name of environment variable that configures whether we implement support for
// NodePorts on the primary ENI. This requires that we add additional iptables rules and loosen the kernel's
// RPF check as described below. Defaults to true.
Expand Down Expand Up @@ -98,6 +104,7 @@ type NetworkAPIs interface {

type linuxNetwork struct {
useExternalSNAT bool
typeOfSNAT snatType
nodePortSupportEnabled bool
connmark uint32

Expand All @@ -110,15 +117,30 @@ type linuxNetwork struct {

type iptablesIface interface {
Exists(table, chain string, rulespec ...string) (bool, error)
Insert(table, chain string, pos int, rulespec ...string) error
Append(table, chain string, rulespec ...string) error
Delete(table, chain string, rulespec ...string) error
List(table, chain string) ([]string, error)
NewChain(table, chain string) error
ClearChain(table, chain string) error
DeleteChain(table, chain string) error
ListChains(table string) ([]string, error)
HasRandomFully() bool
}

type snatType uint32

const (
sequentialSNAT snatType = iota
randomHashSNAT
randomPRNGSNAT
)

// New creates a linuxNetwork object
func New() NetworkAPIs {
return &linuxNetwork{
useExternalSNAT: useExternalSNAT(),
typeOfSNAT: typeOfSNAT(),
nodePortSupportEnabled: nodePortSupportEnabled(),
mainENIMark: getConnmark(),

Expand Down Expand Up @@ -294,22 +316,34 @@ func (n *linuxNetwork) SetupHostNetwork(vpcCIDR *net.IPNet, vpcCIDRs []*string,
}})
}

// Prepare the Desired Rule for SNAT Rule
curChain := fmt.Sprintf("AWS-SNAT-CHAIN-%d", len(vpcCIDRs))
snatRule := []string{"-m", "comment", "--comment", "AWS, SNAT",
"-m", "addrtype", "!", "--dst-type", "LOCAL",
"-j", "SNAT", "--to-source", primaryAddr.String()}
if n.typeOfSNAT == randomHashSNAT {
snatRule = append(snatRule, "--random")
}
if n.typeOfSNAT == randomPRNGSNAT {
if ipt.HasRandomFully() {
snatRule = append(snatRule, "--random-fully")
} else {
log.Warn("prng (--random-fully) requested, but iptables version does not support it. " +
"Falling back to hashrandom (--random)")
snatRule = append(snatRule, "--random")
}
}
iptableRules = append(iptableRules, iptablesRule{
name: "last SNAT rule for non-VPC outbound traffic",
shouldExist: !n.useExternalSNAT,
table: "nat",
chain: curChain,
rule: []string{
"-m", "comment", "--comment", "AWS, SNAT",
"-m", "addrtype", "!", "--dst-type", "LOCAL",
"-j", "SNAT", "--to-source", primaryAddr.String()},
rule: snatRule,
})

log.Debugf("iptableRules: %v", iptableRules)

iptableRules = append(iptableRules, iptablesRule{

name: "connmark for primary ENI",
shouldExist: n.nodePortSupportEnabled,
table: "mangle",
Expand Down Expand Up @@ -428,6 +462,33 @@ func useExternalSNAT() bool {
return getBoolEnvVar(envExternalSNAT, false)
}

func typeOfSNAT() snatType {
defaultValue := randomHashSNAT
defaultString := "hashrandom"
strValue := os.Getenv(envRandomizeSNAT)
switch strValue {
case "":
// empty means default
return defaultValue
case "prng":
// prng means to use --random-fully
// note: for old versions of iptables, this will fall back to --random
return randomPRNGSNAT
case "none":
// none means to disable randomisation (no flag)
return sequentialSNAT

case defaultString:
// hashrandom means to use --random
return randomHashSNAT
default:
// if we get to this point, the environment variable has an invalid value
log.Errorf("Failed to parse %s; using default: %s. Provided string was \"%s\"", envRandomizeSNAT, defaultString,
strValue)
return defaultValue
}
}

func nodePortSupportEnabled() bool {
return getBoolEnvVar(envNodePortSupport, true)
}
Expand Down
26 changes: 26 additions & 0 deletions pkg/networkutils/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ func (ipt *mockIptables) Exists(table, chainName string, rulespec ...string) (bo
return false, nil
}

func (ipt *mockIptables) Insert(table, chain string, pos int, rulespec ...string) error {
return nil
}

func (ipt *mockIptables) Append(table, chain string, rulespec ...string) error {
if ipt.dataplaneState[table] == nil {
ipt.dataplaneState[table] = map[string][][]string{}
Expand All @@ -348,10 +352,32 @@ func (ipt *mockIptables) Delete(table, chainName string, rulespec ...string) err
return nil
}

func (ipt *mockIptables) List(table, chain string) ([]string, error) {
return nil, nil

}

func (ipt *mockIptables) NewChain(table, chain string) error {
return nil
}

func (ipt *mockIptables) ClearChain(table, chain string) error {
return nil
}

func (ipt *mockIptables) DeleteChain(table, chain string) error {
return nil
}

func (ipt *mockIptables) ListChains(table string) ([]string, error) {
return nil, nil
}

func (ipt *mockIptables) HasRandomFully() bool {
// TODO: Work out how to write a test case for this
return true
}

type mockFile struct {
closed bool
data string
Expand Down

0 comments on commit 5b560fb

Please sign in to comment.