Remove PublicIP from QEMU metadata (#396)

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2022-11-02 12:56:16 +01:00 committed by GitHub
parent 6d2ec109d0
commit 55cfff034a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 63 additions and 711 deletions

View file

@ -1,12 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cloudtypes
/*
Package cloudtypes contains code that is shared by all cloud providers
and provides a common interface for interacting with cloud resources.
*/

View file

@ -1,146 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cloudtypes
import (
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"golang.org/x/text/cases"
"golang.org/x/text/language"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
)
// FirewallRule holds a single firewall rule.
type FirewallRule struct {
Name string
Description string
Protocol string
IPRange string
FromPort int
ToPort int
}
// Firewall contains all firewall rules to be applied for either ingress or egress.
type Firewall []FirewallRule
func (f Firewall) GCP() ([]*computepb.Firewall, error) {
var fw []*computepb.Firewall
for _, rule := range f {
var srcRange []string
if rule.IPRange != "" {
srcRange = []string{rule.IPRange}
}
var ports []string
if rule.FromPort != 0 || rule.ToPort != 0 {
port, err := portOrRange(rule.FromPort, rule.ToPort)
if err != nil {
return nil, err
}
ports = []string{port}
}
fw = append(fw, &computepb.Firewall{
Allowed: []*computepb.Allowed{
{
IPProtocol: proto.String(rule.Protocol),
Ports: ports,
},
},
Description: proto.String(rule.Description),
SourceRanges: srcRange,
Name: proto.String(rule.Name),
})
}
return fw, nil
}
func (f Firewall) Azure() ([]*armnetwork.SecurityRule, error) {
var fw []*armnetwork.SecurityRule
for i, rule := range f {
// format string according to armnetwork.SecurityRuleProtocol specification
protocol := cases.Title(language.English).String(rule.Protocol)
dstPortRange, err := portOrRange(rule.FromPort, rule.ToPort)
if err != nil {
return nil, err
}
fw = append(fw, &armnetwork.SecurityRule{
Name: proto.String(rule.Name),
Properties: &armnetwork.SecurityRulePropertiesFormat{
Description: proto.String(rule.Description),
Protocol: (*armnetwork.SecurityRuleProtocol)(proto.String(protocol)),
SourceAddressPrefix: proto.String(rule.IPRange),
SourcePortRange: proto.String("*"),
DestinationAddressPrefix: proto.String(rule.IPRange),
DestinationPortRange: proto.String(dstPortRange),
Access: to.Ptr(armnetwork.SecurityRuleAccessAllow),
Direction: to.Ptr(armnetwork.SecurityRuleDirectionInbound),
// Each security role needs a unique priority
Priority: proto.Int32(int32(100 * (i + 1))),
},
})
}
return fw, nil
}
func (f Firewall) AWS() []ec2types.IpPermission {
var fw []ec2types.IpPermission
for _, rule := range f {
fw = append(fw, ec2types.IpPermission{
FromPort: proto.Int32(int32(rule.FromPort)),
ToPort: proto.Int32(int32(rule.ToPort)),
IpProtocol: proto.String(rule.Protocol),
IpRanges: []ec2types.IpRange{
{
CidrIp: proto.String(rule.IPRange),
Description: proto.String(rule.Description),
},
},
})
}
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

@ -1,282 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cloudtypes
import (
"strconv"
"testing"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestFirewallGCP(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
testFw := Firewall{
{
Name: "test-1",
Description: "This is the Test-1 Permission",
Protocol: "tcp",
IPRange: "",
FromPort: 9000,
},
{
Name: "test-2",
Description: "This is the Test-2 Permission",
Protocol: "udp",
IPRange: "",
FromPort: 51820,
},
{
Name: "test-3",
Description: "This is the Test-3 Permission",
Protocol: "tcp",
IPRange: "192.0.2.0/24",
FromPort: 4000,
},
}
firewalls, err := testFw.GCP()
assert.NoError(err)
assert.Equal(len(testFw), len(firewalls))
// Check permissions
for i := 0; i < len(testFw); i++ {
firewall1 := firewalls[i]
actualPermission1 := firewall1.Allowed[0]
actualPort, err := strconv.Atoi(actualPermission1.GetPorts()[0])
require.NoError(err)
assert.Equal(testFw[i].FromPort, actualPort)
assert.Equal(testFw[i].Protocol, actualPermission1.GetIPProtocol())
assert.Equal(testFw[i].Name, firewall1.GetName())
assert.Equal(testFw[i].Description, firewall1.GetDescription())
if testFw[i].IPRange != "" {
require.Len(firewall1.GetSourceRanges(), 1)
assert.Equal(testFw[i].IPRange, firewall1.GetSourceRanges()[0])
}
}
}
func TestFirewallAzure(t *testing.T) {
assert := assert.New(t)
input := Firewall{
{
Name: "perm1",
Description: "perm1 description",
Protocol: "TCP",
IPRange: "192.0.2.0/24",
FromPort: 22,
},
{
Name: "perm2",
Description: "perm2 description",
Protocol: "udp",
IPRange: "192.0.2.0/24",
FromPort: 4433,
},
{
Name: "perm3",
Description: "perm3 description",
Protocol: "tcp",
IPRange: "192.0.2.0/24",
FromPort: 4433,
},
}
wantOutput := []*armnetwork.SecurityRule{
{
Name: proto.String("perm1"),
Properties: &armnetwork.SecurityRulePropertiesFormat{
Description: proto.String("perm1 description"),
Protocol: to.Ptr(armnetwork.SecurityRuleProtocolTCP),
SourceAddressPrefix: proto.String("192.0.2.0/24"),
SourcePortRange: proto.String("*"),
DestinationAddressPrefix: proto.String("192.0.2.0/24"),
DestinationPortRange: proto.String("22"),
Access: to.Ptr(armnetwork.SecurityRuleAccessAllow),
Direction: to.Ptr(armnetwork.SecurityRuleDirectionInbound),
Priority: proto.Int32(100),
},
},
{
Name: proto.String("perm2"),
Properties: &armnetwork.SecurityRulePropertiesFormat{
Description: proto.String("perm2 description"),
Protocol: to.Ptr(armnetwork.SecurityRuleProtocolUDP),
SourceAddressPrefix: proto.String("192.0.2.0/24"),
SourcePortRange: proto.String("*"),
DestinationAddressPrefix: proto.String("192.0.2.0/24"),
DestinationPortRange: proto.String("4433"),
Access: to.Ptr(armnetwork.SecurityRuleAccessAllow),
Direction: to.Ptr(armnetwork.SecurityRuleDirectionInbound),
Priority: proto.Int32(200),
},
},
{
Name: proto.String("perm3"),
Properties: &armnetwork.SecurityRulePropertiesFormat{
Description: proto.String("perm3 description"),
Protocol: to.Ptr(armnetwork.SecurityRuleProtocolTCP),
SourceAddressPrefix: proto.String("192.0.2.0/24"),
SourcePortRange: proto.String("*"),
DestinationAddressPrefix: proto.String("192.0.2.0/24"),
DestinationPortRange: proto.String("4433"),
Access: to.Ptr(armnetwork.SecurityRuleAccessAllow),
Direction: to.Ptr(armnetwork.SecurityRuleDirectionInbound),
Priority: proto.Int32(300),
},
},
}
out, err := input.Azure()
assert.NoError(err)
assert.Equal(wantOutput, out)
}
func TestIPPermissonsToAWS(t *testing.T) {
assert := assert.New(t)
input := Firewall{
{
Description: "perm1",
Protocol: "TCP",
IPRange: "192.0.2.0/24",
FromPort: 22,
ToPort: 22,
},
{
Description: "perm2",
Protocol: "UDP",
IPRange: "192.0.2.0/24",
FromPort: 4433,
ToPort: 4433,
},
{
Description: "perm3",
Protocol: "TCP",
IPRange: "192.0.2.0/24",
FromPort: 4433,
ToPort: 4433,
},
}
wantOutput := []ec2types.IpPermission{
{
FromPort: proto.Int32(int32(22)),
ToPort: proto.Int32(int32(22)),
IpProtocol: proto.String("TCP"),
IpRanges: []ec2types.IpRange{
{
CidrIp: proto.String("192.0.2.0/24"),
Description: proto.String("perm1"),
},
},
},
{
FromPort: proto.Int32(int32(4433)),
ToPort: proto.Int32(int32(4433)),
IpProtocol: proto.String("UDP"),
IpRanges: []ec2types.IpRange{
{
CidrIp: proto.String("192.0.2.0/24"),
Description: proto.String("perm2"),
},
},
},
{
FromPort: proto.Int32(int32(4433)),
ToPort: proto.Int32(int32(4433)),
IpProtocol: proto.String("TCP"),
IpRanges: []ec2types.IpRange{
{
CidrIp: proto.String("192.0.2.0/24"),
Description: proto.String("perm3"),
},
},
},
}
out := input.AWS()
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

@ -1,70 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cloudtypes
import "errors"
// Instance is a gcp instance.
type Instance struct {
PublicIP string
PrivateIP string
}
// Instances is a map of gcp Instances. The ID of an instance is used as key.
type Instances map[string]Instance
// IDs returns the IDs of all instances of the Constellation.
func (i Instances) IDs() []string {
var ids []string
for id := range i {
ids = append(ids, id)
}
return ids
}
// PublicIPs returns the public IPs of all the instances of the Constellation.
func (i Instances) PublicIPs() []string {
var ips []string
for _, instance := range i {
ips = append(ips, instance.PublicIP)
}
return ips
}
// PrivateIPs returns the private IPs of all the instances of the Constellation.
func (i Instances) PrivateIPs() []string {
var ips []string
for _, instance := range i {
ips = append(ips, instance.PrivateIP)
}
return ips
}
// GetOne return anyone instance out of the instances and its ID.
func (i Instances) GetOne() (string, Instance, error) {
for id, instance := range i {
return id, instance, nil
}
return "", Instance{}, errors.New("map is empty")
}
// GetOthers returns all instances but the one with the handed ID.
func (i Instances) GetOthers(id string) Instances {
others := make(Instances)
for key, instance := range i {
if key != id {
others[key] = instance
}
}
return others
}
// ScalingGroup is a group of instances, with an identifying group ID.
type ScalingGroup struct {
Instances
GroupID string
}

View file

@ -1,82 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cloudtypes
import (
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestIDs(t *testing.T) {
assert := assert.New(t)
testState := testInstances()
wantIDs := []string{"id-9", "id-10", "id-11", "id-12"}
assert.ElementsMatch(wantIDs, testState.IDs())
}
func TestPublicIPs(t *testing.T) {
assert := assert.New(t)
testState := testInstances()
wantIPs := []string{"192.0.2.1", "192.0.2.3", "192.0.2.5", "192.0.2.7"}
assert.ElementsMatch(wantIPs, testState.PublicIPs())
}
func TestPrivateIPs(t *testing.T) {
assert := assert.New(t)
testState := testInstances()
wantIPs := []string{"192.0.2.2", "192.0.2.4", "192.0.2.6", "192.0.2.8"}
assert.ElementsMatch(wantIPs, testState.PrivateIPs())
}
func TestGetOne(t *testing.T) {
assert := assert.New(t)
testState := testInstances()
id, instance, err := testState.GetOne()
assert.NoError(err)
assert.Contains(testState, id)
assert.Equal(testState[id], instance)
}
func TestGetOthers(t *testing.T) {
assert := assert.New(t)
testCases := testInstances().IDs()
for _, id := range testCases {
others := testInstances().GetOthers(id)
assert.NotContains(others, id)
wantInstances := testInstances()
delete(wantInstances, id)
assert.ElementsMatch(others.IDs(), wantInstances.IDs())
}
}
func testInstances() Instances {
return Instances{
"id-9": {
PublicIP: "192.0.2.1",
PrivateIP: "192.0.2.2",
},
"id-10": {
PublicIP: "192.0.2.3",
PrivateIP: "192.0.2.4",
},
"id-11": {
PublicIP: "192.0.2.5",
PrivateIP: "192.0.2.6",
},
"id-12": {
PublicIP: "192.0.2.7",
PrivateIP: "192.0.2.8",
},
}
}

View file

@ -52,7 +52,7 @@ func (m *Metadata) Self(ctx context.Context) (metadata.InstanceMetadata, error)
}
// GetInstance retrieves an instance using its providerID.
func (m Metadata) GetInstance(ctx context.Context, providerID string) (metadata.InstanceMetadata, error) {
func (m *Metadata) GetInstance(ctx context.Context, providerID string) (metadata.InstanceMetadata, error) {
instances, err := m.List(ctx)
if err != nil {
return metadata.InstanceMetadata{}, err
@ -66,29 +66,31 @@ func (m Metadata) GetInstance(ctx context.Context, providerID string) (metadata.
return metadata.InstanceMetadata{}, errors.New("instance not found")
}
// SupportsLoadBalancer returns true if the cloud provider supports load balancers.
func (m Metadata) SupportsLoadBalancer() bool {
return false
}
// GetLoadBalancerEndpoint returns the endpoint of the load balancer.
func (m Metadata) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
panic("function *Metadata.GetLoadBalancerEndpoint not implemented")
// For QEMU, the load balancer is the first control plane node returned by the metadata API.
func (m *Metadata) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
endpointRaw, err := m.retrieveMetadata(ctx, "/endpoint")
if err != nil {
return "", err
}
var endpoint string
err = json.Unmarshal(endpointRaw, &endpoint)
return endpoint, err
}
// UID returns the UID of the constellation.
func (m Metadata) UID(ctx context.Context) (string, error) {
func (m *Metadata) UID(ctx context.Context) (string, error) {
// We expect only one constellation to be deployed in the same QEMU / libvirt environment.
// the UID can be an empty string.
return "", nil
}
// GetSubnetworkCIDR retrieves the subnetwork CIDR from cloud provider metadata.
func (m Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) {
func (m *Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) {
return "10.244.0.0/16", nil
}
func (m Metadata) retrieveMetadata(ctx context.Context, uri string) ([]byte, error) {
func (m *Metadata) retrieveMetadata(ctx context.Context, uri string) ([]byte, error) {
url := &url.URL{
Scheme: "http",
Host: qemuMetadataEndpoint,

View file

@ -1,74 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package constants
import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
)
var (
// IngressRulesNoDebug is the default set of ingress rules for a Constellation cluster without debug mode.
IngressRulesNoDebug = cloudtypes.Firewall{
{
Name: "bootstrapper",
Description: "bootstrapper default port",
Protocol: "tcp",
IPRange: "0.0.0.0/0",
FromPort: BootstrapperPort,
},
{
Name: "ssh",
Description: "SSH",
Protocol: "tcp",
IPRange: "0.0.0.0/0",
FromPort: SSHPort,
},
{
Name: "nodeport",
Description: "NodePort",
Protocol: "tcp",
IPRange: "0.0.0.0/0",
FromPort: NodePortFrom,
ToPort: NodePortTo,
},
{
Name: "kubernetes",
Description: "Kubernetes",
Protocol: "tcp",
IPRange: "0.0.0.0/0",
FromPort: KubernetesPort,
},
{
Name: "konnectivity",
Description: "konnectivity",
Protocol: "tcp",
IPRange: "0.0.0.0/0",
FromPort: KonnectivityPort,
},
{
Name: "recovery",
Description: "control-plane recovery",
Protocol: "tcp",
IPRange: "0.0.0.0/0",
FromPort: RecoveryPort,
},
}
// IngressRulesDebug is the default set of ingress rules for a Constellation cluster with debug mode.
IngressRulesDebug = append(IngressRulesNoDebug, cloudtypes.Firewall{
{
Name: "debugd",
Description: "debugd",
Protocol: "tcp",
IPRange: "0.0.0.0/0",
FromPort: DebugdPort,
},
}...)
// EgressRules is the default set of egress rules for a Constellation cluster.
EgressRules = cloudtypes.Firewall{}
)