Feat/conformity test (#79)

* Added files required to request conformance with kubernetes
* Extended firewall implementation to allow port ranges
* Added default nodeport range to vpc network config
This commit is contained in:
datosh 2022-04-26 17:09:03 +02:00 committed by GitHub
parent 51068abc27
commit 2a766a3ab5
14 changed files with 263 additions and 47 deletions

View File

@ -65,7 +65,10 @@ type createNetworkSecurityGroupInput struct {
// CreateSecurityGroup creates a security group containing firewall rules. // CreateSecurityGroup creates a security group containing firewall rules.
func (c *Client) CreateSecurityGroup(ctx context.Context, input NetworkSecurityGroupInput) error { func (c *Client) CreateSecurityGroup(ctx context.Context, input NetworkSecurityGroupInput) error {
rules := input.Ingress.Azure() rules, err := input.Ingress.Azure()
if err != nil {
return err
}
createNetworkSecurityGroupInput := createNetworkSecurityGroupInput{ createNetworkSecurityGroupInput := createNetworkSecurityGroupInput{
name: "constellation-security-group-" + c.uid, name: "constellation-security-group-" + c.uid,

View File

@ -63,14 +63,14 @@ func TestCreateSecurityGroup(t *testing.T) {
Description: "test-1 description", Description: "test-1 description",
Protocol: "tcp", Protocol: "tcp",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 9000, FromPort: 9000,
}, },
{ {
Name: "test-2", Name: "test-2",
Description: "test-2 description", Description: "test-2 description",
Protocol: "udp", Protocol: "udp",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 51820, FromPort: 51820,
}, },
}, },
Egress: cloudtypes.Firewall{}, Egress: cloudtypes.Firewall{},

View File

@ -33,7 +33,7 @@ func TestCreator(t *testing.T) {
GCPCoordinatorInstanceTemplate: "coordinator-template", GCPCoordinatorInstanceTemplate: "coordinator-template",
GCPNetwork: "network", GCPNetwork: "network",
GCPSubnetwork: "subnetwork", GCPSubnetwork: "subnetwork",
GCPFirewalls: []string{"coordinator", "wireguard", "ssh"}, GCPFirewalls: []string{"coordinator", "wireguard", "ssh", "nodeport"},
} }
wantAzureState := state.ConstellationState{ wantAzureState := state.ConstellationState{

View File

@ -2,7 +2,6 @@ package cloudtypes
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
@ -16,12 +15,13 @@ type FirewallRule struct {
Description string Description string
Protocol string Protocol string
IPRange string IPRange string
Port int FromPort int
ToPort int
} }
type Firewall []FirewallRule type Firewall []FirewallRule
func (f Firewall) GCP() []*computepb.Firewall { func (f Firewall) GCP() ([]*computepb.Firewall, error) {
var fw []*computepb.Firewall var fw []*computepb.Firewall
for _, rule := range f { for _, rule := range f {
var destRange []string = nil var destRange []string = nil
@ -29,11 +29,15 @@ func (f Firewall) GCP() []*computepb.Firewall {
destRange = append(destRange, rule.IPRange) destRange = append(destRange, rule.IPRange)
} }
ports, err := portOrRange(rule.FromPort, rule.ToPort)
if err != nil {
return nil, err
}
fw = append(fw, &computepb.Firewall{ fw = append(fw, &computepb.Firewall{
Allowed: []*computepb.Allowed{ Allowed: []*computepb.Allowed{
{ {
IPProtocol: proto.String(rule.Protocol), IPProtocol: proto.String(rule.Protocol),
Ports: []string{fmt.Sprint(rule.Port)}, Ports: []string{ports},
}, },
}, },
Description: proto.String(rule.Description), Description: proto.String(rule.Description),
@ -41,15 +45,19 @@ func (f Firewall) GCP() []*computepb.Firewall {
Name: proto.String(rule.Name), Name: proto.String(rule.Name),
}) })
} }
return fw return fw, nil
} }
func (f Firewall) Azure() []*armnetwork.SecurityRule { func (f Firewall) Azure() ([]*armnetwork.SecurityRule, error) {
var fw []*armnetwork.SecurityRule var fw []*armnetwork.SecurityRule
for i, rule := range f { for i, rule := range f {
// format string according to armnetwork.SecurityRuleProtocol specification // format string according to armnetwork.SecurityRuleProtocol specification
protocol := strings.Title(strings.ToLower(rule.Protocol)) protocol := strings.Title(strings.ToLower(rule.Protocol))
dstPortRange, err := portOrRange(rule.FromPort, rule.ToPort)
if err != nil {
return nil, err
}
fw = append(fw, &armnetwork.SecurityRule{ fw = append(fw, &armnetwork.SecurityRule{
Name: proto.String(rule.Name), Name: proto.String(rule.Name),
Properties: &armnetwork.SecurityRulePropertiesFormat{ Properties: &armnetwork.SecurityRulePropertiesFormat{
@ -58,7 +66,7 @@ func (f Firewall) Azure() []*armnetwork.SecurityRule {
SourceAddressPrefix: proto.String(rule.IPRange), SourceAddressPrefix: proto.String(rule.IPRange),
SourcePortRange: proto.String("*"), SourcePortRange: proto.String("*"),
DestinationAddressPrefix: proto.String(rule.IPRange), DestinationAddressPrefix: proto.String(rule.IPRange),
DestinationPortRange: proto.String(strconv.Itoa(rule.Port)), DestinationPortRange: proto.String(dstPortRange),
Access: armnetwork.SecurityRuleAccessAllow.ToPtr(), Access: armnetwork.SecurityRuleAccessAllow.ToPtr(),
Direction: armnetwork.SecurityRuleDirectionInbound.ToPtr(), Direction: armnetwork.SecurityRuleDirectionInbound.ToPtr(),
// Each security role needs a unique priority // Each security role needs a unique priority
@ -66,15 +74,15 @@ func (f Firewall) Azure() []*armnetwork.SecurityRule {
}, },
}) })
} }
return fw return fw, nil
} }
func (f Firewall) AWS() []ec2types.IpPermission { func (f Firewall) AWS() []ec2types.IpPermission {
var fw []ec2types.IpPermission var fw []ec2types.IpPermission
for _, rule := range f { for _, rule := range f {
fw = append(fw, ec2types.IpPermission{ fw = append(fw, ec2types.IpPermission{
FromPort: proto.Int32(int32(rule.Port)), FromPort: proto.Int32(int32(rule.FromPort)),
ToPort: proto.Int32(int32(rule.Port)), ToPort: proto.Int32(int32(rule.ToPort)),
IpProtocol: proto.String(rule.Protocol), IpProtocol: proto.String(rule.Protocol),
IpRanges: []ec2types.IpRange{ IpRanges: []ec2types.IpRange{
{ {
@ -86,3 +94,38 @@ func (f Firewall) AWS() []ec2types.IpPermission {
} }
return fw return fw
} }
const (
MinPort = 0
MaxPort = 65535
)
// PortOutOfRangeError occurs when either FromPort or ToPort are out of range
// of [MinPort-MaxPort].
type PortOutOfRangeError struct {
FromPort int
ToPort int
}
func (p *PortOutOfRangeError) Error() string {
return fmt.Sprintf(
"[%d-%d] not in allowed port range of [%d-%d]",
p.FromPort, p.ToPort, MinPort, MaxPort,
)
}
// portOrRange returns "fromPort" as single port, if toPort is zero.
// If toPort is >0 a port range of form "fromPort-toPort".
// If either value is negative PortOutOfRangeError is returned.
func portOrRange(fromPort, toPort int) (string, error) {
if fromPort < MinPort || toPort < MinPort || fromPort > MaxPort || toPort > MaxPort {
return "", &PortOutOfRangeError{FromPort: fromPort, ToPort: toPort}
}
if toPort == MinPort || fromPort == toPort {
return fmt.Sprintf("%d", fromPort), nil
}
if toPort > MinPort {
return fmt.Sprintf("%d-%d", fromPort, toPort), nil
}
return "", &PortOutOfRangeError{FromPort: fromPort, ToPort: toPort}
}

View File

@ -20,18 +20,19 @@ func TestFirewallGCP(t *testing.T) {
Description: "This is the Test-1 Permission", Description: "This is the Test-1 Permission",
Protocol: "tcp", Protocol: "tcp",
IPRange: "", IPRange: "",
Port: 9000, FromPort: 9000,
}, },
{ {
Name: "test-2", Name: "test-2",
Description: "This is the Test-2 Permission", Description: "This is the Test-2 Permission",
Protocol: "udp", Protocol: "udp",
IPRange: "", IPRange: "",
Port: 51820, FromPort: 51820,
}, },
} }
firewalls := testFw.GCP() firewalls, err := testFw.GCP()
assert.NoError(err)
assert.Equal(2, len(firewalls)) assert.Equal(2, len(firewalls))
// Check permissions // Check permissions
@ -41,7 +42,7 @@ func TestFirewallGCP(t *testing.T) {
actualPort, err := strconv.Atoi(actualPermission1.GetPorts()[0]) actualPort, err := strconv.Atoi(actualPermission1.GetPorts()[0])
require.NoError(err) require.NoError(err)
assert.Equal(testFw[i].Port, actualPort) assert.Equal(testFw[i].FromPort, actualPort)
assert.Equal(testFw[i].Protocol, actualPermission1.GetIPProtocol()) assert.Equal(testFw[i].Protocol, actualPermission1.GetIPProtocol())
assert.Equal(testFw[i].Name, firewall1.GetName()) assert.Equal(testFw[i].Name, firewall1.GetName())
@ -58,21 +59,21 @@ func TestFirewallAzure(t *testing.T) {
Description: "perm1 description", Description: "perm1 description",
Protocol: "TCP", Protocol: "TCP",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 22, FromPort: 22,
}, },
{ {
Name: "perm2", Name: "perm2",
Description: "perm2 description", Description: "perm2 description",
Protocol: "udp", Protocol: "udp",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 4433, FromPort: 4433,
}, },
{ {
Name: "perm3", Name: "perm3",
Description: "perm3 description", Description: "perm3 description",
Protocol: "tcp", Protocol: "tcp",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 4433, FromPort: 4433,
}, },
} }
wantOutput := []*armnetwork.SecurityRule{ wantOutput := []*armnetwork.SecurityRule{
@ -120,7 +121,8 @@ func TestFirewallAzure(t *testing.T) {
}, },
} }
out := input.Azure() out, err := input.Azure()
assert.NoError(err)
assert.Equal(wantOutput, out) assert.Equal(wantOutput, out)
} }
@ -132,19 +134,22 @@ func TestIPPermissonsToAWS(t *testing.T) {
Description: "perm1", Description: "perm1",
Protocol: "TCP", Protocol: "TCP",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 22, FromPort: 22,
ToPort: 22,
}, },
{ {
Description: "perm2", Description: "perm2",
Protocol: "UDP", Protocol: "UDP",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 4433, FromPort: 4433,
ToPort: 4433,
}, },
{ {
Description: "perm3", Description: "perm3",
Protocol: "TCP", Protocol: "TCP",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 4433, FromPort: 4433,
ToPort: 4433,
}, },
} }
wantOutput := []ec2types.IpPermission{ wantOutput := []ec2types.IpPermission{
@ -186,3 +191,73 @@ func TestIPPermissonsToAWS(t *testing.T) {
out := input.AWS() out := input.AWS()
assert.Equal(wantOutput, out) assert.Equal(wantOutput, out)
} }
func TestPortOrRange(t *testing.T) {
testCases := map[string]struct {
fromPort int
toPort int
result string
wantErr bool
}{
"ssh": {
fromPort: 22,
result: "22",
},
"https": {
fromPort: 443,
result: "443",
},
"nodePorts": {
fromPort: 30000,
toPort: 32767,
result: "30000-32767",
},
"negative fromPort": {
fromPort: -1,
wantErr: true,
},
"negative toPort": {
toPort: -1,
wantErr: true,
},
"same value no range": {
fromPort: 22,
toPort: 22,
result: "22",
},
"from zero to ssh": {
toPort: 22,
result: "0-22",
},
"from max": {
fromPort: MaxPort,
result: "65535",
},
"from max+1": {
fromPort: MaxPort + 1,
wantErr: true,
},
"to max": {
toPort: MaxPort,
result: "0-65535",
},
"to max+1": {
toPort: MaxPort + 1,
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
got, err := portOrRange(tc.fromPort, tc.toPort)
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
assert.Equal(tc.result, got)
})
}
}

View File

@ -19,13 +19,13 @@ func TestCreateSecurityGroup(t *testing.T) {
Description: "perm1", Description: "perm1",
Protocol: "TCP", Protocol: "TCP",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 22, FromPort: 22,
}, },
{ {
Description: "perm2", Description: "perm2",
Protocol: "UDP", Protocol: "UDP",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 4433, FromPort: 4433,
}, },
}, },
Outbound: cloudtypes.Firewall{ Outbound: cloudtypes.Firewall{
@ -33,7 +33,7 @@ func TestCreateSecurityGroup(t *testing.T) {
Description: "perm3", Description: "perm3",
Protocol: "TCP", Protocol: "TCP",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 4040, FromPort: 4040,
}, },
}, },
} }
@ -173,13 +173,13 @@ func TestAuthorizeSecurityGroup(t *testing.T) {
Description: "perm1", Description: "perm1",
Protocol: "TCP", Protocol: "TCP",
IPRange: " 192.0.2.0/24", IPRange: " 192.0.2.0/24",
Port: 22, FromPort: 22,
}, },
{ {
Description: "perm2", Description: "perm2",
Protocol: "UDP", Protocol: "UDP",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 4433, FromPort: 4433,
}, },
}, },
Outbound: cloudtypes.Firewall{ Outbound: cloudtypes.Firewall{
@ -187,7 +187,7 @@ func TestAuthorizeSecurityGroup(t *testing.T) {
Description: "perm3", Description: "perm3",
Protocol: "TCP", Protocol: "TCP",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 4040, FromPort: 4040,
}, },
}, },
} }

View File

@ -16,7 +16,10 @@ func (c *Client) CreateFirewall(ctx context.Context, input FirewallInput) error
if c.network == "" { if c.network == "" {
return errors.New("client has not network") return errors.New("client has not network")
} }
firewallRules := input.Ingress.GCP() firewallRules, err := input.Ingress.GCP()
if err != nil {
return err
}
var ops []Operation var ops []Operation
for _, rule := range firewallRules { for _, rule := range firewallRules {
c.firewalls = append(c.firewalls, rule.GetName()) c.firewalls = append(c.firewalls, rule.GetName())

View File

@ -174,14 +174,14 @@ func TestCreateFirewall(t *testing.T) {
Description: "test-1 description", Description: "test-1 description",
Protocol: "tcp", Protocol: "tcp",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 9000, FromPort: 9000,
}, },
cloudtypes.FirewallRule{ cloudtypes.FirewallRule{
Name: "test-2", Name: "test-2",
Description: "test-2 description", Description: "test-2 description",
Protocol: "udp", Protocol: "udp",
IPRange: "192.0.2.0/24", IPRange: "192.0.2.0/24",
Port: 51820, FromPort: 51820,
}, },
}, },
Egress: cloudtypes.Firewall{}, Egress: cloudtypes.Firewall{},

2
conformance/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
e2e.log
junit_01.xml

8
conformance/PRODUCT.yml Normal file
View File

@ -0,0 +1,8 @@
vendor: Edgeless Systems
name: Constellation
version: v1.0.0
website_url: https://www.edgeless.systems/products/constellation/
documentation_url: https://constellation-docs.edgeless.systems/6c320851-bdd2-41d5-bf10-e27427398692/
product_logo_url: https://constellation-docs.edgeless.systems/6c320851-bdd2-41d5-bf10-e27427398692/_assets/constellation_white_bg.svg
type: distribution
description: Constellation creates a confidential Kubernetes cluster.

56
conformance/README.md Normal file
View File

@ -0,0 +1,56 @@
# Reproducing Conformance Test Results
## Prerequisites
[Install & configure `gcloud` CLI](https://cloud.google.com/sdk/gcloud) for access to GCP.
[Install WireGuard](https://www.wireguard.com/install/) for connecting to your cluster
[Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/) for working with Kubernetes
For more information [follow our documentation.](https://constellation-docs.edgeless.systems/6c320851-bdd2-41d5-bf10-e27427398692/#/getting-started/install)
Additionally, [Sonobuoy CLI is required.](https://github.com/vmware-tanzu/sonobuoy/releases)
These tests results were produced using Sonobuoy v0.56.4.
## Provision Constellation Cluster
```sh
constellation create gcp 1 2 n2d-standard-2 -y
constellation init
wg-quick up ./wg0.conf
export KUBECONFIG="$PWD/constellation-admin.conf"
```
## Run Conformance Tests
```sh
# Runs for ~2 hours.
sonobuoy run --mode certified-conformance
# Once status shows tests have completed...
sonobuoy status
# ... download & display results.
outfile=$(sonobuoy retrieve)
sonobuoy results $outfile
```
## Fetch Test Log & Report
The provided `e2e.log` & `junit_01.xml` were fetched like this:
```sh
tar -xvf $outfile
cat plugins/e2e/results/global/e2e.log
cat plugins/e2e/results/global/junit_01.xml
```
## Cleanup
```sh
# Remove test deployments
sonobuoy delete --wait
# Or, shutdown cluster
wg-quick down ./wg0.conf
./constellation terminate
rm constellation-mastersecret.base64
```

View File

@ -69,31 +69,38 @@ func Default() *Config {
Description: "Coordinator default port", Description: "Coordinator default port",
Protocol: "TCP", Protocol: "TCP",
IPRange: "0.0.0.0/0", IPRange: "0.0.0.0/0",
Port: constants.CoordinatorPort, FromPort: constants.CoordinatorPort,
}, },
{ {
Description: "Enclave SSH", Description: "Enclave SSH",
Protocol: "TCP", Protocol: "TCP",
IPRange: "0.0.0.0/0", IPRange: "0.0.0.0/0",
Port: constants.EnclaveSSHPort, FromPort: constants.EnclaveSSHPort,
}, },
{ {
Description: "WireGuard default port", Description: "WireGuard default port",
Protocol: "UDP", Protocol: "UDP",
IPRange: "0.0.0.0/0", IPRange: "0.0.0.0/0",
Port: constants.WireguardPort, FromPort: constants.WireguardPort,
}, },
{ {
Description: "SSH", Description: "SSH",
Protocol: "TCP", Protocol: "TCP",
IPRange: "0.0.0.0/0", IPRange: "0.0.0.0/0",
Port: constants.SSHPort, FromPort: constants.SSHPort,
}, },
{ {
Description: "NVMe over TCP", Description: "NVMe over TCP",
Protocol: "TCP", Protocol: "TCP",
IPRange: "0.0.0.0/0", IPRange: "0.0.0.0/0",
Port: constants.NVMEOverTCPPort, FromPort: constants.NVMEOverTCPPort,
},
{
Description: "NodePort",
Protocol: "TCP",
IPRange: "0.0.0.0/0",
FromPort: constants.NodePortFrom,
ToPort: constants.NodePortTo,
}, },
}, },
}, },
@ -110,21 +117,29 @@ func Default() *Config {
Description: "Coordinator default port", Description: "Coordinator default port",
Protocol: "tcp", Protocol: "tcp",
IPRange: "0.0.0.0/0", IPRange: "0.0.0.0/0",
Port: constants.CoordinatorPort, FromPort: constants.CoordinatorPort,
}, },
{ {
Name: "wireguard", Name: "wireguard",
Description: "WireGuard default port", Description: "WireGuard default port",
Protocol: "udp", Protocol: "udp",
IPRange: "0.0.0.0/0", IPRange: "0.0.0.0/0",
Port: constants.WireguardPort, FromPort: constants.WireguardPort,
}, },
{ {
Name: "ssh", Name: "ssh",
Description: "SSH", Description: "SSH",
Protocol: "tcp", Protocol: "tcp",
IPRange: "0.0.0.0/0", IPRange: "0.0.0.0/0",
Port: constants.SSHPort, FromPort: constants.SSHPort,
},
{
Name: "nodeport",
Description: "NodePort",
Protocol: "tcp",
IPRange: "0.0.0.0/0",
FromPort: constants.NodePortFrom,
ToPort: constants.NodePortTo,
}, },
}, },
}, },
@ -142,19 +157,26 @@ func Default() *Config {
Name: "coordinator", Name: "coordinator",
Description: "Coordinator default port", Description: "Coordinator default port",
Protocol: "tcp", Protocol: "tcp",
Port: constants.CoordinatorPort, FromPort: constants.CoordinatorPort,
}, },
{ {
Name: "wireguard", Name: "wireguard",
Description: "WireGuard default port", Description: "WireGuard default port",
Protocol: "udp", Protocol: "udp",
Port: constants.WireguardPort, FromPort: constants.WireguardPort,
}, },
{ {
Name: "ssh", Name: "ssh",
Description: "SSH", Description: "SSH",
Protocol: "tcp", Protocol: "tcp",
Port: constants.SSHPort, FromPort: constants.SSHPort,
},
{
Name: "nodeport",
Description: "NodePort",
Protocol: "tcp",
FromPort: constants.NodePortFrom,
ToPort: constants.NodePortTo,
}, },
}, },
}, },

View File

@ -27,13 +27,13 @@ func TestFromFile(t *testing.T) {
Name: "firstFirewallRule", Name: "firstFirewallRule",
Description: "firstFirewallRule description", Description: "firstFirewallRule description",
Protocol: "tcp", Protocol: "tcp",
Port: 4444, FromPort: 4444,
}, },
{ {
Name: "secondFirewallRule", Name: "secondFirewallRule",
Description: "secondFirewallRule description", Description: "secondFirewallRule description",
Protocol: "udp", Protocol: "udp",
Port: 5555, FromPort: 5555,
}, },
}, },
}, },

View File

@ -14,6 +14,10 @@ const (
SSHPort = 22 SSHPort = 22
WireguardPort = 51820 WireguardPort = 51820
NVMEOverTCPPort = 8009 NVMEOverTCPPort = 8009
// Default NodePort Range
// https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
NodePortFrom = 30000
NodePortTo = 32767
// //
// Filenames. // Filenames.