replace flannel with cilium

This commit is contained in:
Leonard Cohnen 2022-05-24 10:04:42 +02:00
parent 7e1c898870
commit 791d5564ba
98 changed files with 3626 additions and 2156 deletions

View File

@ -34,6 +34,18 @@ type networkSecurityGroupsAPI interface {
networkSecurityGroupsCreateOrUpdatePollerResponse, error) networkSecurityGroupsCreateOrUpdatePollerResponse, error)
} }
type loadBalancersClientCreateOrUpdatePollerResponse interface {
PollUntilDone(ctx context.Context, freq time.Duration) (armnetwork.LoadBalancersClientCreateOrUpdateResponse, error)
}
type loadBalancersAPI interface {
BeginCreateOrUpdate(ctx context.Context, resourceGroupName string,
loadBalancerName string, parameters armnetwork.LoadBalancer,
options *armnetwork.LoadBalancersClientBeginCreateOrUpdateOptions) (
loadBalancersClientCreateOrUpdatePollerResponse, error,
)
}
type virtualMachineScaleSetsCreateOrUpdatePollerResponse interface { type virtualMachineScaleSetsCreateOrUpdatePollerResponse interface {
PollUntilDone(ctx context.Context, freq time.Duration) (armcompute.VirtualMachineScaleSetsClientCreateOrUpdateResponse, error) PollUntilDone(ctx context.Context, freq time.Duration) (armcompute.VirtualMachineScaleSetsClientCreateOrUpdateResponse, error)
} }

View File

@ -48,6 +48,29 @@ func (a stubNetworksAPI) BeginCreateOrUpdate(ctx context.Context, resourceGroupN
return a.stubResponse, a.createErr return a.stubResponse, a.createErr
} }
type stubLoadBalancersAPI struct {
createErr error
stubResponse stubLoadBalancersClientCreateOrUpdatePollerResponse
}
type stubLoadBalancersClientCreateOrUpdatePollerResponse struct {
pollResponse armnetwork.LoadBalancersClientCreateOrUpdateResponse
pollErr error
}
func (r stubLoadBalancersClientCreateOrUpdatePollerResponse) PollUntilDone(ctx context.Context, freq time.Duration,
) (armnetwork.LoadBalancersClientCreateOrUpdateResponse, error) {
return r.pollResponse, r.pollErr
}
func (a stubLoadBalancersAPI) BeginCreateOrUpdate(ctx context.Context, resourceGroupName string,
loadBalancerName string, parameters armnetwork.LoadBalancer,
options *armnetwork.LoadBalancersClientBeginCreateOrUpdateOptions) (
loadBalancersClientCreateOrUpdatePollerResponse, error,
) {
return a.stubResponse, a.createErr
}
type stubNetworkSecurityGroupsCreateOrUpdatePollerResponse struct { type stubNetworkSecurityGroupsCreateOrUpdatePollerResponse struct {
armnetwork.SecurityGroupsClientCreateOrUpdatePollerResponse armnetwork.SecurityGroupsClientCreateOrUpdatePollerResponse
pollerErr error pollerErr error
@ -143,23 +166,17 @@ func (a stubScaleSetsAPI) BeginCreateOrUpdate(ctx context.Context, resourceGroup
return a.stubResponse, a.createErr return a.stubResponse, a.createErr
} }
// TODO: deprecate as soon as scale sets are available.
type stubPublicIPAddressesAPI struct { type stubPublicIPAddressesAPI struct {
// TODO: deprecate as soon as scale sets are available. createErr error
createErr error getErr error
// TODO: deprecate as soon as scale sets are available.
getErr error
// TODO: deprecate as soon as scale sets are available.
stubCreateResponse stubPublicIPAddressesClientCreateOrUpdatePollerResponse stubCreateResponse stubPublicIPAddressesClientCreateOrUpdatePollerResponse
} }
// TODO: deprecate as soon as scale sets are available.
type stubPublicIPAddressesClientCreateOrUpdatePollerResponse struct { type stubPublicIPAddressesClientCreateOrUpdatePollerResponse struct {
armnetwork.PublicIPAddressesClientCreateOrUpdatePollerResponse armnetwork.PublicIPAddressesClientCreateOrUpdatePollerResponse
pollErr error pollErr error
} }
// TODO: deprecate as soon as scale sets are available.
func (r stubPublicIPAddressesClientCreateOrUpdatePollerResponse) PollUntilDone(ctx context.Context, freq time.Duration) ( func (r stubPublicIPAddressesClientCreateOrUpdatePollerResponse) PollUntilDone(ctx context.Context, freq time.Duration) (
armnetwork.PublicIPAddressesClientCreateOrUpdateResponse, error, armnetwork.PublicIPAddressesClientCreateOrUpdateResponse, error,
) { ) {
@ -167,6 +184,9 @@ func (r stubPublicIPAddressesClientCreateOrUpdatePollerResponse) PollUntilDone(c
PublicIPAddressesClientCreateOrUpdateResult: armnetwork.PublicIPAddressesClientCreateOrUpdateResult{ PublicIPAddressesClientCreateOrUpdateResult: armnetwork.PublicIPAddressesClientCreateOrUpdateResult{
PublicIPAddress: armnetwork.PublicIPAddress{ PublicIPAddress: armnetwork.PublicIPAddress{
ID: to.StringPtr("pubIP-id"), ID: to.StringPtr("pubIP-id"),
Properties: &armnetwork.PublicIPAddressPropertiesFormat{
IPAddress: to.StringPtr("192.0.2.1"),
},
}, },
}, },
}, r.pollErr }, r.pollErr
@ -206,7 +226,6 @@ func (a stubPublicIPAddressesAPI) ListVirtualMachineScaleSetVMPublicIPAddresses(
return &stubPublicIPAddressesListVirtualMachineScaleSetVMPublicIPAddressesPager{pagesCounter: 0, PagesMax: 1} return &stubPublicIPAddressesListVirtualMachineScaleSetVMPublicIPAddressesPager{pagesCounter: 0, PagesMax: 1}
} }
// TODO: deprecate as soon as scale sets are available.
func (a stubPublicIPAddressesAPI) BeginCreateOrUpdate(ctx context.Context, resourceGroupName string, publicIPAddressName string, func (a stubPublicIPAddressesAPI) BeginCreateOrUpdate(ctx context.Context, resourceGroupName string, publicIPAddressName string,
parameters armnetwork.PublicIPAddress, options *armnetwork.PublicIPAddressesClientBeginCreateOrUpdateOptions) ( parameters armnetwork.PublicIPAddress, options *armnetwork.PublicIPAddressesClientBeginCreateOrUpdateOptions) (
publicIPAddressesClientCreateOrUpdatePollerResponse, error, publicIPAddressesClientCreateOrUpdatePollerResponse, error,
@ -214,7 +233,6 @@ func (a stubPublicIPAddressesAPI) BeginCreateOrUpdate(ctx context.Context, resou
return a.stubCreateResponse, a.createErr return a.stubCreateResponse, a.createErr
} }
// TODO: deprecate as soon as scale sets are available.
func (a stubPublicIPAddressesAPI) Get(ctx context.Context, resourceGroupName string, publicIPAddressName string, options *armnetwork.PublicIPAddressesClientGetOptions) ( func (a stubPublicIPAddressesAPI) Get(ctx context.Context, resourceGroupName string, publicIPAddressName string, options *armnetwork.PublicIPAddressesClientGetOptions) (
armnetwork.PublicIPAddressesClientGetResponse, error, armnetwork.PublicIPAddressesClientGetResponse, error,
) { ) {
@ -230,11 +248,9 @@ func (a stubPublicIPAddressesAPI) Get(ctx context.Context, resourceGroupName str
} }
type stubNetworkInterfacesAPI struct { type stubNetworkInterfacesAPI struct {
getErr error getErr error
// TODO: deprecate as soon as scale sets are available
createErr error createErr error
// TODO: deprecate as soon as scale sets are available stubResp stubInterfacesClientCreateOrUpdatePollerResponse
stubResp stubInterfacesClientCreateOrUpdatePollerResponse
} }
func (a stubNetworkInterfacesAPI) GetVirtualMachineScaleSetNetworkInterface(ctx context.Context, resourceGroupName string, func (a stubNetworkInterfacesAPI) GetVirtualMachineScaleSetNetworkInterface(ctx context.Context, resourceGroupName string,

View File

@ -35,6 +35,17 @@ func (c *networkInterfacesClient) BeginCreateOrUpdate(ctx context.Context, resou
return c.InterfacesClient.BeginCreateOrUpdate(ctx, resourceGroupName, networkInterfaceName, parameters, options) return c.InterfacesClient.BeginCreateOrUpdate(ctx, resourceGroupName, networkInterfaceName, parameters, options)
} }
type loadBalancersClient struct {
*armnetwork.LoadBalancersClient
}
func (c *loadBalancersClient) BeginCreateOrUpdate(ctx context.Context, resourceGroupName string, loadBalancerName string,
parameters armnetwork.LoadBalancer, options *armnetwork.LoadBalancersClientBeginCreateOrUpdateOptions) (
loadBalancersClientCreateOrUpdatePollerResponse, error,
) {
return c.LoadBalancersClient.BeginCreateOrUpdate(ctx, resourceGroupName, loadBalancerName, parameters, options)
}
type networkSecurityGroupsClient struct { type networkSecurityGroupsClient struct {
*armnetwork.SecurityGroupsClient *armnetwork.SecurityGroupsClient
} }

View File

@ -33,6 +33,7 @@ type Client struct {
scaleSetsAPI scaleSetsAPI
publicIPAddressesAPI publicIPAddressesAPI
networkInterfacesAPI networkInterfacesAPI
loadBalancersAPI
virtualMachinesAPI virtualMachinesAPI
applicationsAPI applicationsAPI
servicePrincipalsAPI servicePrincipalsAPI
@ -53,6 +54,8 @@ type Client struct {
subnetID string subnetID string
coordinatorsScaleSet string coordinatorsScaleSet string
nodesScaleSet string nodesScaleSet string
loadBalancerName string
loadBalancerPubIP string
networkSecurityGroup string networkSecurityGroup string
adAppObjectID string adAppObjectID string
} }
@ -77,6 +80,7 @@ func NewFromDefault(subscriptionID, tenantID string) (*Client, error) {
scaleSetAPI := armcompute.NewVirtualMachineScaleSetsClient(subscriptionID, cred, nil) scaleSetAPI := armcompute.NewVirtualMachineScaleSetsClient(subscriptionID, cred, nil)
publicIPAddressesAPI := armnetwork.NewPublicIPAddressesClient(subscriptionID, cred, nil) publicIPAddressesAPI := armnetwork.NewPublicIPAddressesClient(subscriptionID, cred, nil)
networkInterfacesAPI := armnetwork.NewInterfacesClient(subscriptionID, cred, nil) networkInterfacesAPI := armnetwork.NewInterfacesClient(subscriptionID, cred, nil)
loadBalancersAPI := armnetwork.NewLoadBalancersClient(subscriptionID, cred, nil)
virtualMachinesAPI := armcompute.NewVirtualMachinesClient(subscriptionID, cred, nil) virtualMachinesAPI := armcompute.NewVirtualMachinesClient(subscriptionID, cred, nil)
applicationsAPI := graphrbac.NewApplicationsClient(tenantID) applicationsAPI := graphrbac.NewApplicationsClient(tenantID)
applicationsAPI.Authorizer = graphAuthorizer applicationsAPI.Authorizer = graphAuthorizer
@ -92,6 +96,7 @@ func NewFromDefault(subscriptionID, tenantID string) (*Client, error) {
scaleSetsAPI: &virtualMachineScaleSetsClient{scaleSetAPI}, scaleSetsAPI: &virtualMachineScaleSetsClient{scaleSetAPI},
publicIPAddressesAPI: &publicIPAddressesClient{publicIPAddressesAPI}, publicIPAddressesAPI: &publicIPAddressesClient{publicIPAddressesAPI},
networkInterfacesAPI: &networkInterfacesClient{networkInterfacesAPI}, networkInterfacesAPI: &networkInterfacesClient{networkInterfacesAPI},
loadBalancersAPI: &loadBalancersClient{loadBalancersAPI},
applicationsAPI: &applicationsClient{&applicationsAPI}, applicationsAPI: &applicationsClient{&applicationsAPI},
servicePrincipalsAPI: &servicePrincipalsClient{&servicePrincipalsAPI}, servicePrincipalsAPI: &servicePrincipalsClient{&servicePrincipalsAPI},
roleAssignmentsAPI: &roleAssignmentsClient{&roleAssignmentsAPI}, roleAssignmentsAPI: &roleAssignmentsClient{&roleAssignmentsAPI},
@ -233,7 +238,7 @@ func (c *Client) SetState(stat state.ConstellationState) error {
} }
c.coordinatorsScaleSet = stat.AzureCoordinatorsScaleSet c.coordinatorsScaleSet = stat.AzureCoordinatorsScaleSet
if len(stat.AzureNodes) == 0 { if len(stat.AzureNodes) == 0 {
return errors.New("state has no coordinator scale set") return errors.New("state has no nodes")
} }
c.nodes = stat.AzureNodes c.nodes = stat.AzureNodes
if len(stat.AzureCoordinators) == 0 { if len(stat.AzureCoordinators) == 0 {

View File

@ -13,13 +13,14 @@ import (
func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput) error { func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput) error {
// Create nodes scale set // Create nodes scale set
createNodesInput := CreateScaleSetInput{ createNodesInput := CreateScaleSetInput{
Name: "constellation-scale-set-nodes-" + c.uid, Name: "constellation-scale-set-nodes-" + c.uid,
NamePrefix: c.name + "-worker-" + c.uid + "-", NamePrefix: c.name + "-worker-" + c.uid + "-",
Count: input.CountNodes, Count: input.CountNodes,
InstanceType: input.InstanceType, InstanceType: input.InstanceType,
StateDiskSizeGB: int32(input.StateDiskSizeGB), StateDiskSizeGB: int32(input.StateDiskSizeGB),
Image: input.Image, Image: input.Image,
UserAssingedIdentity: input.UserAssingedIdentity, UserAssingedIdentity: input.UserAssingedIdentity,
LoadBalancerBackendAddressPool: azure.BackendAddressPoolWorkerName + "-" + c.uid,
} }
if err := c.createScaleSet(ctx, createNodesInput); err != nil { if err := c.createScaleSet(ctx, createNodesInput); err != nil {
@ -30,13 +31,14 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput
// Create coordinator scale set // Create coordinator scale set
createCoordinatorsInput := CreateScaleSetInput{ createCoordinatorsInput := CreateScaleSetInput{
Name: "constellation-scale-set-coordinators-" + c.uid, Name: "constellation-scale-set-coordinators-" + c.uid,
NamePrefix: c.name + "-control-plane-" + c.uid + "-", NamePrefix: c.name + "-control-plane-" + c.uid + "-",
Count: input.CountCoordinators, Count: input.CountCoordinators,
InstanceType: input.InstanceType, InstanceType: input.InstanceType,
StateDiskSizeGB: int32(input.StateDiskSizeGB), StateDiskSizeGB: int32(input.StateDiskSizeGB),
Image: input.Image, Image: input.Image,
UserAssingedIdentity: input.UserAssingedIdentity, UserAssingedIdentity: input.UserAssingedIdentity,
LoadBalancerBackendAddressPool: azure.BackendAddressPoolControlPlaneName + "-" + c.uid,
} }
if err := c.createScaleSet(ctx, createCoordinatorsInput); err != nil { if err := c.createScaleSet(ctx, createCoordinatorsInput); err != nil {
@ -58,6 +60,14 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput
} }
c.coordinators = instances c.coordinators = instances
// Set the load balancer public IP in the first coordinator
coord, ok := c.coordinators["0"]
if !ok {
return errors.New("coordinator 0 not found")
}
coord.PublicIP = c.loadBalancerPubIP
c.coordinators["0"] = coord
return nil return nil
} }
@ -119,13 +129,13 @@ func (c *Client) CreateInstancesVMs(ctx context.Context, input CreateInstancesIn
// TODO: deprecate as soon as scale sets are available. // TODO: deprecate as soon as scale sets are available.
func (c *Client) createInstanceVM(ctx context.Context, input azure.VMInstance) (azure.Instance, error) { func (c *Client) createInstanceVM(ctx context.Context, input azure.VMInstance) (azure.Instance, error) {
pubIPName := input.Name + "-pubIP" pubIPName := input.Name + "-pubIP"
pubIPID, err := c.createPublicIPAddress(ctx, pubIPName) pubIP, err := c.createPublicIPAddress(ctx, pubIPName)
if err != nil { if err != nil {
return azure.Instance{}, err return azure.Instance{}, err
} }
nicName := input.Name + "-NIC" nicName := input.Name + "-NIC"
privIP, nicID, err := c.createNIC(ctx, nicName, pubIPID) privIP, nicID, err := c.createNIC(ctx, nicName, *pubIP.ID)
if err != nil { if err != nil {
return azure.Instance{}, err return azure.Instance{}, err
} }
@ -167,18 +177,22 @@ func (c *Client) createScaleSet(ctx context.Context, input CreateScaleSetInput)
return err return err
} }
scaleSet := azure.ScaleSet{ scaleSet := azure.ScaleSet{
Name: input.Name, Name: input.Name,
NamePrefix: input.NamePrefix, NamePrefix: input.NamePrefix,
Location: c.location, Location: c.location,
InstanceType: input.InstanceType, InstanceType: input.InstanceType,
StateDiskSizeGB: input.StateDiskSizeGB, StateDiskSizeGB: input.StateDiskSizeGB,
Count: int64(input.Count), Count: int64(input.Count),
Username: "constellation", Username: "constellation",
SubnetID: c.subnetID, SubnetID: c.subnetID,
NetworkSecurityGroup: c.networkSecurityGroup, NetworkSecurityGroup: c.networkSecurityGroup,
Image: input.Image, Image: input.Image,
Password: pw, Password: pw,
UserAssignedIdentity: input.UserAssingedIdentity, UserAssignedIdentity: input.UserAssingedIdentity,
Subscription: c.subscriptionID,
ResourceGroup: c.resourceGroup,
LoadBalancerName: c.loadBalancerName,
LoadBalancerBackendAddressPool: input.LoadBalancerBackendAddressPool,
}.Azure() }.Azure()
poller, err := c.scaleSetsAPI.BeginCreateOrUpdate( poller, err := c.scaleSetsAPI.BeginCreateOrUpdate(
@ -242,13 +256,14 @@ func (c *Client) getInstanceIPs(ctx context.Context, scaleSet string, count int)
// CreateScaleSetInput is the input for a CreateScaleSet operation. // CreateScaleSetInput is the input for a CreateScaleSet operation.
type CreateScaleSetInput struct { type CreateScaleSetInput struct {
Name string Name string
NamePrefix string NamePrefix string
Count int Count int
InstanceType string InstanceType string
StateDiskSizeGB int32 StateDiskSizeGB int32
Image string Image string
UserAssingedIdentity string UserAssingedIdentity string
LoadBalancerBackendAddressPool string
} }
// CreateResourceGroup creates a resource group. // CreateResourceGroup creates a resource group.

View File

@ -216,6 +216,7 @@ func TestCreateInstances(t *testing.T) {
roleAssignmentsAPI: tc.roleAssignmentsAPI, roleAssignmentsAPI: tc.roleAssignmentsAPI,
nodes: make(azure.Instances), nodes: make(azure.Instances),
coordinators: make(azure.Instances), coordinators: make(azure.Instances),
loadBalancerPubIP: "lbip",
} }
if tc.wantErr { if tc.wantErr {
@ -227,7 +228,7 @@ func TestCreateInstances(t *testing.T) {
assert.NotEmpty(client.nodes["0"].PrivateIP) assert.NotEmpty(client.nodes["0"].PrivateIP)
assert.NotEmpty(client.nodes["0"].PublicIP) assert.NotEmpty(client.nodes["0"].PublicIP)
assert.NotEmpty(client.coordinators["0"].PrivateIP) assert.NotEmpty(client.coordinators["0"].PrivateIP)
assert.NotEmpty(client.coordinators["0"].PublicIP) assert.Equal("lbip", client.coordinators["0"].PublicIP)
} }
}) })
} }

View File

@ -6,21 +6,34 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/cli/azure"
"github.com/edgelesssys/constellation/cli/cloud/cloudtypes" "github.com/edgelesssys/constellation/cli/cloud/cloudtypes"
) )
type createNetworkInput struct { type createNetworkInput struct {
name string name string
location string location string
addressSpace string addressSpace string
nodeAddressSpace string
podAddressSpace string
} }
const (
nodeNetworkName = "nodeNetwork"
podNetworkName = "podNetwork"
networkAddressSpace = "10.0.0.0/8"
nodeAddressSpace = "10.9.0.0/16"
podAddressSpace = "10.10.0.0/16"
)
// CreateVirtualNetwork creates a virtual network. // CreateVirtualNetwork creates a virtual network.
func (c *Client) CreateVirtualNetwork(ctx context.Context) error { func (c *Client) CreateVirtualNetwork(ctx context.Context) error {
createNetworkInput := createNetworkInput{ createNetworkInput := createNetworkInput{
name: "constellation-" + c.uid, name: "constellation-" + c.uid,
location: c.location, location: c.location,
addressSpace: "172.20.0.0/16", addressSpace: networkAddressSpace,
nodeAddressSpace: nodeAddressSpace,
podAddressSpace: podAddressSpace,
} }
poller, err := c.networksAPI.BeginCreateOrUpdate( poller, err := c.networksAPI.BeginCreateOrUpdate(
@ -36,9 +49,15 @@ func (c *Client) CreateVirtualNetwork(ctx context.Context) error {
}, },
Subnets: []*armnetwork.Subnet{ Subnets: []*armnetwork.Subnet{
{ {
Name: to.StringPtr("default"), Name: to.StringPtr(nodeNetworkName),
Properties: &armnetwork.SubnetPropertiesFormat{ Properties: &armnetwork.SubnetPropertiesFormat{
AddressPrefix: to.StringPtr(createNetworkInput.addressSpace), AddressPrefix: to.StringPtr(createNetworkInput.nodeAddressSpace),
},
},
{
Name: to.StringPtr(podNetworkName),
Properties: &armnetwork.SubnetPropertiesFormat{
AddressPrefix: to.StringPtr(createNetworkInput.podAddressSpace),
}, },
}, },
}, },
@ -141,25 +160,29 @@ func (c *Client) createNIC(ctx context.Context, name, publicIPAddressID string)
nil nil
} }
// createPublicIPAddress creates a public IP address. func (c *Client) createPublicIPAddress(ctx context.Context, name string) (*armnetwork.PublicIPAddress, error) {
// TODO: deprecate as soon as scale sets are available.
func (c *Client) createPublicIPAddress(ctx context.Context, name string) (string, error) {
poller, err := c.publicIPAddressesAPI.BeginCreateOrUpdate( poller, err := c.publicIPAddressesAPI.BeginCreateOrUpdate(
ctx, c.resourceGroup, name, ctx, c.resourceGroup, name,
armnetwork.PublicIPAddress{ armnetwork.PublicIPAddress{
Location: to.StringPtr(c.location), Location: to.StringPtr(c.location),
SKU: &armnetwork.PublicIPAddressSKU{
Name: armnetwork.PublicIPAddressSKUNameStandard.ToPtr(),
},
Properties: &armnetwork.PublicIPAddressPropertiesFormat{
PublicIPAllocationMethod: armnetwork.IPAllocationMethodStatic.ToPtr(),
},
}, },
nil, nil,
) )
if err != nil { if err != nil {
return "", err return nil, err
} }
pollerResp, err := poller.PollUntilDone(ctx, 30*time.Second) pollerResp, err := poller.PollUntilDone(ctx, 30*time.Second)
if err != nil { if err != nil {
return "", err return nil, err
} }
return *pollerResp.PublicIPAddressesClientCreateOrUpdateResult.PublicIPAddress.ID, nil return &pollerResp.PublicIPAddressesClientCreateOrUpdateResult.PublicIPAddress, nil
} }
// NetworkSecurityGroupInput defines firewall rules to be set. // NetworkSecurityGroupInput defines firewall rules to be set.
@ -167,3 +190,42 @@ type NetworkSecurityGroupInput struct {
Ingress cloudtypes.Firewall Ingress cloudtypes.Firewall
Egress cloudtypes.Firewall Egress cloudtypes.Firewall
} }
// CreateExternalLoadBalancer creates an external load balancer.
func (c *Client) CreateExternalLoadBalancer(ctx context.Context) error {
// First, create a public IP address for the load balancer.
publicIPAddress, err := c.createPublicIPAddress(ctx, "loadbalancer-public-ip-"+c.uid)
if err != nil {
return err
}
// Then, create the load balancer.
loadBalancerName := "constellation-load-balancer-" + c.uid
loadBalancer := azure.LoadBalancer{
Name: loadBalancerName,
Location: c.location,
ResourceGroup: c.resourceGroup,
Subscription: c.subscriptionID,
PublicIPID: *publicIPAddress.ID,
UID: c.uid,
}
azureLoadBalancer := loadBalancer.Azure()
poller, err := c.loadBalancersAPI.BeginCreateOrUpdate(
ctx, c.resourceGroup, loadBalancerName,
azureLoadBalancer,
nil,
)
if err != nil {
return err
}
_, err = poller.PollUntilDone(ctx, 30*time.Second)
if err != nil {
return err
}
c.loadBalancerName = loadBalancerName
c.loadBalancerPubIP = *publicIPAddress.Properties.IPAddress
return nil
}

View File

@ -170,7 +170,6 @@ func TestCreateNIC(t *testing.T) {
} }
} }
// TODO: deprecate as soon as scale sets are available.
func TestCreatePublicIPAddress(t *testing.T) { func TestCreatePublicIPAddress(t *testing.T) {
someErr := errors.New("failed") someErr := errors.New("failed")
@ -218,3 +217,58 @@ func TestCreatePublicIPAddress(t *testing.T) {
}) })
} }
} }
func TestCreateExternalLoadBalancer(t *testing.T) {
someErr := errors.New("failed")
testCases := map[string]struct {
publicIPAddressesAPI publicIPAddressesAPI
loadBalancersAPI loadBalancersAPI
wantErr bool
}{
"successful create": {
publicIPAddressesAPI: stubPublicIPAddressesAPI{stubCreateResponse: stubPublicIPAddressesClientCreateOrUpdatePollerResponse{}},
loadBalancersAPI: stubLoadBalancersAPI{},
},
"failed to get response from successful create": {
loadBalancersAPI: stubLoadBalancersAPI{stubResponse: stubLoadBalancersClientCreateOrUpdatePollerResponse{pollErr: someErr}},
publicIPAddressesAPI: stubPublicIPAddressesAPI{},
wantErr: true,
},
"failed create": {
loadBalancersAPI: stubLoadBalancersAPI{createErr: someErr},
publicIPAddressesAPI: stubPublicIPAddressesAPI{},
wantErr: true,
},
"cannot create public IP": {
publicIPAddressesAPI: stubPublicIPAddressesAPI{createErr: someErr},
loadBalancersAPI: stubLoadBalancersAPI{},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
ctx := context.Background()
client := Client{
resourceGroup: "resource-group",
location: "location",
name: "name",
uid: "uid",
nodes: make(azure.Instances),
coordinators: make(azure.Instances),
loadBalancersAPI: tc.loadBalancersAPI,
publicIPAddressesAPI: tc.publicIPAddressesAPI,
}
err := client.CreateExternalLoadBalancer(ctx)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
})
}
}

162
cli/azure/loadbalancer.go Normal file
View File

@ -0,0 +1,162 @@
package azure
import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/internal/constants"
)
// LoadBalancer defines a Azure load balancer.
type LoadBalancer struct {
Name string
Subscription string
ResourceGroup string
Location string
PublicIPID string
UID string
}
const (
BackendAddressPoolWorkerName = "backendAddressWorkerPool"
BackendAddressPoolControlPlaneName = "backendAddressControlPlanePool"
)
// Azure returns a Azure representation of LoadBalancer.
func (l LoadBalancer) Azure() armnetwork.LoadBalancer {
frontEndIPConfigName := "frontEndIPConfig"
kubeHealthProbeName := "kubeHealthProbe"
coordHealthProbeName := "coordHealthProbe"
debugdHealthProbeName := "debugdHealthProbe"
backEndAddressPoolNodeName := BackendAddressPoolWorkerName + "-" + l.UID
backEndAddressPoolControlPlaneName := BackendAddressPoolControlPlaneName + "-" + l.UID
return armnetwork.LoadBalancer{
Name: to.StringPtr(l.Name),
Location: to.StringPtr(l.Location),
SKU: &armnetwork.LoadBalancerSKU{Name: armnetwork.LoadBalancerSKUNameStandard.ToPtr()},
Properties: &armnetwork.LoadBalancerPropertiesFormat{
FrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
{
Name: to.StringPtr(frontEndIPConfigName),
Properties: &armnetwork.FrontendIPConfigurationPropertiesFormat{
PublicIPAddress: &armnetwork.PublicIPAddress{
ID: to.StringPtr(l.PublicIPID),
},
},
},
},
BackendAddressPools: []*armnetwork.BackendAddressPool{
{
Name: to.StringPtr(backEndAddressPoolNodeName),
},
{
Name: to.StringPtr(backEndAddressPoolControlPlaneName),
},
{
Name: to.StringPtr("all"),
},
},
Probes: []*armnetwork.Probe{
{
Name: to.StringPtr(kubeHealthProbeName),
Properties: &armnetwork.ProbePropertiesFormat{
Protocol: armnetwork.ProbeProtocolTCP.ToPtr(),
Port: to.Int32Ptr(int32(6443)),
},
},
{
Name: to.StringPtr(coordHealthProbeName),
Properties: &armnetwork.ProbePropertiesFormat{
Protocol: armnetwork.ProbeProtocolTCP.ToPtr(),
Port: to.Int32Ptr(int32(constants.CoordinatorPort)),
},
},
{
Name: to.StringPtr(debugdHealthProbeName),
Properties: &armnetwork.ProbePropertiesFormat{
Protocol: armnetwork.ProbeProtocolTCP.ToPtr(),
Port: to.Int32Ptr(int32(4000)),
},
},
},
LoadBalancingRules: []*armnetwork.LoadBalancingRule{
{
Name: to.StringPtr("kubeLoadBalancerRule"),
Properties: &armnetwork.LoadBalancingRulePropertiesFormat{
FrontendIPConfiguration: &armnetwork.SubResource{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/frontendIPConfigurations/" + frontEndIPConfigName),
},
FrontendPort: to.Int32Ptr(int32(6443)),
BackendPort: to.Int32Ptr(int32(6443)),
Protocol: armnetwork.TransportProtocolTCP.ToPtr(),
Probe: &armnetwork.SubResource{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/probes/" + kubeHealthProbeName),
},
DisableOutboundSnat: to.BoolPtr(true),
BackendAddressPools: []*armnetwork.SubResource{
{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/backendAddressPools/" + backEndAddressPoolControlPlaneName),
},
},
},
},
{
Name: to.StringPtr("coordLoadBalancerRule"),
Properties: &armnetwork.LoadBalancingRulePropertiesFormat{
FrontendIPConfiguration: &armnetwork.SubResource{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/frontendIPConfigurations/" + frontEndIPConfigName),
},
FrontendPort: to.Int32Ptr(int32(constants.CoordinatorPort)),
BackendPort: to.Int32Ptr(int32(constants.CoordinatorPort)),
Protocol: armnetwork.TransportProtocolTCP.ToPtr(),
Probe: &armnetwork.SubResource{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/probes/" + coordHealthProbeName),
},
DisableOutboundSnat: to.BoolPtr(true),
BackendAddressPools: []*armnetwork.SubResource{
{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/backendAddressPools/" + backEndAddressPoolControlPlaneName),
},
},
},
},
{
Name: to.StringPtr("debudLoadBalancerRule"),
Properties: &armnetwork.LoadBalancingRulePropertiesFormat{
FrontendIPConfiguration: &armnetwork.SubResource{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/frontendIPConfigurations/" + frontEndIPConfigName),
},
FrontendPort: to.Int32Ptr(int32(4000)),
BackendPort: to.Int32Ptr(int32(4000)),
Protocol: armnetwork.TransportProtocolTCP.ToPtr(),
Probe: &armnetwork.SubResource{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/probes/" + debugdHealthProbeName),
},
DisableOutboundSnat: to.BoolPtr(true),
BackendAddressPools: []*armnetwork.SubResource{
{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/backendAddressPools/" + backEndAddressPoolControlPlaneName),
},
},
},
},
},
OutboundRules: []*armnetwork.OutboundRule{
{
Name: to.StringPtr("outboundRuleControlPlane"),
Properties: &armnetwork.OutboundRulePropertiesFormat{
FrontendIPConfigurations: []*armnetwork.SubResource{
{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/frontendIPConfigurations/" + frontEndIPConfigName),
},
},
BackendAddressPool: &armnetwork.SubResource{
ID: to.StringPtr("/subscriptions/" + l.Subscription + "/resourceGroups/" + l.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + l.Name + "/backendAddressPools/all"),
},
Protocol: armnetwork.LoadBalancerOutboundRuleProtocolAll.ToPtr(),
},
},
},
},
}
}

View File

@ -10,18 +10,22 @@ import (
// ScaleSet defines a Azure scale set. // ScaleSet defines a Azure scale set.
type ScaleSet struct { type ScaleSet struct {
Name string Name string
NamePrefix string NamePrefix string
Location string Subscription string
InstanceType string ResourceGroup string
StateDiskSizeGB int32 Location string
Count int64 InstanceType string
Username string StateDiskSizeGB int32
SubnetID string Count int64
NetworkSecurityGroup string Username string
Password string SubnetID string
Image string NetworkSecurityGroup string
UserAssignedIdentity string Password string
Image string
UserAssignedIdentity string
LoadBalancerName string
LoadBalancerBackendAddressPool string
} }
// Azure returns the Azure representation of ScaleSet. // Azure returns the Azure representation of ScaleSet.
@ -72,13 +76,16 @@ func (s ScaleSet) Azure() armcompute.VirtualMachineScaleSet {
{ {
Name: to.StringPtr(s.Name), Name: to.StringPtr(s.Name),
Properties: &armcompute.VirtualMachineScaleSetIPConfigurationProperties{ Properties: &armcompute.VirtualMachineScaleSetIPConfigurationProperties{
Primary: to.BoolPtr(true),
Subnet: &armcompute.APIEntityReference{ Subnet: &armcompute.APIEntityReference{
ID: to.StringPtr(s.SubnetID), ID: to.StringPtr(s.SubnetID),
}, },
PublicIPAddressConfiguration: &armcompute.VirtualMachineScaleSetPublicIPAddressConfiguration{ LoadBalancerBackendAddressPools: []*armcompute.SubResource{
Name: to.StringPtr(s.Name), {
Properties: &armcompute.VirtualMachineScaleSetPublicIPAddressConfigurationProperties{ ID: to.StringPtr("/subscriptions/" + s.Subscription + "/resourcegroups/" + s.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + s.LoadBalancerName + "/backendAddressPools/" + s.LoadBalancerBackendAddressPool),
IdleTimeoutInMinutes: to.Int32Ptr(15), // default per https://docs.microsoft.com/en-us/azure/virtual-machine-scale-sets/virtual-machine-scale-sets-networking#creating-a-scale-set-with-public-ip-per-virtual-machine },
{
ID: to.StringPtr("/subscriptions/" + s.Subscription + "/resourcegroups/" + s.ResourceGroup + "/providers/Microsoft.Network/loadBalancers/" + s.LoadBalancerName + "/backendAddressPools/all"),
}, },
}, },
}, },

View File

@ -26,6 +26,7 @@ type azureclient interface {
GetState() (state.ConstellationState, error) GetState() (state.ConstellationState, error)
SetState(state.ConstellationState) error SetState(state.ConstellationState) error
CreateResourceGroup(ctx context.Context) error CreateResourceGroup(ctx context.Context) error
CreateExternalLoadBalancer(ctx context.Context) error
CreateVirtualNetwork(ctx context.Context) error CreateVirtualNetwork(ctx context.Context) error
CreateSecurityGroup(ctx context.Context, input azurecl.NetworkSecurityGroupInput) error CreateSecurityGroup(ctx context.Context, input azurecl.NetworkSecurityGroupInput) error
CreateInstances(ctx context.Context, input azurecl.CreateInstancesInput) error CreateInstances(ctx context.Context, input azurecl.CreateInstancesInput) error

View File

@ -24,6 +24,7 @@ type fakeAzureClient struct {
subscriptionID string subscriptionID string
tenantID string tenantID string
subnetID string subnetID string
loadBalancerName string
coordinatorsScaleSet string coordinatorsScaleSet string
nodesScaleSet string nodesScaleSet string
networkSecurityGroup string networkSecurityGroup string
@ -77,6 +78,11 @@ func (c *fakeAzureClient) CreateVirtualNetwork(ctx context.Context) error {
return nil return nil
} }
func (c *fakeAzureClient) CreateExternalLoadBalancer(ctx context.Context) error {
c.loadBalancerName = "loadBalancer"
return nil
}
func (c *fakeAzureClient) CreateSecurityGroup(ctx context.Context, input azurecl.NetworkSecurityGroupInput) error { func (c *fakeAzureClient) CreateSecurityGroup(ctx context.Context, input azurecl.NetworkSecurityGroupInput) error {
c.networkSecurityGroup = "network-security-group" c.networkSecurityGroup = "network-security-group"
return nil return nil
@ -152,6 +158,7 @@ type stubAzureClient struct {
createResourceGroupErr error createResourceGroupErr error
createVirtualNetworkErr error createVirtualNetworkErr error
createSecurityGroupErr error createSecurityGroupErr error
createLoadBalancerErr error
createInstancesErr error createInstancesErr error
createServicePrincipalErr error createServicePrincipalErr error
terminateResourceGroupErr error terminateResourceGroupErr error
@ -166,6 +173,10 @@ func (c *stubAzureClient) SetState(state.ConstellationState) error {
return c.setStateErr return c.setStateErr
} }
func (c *stubAzureClient) CreateExternalLoadBalancer(ctx context.Context) error {
return c.createLoadBalancerErr
}
func (c *stubAzureClient) CreateResourceGroup(ctx context.Context) error { func (c *stubAzureClient) CreateResourceGroup(ctx context.Context) error {
return c.createResourceGroupErr return c.createResourceGroupErr
} }
@ -271,11 +282,9 @@ func (c *fakeGcpClient) CreateFirewall(ctx context.Context, input gcpcl.Firewall
if c.network == "" { if c.network == "" {
return errors.New("client has not network") return errors.New("client has not network")
} }
var firewalls []string
for _, rule := range input.Ingress { for _, rule := range input.Ingress {
firewalls = append(firewalls, rule.Name) c.firewalls = append(c.firewalls, rule.Name)
} }
c.firewalls = firewalls
return nil return nil
} }

View File

@ -82,6 +82,45 @@ func (c *Creator) createGCP(ctx context.Context, cl gcpclient, config *config.Co
return state.ConstellationState{}, err return state.ConstellationState{}, err
} }
// additionally create allow-internal rules
internalFirewallInput := gcpcl.FirewallInput{
Ingress: cloudtypes.Firewall{
{
Name: "allow-cluster-internal-tcp",
Protocol: "tcp",
IPRange: gcpcl.SubnetExtCIDR,
},
{
Name: "allow-cluster-internal-udp",
Protocol: "udp",
IPRange: gcpcl.SubnetExtCIDR,
},
{
Name: "allow-cluster-internal-icmp",
Protocol: "icmp",
IPRange: gcpcl.SubnetExtCIDR,
},
{
Name: "allow-node-internal-tcp",
Protocol: "tcp",
IPRange: gcpcl.SubnetCIDR,
},
{
Name: "allow-node-internal-udp",
Protocol: "udp",
IPRange: gcpcl.SubnetCIDR,
},
{
Name: "allow-node-internal-icmp",
Protocol: "icmp",
IPRange: gcpcl.SubnetCIDR,
},
},
}
if err := cl.CreateFirewall(ctx, internalFirewallInput); err != nil {
return state.ConstellationState{}, err
}
createInput := client.CreateInstancesInput{ createInput := client.CreateInstancesInput{
CountCoordinators: coordCount, CountCoordinators: coordCount,
CountNodes: nodeCount, CountNodes: nodeCount,
@ -104,6 +143,9 @@ func (c *Creator) createAzure(ctx context.Context, cl azureclient, config *confi
if err := cl.CreateResourceGroup(ctx); err != nil { if err := cl.CreateResourceGroup(ctx); err != nil {
return state.ConstellationState{}, err return state.ConstellationState{}, err
} }
if err := cl.CreateExternalLoadBalancer(ctx); err != nil {
return state.ConstellationState{}, err
}
if err := cl.CreateVirtualNetwork(ctx); err != nil { if err := cl.CreateVirtualNetwork(ctx); err != nil {
return state.ConstellationState{}, err return state.ConstellationState{}, err
} }

View File

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

View File

@ -19,25 +19,30 @@ type Firewall config.Firewall
func (f Firewall) GCP() ([]*computepb.Firewall, error) { 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 srcRange []string
if rule.IPRange != "" { if rule.IPRange != "" {
destRange = append(destRange, rule.IPRange) srcRange = []string{rule.IPRange}
} }
ports, err := portOrRange(rule.FromPort, rule.ToPort) var ports []string
if err != nil { if rule.FromPort != 0 || rule.ToPort != 0 {
return nil, err port, err := portOrRange(rule.FromPort, rule.ToPort)
if err != nil {
return nil, err
}
ports = []string{port}
} }
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{ports}, Ports: ports,
}, },
}, },
Description: proto.String(rule.Description), Description: proto.String(rule.Description),
DestinationRanges: destRange, SourceRanges: srcRange,
Name: proto.String(rule.Name), Name: proto.String(rule.Name),
}) })
} }
return fw, nil return fw, nil

View File

@ -29,11 +29,18 @@ func TestFirewallGCP(t *testing.T) {
IPRange: "", IPRange: "",
FromPort: 51820, 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() firewalls, err := testFw.GCP()
assert.NoError(err) assert.NoError(err)
assert.Equal(2, len(firewalls)) assert.Equal(len(testFw), len(firewalls))
// Check permissions // Check permissions
for i := 0; i < len(testFw); i++ { for i := 0; i < len(testFw); i++ {
@ -47,6 +54,11 @@ func TestFirewallGCP(t *testing.T) {
assert.Equal(testFw[i].Name, firewall1.GetName()) assert.Equal(testFw[i].Name, firewall1.GetName())
assert.Equal(testFw[i].Description, firewall1.GetDescription()) assert.Equal(testFw[i].Description, firewall1.GetDescription())
if testFw[i].IPRange != "" {
require.Len(firewall1.GetSourceRanges(), 1)
assert.Equal(testFw[i].IPRange, firewall1.GetSourceRanges()[0])
}
} }
} }

View File

@ -357,6 +357,9 @@ func readOrGenerateVPNKey(fileHandler file.Handler, privKeyPath string) (privKey
func ipsToEndpoints(ips []string, port string) []string { func ipsToEndpoints(ips []string, port string) []string {
var endpoints []string var endpoints []string
for _, ip := range ips { for _, ip := range ips {
if ip == "" {
continue
}
endpoints = append(endpoints, net.JoinHostPort(ip, port)) endpoints = append(endpoints, net.JoinHostPort(ip, port))
} }
return endpoints return endpoints

View File

@ -355,7 +355,7 @@ func TestWriteOutput(t *testing.T) {
func TestIpsToEndpoints(t *testing.T) { func TestIpsToEndpoints(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
ips := []string{"192.0.2.1", "192.0.2.2", "192.0.2.3"} ips := []string{"192.0.2.1", "192.0.2.2", "", "192.0.2.3"}
port := "8080" port := "8080"
endpoints := ipsToEndpoints(ips, port) endpoints := ipsToEndpoints(ips, port)
assert.Equal([]string{"192.0.2.1:8080", "192.0.2.2:8080", "192.0.2.3:8080"}, endpoints) assert.Equal([]string{"192.0.2.1:8080", "192.0.2.2:8080", "192.0.2.3:8080"}, endpoints)

View File

@ -48,18 +48,19 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput
c.nodeTemplate = nodeTemplateInput.Name c.nodeTemplate = nodeTemplateInput.Name
coordinatorTemplateInput := insertInstanceTemplateInput{ coordinatorTemplateInput := insertInstanceTemplateInput{
Name: c.name + "-control-plane-" + c.uid, Name: c.name + "-control-plane-" + c.uid,
Network: c.network, Network: c.network,
Subnetwork: c.subnetwork, Subnetwork: c.subnetwork,
ImageId: input.ImageId, SecondarySubnetworkRangeName: c.secondarySubnetworkRange,
InstanceType: input.InstanceType, ImageId: input.ImageId,
StateDiskSizeGB: int64(input.StateDiskSizeGB), InstanceType: input.InstanceType,
Role: role.Coordinator.String(), StateDiskSizeGB: int64(input.StateDiskSizeGB),
KubeEnv: input.KubeEnv, Role: role.Coordinator.String(),
Project: c.project, KubeEnv: input.KubeEnv,
Zone: c.zone, Project: c.project,
Region: c.region, Zone: c.zone,
UID: c.uid, Region: c.region,
UID: c.uid,
} }
op, err = c.insertInstanceTemplate(ctx, coordinatorTemplateInput) op, err = c.insertInstanceTemplate(ctx, coordinatorTemplateInput)
if err != nil { if err != nil {
@ -72,6 +73,21 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput
} }
ops = []Operation{} ops = []Operation{}
coordinatorGroupInput := instanceGroupManagerInput{
Count: input.CountCoordinators,
Name: strings.Join([]string{c.name, "control-plane", c.uid}, "-"),
Template: c.coordinatorTemplate,
UID: c.uid,
Project: c.project,
Zone: c.zone,
}
op, err = c.insertInstanceGroupManger(ctx, coordinatorGroupInput)
if err != nil {
return fmt.Errorf("inserting instanceGroupManager failed: %w", err)
}
ops = append(ops, op)
c.coordinatorInstanceGroup = coordinatorGroupInput.Name
nodeGroupInput := instanceGroupManagerInput{ nodeGroupInput := instanceGroupManagerInput{
Count: input.CountNodes, Count: input.CountNodes,
Name: strings.Join([]string{c.name, "worker", c.uid}, "-"), Name: strings.Join([]string{c.name, "worker", c.uid}, "-"),
@ -87,20 +103,6 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput
ops = append(ops, op) ops = append(ops, op)
c.nodesInstanceGroup = nodeGroupInput.Name c.nodesInstanceGroup = nodeGroupInput.Name
coordinatorGroupInput := instanceGroupManagerInput{
Count: input.CountCoordinators,
Name: strings.Join([]string{c.name, "control-plane", c.uid}, "-"),
Template: c.coordinatorTemplate,
UID: c.uid,
Project: c.project,
Zone: c.zone,
}
op, err = c.insertInstanceGroupManger(ctx, coordinatorGroupInput)
if err != nil {
return fmt.Errorf("inserting instanceGroupManager failed: %w", err)
}
ops = append(ops, op)
c.coordinatorInstanceGroup = coordinatorGroupInput.Name
if err := c.waitForOperations(ctx, ops); err != nil { if err := c.waitForOperations(ctx, ops); err != nil {
return err return err
} }
@ -390,7 +392,7 @@ func (i insertInstanceTemplateInput) insertInstanceTemplateRequest() *computepb.
EnableVtpm: proto.Bool(true), EnableVtpm: proto.Bool(true),
}, },
Tags: &computepb.Tags{ Tags: &computepb.Tags{
Items: []string{"constellation"}, Items: []string{"constellation-" + i.UID},
}, },
}, },
}, },

View File

@ -9,6 +9,11 @@ import (
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
const (
SubnetCIDR = "192.168.178.0/24"
SubnetExtCIDR = "10.10.0.0/16"
)
// CreateFirewall creates a set of firewall rules for the client's network. // CreateFirewall creates a set of firewall rules for the client's network.
// //
// The client must have a VPC network to set firewall rules. // The client must have a VPC network to set firewall rules.
@ -163,13 +168,13 @@ func (c *Client) createSubnet(ctx context.Context, name, network, secondaryRange
Project: c.project, Project: c.project,
Region: c.region, Region: c.region,
SubnetworkResource: &computepb.Subnetwork{ SubnetworkResource: &computepb.Subnetwork{
IpCidrRange: proto.String("192.168.178.0/24"), IpCidrRange: proto.String(SubnetCIDR),
Name: proto.String(name), Name: proto.String(name),
Network: proto.String("projects/" + c.project + "/global/networks/" + network), Network: proto.String("projects/" + c.project + "/global/networks/" + network),
SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{ SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{
{ {
RangeName: proto.String(secondaryRangeName), RangeName: proto.String(secondaryRangeName),
IpCidrRange: proto.String("10.10.0.0/16"), IpCidrRange: proto.String(SubnetExtCIDR),
}, },
}, },
}, },

View File

@ -1,7 +1,7 @@
package aws package aws
import ( import (
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
) )
@ -15,7 +15,7 @@ func (a Autoscaler) Name() string {
} }
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler. // Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (a Autoscaler) Secrets(instance core.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (a Autoscaler) Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil return resources.Secrets{}, nil
} }

View File

@ -1,8 +1,10 @@
package aws package aws
import ( import (
"context"
"github.com/edgelesssys/constellation/coordinator/cloudprovider" "github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
) )
@ -32,13 +34,13 @@ func (c CloudControllerManager) ExtraArgs() []string {
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager // ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ . // Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
func (c CloudControllerManager) ConfigMaps(instance core.Instance) (resources.ConfigMaps, error) { func (c CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resources.ConfigMaps, error) {
return resources.ConfigMaps{}, nil return resources.ConfigMaps{}, nil
} }
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager. // Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ . // Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
func (c CloudControllerManager) Secrets(instance core.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (c CloudControllerManager) Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil return resources.Secrets{}, nil
} }
@ -58,13 +60,6 @@ func (c CloudControllerManager) Env() []k8s.EnvVar {
return []k8s.EnvVar{} return []k8s.EnvVar{}
} }
// PrepareInstance is called on every instance before deploying the cloud-controller-manager.
// Allows for cloud-provider specific hooks.
func (c CloudControllerManager) PrepareInstance(instance core.Instance, vpnIP string) error {
// no specific hook required.
return nil
}
// Supported is used to determine if cloud controller manager is implemented for this cloud provider. // Supported is used to determine if cloud controller manager is implemented for this cloud provider.
func (c CloudControllerManager) Supported() bool { func (c CloudControllerManager) Supported() bool {
return false return false

View File

@ -6,7 +6,7 @@ import (
"github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
) )
@ -14,30 +14,30 @@ import (
type Metadata struct{} type Metadata struct{}
// List retrieves all instances belonging to the current constellation. // List retrieves all instances belonging to the current constellation.
func (m Metadata) List(ctx context.Context) ([]core.Instance, error) { func (m Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
// TODO: implement using https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ec2#Client.DescribeInstances // TODO: implement using https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ec2#Client.DescribeInstances
// And using AWS ec2 instance tags // And using AWS ec2 instance tags
panic("function *Metadata.List not implemented") panic("function *Metadata.List not implemented")
} }
// Self retrieves the current instance. // Self retrieves the current instance.
func (m Metadata) Self(ctx context.Context) (core.Instance, error) { func (m Metadata) Self(ctx context.Context) (cloudtypes.Instance, error) {
identityDocument, err := retrieveIdentityDocument(ctx) identityDocument, err := retrieveIdentityDocument(ctx)
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
// TODO: implement metadata using AWS ec2 instance tags // TODO: implement metadata using AWS ec2 instance tags
return core.Instance{ return cloudtypes.Instance{
Name: identityDocument.InstanceID, Name: identityDocument.InstanceID,
ProviderID: providerID(identityDocument), ProviderID: providerID(identityDocument),
IPs: []string{ PrivateIPs: []string{
identityDocument.PrivateIP, identityDocument.PrivateIP,
}, },
}, nil }, nil
} }
// GetInstance retrieves an instance using its providerID. // GetInstance retrieves an instance using its providerID.
func (m Metadata) GetInstance(ctx context.Context, providerID string) (core.Instance, error) { func (m Metadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
// TODO: implement using https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ec2#DescribeInstancesAPIClient.DescribeInstances // TODO: implement using https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ec2#DescribeInstancesAPIClient.DescribeInstances
// And using AWS ec2 instance tags // And using AWS ec2 instance tags
// Filter request to only return info on this instance // Filter request to only return info on this instance

View File

@ -12,6 +12,24 @@ type imdsAPI interface {
Retrieve(ctx context.Context) (metadataResponse, error) Retrieve(ctx context.Context) (metadataResponse, error)
} }
type virtualNetworksClientListPager interface {
NextPage(ctx context.Context) bool
PageResponse() armnetwork.VirtualNetworksClientListResponse
}
type virtualNetworksAPI interface {
List(resourceGroupName string, options *armnetwork.VirtualNetworksClientListOptions) virtualNetworksClientListPager
}
type securityGroupsClientListPager interface {
NextPage(ctx context.Context) bool
PageResponse() armnetwork.SecurityGroupsClientListResponse
}
type securityGroupsAPI interface {
List(resourceGroupName string, options *armnetwork.SecurityGroupsClientListOptions) securityGroupsClientListPager
}
type networkInterfacesAPI interface { type networkInterfacesAPI interface {
GetVirtualMachineScaleSetNetworkInterface(ctx context.Context, resourceGroupName string, GetVirtualMachineScaleSetNetworkInterface(ctx context.Context, resourceGroupName string,
virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string,
@ -21,6 +39,16 @@ type networkInterfacesAPI interface {
options *armnetwork.InterfacesClientGetOptions) (armnetwork.InterfacesClientGetResponse, error) options *armnetwork.InterfacesClientGetOptions) (armnetwork.InterfacesClientGetResponse, error)
} }
type publicIPAddressesAPI interface {
GetVirtualMachineScaleSetPublicIPAddress(ctx context.Context, resourceGroupName string,
virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string,
ipConfigurationName string, publicIPAddressName string,
options *armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressOptions,
) (armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResponse, error)
Get(ctx context.Context, resourceGroupName string, publicIPAddressName string,
options *armnetwork.PublicIPAddressesClientGetOptions) (armnetwork.PublicIPAddressesClientGetResponse, error)
}
type virtualMachinesAPI interface { type virtualMachinesAPI interface {
Get(ctx context.Context, resourceGroupName string, vmName string, options *armcompute.VirtualMachinesClientGetOptions) (armcompute.VirtualMachinesClientGetResponse, error) Get(ctx context.Context, resourceGroupName string, vmName string, options *armcompute.VirtualMachinesClientGetOptions) (armcompute.VirtualMachinesClientGetResponse, error)
List(resourceGroupName string, options *armcompute.VirtualMachinesClientListOptions) virtualMachinesClientListPager List(resourceGroupName string, options *armcompute.VirtualMachinesClientListOptions) virtualMachinesClientListPager
@ -45,6 +73,15 @@ type scaleSetsAPI interface {
List(resourceGroupName string, options *armcompute.VirtualMachineScaleSetsClientListOptions) virtualMachineScaleSetsClientListPager List(resourceGroupName string, options *armcompute.VirtualMachineScaleSetsClientListOptions) virtualMachineScaleSetsClientListPager
} }
type loadBalancersClientListPager interface {
NextPage(ctx context.Context) bool
PageResponse() armnetwork.LoadBalancersClientListResponse
}
type loadBalancerAPI interface {
List(resourceGroupName string, options *armnetwork.LoadBalancersClientListOptions) loadBalancersClientListPager
}
type virtualMachineScaleSetsClientListPager interface { type virtualMachineScaleSetsClientListPager interface {
NextPage(ctx context.Context) bool NextPage(ctx context.Context) bool
PageResponse() armcompute.VirtualMachineScaleSetsClientListResponse PageResponse() armcompute.VirtualMachineScaleSetsClientListResponse

View File

@ -174,3 +174,121 @@ func (a *stubTagsAPI) CreateOrUpdateAtScope(ctx context.Context, scope string, p
func (a *stubTagsAPI) UpdateAtScope(ctx context.Context, scope string, parameters armresources.TagsPatchResource, options *armresources.TagsClientUpdateAtScopeOptions) (armresources.TagsClientUpdateAtScopeResponse, error) { func (a *stubTagsAPI) UpdateAtScope(ctx context.Context, scope string, parameters armresources.TagsPatchResource, options *armresources.TagsClientUpdateAtScopeOptions) (armresources.TagsClientUpdateAtScopeResponse, error) {
return armresources.TagsClientUpdateAtScopeResponse{}, a.updateAtScopeErr return armresources.TagsClientUpdateAtScopeResponse{}, a.updateAtScopeErr
} }
type stubSecurityGroupsClientListPager struct {
pagesCounter int
pages [][]*armnetwork.SecurityGroup
}
func (p *stubSecurityGroupsClientListPager) NextPage(ctx context.Context) bool {
return p.pagesCounter < len(p.pages)
}
func (p *stubSecurityGroupsClientListPager) PageResponse() armnetwork.SecurityGroupsClientListResponse {
if p.pagesCounter >= len(p.pages) {
return armnetwork.SecurityGroupsClientListResponse{}
}
p.pagesCounter = p.pagesCounter + 1
return armnetwork.SecurityGroupsClientListResponse{
SecurityGroupsClientListResult: armnetwork.SecurityGroupsClientListResult{
SecurityGroupListResult: armnetwork.SecurityGroupListResult{
Value: p.pages[p.pagesCounter-1],
},
},
}
}
type stubSecurityGroupsAPI struct {
listPages [][]*armnetwork.SecurityGroup
}
func (a *stubSecurityGroupsAPI) List(resourceGroupName string, options *armnetwork.SecurityGroupsClientListOptions) securityGroupsClientListPager {
return &stubSecurityGroupsClientListPager{
pages: a.listPages,
}
}
type stubVirtualNetworksClientListPager struct {
pagesCounter int
pages [][]*armnetwork.VirtualNetwork
}
func (p *stubVirtualNetworksClientListPager) NextPage(ctx context.Context) bool {
return p.pagesCounter < len(p.pages)
}
func (p *stubVirtualNetworksClientListPager) PageResponse() armnetwork.VirtualNetworksClientListResponse {
if p.pagesCounter >= len(p.pages) {
return armnetwork.VirtualNetworksClientListResponse{}
}
p.pagesCounter = p.pagesCounter + 1
return armnetwork.VirtualNetworksClientListResponse{
VirtualNetworksClientListResult: armnetwork.VirtualNetworksClientListResult{
VirtualNetworkListResult: armnetwork.VirtualNetworkListResult{
Value: p.pages[p.pagesCounter-1],
},
},
}
}
type stubVirtualNetworksAPI struct {
listPages [][]*armnetwork.VirtualNetwork
}
func (a *stubVirtualNetworksAPI) List(resourceGroupName string, options *armnetwork.VirtualNetworksClientListOptions) virtualNetworksClientListPager {
return &stubVirtualNetworksClientListPager{
pages: a.listPages,
}
}
type stubLoadBalancersClientListPager struct {
pagesCounter int
pages [][]*armnetwork.LoadBalancer
}
func (p *stubLoadBalancersClientListPager) NextPage(ctx context.Context) bool {
return p.pagesCounter < len(p.pages)
}
func (p *stubLoadBalancersClientListPager) PageResponse() armnetwork.LoadBalancersClientListResponse {
if p.pagesCounter >= len(p.pages) {
return armnetwork.LoadBalancersClientListResponse{}
}
p.pagesCounter = p.pagesCounter + 1
return armnetwork.LoadBalancersClientListResponse{
LoadBalancersClientListResult: armnetwork.LoadBalancersClientListResult{
LoadBalancerListResult: armnetwork.LoadBalancerListResult{
Value: p.pages[p.pagesCounter-1],
},
},
}
}
type stubLoadBalancersAPI struct {
listPages [][]*armnetwork.LoadBalancer
}
func (a *stubLoadBalancersAPI) List(resourceGroupName string, options *armnetwork.LoadBalancersClientListOptions) loadBalancersClientListPager {
return &stubLoadBalancersClientListPager{
pages: a.listPages,
}
}
type stubPublicIPAddressesAPI struct {
getResponse armnetwork.PublicIPAddressesClientGetResponse
getVirtualMachineScaleSetPublicIPAddressResponse armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResponse
getErr error
}
func (a *stubPublicIPAddressesAPI) Get(ctx context.Context, resourceGroupName string, publicIPAddressName string,
options *armnetwork.PublicIPAddressesClientGetOptions,
) (armnetwork.PublicIPAddressesClientGetResponse, error) {
return a.getResponse, a.getErr
}
func (a *stubPublicIPAddressesAPI) GetVirtualMachineScaleSetPublicIPAddress(ctx context.Context, resourceGroupName string, virtualMachineScaleSetName string,
virtualmachineIndex string, networkInterfaceName string, IPConfigurationName string, publicIPAddressName string,
options *armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressOptions,
) (armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResponse, error) {
return a.getVirtualMachineScaleSetPublicIPAddressResponse, a.getErr
}

View File

@ -1,7 +1,7 @@
package azure package azure
import ( import (
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -16,7 +16,7 @@ func (a *Autoscaler) Name() string {
} }
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler. // Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (a *Autoscaler) Secrets(instance core.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (a *Autoscaler) Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
subscriptionID, resourceGroup, err := extractBasicsFromProviderID(instance.ProviderID) subscriptionID, resourceGroup, err := extractBasicsFromProviderID(instance.ProviderID)
if err != nil { if err != nil {
return resources.Secrets{}, err return resources.Secrets{}, err

View File

@ -3,7 +3,7 @@ package azure
import ( import (
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -13,13 +13,13 @@ import (
func TestAutoscalerSecrets(t *testing.T) { func TestAutoscalerSecrets(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
instance core.Instance instance cloudtypes.Instance
cloudServiceAccountURI string cloudServiceAccountURI string
wantSecrets resources.Secrets wantSecrets resources.Secrets
wantErr bool wantErr bool
}{ }{
"Secrets works": { "Secrets works": {
instance: core.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"}, instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"},
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret", cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret",
wantSecrets: resources.Secrets{ wantSecrets: resources.Secrets{
&k8s.Secret{ &k8s.Secret{
@ -43,11 +43,11 @@ func TestAutoscalerSecrets(t *testing.T) {
}, },
}, },
"invalid providerID fails": { "invalid providerID fails": {
instance: core.Instance{ProviderID: "invalid"}, instance: cloudtypes.Instance{ProviderID: "invalid"},
wantErr: true, wantErr: true,
}, },
"invalid cloudServiceAccountURI fails": { "invalid cloudServiceAccountURI fails": {
instance: core.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"}, instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"},
cloudServiceAccountURI: "invalid", cloudServiceAccountURI: "invalid",
wantErr: true, wantErr: true,
}, },

View File

@ -1,17 +1,31 @@
package azure package azure
import ( import (
"context"
"encoding/json" "encoding/json"
"github.com/edgelesssys/constellation/coordinator/cloudprovider" "github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type ccmMetadata interface {
GetNetworkSecurityGroupName(ctx context.Context) (string, error)
GetLoadBalancerName(ctx context.Context) (string, error)
}
// CloudControllerManager holds the Azure cloud-controller-manager configuration. // CloudControllerManager holds the Azure cloud-controller-manager configuration.
type CloudControllerManager struct{} type CloudControllerManager struct {
metadata ccmMetadata
}
func NewCloudControllerManager(metadata ccmMetadata) *CloudControllerManager {
return &CloudControllerManager{
metadata: metadata,
}
}
// Image returns the container image used to provide cloud-controller-manager for the cloud-provider. // Image returns the container image used to provide cloud-controller-manager for the cloud-provider.
func (c *CloudControllerManager) Image() string { func (c *CloudControllerManager) Image() string {
@ -33,18 +47,20 @@ func (c *CloudControllerManager) ExtraArgs() []string {
return []string{ return []string{
"--controllers=*,-cloud-node", "--controllers=*,-cloud-node",
"--cloud-config=/etc/azure/azure.json", "--cloud-config=/etc/azure/azure.json",
"--allocate-node-cidrs=false",
"--configure-cloud-routes=true",
} }
} }
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager // ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ . // Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
func (c *CloudControllerManager) ConfigMaps(instance core.Instance) (resources.ConfigMaps, error) { func (c *CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resources.ConfigMaps, error) {
return resources.ConfigMaps{}, nil return resources.ConfigMaps{}, nil
} }
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager. // Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ . // Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
func (c *CloudControllerManager) Secrets(instance core.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (c *CloudControllerManager) Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
// Azure CCM expects cloud provider config to contain cluster configuration and service principal client secrets // Azure CCM expects cloud provider config to contain cluster configuration and service principal client secrets
// reference: https://kubernetes-sigs.github.io/cloud-provider-azure/install/configs/ // reference: https://kubernetes-sigs.github.io/cloud-provider-azure/install/configs/
@ -62,11 +78,24 @@ func (c *CloudControllerManager) Secrets(instance core.Instance, cloudServiceAcc
vmType = "vmss" vmType = "vmss"
} }
securityGroupName, err := c.metadata.GetNetworkSecurityGroupName(ctx)
if err != nil {
return resources.Secrets{}, err
}
loadBalancerName, err := c.metadata.GetLoadBalancerName(ctx)
if err != nil {
return resources.Secrets{}, err
}
config := cloudConfig{ config := cloudConfig{
Cloud: "AzurePublicCloud", Cloud: "AzurePublicCloud",
TenantID: creds.TenantID, TenantID: creds.TenantID,
SubscriptionID: subscriptionID, SubscriptionID: subscriptionID,
ResourceGroup: resourceGroup, ResourceGroup: resourceGroup,
LoadBalancerSku: "standard",
SecurityGroupName: securityGroupName,
LoadBalancerName: loadBalancerName,
UseInstanceMetadata: true, UseInstanceMetadata: true,
VmType: vmType, VmType: vmType,
Location: creds.Location, Location: creds.Location,
@ -127,13 +156,6 @@ func (c *CloudControllerManager) Env() []k8s.EnvVar {
return []k8s.EnvVar{} return []k8s.EnvVar{}
} }
// PrepareInstance is called on every instance before deploying the cloud-controller-manager.
// Allows for cloud-provider specific hooks.
func (c *CloudControllerManager) PrepareInstance(instance core.Instance, vpnIP string) error {
// no specific hook required.
return nil
}
// Supported is used to determine if cloud controller manager is implemented for this cloud provider. // Supported is used to determine if cloud controller manager is implemented for this cloud provider.
func (c *CloudControllerManager) Supported() bool { func (c *CloudControllerManager) Supported() bool {
return true return true
@ -148,6 +170,8 @@ type cloudConfig struct {
SubnetName string `json:"subnetName,omitempty"` SubnetName string `json:"subnetName,omitempty"`
SecurityGroupName string `json:"securityGroupName,omitempty"` SecurityGroupName string `json:"securityGroupName,omitempty"`
SecurityGroupResourceGroup string `json:"securityGroupResourceGroup,omitempty"` SecurityGroupResourceGroup string `json:"securityGroupResourceGroup,omitempty"`
LoadBalancerName string `json:"loadBalancerName,omitempty"`
LoadBalancerSku string `json:"loadBalancerSku,omitempty"`
VNetName string `json:"vnetName,omitempty"` VNetName string `json:"vnetName,omitempty"`
VNetResourceGroup string `json:"vnetResourceGroup,omitempty"` VNetResourceGroup string `json:"vnetResourceGroup,omitempty"`
CloudProviderBackoff bool `json:"cloudProviderBackoff,omitempty"` CloudProviderBackoff bool `json:"cloudProviderBackoff,omitempty"`

View File

@ -1,9 +1,11 @@
package azure package azure
import ( import (
"context"
"errors"
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -12,15 +14,18 @@ import (
) )
func TestSecrets(t *testing.T) { func TestSecrets(t *testing.T) {
someErr := errors.New("some error")
testCases := map[string]struct { testCases := map[string]struct {
instance core.Instance instance cloudtypes.Instance
metadata ccmMetadata
cloudServiceAccountURI string cloudServiceAccountURI string
wantSecrets resources.Secrets wantSecrets resources.Secrets
wantErr bool wantErr bool
}{ }{
"Secrets works": { "Secrets works": {
instance: core.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"}, instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"},
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location", cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
metadata: &ccmMetadataStub{loadBalancerName: "load-balancer-name", networkSecurityGroupName: "network-security-group-name"},
wantSecrets: resources.Secrets{ wantSecrets: resources.Secrets{
&k8s.Secret{ &k8s.Secret{
TypeMeta: meta.TypeMeta{ TypeMeta: meta.TypeMeta{
@ -32,14 +37,15 @@ func TestSecrets(t *testing.T) {
Namespace: "kube-system", Namespace: "kube-system",
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"azure.json": []byte(`{"cloud":"AzurePublicCloud","tenantId":"tenant-id","subscriptionId":"subscription-id","resourceGroup":"resource-group","location":"location","useInstanceMetadata":true,"vmType":"standard","aadClientId":"client-id","aadClientSecret":"client-secret"}`), "azure.json": []byte(`{"cloud":"AzurePublicCloud","tenantId":"tenant-id","subscriptionId":"subscription-id","resourceGroup":"resource-group","location":"location","securityGroupName":"network-security-group-name","loadBalancerName":"load-balancer-name","loadBalancerSku":"standard","useInstanceMetadata":true,"vmType":"standard","aadClientId":"client-id","aadClientSecret":"client-secret"}`),
}, },
}, },
}, },
}, },
"Secrets works for scale sets": { "Secrets works for scale sets": {
instance: core.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"}, instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"},
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location", cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
metadata: &ccmMetadataStub{loadBalancerName: "load-balancer-name", networkSecurityGroupName: "network-security-group-name"},
wantSecrets: resources.Secrets{ wantSecrets: resources.Secrets{
&k8s.Secret{ &k8s.Secret{
TypeMeta: meta.TypeMeta{ TypeMeta: meta.TypeMeta{
@ -51,17 +57,31 @@ func TestSecrets(t *testing.T) {
Namespace: "kube-system", Namespace: "kube-system",
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"azure.json": []byte(`{"cloud":"AzurePublicCloud","tenantId":"tenant-id","subscriptionId":"subscription-id","resourceGroup":"resource-group","location":"location","useInstanceMetadata":true,"vmType":"vmss","aadClientId":"client-id","aadClientSecret":"client-secret"}`), "azure.json": []byte(`{"cloud":"AzurePublicCloud","tenantId":"tenant-id","subscriptionId":"subscription-id","resourceGroup":"resource-group","location":"location","securityGroupName":"network-security-group-name","loadBalancerName":"load-balancer-name","loadBalancerSku":"standard","useInstanceMetadata":true,"vmType":"vmss","aadClientId":"client-id","aadClientSecret":"client-secret"}`),
}, },
}, },
}, },
}, },
"cannot get load balancer Name": {
instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"},
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
metadata: &ccmMetadataStub{getLoadBalancerNameErr: someErr},
wantErr: true,
},
"cannot get network security group name": {
instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"},
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
metadata: &ccmMetadataStub{getNetworkSecurityGroupNameErr: someErr},
wantErr: true,
},
"invalid providerID fails": { "invalid providerID fails": {
instance: core.Instance{ProviderID: "invalid"}, instance: cloudtypes.Instance{ProviderID: "invalid"},
metadata: &ccmMetadataStub{},
wantErr: true, wantErr: true,
}, },
"invalid cloudServiceAccountURI fails": { "invalid cloudServiceAccountURI fails": {
instance: core.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"}, instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"},
metadata: &ccmMetadataStub{},
cloudServiceAccountURI: "invalid", cloudServiceAccountURI: "invalid",
wantErr: true, wantErr: true,
}, },
@ -72,8 +92,8 @@ func TestSecrets(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
cloud := CloudControllerManager{} cloud := NewCloudControllerManager(tc.metadata)
secrets, err := cloud.Secrets(tc.instance, tc.cloudServiceAccountURI) secrets, err := cloud.Secrets(context.Background(), tc.instance, tc.cloudServiceAccountURI)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
@ -92,10 +112,25 @@ func TestTrivialCCMFunctions(t *testing.T) {
assert.NotEmpty(cloud.Path()) assert.NotEmpty(cloud.Path())
assert.NotEmpty(cloud.Name()) assert.NotEmpty(cloud.Name())
assert.NotEmpty(cloud.ExtraArgs()) assert.NotEmpty(cloud.ExtraArgs())
assert.Empty(cloud.ConfigMaps(core.Instance{})) assert.Empty(cloud.ConfigMaps(cloudtypes.Instance{}))
assert.NotEmpty(cloud.Volumes()) assert.NotEmpty(cloud.Volumes())
assert.NotEmpty(cloud.VolumeMounts()) assert.NotEmpty(cloud.VolumeMounts())
assert.Empty(cloud.Env()) assert.Empty(cloud.Env())
assert.NoError(cloud.PrepareInstance(core.Instance{}, "192.0.2.0"))
assert.True(cloud.Supported()) assert.True(cloud.Supported())
} }
type ccmMetadataStub struct {
networkSecurityGroupName string
loadBalancerName string
getNetworkSecurityGroupNameErr error
getLoadBalancerNameErr error
}
func (c *ccmMetadataStub) GetNetworkSecurityGroupName(ctx context.Context) (string, error) {
return c.networkSecurityGroupName, c.getNetworkSecurityGroupNameErr
}
func (c *ccmMetadataStub) GetLoadBalancerName(ctx context.Context) (string, error) {
return c.loadBalancerName, c.getLoadBalancerNameErr
}

View File

@ -19,7 +19,7 @@ func (c *CloudNodeManager) Path() string {
// ExtraArgs returns a list of arguments to append to the cloud-node-manager command. // ExtraArgs returns a list of arguments to append to the cloud-node-manager command.
func (c *CloudNodeManager) ExtraArgs() []string { func (c *CloudNodeManager) ExtraArgs() []string {
return []string{ return []string{
"--wait-routes=false", "--wait-routes=true",
} }
} }

View File

@ -10,15 +10,25 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
) )
var (
publicIPAddressRegexp = regexp.MustCompile(`/subscriptions/[^/]+/resourceGroups/[^/]+/providers/Microsoft.Network/publicIPAddresses/(?P<IPname>[^/]+)`)
keyPathRegexp = regexp.MustCompile(`^\/home\/([^\/]+)\/\.ssh\/authorized_keys$`)
)
// Metadata implements azure metadata APIs. // Metadata implements azure metadata APIs.
type Metadata struct { type Metadata struct {
imdsAPI imdsAPI
virtualNetworksAPI
securityGroupsAPI
networkInterfacesAPI networkInterfacesAPI
publicIPAddressesAPI
scaleSetsAPI scaleSetsAPI
loadBalancerAPI
virtualMachinesAPI virtualMachinesAPI
virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI
tagsAPI tagsAPI
@ -44,15 +54,23 @@ func NewMetadata(ctx context.Context) (*Metadata, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
virtualNetworksAPI := armnetwork.NewVirtualNetworksClient(subscriptionID, cred, nil)
networkInterfacesAPI := armnetwork.NewInterfacesClient(subscriptionID, cred, nil) networkInterfacesAPI := armnetwork.NewInterfacesClient(subscriptionID, cred, nil)
publicIPAddressesAPI := armnetwork.NewPublicIPAddressesClient(subscriptionID, cred, nil)
securityGroupsAPI := armnetwork.NewSecurityGroupsClient(subscriptionID, cred, nil)
scaleSetsAPI := armcompute.NewVirtualMachineScaleSetsClient(subscriptionID, cred, nil) scaleSetsAPI := armcompute.NewVirtualMachineScaleSetsClient(subscriptionID, cred, nil)
loadBalancerAPI := armnetwork.NewLoadBalancersClient(subscriptionID, cred, nil)
virtualMachinesAPI := armcompute.NewVirtualMachinesClient(subscriptionID, cred, nil) virtualMachinesAPI := armcompute.NewVirtualMachinesClient(subscriptionID, cred, nil)
virtualMachineScaleSetVMsAPI := armcompute.NewVirtualMachineScaleSetVMsClient(subscriptionID, cred, nil) virtualMachineScaleSetVMsAPI := armcompute.NewVirtualMachineScaleSetVMsClient(subscriptionID, cred, nil)
tagsAPI := armresources.NewTagsClient(subscriptionID, cred, nil) tagsAPI := armresources.NewTagsClient(subscriptionID, cred, nil)
return &Metadata{ return &Metadata{
imdsAPI: &imdsAPI, imdsAPI: &imdsAPI,
virtualNetworksAPI: &virtualNetworksClient{virtualNetworksAPI},
networkInterfacesAPI: &networkInterfacesClient{networkInterfacesAPI}, networkInterfacesAPI: &networkInterfacesClient{networkInterfacesAPI},
securityGroupsAPI: &securityGroupsClient{securityGroupsAPI},
publicIPAddressesAPI: &publicIPAddressesClient{publicIPAddressesAPI},
loadBalancerAPI: &loadBalancersClient{loadBalancerAPI},
scaleSetsAPI: &scaleSetsClient{scaleSetsAPI}, scaleSetsAPI: &scaleSetsClient{scaleSetsAPI},
virtualMachinesAPI: &virtualMachinesClient{virtualMachinesAPI}, virtualMachinesAPI: &virtualMachinesClient{virtualMachinesAPI},
virtualMachineScaleSetVMsAPI: &virtualMachineScaleSetVMsClient{virtualMachineScaleSetVMsAPI}, virtualMachineScaleSetVMsAPI: &virtualMachineScaleSetVMsClient{virtualMachineScaleSetVMsAPI},
@ -61,7 +79,7 @@ func NewMetadata(ctx context.Context) (*Metadata, error) {
} }
// List retrieves all instances belonging to the current constellation. // List retrieves all instances belonging to the current constellation.
func (m *Metadata) List(ctx context.Context) ([]core.Instance, error) { func (m *Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
providerID, err := m.providerID(ctx) providerID, err := m.providerID(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -78,23 +96,23 @@ func (m *Metadata) List(ctx context.Context) ([]core.Instance, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
instances := make([]core.Instance, 0, len(singleInstances)+len(scaleSetInstances)) instances := make([]cloudtypes.Instance, 0, len(singleInstances)+len(scaleSetInstances))
instances = append(instances, singleInstances...) instances = append(instances, singleInstances...)
instances = append(instances, scaleSetInstances...) instances = append(instances, scaleSetInstances...)
return instances, nil return instances, nil
} }
// Self retrieves the current instance. // Self retrieves the current instance.
func (m *Metadata) Self(ctx context.Context) (core.Instance, error) { func (m *Metadata) Self(ctx context.Context) (cloudtypes.Instance, error) {
providerID, err := m.providerID(ctx) providerID, err := m.providerID(ctx)
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
return m.GetInstance(ctx, providerID) return m.GetInstance(ctx, providerID)
} }
// GetInstance retrieves an instance using its providerID. // GetInstance retrieves an instance using its providerID.
func (m *Metadata) GetInstance(ctx context.Context, providerID string) (core.Instance, error) { func (m *Metadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
instance, singleErr := m.getVM(ctx, providerID) instance, singleErr := m.getVM(ctx, providerID)
if singleErr == nil { if singleErr == nil {
return instance, nil return instance, nil
@ -103,7 +121,7 @@ func (m *Metadata) GetInstance(ctx context.Context, providerID string) (core.Ins
if scaleSetErr == nil { if scaleSetErr == nil {
return instance, nil return instance, nil
} }
return core.Instance{}, fmt.Errorf("could not retrieve instance given providerID %v as either single vm or scale set vm: %v %v", providerID, singleErr, scaleSetErr) return cloudtypes.Instance{}, fmt.Errorf("could not retrieve instance given providerID %v as either single vm or scale set vm: %v %v", providerID, singleErr, scaleSetErr)
} }
// SignalRole signals the constellation role via cloud provider metadata. // SignalRole signals the constellation role via cloud provider metadata.
@ -120,6 +138,135 @@ func (m *Metadata) SignalRole(ctx context.Context, role role.Role) error {
return m.setTag(ctx, core.RoleMetadataKey, role.String()) return m.setTag(ctx, core.RoleMetadataKey, role.String())
} }
// GetNetworkSecurityGroupName returns the security group name of the resource group.
func (m *Metadata) GetNetworkSecurityGroupName(ctx context.Context) (string, error) {
providerID, err := m.providerID(ctx)
if err != nil {
return "", err
}
_, resourceGroup, err := extractBasicsFromProviderID(providerID)
if err != nil {
return "", err
}
nsg, err := m.getNetworkSecurityGroup(ctx, resourceGroup)
if err != nil {
return "", err
}
if nsg == nil || nsg.Name == nil {
return "", fmt.Errorf("could not dereference network security group name")
}
return *nsg.Name, nil
}
// GetSubnetworkCIDR retrieves the subnetwork CIDR from cloud provider metadata.
func (m *Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) {
providerID, err := m.providerID(ctx)
if err != nil {
return "", err
}
_, resourceGroup, err := extractBasicsFromProviderID(providerID)
if err != nil {
return "", err
}
virtualNetwork, err := m.getVirtualNetwork(ctx, resourceGroup)
if err != nil {
return "", err
}
if virtualNetwork == nil || virtualNetwork.Properties == nil || len(virtualNetwork.Properties.Subnets) == 0 ||
virtualNetwork.Properties.Subnets[0].Properties == nil || virtualNetwork.Properties.Subnets[0].Properties.AddressPrefix == nil {
return "", fmt.Errorf("could not retrieve subnetwork CIDR from virtual network %v", virtualNetwork)
}
return *virtualNetwork.Properties.Subnets[0].Properties.AddressPrefix, nil
}
// getLoadBalancer retrieves the load balancer from cloud provider metadata.
func (m *Metadata) getLoadBalancer(ctx context.Context) (*armnetwork.LoadBalancer, error) {
providerID, err := m.providerID(ctx)
if err != nil {
return nil, err
}
_, resourceGroup, err := extractBasicsFromProviderID(providerID)
if err != nil {
return nil, err
}
pager := m.loadBalancerAPI.List(resourceGroup, nil)
for pager.NextPage(ctx) {
for _, lb := range pager.PageResponse().Value {
if lb != nil && lb.Properties != nil {
return lb, nil
}
}
}
return nil, fmt.Errorf("could not get any load balancer")
}
// SupportsLoadBalancer returns true if the cloud provider supports load balancers.
func (m *Metadata) SupportsLoadBalancer() bool {
return true
}
// GetLoadBalancerName returns the load balancer name of the resource group.
func (m *Metadata) GetLoadBalancerName(ctx context.Context) (string, error) {
lb, err := m.getLoadBalancer(ctx)
if err != nil {
return "", err
}
if lb == nil || lb.Name == nil {
return "", fmt.Errorf("could not dereference load balancer name")
}
return *lb.Name, nil
}
// GetLoadBalancerIP retrieves the first load balancer IP from cloud provider metadata.
func (m *Metadata) GetLoadBalancerIP(ctx context.Context) (string, error) {
lb, err := m.getLoadBalancer(ctx)
if err != nil {
return "", err
}
if lb == nil || lb.Properties == nil {
return "", fmt.Errorf("could not dereference load balancer IP configuration")
}
var pubIPID string
for _, fipConf := range lb.Properties.FrontendIPConfigurations {
if fipConf == nil || fipConf.Properties == nil || fipConf.Properties.PublicIPAddress == nil || fipConf.Properties.PublicIPAddress.ID == nil {
continue
}
pubIPID = *fipConf.Properties.PublicIPAddress.ID
break
}
if pubIPID == "" {
return "", fmt.Errorf("could not find public IP address reference in load balancer")
}
matches := publicIPAddressRegexp.FindStringSubmatch(pubIPID)
if len(matches) != 2 {
return "", fmt.Errorf("could not find public IP address name in load balancer: %v", pubIPID)
}
pubIPName := matches[1]
providerID, err := m.providerID(ctx)
if err != nil {
return "", err
}
_, resourceGroup, err := extractBasicsFromProviderID(providerID)
if err != nil {
return "", err
}
resp, err := m.publicIPAddressesAPI.Get(ctx, resourceGroup, pubIPName, nil)
if err != nil {
return "", fmt.Errorf("could not retrieve public IP address: %w", err)
}
if resp.Properties == nil || resp.Properties.IPAddress == nil {
return "", fmt.Errorf("could not resolve public IP address reference for load balancer")
}
return *resp.Properties.IPAddress, nil
}
// SetVPNIP stores the internally used VPN IP in cloud provider metadata (not required on azure). // SetVPNIP stores the internally used VPN IP in cloud provider metadata (not required on azure).
func (m *Metadata) SetVPNIP(ctx context.Context, vpnIP string) error { func (m *Metadata) SetVPNIP(ctx context.Context, vpnIP string) error {
return nil return nil
@ -166,7 +313,6 @@ func extractInstanceTags(tags map[string]*string) map[string]string {
// extractSSHKeys extracts SSH public keys from azure instance OS Profile. // extractSSHKeys extracts SSH public keys from azure instance OS Profile.
func extractSSHKeys(sshConfig armcompute.SSHConfiguration) map[string][]string { func extractSSHKeys(sshConfig armcompute.SSHConfiguration) map[string][]string {
keyPathRegexp := regexp.MustCompile(`^\/home\/([^\/]+)\/\.ssh\/authorized_keys$`)
sshKeys := map[string][]string{} sshKeys := map[string][]string{}
for _, key := range sshConfig.PublicKeys { for _, key := range sshConfig.PublicKeys {
if key == nil || key.Path == nil || key.KeyData == nil { if key == nil || key.Path == nil || key.KeyData == nil {

View File

@ -8,24 +8,24 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestList(t *testing.T) { func TestList(t *testing.T) {
wantInstances := []core.Instance{ wantInstances := []cloudtypes.Instance{
{ {
Name: "instance-name", Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}}, SSHKeys: map[string][]string{"user": {"key-data"}},
}, },
{ {
Name: "scale-set-name-instance-id", Name: "scale-set-name-instance-id",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}}, SSHKeys: map[string][]string{"user": {"key-data"}},
}, },
} }
@ -37,7 +37,7 @@ func TestList(t *testing.T) {
virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI
tagsAPI tagsAPI tagsAPI tagsAPI
wantErr bool wantErr bool
wantInstances []core.Instance wantInstances []cloudtypes.Instance
}{ }{
"List works": { "List works": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
@ -98,16 +98,16 @@ func TestList(t *testing.T) {
} }
func TestSelf(t *testing.T) { func TestSelf(t *testing.T) {
wantVMInstance := core.Instance{ wantVMInstance := cloudtypes.Instance{
Name: "instance-name", Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}}, SSHKeys: map[string][]string{"user": {"key-data"}},
} }
wantScaleSetInstance := core.Instance{ wantScaleSetInstance := cloudtypes.Instance{
Name: "scale-set-name-instance-id", Name: "scale-set-name-instance-id",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}}, SSHKeys: map[string][]string{"user": {"key-data"}},
} }
testCases := map[string]struct { testCases := map[string]struct {
@ -116,7 +116,7 @@ func TestSelf(t *testing.T) {
virtualMachinesAPI virtualMachinesAPI virtualMachinesAPI virtualMachinesAPI
virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI
wantErr bool wantErr bool
wantInstance core.Instance wantInstance cloudtypes.Instance
}{ }{
"self for individual instance works": { "self for individual instance works": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
@ -210,6 +210,349 @@ func TestSignalRole(t *testing.T) {
} }
} }
func TestGetNetworkSecurityGroupName(t *testing.T) {
name := "network-security-group-name"
testCases := map[string]struct {
securityGroupsAPI securityGroupsAPI
imdsAPI imdsAPI
wantName string
wantErr bool
}{
"GetNetworkSecurityGroupName works": {
imdsAPI: newIMDSStub(),
securityGroupsAPI: &stubSecurityGroupsAPI{
listPages: [][]*armnetwork.SecurityGroup{
{
{
Name: to.StringPtr(name),
},
},
},
},
wantName: name,
},
"no security group": {
imdsAPI: newIMDSStub(),
securityGroupsAPI: &stubSecurityGroupsAPI{},
wantErr: true,
},
"missing name in security group struct": {
imdsAPI: newIMDSStub(),
securityGroupsAPI: &stubSecurityGroupsAPI{listPages: [][]*armnetwork.SecurityGroup{{{}}}},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
metadata := Metadata{
imdsAPI: tc.imdsAPI,
securityGroupsAPI: tc.securityGroupsAPI,
}
name, err := metadata.GetNetworkSecurityGroupName(context.Background())
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantName, name)
})
}
}
func TestGetSubnetworkCIDR(t *testing.T) {
subnetworkCIDR := "192.0.2.0/24"
name := "name"
testCases := map[string]struct {
virtualNetworksAPI virtualNetworksAPI
imdsAPI imdsAPI
wantNetworkCIDR string
wantErr bool
}{
"GetSubnetworkCIDR works": {
imdsAPI: newIMDSStub(),
virtualNetworksAPI: &stubVirtualNetworksAPI{listPages: [][]*armnetwork.VirtualNetwork{
{
{
Name: to.StringPtr(name),
Properties: &armnetwork.VirtualNetworkPropertiesFormat{
Subnets: []*armnetwork.Subnet{
{Properties: &armnetwork.SubnetPropertiesFormat{AddressPrefix: to.StringPtr(subnetworkCIDR)}},
},
},
},
},
}},
wantNetworkCIDR: subnetworkCIDR,
},
"no virtual networks found": {
imdsAPI: newIMDSStub(),
virtualNetworksAPI: &stubVirtualNetworksAPI{listPages: [][]*armnetwork.VirtualNetwork{
{},
}},
wantErr: true,
wantNetworkCIDR: subnetworkCIDR,
},
"malformed network struct": {
imdsAPI: newIMDSStub(),
virtualNetworksAPI: &stubVirtualNetworksAPI{listPages: [][]*armnetwork.VirtualNetwork{
{
{},
},
}},
wantErr: true,
wantNetworkCIDR: subnetworkCIDR,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
metadata := Metadata{
imdsAPI: tc.imdsAPI,
virtualNetworksAPI: tc.virtualNetworksAPI,
}
subnetworkCIDR, err := metadata.GetSubnetworkCIDR(context.Background())
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantNetworkCIDR, subnetworkCIDR)
})
}
}
func TestGetLoadBalancerName(t *testing.T) {
loadBalancerName := "load-balancer-name"
testCases := map[string]struct {
loadBalancerAPI loadBalancerAPI
imdsAPI imdsAPI
wantName string
wantErr bool
}{
"GetLoadBalancerName works": {
imdsAPI: newIMDSStub(),
loadBalancerAPI: &stubLoadBalancersAPI{
listPages: [][]*armnetwork.LoadBalancer{
{
{
Name: to.StringPtr(loadBalancerName),
Properties: &armnetwork.LoadBalancerPropertiesFormat{},
},
},
},
},
wantName: loadBalancerName,
},
"invalid load balancer struct": {
imdsAPI: newIMDSStub(),
loadBalancerAPI: &stubLoadBalancersAPI{listPages: [][]*armnetwork.LoadBalancer{{{}}}},
wantErr: true,
},
"invalid missing name": {
imdsAPI: newIMDSStub(),
loadBalancerAPI: &stubLoadBalancersAPI{listPages: [][]*armnetwork.LoadBalancer{{{
Properties: &armnetwork.LoadBalancerPropertiesFormat{},
}}}},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
metadata := Metadata{
imdsAPI: tc.imdsAPI,
loadBalancerAPI: tc.loadBalancerAPI,
}
loadbalancerName, err := metadata.GetLoadBalancerName(context.Background())
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantName, loadbalancerName)
})
}
}
func TestGetLoadBalancerIP(t *testing.T) {
loadBalancerName := "load-balancer-name"
publicIP := "192.0.2.1"
correctPublicIPID := "/subscriptions/subscription/resourceGroups/resourceGroup/providers/Microsoft.Network/publicIPAddresses/pubIPName"
someErr := errors.New("some error")
testCases := map[string]struct {
loadBalancerAPI loadBalancerAPI
publicIPAddressesAPI publicIPAddressesAPI
imdsAPI imdsAPI
wantIP string
wantErr bool
}{
"GetLoadBalancerIP works": {
imdsAPI: newIMDSStub(),
loadBalancerAPI: &stubLoadBalancersAPI{
listPages: [][]*armnetwork.LoadBalancer{
{
{
Name: to.StringPtr(loadBalancerName),
Properties: &armnetwork.LoadBalancerPropertiesFormat{
FrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
{
Properties: &armnetwork.FrontendIPConfigurationPropertiesFormat{
PublicIPAddress: &armnetwork.PublicIPAddress{
ID: &correctPublicIPID,
},
},
},
},
},
},
},
},
},
publicIPAddressesAPI: &stubPublicIPAddressesAPI{getResponse: armnetwork.PublicIPAddressesClientGetResponse{
PublicIPAddressesClientGetResult: armnetwork.PublicIPAddressesClientGetResult{
PublicIPAddress: armnetwork.PublicIPAddress{
Properties: &armnetwork.PublicIPAddressPropertiesFormat{
IPAddress: &publicIP,
},
},
},
}},
wantIP: publicIP,
},
"no load balancer": {
imdsAPI: newIMDSStub(),
loadBalancerAPI: &stubLoadBalancersAPI{
listPages: [][]*armnetwork.LoadBalancer{
{},
},
},
wantErr: true,
},
"load balancer missing public IP reference": {
imdsAPI: newIMDSStub(),
loadBalancerAPI: &stubLoadBalancersAPI{
listPages: [][]*armnetwork.LoadBalancer{
{
{
Name: to.StringPtr(loadBalancerName),
Properties: &armnetwork.LoadBalancerPropertiesFormat{
FrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{},
},
},
},
},
},
wantErr: true,
},
"public IP reference has wrong format": {
imdsAPI: newIMDSStub(),
loadBalancerAPI: &stubLoadBalancersAPI{
listPages: [][]*armnetwork.LoadBalancer{
{
{
Name: to.StringPtr(loadBalancerName),
Properties: &armnetwork.LoadBalancerPropertiesFormat{
FrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
{
Properties: &armnetwork.FrontendIPConfigurationPropertiesFormat{
PublicIPAddress: &armnetwork.PublicIPAddress{
ID: to.StringPtr("wrong-format"),
},
},
},
},
},
},
},
},
},
wantErr: true,
},
"no public IP address found": {
imdsAPI: newIMDSStub(),
loadBalancerAPI: &stubLoadBalancersAPI{
listPages: [][]*armnetwork.LoadBalancer{
{
{
Name: to.StringPtr(loadBalancerName),
Properties: &armnetwork.LoadBalancerPropertiesFormat{
FrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
{
Properties: &armnetwork.FrontendIPConfigurationPropertiesFormat{
PublicIPAddress: &armnetwork.PublicIPAddress{
ID: &correctPublicIPID,
},
},
},
},
},
},
},
},
},
publicIPAddressesAPI: &stubPublicIPAddressesAPI{getErr: someErr},
wantErr: true,
},
"found public IP has no address field": {
imdsAPI: newIMDSStub(),
loadBalancerAPI: &stubLoadBalancersAPI{
listPages: [][]*armnetwork.LoadBalancer{
{
{
Name: to.StringPtr(loadBalancerName),
Properties: &armnetwork.LoadBalancerPropertiesFormat{
FrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
{
Properties: &armnetwork.FrontendIPConfigurationPropertiesFormat{
PublicIPAddress: &armnetwork.PublicIPAddress{
ID: &correctPublicIPID,
},
},
},
},
},
},
},
},
},
publicIPAddressesAPI: &stubPublicIPAddressesAPI{getResponse: armnetwork.PublicIPAddressesClientGetResponse{
PublicIPAddressesClientGetResult: armnetwork.PublicIPAddressesClientGetResult{
PublicIPAddress: armnetwork.PublicIPAddress{
Properties: &armnetwork.PublicIPAddressPropertiesFormat{},
},
},
}},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
metadata := Metadata{
imdsAPI: tc.imdsAPI,
loadBalancerAPI: tc.loadBalancerAPI,
publicIPAddressesAPI: tc.publicIPAddressesAPI,
}
loadbalancerName, err := metadata.GetLoadBalancerIP(context.Background())
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantIP, loadbalancerName)
})
}
}
func TestSetVPNIP(t *testing.T) { func TestSetVPNIP(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
metadata := Metadata{} metadata := Metadata{}

View File

@ -11,53 +11,81 @@ import (
) )
// getVMInterfaces retrieves all network interfaces referenced by a virtual machine. // getVMInterfaces retrieves all network interfaces referenced by a virtual machine.
func (m *Metadata) getVMInterfaces(ctx context.Context, vm armcompute.VirtualMachine, resourceGroup string) ([]*armnetwork.InterfaceIPConfiguration, error) { func (m *Metadata) getVMInterfaces(ctx context.Context, vm armcompute.VirtualMachine, resourceGroup string) ([]armnetwork.Interface, error) {
if vm.Properties == nil || vm.Properties.NetworkProfile == nil { if vm.Properties == nil || vm.Properties.NetworkProfile == nil {
return []*armnetwork.InterfaceIPConfiguration{}, nil return []armnetwork.Interface{}, nil
} }
interfaceNames := extractInterfaceNamesFromInterfaceReferences(vm.Properties.NetworkProfile.NetworkInterfaces) interfaceNames := extractInterfaceNamesFromInterfaceReferences(vm.Properties.NetworkProfile.NetworkInterfaces)
interfaceIPConfigurations := []*armnetwork.InterfaceIPConfiguration{} networkInterfaces := []armnetwork.Interface{}
for _, interfaceName := range interfaceNames { for _, interfaceName := range interfaceNames {
networkInterfacesResp, err := m.networkInterfacesAPI.Get(ctx, resourceGroup, interfaceName, nil) networkInterfacesResp, err := m.networkInterfacesAPI.Get(ctx, resourceGroup, interfaceName, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to retrieve network interface %v: %w", interfaceName, err) return nil, fmt.Errorf("failed to retrieve network interface %v: %w", interfaceName, err)
} }
if networkInterfacesResp.Interface.Properties == nil || networkInterfacesResp.Interface.Properties.IPConfigurations == nil { networkInterfaces = append(networkInterfaces, networkInterfacesResp.Interface)
return nil, errors.New("retrieved network interface has invalid ip configuration")
}
interfaceIPConfigurations = append(interfaceIPConfigurations, networkInterfacesResp.Properties.IPConfigurations...)
} }
return interfaceIPConfigurations, nil return networkInterfaces, nil
} }
// getScaleSetVMInterfaces retrieves all network interfaces referenced by a scale set virtual machine. // getScaleSetVMInterfaces retrieves all network interfaces referenced by a scale set virtual machine.
func (m *Metadata) getScaleSetVMInterfaces(ctx context.Context, vm armcompute.VirtualMachineScaleSetVM, resourceGroup, scaleSet, instanceID string) ([]*armnetwork.InterfaceIPConfiguration, error) { func (m *Metadata) getScaleSetVMInterfaces(ctx context.Context, vm armcompute.VirtualMachineScaleSetVM, resourceGroup, scaleSet, instanceID string) ([]armnetwork.Interface, error) {
if vm.Properties == nil || vm.Properties.NetworkProfile == nil { if vm.Properties == nil || vm.Properties.NetworkProfile == nil {
return []*armnetwork.InterfaceIPConfiguration{}, nil return []armnetwork.Interface{}, nil
} }
interfaceNames := extractInterfaceNamesFromInterfaceReferences(vm.Properties.NetworkProfile.NetworkInterfaces) interfaceNames := extractInterfaceNamesFromInterfaceReferences(vm.Properties.NetworkProfile.NetworkInterfaces)
interfaceIPConfigurations := []*armnetwork.InterfaceIPConfiguration{} networkInterfaces := []armnetwork.Interface{}
for _, interfaceName := range interfaceNames { for _, interfaceName := range interfaceNames {
networkInterfacesResp, err := m.networkInterfacesAPI.GetVirtualMachineScaleSetNetworkInterface(ctx, resourceGroup, scaleSet, instanceID, interfaceName, nil) networkInterfacesResp, err := m.networkInterfacesAPI.GetVirtualMachineScaleSetNetworkInterface(ctx, resourceGroup, scaleSet, instanceID, interfaceName, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to retrieve network interface %v: %w", interfaceName, err) return nil, fmt.Errorf("failed to retrieve network interface %v: %w", interfaceName, err)
} }
if networkInterfacesResp.Interface.Properties == nil || networkInterfacesResp.Interface.Properties.IPConfigurations == nil { networkInterfaces = append(networkInterfaces, networkInterfacesResp.Interface)
return nil, errors.New("retrieved network interface has invalid ip configuration")
}
interfaceIPConfigurations = append(interfaceIPConfigurations, networkInterfacesResp.Properties.IPConfigurations...)
} }
return interfaceIPConfigurations, nil return networkInterfaces, nil
}
// getScaleSetVMPublicIPAddresses retrieves all public IP addresses from a network interface which is referenced by a scale set virtual machine.
func (m *Metadata) getScaleSetVMPublicIPAddresses(ctx context.Context, resourceGroup, scaleSet, instanceID string,
networkInterfaces []armnetwork.Interface,
) ([]string, error) {
var publicIPAddresses []string
for _, networkInterface := range networkInterfaces {
if networkInterface.Properties == nil || networkInterface.Name == nil {
continue
}
for _, config := range networkInterface.Properties.IPConfigurations {
if config == nil || config.Properties == nil || config.Properties.PublicIPAddress == nil || config.Name == nil {
continue
}
publicIPAddressName := *config.Properties.PublicIPAddress.ID
publicIPAddressNameParts := strings.Split(publicIPAddressName, "/")
publicIPAddressName = publicIPAddressNameParts[len(publicIPAddressNameParts)-1]
publicIPAddress, err := m.publicIPAddressesAPI.GetVirtualMachineScaleSetPublicIPAddress(ctx, resourceGroup, scaleSet, instanceID, *networkInterface.Name, *config.Name, publicIPAddressName, nil)
if err != nil {
return nil, fmt.Errorf("failed to retrieve public ip address %v: %w", publicIPAddressName, err)
}
if publicIPAddress.Properties == nil || publicIPAddress.Properties.IPAddress == nil {
return nil, errors.New("retrieved public ip address has invalid ip address")
}
publicIPAddresses = append(publicIPAddresses, *publicIPAddress.Properties.IPAddress)
}
}
return publicIPAddresses, nil
} }
// extractPrivateIPs extracts private IPs from a list of network interface IP configurations. // extractPrivateIPs extracts private IPs from a list of network interface IP configurations.
func extractPrivateIPs(interfaceIPConfigs []*armnetwork.InterfaceIPConfiguration) []string { func extractPrivateIPs(networkInterfaces []armnetwork.Interface) []string {
addresses := []string{} addresses := []string{}
for _, config := range interfaceIPConfigs { for _, networkInterface := range networkInterfaces {
if config == nil || config.Properties == nil || config.Properties.PrivateIPAddress == nil { if networkInterface.Properties == nil || len(networkInterface.Properties.IPConfigurations) == 0 {
continue continue
} }
addresses = append(addresses, *config.Properties.PrivateIPAddress) for _, config := range networkInterface.Properties.IPConfigurations {
if config == nil || config.Properties == nil || config.Properties.PrivateIPAddress == nil {
continue
}
addresses = append(addresses, *config.Properties.PrivateIPAddress)
}
} }
return addresses return addresses
} }

View File

@ -2,6 +2,7 @@ package azure
import ( import (
"context" "context"
"errors"
"testing" "testing"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
@ -12,10 +13,17 @@ import (
) )
func TestGetVMInterfaces(t *testing.T) { func TestGetVMInterfaces(t *testing.T) {
wantConfigs := []*armnetwork.InterfaceIPConfiguration{ wantNetworkInterfaces := []armnetwork.Interface{
{ {
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ Name: to.StringPtr("interface-name"),
PrivateIPAddress: to.StringPtr("192.0.2.0"), Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
}, },
}, },
} }
@ -31,25 +39,53 @@ func TestGetVMInterfaces(t *testing.T) {
}, },
} }
testCases := map[string]struct { testCases := map[string]struct {
vm armcompute.VirtualMachine vm armcompute.VirtualMachine
networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI
wantErr bool wantErr bool
wantConfigs []*armnetwork.InterfaceIPConfiguration wantNetworkInterfaces []armnetwork.Interface
}{ }{
"retrieval works": { "retrieval works": {
vm: vm, vm: vm,
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: &stubNetworkInterfacesAPI{
wantConfigs: wantConfigs, getInterface: armnetwork.Interface{
Name: to.StringPtr("interface-name"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
},
},
},
wantNetworkInterfaces: wantNetworkInterfaces,
}, },
"vm can have 0 interfaces": { "vm can have 0 interfaces": {
vm: armcompute.VirtualMachine{}, vm: armcompute.VirtualMachine{},
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: &stubNetworkInterfacesAPI{
wantConfigs: []*armnetwork.InterfaceIPConfiguration{}, getInterface: armnetwork.Interface{
Name: to.StringPtr("interface-name"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
},
},
},
wantNetworkInterfaces: []armnetwork.Interface{},
}, },
"interface retrieval fails": { "interface retrieval fails": {
vm: vm, vm: vm,
networkInterfacesAPI: newFailingNetworkInterfacesStub(), networkInterfacesAPI: &stubNetworkInterfacesAPI{
wantErr: true, getErr: errors.New("get err"),
},
wantErr: true,
}, },
} }
@ -61,23 +97,30 @@ func TestGetVMInterfaces(t *testing.T) {
metadata := Metadata{ metadata := Metadata{
networkInterfacesAPI: tc.networkInterfacesAPI, networkInterfacesAPI: tc.networkInterfacesAPI,
} }
configs, err := metadata.getVMInterfaces(context.Background(), tc.vm, "resource-group") vmNetworkInteraces, err := metadata.getVMInterfaces(context.Background(), tc.vm, "resource-group")
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
} }
require.NoError(err) require.NoError(err)
assert.Equal(tc.wantConfigs, configs) assert.Equal(tc.wantNetworkInterfaces, vmNetworkInteraces)
}) })
} }
} }
func TestGetScaleSetVMInterfaces(t *testing.T) { func TestGetScaleSetVMInterfaces(t *testing.T) {
wantConfigs := []*armnetwork.InterfaceIPConfiguration{ wantNetworkInterfaces := []armnetwork.Interface{
{ {
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ Name: to.StringPtr("interface-name"),
PrivateIPAddress: to.StringPtr("192.0.2.0"), Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
}, },
}, },
} }
@ -93,25 +136,53 @@ func TestGetScaleSetVMInterfaces(t *testing.T) {
}, },
} }
testCases := map[string]struct { testCases := map[string]struct {
vm armcompute.VirtualMachineScaleSetVM vm armcompute.VirtualMachineScaleSetVM
networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI
wantErr bool wantErr bool
wantConfigs []*armnetwork.InterfaceIPConfiguration wantNetworkInterfaces []armnetwork.Interface
}{ }{
"retrieval works": { "retrieval works": {
vm: vm, vm: vm,
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: &stubNetworkInterfacesAPI{
wantConfigs: wantConfigs, getInterface: armnetwork.Interface{
Name: to.StringPtr("interface-name"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
},
},
},
wantNetworkInterfaces: wantNetworkInterfaces,
}, },
"vm can have 0 interfaces": { "vm can have 0 interfaces": {
vm: armcompute.VirtualMachineScaleSetVM{}, vm: armcompute.VirtualMachineScaleSetVM{},
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: &stubNetworkInterfacesAPI{
wantConfigs: []*armnetwork.InterfaceIPConfiguration{}, getInterface: armnetwork.Interface{
Name: to.StringPtr("interface-name"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
},
},
},
wantNetworkInterfaces: []armnetwork.Interface{},
}, },
"interface retrieval fails": { "interface retrieval fails": {
vm: vm, vm: vm,
networkInterfacesAPI: newFailingNetworkInterfacesStub(), networkInterfacesAPI: &stubNetworkInterfacesAPI{
wantErr: true, getErr: errors.New("get err"),
},
wantErr: true,
}, },
} }
@ -130,33 +201,147 @@ func TestGetScaleSetVMInterfaces(t *testing.T) {
return return
} }
require.NoError(err) require.NoError(err)
assert.Equal(tc.wantConfigs, configs) assert.Equal(tc.wantNetworkInterfaces, configs)
})
}
}
func TestGetScaleSetVMPublicIPAddresses(t *testing.T) {
someErr := errors.New("some err")
newNetworkInterfaces := func() []armnetwork.Interface {
return []armnetwork.Interface{{
Name: to.StringPtr("interface-name"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Name: to.StringPtr("ip-config-name"),
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PublicIPAddress: &armnetwork.PublicIPAddress{
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/publicIPAddresses/public-ip-name"),
},
},
},
},
},
}, {
Name: to.StringPtr("interface-name2"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Name: to.StringPtr("ip-config-name2"),
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PublicIPAddress: &armnetwork.PublicIPAddress{
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/publicIPAddresses/public-ip-name2"),
},
},
},
},
},
}}
}
testCases := map[string]struct {
networkInterfacesMutator func(*[]armnetwork.Interface)
networkInterfaces []armnetwork.Interface
publicIPAddressesAPI publicIPAddressesAPI
wantIPs []string
wantErr bool
}{
"retrieval works": {
publicIPAddressesAPI: &stubPublicIPAddressesAPI{getVirtualMachineScaleSetPublicIPAddressResponse: armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResponse{
PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResult: armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResult{
PublicIPAddress: armnetwork.PublicIPAddress{
Properties: &armnetwork.PublicIPAddressPropertiesFormat{
IPAddress: to.StringPtr("192.0.2.1"),
},
},
},
}},
networkInterfaces: newNetworkInterfaces(),
wantIPs: []string{"192.0.2.1", "192.0.2.1"},
},
"retrieval works for no valid interfaces": {
publicIPAddressesAPI: &stubPublicIPAddressesAPI{getVirtualMachineScaleSetPublicIPAddressResponse: armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResponse{
PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResult: armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResult{
PublicIPAddress: armnetwork.PublicIPAddress{
Properties: &armnetwork.PublicIPAddressPropertiesFormat{
IPAddress: to.StringPtr("192.0.2.1"),
},
},
},
}},
networkInterfaces: newNetworkInterfaces(),
networkInterfacesMutator: func(nets *[]armnetwork.Interface) {
(*nets)[0].Properties.IPConfigurations = []*armnetwork.InterfaceIPConfiguration{nil}
(*nets)[1] = armnetwork.Interface{Name: nil}
},
},
"fail to get public IP": {
publicIPAddressesAPI: &stubPublicIPAddressesAPI{getErr: someErr},
networkInterfaces: newNetworkInterfaces(),
wantErr: true,
},
"fail to parse IPv4 address of public IP": {
publicIPAddressesAPI: &stubPublicIPAddressesAPI{getVirtualMachineScaleSetPublicIPAddressResponse: armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResponse{
PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResult: armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResult{
PublicIPAddress: armnetwork.PublicIPAddress{},
},
}},
networkInterfaces: newNetworkInterfaces(),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
if tc.networkInterfacesMutator != nil {
tc.networkInterfacesMutator(&tc.networkInterfaces)
}
metadata := Metadata{
publicIPAddressesAPI: tc.publicIPAddressesAPI,
}
ips, err := metadata.getScaleSetVMPublicIPAddresses(context.Background(), "resource-group", "scale-set-name", "instance-id", tc.networkInterfaces)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantIPs, ips)
}) })
} }
} }
func TestExtractPrivateIPs(t *testing.T) { func TestExtractPrivateIPs(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
interfaceIPConfigs []*armnetwork.InterfaceIPConfiguration networkInterfaces []armnetwork.Interface
wantIPs []string wantIPs []string
}{ }{
"extraction works": { "extraction works": {
interfaceIPConfigs: []*armnetwork.InterfaceIPConfiguration{ networkInterfaces: []armnetwork.Interface{
{ {
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ Properties: &armnetwork.InterfacePropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"), IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
}, },
}, },
}, },
wantIPs: []string{"192.0.2.0"}, wantIPs: []string{"192.0.2.0"},
}, },
"can be empty": { "can be empty": {
interfaceIPConfigs: []*armnetwork.InterfaceIPConfiguration{}, networkInterfaces: []armnetwork.Interface{},
}, },
"invalid interface is skipped": { "invalid interface is skipped": {
interfaceIPConfigs: []*armnetwork.InterfaceIPConfiguration{ networkInterfaces: []armnetwork.Interface{{}},
{},
},
}, },
} }
@ -164,7 +349,7 @@ func TestExtractPrivateIPs(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
ips := extractPrivateIPs(tc.interfaceIPConfigs) ips := extractPrivateIPs(tc.networkInterfaces)
assert.ElementsMatch(tc.wantIPs, ips) assert.ElementsMatch(tc.wantIPs, ips)
}) })

View File

@ -7,31 +7,41 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
) )
var (
azureVMSSProviderIDRegexp = regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachineScaleSets/([^/]+)/virtualMachines/([^/]+)$`)
coordinatorScaleSetRegexp = regexp.MustCompile(`constellation-scale-set-coordinators-[0-9a-zA-Z]+$`)
nodeScaleSetRegexp = regexp.MustCompile(`constellation-scale-set-nodes-[0-9a-zA-Z]+$`)
)
// getScaleSetVM tries to get an azure vm belonging to a scale set. // getScaleSetVM tries to get an azure vm belonging to a scale set.
func (m *Metadata) getScaleSetVM(ctx context.Context, providerID string) (core.Instance, error) { func (m *Metadata) getScaleSetVM(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
_, resourceGroup, scaleSet, instanceID, err := splitScaleSetProviderID(providerID) _, resourceGroup, scaleSet, instanceID, err := splitScaleSetProviderID(providerID)
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
vmResp, err := m.virtualMachineScaleSetVMsAPI.Get(ctx, resourceGroup, scaleSet, instanceID, nil) vmResp, err := m.virtualMachineScaleSetVMsAPI.Get(ctx, resourceGroup, scaleSet, instanceID, nil)
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
interfaceIPConfigurations, err := m.getScaleSetVMInterfaces(ctx, vmResp.VirtualMachineScaleSetVM, resourceGroup, scaleSet, instanceID) networkInterfaces, err := m.getScaleSetVMInterfaces(ctx, vmResp.VirtualMachineScaleSetVM, resourceGroup, scaleSet, instanceID)
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
}
publicIPAddresses, err := m.getScaleSetVMPublicIPAddresses(ctx, resourceGroup, scaleSet, instanceID, networkInterfaces)
if err != nil {
return cloudtypes.Instance{}, err
} }
return convertScaleSetVMToCoreInstance(scaleSet, vmResp.VirtualMachineScaleSetVM, interfaceIPConfigurations) return convertScaleSetVMToCoreInstance(scaleSet, vmResp.VirtualMachineScaleSetVM, networkInterfaces, publicIPAddresses)
} }
// listScaleSetVMs lists all scale set VMs in the current resource group. // listScaleSetVMs lists all scale set VMs in the current resource group.
func (m *Metadata) listScaleSetVMs(ctx context.Context, resourceGroup string) ([]core.Instance, error) { func (m *Metadata) listScaleSetVMs(ctx context.Context, resourceGroup string) ([]cloudtypes.Instance, error) {
instances := []core.Instance{} instances := []cloudtypes.Instance{}
scaleSetPager := m.scaleSetsAPI.List(resourceGroup, nil) scaleSetPager := m.scaleSetsAPI.List(resourceGroup, nil)
for scaleSetPager.NextPage(ctx) { for scaleSetPager.NextPage(ctx) {
for _, scaleSet := range scaleSetPager.PageResponse().Value { for _, scaleSet := range scaleSetPager.PageResponse().Value {
@ -48,7 +58,7 @@ func (m *Metadata) listScaleSetVMs(ctx context.Context, resourceGroup string) ([
if err != nil { if err != nil {
return nil, err return nil, err
} }
instance, err := convertScaleSetVMToCoreInstance(*scaleSet.Name, *vm, interfaces) instance, err := convertScaleSetVMToCoreInstance(*scaleSet.Name, *vm, interfaces, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -64,9 +74,7 @@ func (m *Metadata) listScaleSetVMs(ctx context.Context, resourceGroup string) ([
// A providerID for scale set VMs is build after the following schema: // A providerID for scale set VMs is build after the following schema:
// - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachineScaleSets/<scale-set-name>/virtualMachines/<instance-id>' // - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachineScaleSets/<scale-set-name>/virtualMachines/<instance-id>'
func splitScaleSetProviderID(providerID string) (subscriptionID, resourceGroup, scaleSet, instanceID string, err error) { func splitScaleSetProviderID(providerID string) (subscriptionID, resourceGroup, scaleSet, instanceID string, err error) {
// providerIDregex is a regex matching an azure scaleset vm providerID with each part of the URI being a submatch. matches := azureVMSSProviderIDRegexp.FindStringSubmatch(providerID)
providerIDregex := regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachineScaleSets/([^/]+)/virtualMachines/([^/]+)$`)
matches := providerIDregex.FindStringSubmatch(providerID)
if len(matches) != 5 { if len(matches) != 5 {
return "", "", "", "", errors.New("error splitting providerID") return "", "", "", "", errors.New("error splitting providerID")
} }
@ -74,12 +82,12 @@ func splitScaleSetProviderID(providerID string) (subscriptionID, resourceGroup,
} }
// convertScaleSetVMToCoreInstance converts an azure scale set virtual machine with interface configurations into a core.Instance. // convertScaleSetVMToCoreInstance converts an azure scale set virtual machine with interface configurations into a core.Instance.
func convertScaleSetVMToCoreInstance(scaleSet string, vm armcompute.VirtualMachineScaleSetVM, interfaceIPConfigs []*armnetwork.InterfaceIPConfiguration) (core.Instance, error) { func convertScaleSetVMToCoreInstance(scaleSet string, vm armcompute.VirtualMachineScaleSetVM, networkInterfaces []armnetwork.Interface, publicIPAddresses []string) (cloudtypes.Instance, error) {
if vm.ID == nil { if vm.ID == nil {
return core.Instance{}, errors.New("retrieving instance from armcompute API client returned no instance ID") return cloudtypes.Instance{}, errors.New("retrieving instance from armcompute API client returned no instance ID")
} }
if vm.Properties == nil || vm.Properties.OSProfile == nil || vm.Properties.OSProfile.ComputerName == nil { if vm.Properties == nil || vm.Properties.OSProfile == nil || vm.Properties.OSProfile.ComputerName == nil {
return core.Instance{}, errors.New("retrieving instance from armcompute API client returned no computer name") return cloudtypes.Instance{}, errors.New("retrieving instance from armcompute API client returned no computer name")
} }
var sshKeys map[string][]string var sshKeys map[string][]string
if vm.Properties.OSProfile.LinuxConfiguration == nil || vm.Properties.OSProfile.LinuxConfiguration.SSH == nil { if vm.Properties.OSProfile.LinuxConfiguration == nil || vm.Properties.OSProfile.LinuxConfiguration.SSH == nil {
@ -87,19 +95,18 @@ func convertScaleSetVMToCoreInstance(scaleSet string, vm armcompute.VirtualMachi
} else { } else {
sshKeys = extractSSHKeys(*vm.Properties.OSProfile.LinuxConfiguration.SSH) sshKeys = extractSSHKeys(*vm.Properties.OSProfile.LinuxConfiguration.SSH)
} }
return core.Instance{ return cloudtypes.Instance{
Name: *vm.Properties.OSProfile.ComputerName, Name: *vm.Properties.OSProfile.ComputerName,
ProviderID: "azure://" + *vm.ID, ProviderID: "azure://" + *vm.ID,
Role: extractScaleSetVMRole(scaleSet), Role: extractScaleSetVMRole(scaleSet),
IPs: extractPrivateIPs(interfaceIPConfigs), PrivateIPs: extractPrivateIPs(networkInterfaces),
PublicIPs: publicIPAddresses,
SSHKeys: sshKeys, SSHKeys: sshKeys,
}, nil }, nil
} }
// extractScaleSetVMRole extracts the constellation role of a scale set using its name. // extractScaleSetVMRole extracts the constellation role of a scale set using its name.
func extractScaleSetVMRole(scaleSet string) role.Role { func extractScaleSetVMRole(scaleSet string) role.Role {
coordinatorScaleSetRegexp := regexp.MustCompile(`constellation-scale-set-coordinators-[0-9a-zA-Z]+$`)
nodeScaleSetRegexp := regexp.MustCompile(`constellation-scale-set-nodes-[0-9a-zA-Z]+$`)
if coordinatorScaleSetRegexp.MatchString(scaleSet) { if coordinatorScaleSetRegexp.MatchString(scaleSet) {
return role.Coordinator return role.Coordinator
} }

View File

@ -8,17 +8,17 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestGetScaleSetVM(t *testing.T) { func TestGetScaleSetVM(t *testing.T) {
wantInstance := core.Instance{ wantInstance := cloudtypes.Instance{
Name: "scale-set-name-instance-id", Name: "scale-set-name-instance-id",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}}, SSHKeys: map[string][]string{"user": {"key-data"}},
} }
testCases := map[string]struct { testCases := map[string]struct {
@ -26,7 +26,7 @@ func TestGetScaleSetVM(t *testing.T) {
networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI
virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI
wantErr bool wantErr bool
wantInstance core.Instance wantInstance cloudtypes.Instance
}{ }{
"getVM for scale set instance works": { "getVM for scale set instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
@ -79,11 +79,11 @@ func TestGetScaleSetVM(t *testing.T) {
} }
func TestListScaleSetVMs(t *testing.T) { func TestListScaleSetVMs(t *testing.T) {
wantInstances := []core.Instance{ wantInstances := []cloudtypes.Instance{
{ {
Name: "scale-set-name-instance-id", Name: "scale-set-name-instance-id",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}}, SSHKeys: map[string][]string{"user": {"key-data"}},
}, },
} }
@ -93,7 +93,7 @@ func TestListScaleSetVMs(t *testing.T) {
virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI
scaleSetsAPI scaleSetsAPI scaleSetsAPI scaleSetsAPI
wantErr bool wantErr bool
wantInstances []core.Instance wantInstances []cloudtypes.Instance
}{ }{
"listVMs works": { "listVMs works": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
@ -114,7 +114,7 @@ func TestListScaleSetVMs(t *testing.T) {
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: newNetworkInterfacesStub(),
virtualMachineScaleSetVMsAPI: &stubVirtualMachineScaleSetVMsAPI{}, virtualMachineScaleSetVMsAPI: &stubVirtualMachineScaleSetVMsAPI{},
scaleSetsAPI: newScaleSetsStub(), scaleSetsAPI: newScaleSetsStub(),
wantInstances: []core.Instance{}, wantInstances: []cloudtypes.Instance{},
}, },
"can skip nil in VM list": { "can skip nil in VM list": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
@ -210,10 +210,11 @@ func TestSplitScaleSetProviderID(t *testing.T) {
func TestConvertScaleSetVMToCoreInstance(t *testing.T) { func TestConvertScaleSetVMToCoreInstance(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
inVM armcompute.VirtualMachineScaleSetVM inVM armcompute.VirtualMachineScaleSetVM
inInterfaceIPConfigs []*armnetwork.InterfaceIPConfiguration inInterface []armnetwork.Interface
wantErr bool inPublicIPs []string
wantInstance core.Instance wantErr bool
wantInstance cloudtypes.Instance
}{ }{
"conversion works": { "conversion works": {
inVM: armcompute.VirtualMachineScaleSetVM{ inVM: armcompute.VirtualMachineScaleSetVM{
@ -226,17 +227,27 @@ func TestConvertScaleSetVMToCoreInstance(t *testing.T) {
}, },
}, },
}, },
inInterfaceIPConfigs: []*armnetwork.InterfaceIPConfiguration{ inInterface: []armnetwork.Interface{
{ {
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ Name: to.StringPtr("scale-set-name_instance-id"),
PrivateIPAddress: to.StringPtr("192.0.2.0"), ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/interface-name"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
}, },
}, },
}, },
wantInstance: core.Instance{ inPublicIPs: []string{"192.0.2.100", "192.0.2.101"},
wantInstance: cloudtypes.Instance{
Name: "scale-set-name-instance-id", Name: "scale-set-name-instance-id",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
PublicIPs: []string{"192.0.2.100", "192.0.2.101"},
SSHKeys: map[string][]string{}, SSHKeys: map[string][]string{},
}, },
}, },
@ -251,7 +262,7 @@ func TestConvertScaleSetVMToCoreInstance(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
instance, err := convertScaleSetVMToCoreInstance("scale-set", tc.inVM, tc.inInterfaceIPConfigs) instance, err := convertScaleSetVMToCoreInstance("scale-set", tc.inVM, tc.inInterface, tc.inPublicIPs)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)

View File

@ -0,0 +1,19 @@
package azure
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
)
// getNetworkSecurityGroup retrieves the list of security groups for the given resource group.
func (m *Metadata) getNetworkSecurityGroup(ctx context.Context, resourceGroup string) (*armnetwork.SecurityGroup, error) {
pager := m.securityGroupsAPI.List(resourceGroup, nil)
for pager.NextPage(ctx) {
for _, securityGroup := range pager.PageResponse().Value {
return securityGroup, nil
}
}
return nil, fmt.Errorf("no security group found for resource group %q", resourceGroup)
}

View File

@ -11,29 +11,31 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/Azure/go-autorest/autorest/to" "github.com/Azure/go-autorest/autorest/to"
"github.com/edgelesssys/constellation/coordinator/cloudprovider" "github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
) )
var azureVMProviderIDRegexp = regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachines/([^/]+)$`)
// getVM tries to get a single azure vm. // getVM tries to get a single azure vm.
func (m *Metadata) getVM(ctx context.Context, providerID string) (core.Instance, error) { func (m *Metadata) getVM(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
_, resourceGroup, instanceName, err := splitVMProviderID(providerID) _, resourceGroup, instanceName, err := splitVMProviderID(providerID)
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
vmResp, err := m.virtualMachinesAPI.Get(ctx, resourceGroup, instanceName, nil) vmResp, err := m.virtualMachinesAPI.Get(ctx, resourceGroup, instanceName, nil)
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
interfaceIPConfigurations, err := m.getVMInterfaces(ctx, vmResp.VirtualMachine, resourceGroup) interfaceIPConfigurations, err := m.getVMInterfaces(ctx, vmResp.VirtualMachine, resourceGroup)
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
return convertVMToCoreInstance(vmResp.VirtualMachine, interfaceIPConfigurations) return convertVMToCoreInstance(vmResp.VirtualMachine, interfaceIPConfigurations)
} }
// listVMs lists all individual VMs in the current resource group. // listVMs lists all individual VMs in the current resource group.
func (m *Metadata) listVMs(ctx context.Context, resourceGroup string) ([]core.Instance, error) { func (m *Metadata) listVMs(ctx context.Context, resourceGroup string) ([]cloudtypes.Instance, error) {
instances := []core.Instance{} instances := []cloudtypes.Instance{}
pager := m.virtualMachinesAPI.List(resourceGroup, nil) pager := m.virtualMachinesAPI.List(resourceGroup, nil)
for pager.NextPage(ctx) { for pager.NextPage(ctx) {
for _, vm := range pager.PageResponse().Value { for _, vm := range pager.PageResponse().Value {
@ -75,19 +77,17 @@ func (m *Metadata) setTag(ctx context.Context, key, value string) error {
// A providerID for individual VMs is build after the following schema: // A providerID for individual VMs is build after the following schema:
// - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachines/<instance-name>' // - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachines/<instance-name>'
func splitVMProviderID(providerID string) (subscriptionID, resourceGroup, instanceName string, err error) { func splitVMProviderID(providerID string) (subscriptionID, resourceGroup, instanceName string, err error) {
// providerIDregex is a regex matching an azure vm providerID with each part of the URI being a submatch. matches := azureVMProviderIDRegexp.FindStringSubmatch(providerID)
providerIDregex := regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachines/([^/]+)$`)
matches := providerIDregex.FindStringSubmatch(providerID)
if len(matches) != 4 { if len(matches) != 4 {
return "", "", "", errors.New("error splitting providerID") return "", "", "", errors.New("error splitting providerID")
} }
return matches[1], matches[2], matches[3], nil return matches[1], matches[2], matches[3], nil
} }
// convertVMToCoreInstance converts an azure virtual machine with interface configurations into a core.Instance. // convertVMToCoreInstance converts an azure virtual machine with interface configurations into a cloudtypes.Instance.
func convertVMToCoreInstance(vm armcompute.VirtualMachine, interfaceIPConfigs []*armnetwork.InterfaceIPConfiguration) (core.Instance, error) { func convertVMToCoreInstance(vm armcompute.VirtualMachine, networkInterfaces []armnetwork.Interface) (cloudtypes.Instance, error) {
if vm.Name == nil || vm.ID == nil { if vm.Name == nil || vm.ID == nil {
return core.Instance{}, fmt.Errorf("retrieving instance from armcompute API client returned invalid instance Name (%v) or ID (%v)", vm.Name, vm.ID) return cloudtypes.Instance{}, fmt.Errorf("retrieving instance from armcompute API client returned invalid instance Name (%v) or ID (%v)", vm.Name, vm.ID)
} }
var sshKeys map[string][]string var sshKeys map[string][]string
if vm.Properties == nil || vm.Properties.OSProfile == nil || vm.Properties.OSProfile.LinuxConfiguration == nil || vm.Properties.OSProfile.LinuxConfiguration.SSH == nil { if vm.Properties == nil || vm.Properties.OSProfile == nil || vm.Properties.OSProfile.LinuxConfiguration == nil || vm.Properties.OSProfile.LinuxConfiguration.SSH == nil {
@ -96,11 +96,11 @@ func convertVMToCoreInstance(vm armcompute.VirtualMachine, interfaceIPConfigs []
sshKeys = extractSSHKeys(*vm.Properties.OSProfile.LinuxConfiguration.SSH) sshKeys = extractSSHKeys(*vm.Properties.OSProfile.LinuxConfiguration.SSH)
} }
metadata := extractInstanceTags(vm.Tags) metadata := extractInstanceTags(vm.Tags)
return core.Instance{ return cloudtypes.Instance{
Name: *vm.Name, Name: *vm.Name,
ProviderID: "azure://" + *vm.ID, ProviderID: "azure://" + *vm.ID,
Role: cloudprovider.ExtractRole(metadata), Role: cloudprovider.ExtractRole(metadata),
IPs: extractPrivateIPs(interfaceIPConfigs), PrivateIPs: extractPrivateIPs(networkInterfaces),
SSHKeys: sshKeys, SSHKeys: sshKeys,
}, nil }, nil
} }

View File

@ -8,16 +8,16 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestGetVM(t *testing.T) { func TestGetVM(t *testing.T) {
wantInstance := core.Instance{ wantInstance := cloudtypes.Instance{
Name: "instance-name", Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}}, SSHKeys: map[string][]string{"user": {"key-data"}},
} }
testCases := map[string]struct { testCases := map[string]struct {
@ -25,7 +25,7 @@ func TestGetVM(t *testing.T) {
networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI
virtualMachinesAPI virtualMachinesAPI virtualMachinesAPI virtualMachinesAPI
wantErr bool wantErr bool
wantInstance core.Instance wantInstance cloudtypes.Instance
}{ }{
"getVM for individual instance works": { "getVM for individual instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name", providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
@ -78,11 +78,11 @@ func TestGetVM(t *testing.T) {
} }
func TestListVMs(t *testing.T) { func TestListVMs(t *testing.T) {
wantInstances := []core.Instance{ wantInstances := []cloudtypes.Instance{
{ {
Name: "instance-name", Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}}, SSHKeys: map[string][]string{"user": {"key-data"}},
}, },
} }
@ -91,7 +91,7 @@ func TestListVMs(t *testing.T) {
networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI
virtualMachinesAPI virtualMachinesAPI virtualMachinesAPI virtualMachinesAPI
wantErr bool wantErr bool
wantInstances []core.Instance wantInstances []cloudtypes.Instance
}{ }{
"listVMs works": { "listVMs works": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
@ -103,7 +103,7 @@ func TestListVMs(t *testing.T) {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: newNetworkInterfacesStub(),
virtualMachinesAPI: &stubVirtualMachinesAPI{}, virtualMachinesAPI: &stubVirtualMachinesAPI{},
wantInstances: []core.Instance{}, wantInstances: []cloudtypes.Instance{},
}, },
"can skip nil in VM list": { "can skip nil in VM list": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
@ -234,10 +234,10 @@ func TestSplitVMProviderID(t *testing.T) {
func TestConvertVMToCoreInstance(t *testing.T) { func TestConvertVMToCoreInstance(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
inVM armcompute.VirtualMachine inVM armcompute.VirtualMachine
inInterfaceIPConfigs []*armnetwork.InterfaceIPConfiguration inInterface []armnetwork.Interface
wantErr bool wantErr bool
wantInstance core.Instance wantInstance cloudtypes.Instance
}{ }{
"conversion works": { "conversion works": {
inVM: armcompute.VirtualMachine{ inVM: armcompute.VirtualMachine{
@ -259,17 +259,25 @@ func TestConvertVMToCoreInstance(t *testing.T) {
}, },
}, },
}, },
inInterfaceIPConfigs: []*armnetwork.InterfaceIPConfiguration{ inInterface: []armnetwork.Interface{
{ {
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ Name: to.StringPtr("interface-name"),
PrivateIPAddress: to.StringPtr("192.0.2.0"), ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/interface-name"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
}, },
}, },
}, },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "instance-name", Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}}, SSHKeys: map[string][]string{"user": {"key-data"}},
}, },
}, },
@ -279,17 +287,25 @@ func TestConvertVMToCoreInstance(t *testing.T) {
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"), ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"),
Tags: map[string]*string{"tag-key": to.StringPtr("tag-value")}, Tags: map[string]*string{"tag-key": to.StringPtr("tag-value")},
}, },
inInterfaceIPConfigs: []*armnetwork.InterfaceIPConfiguration{ inInterface: []armnetwork.Interface{
{ {
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ Name: to.StringPtr("interface-name"),
PrivateIPAddress: to.StringPtr("192.0.2.0"), ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/interface-name"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
}, },
}, },
}, },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "instance-name", Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{}, SSHKeys: map[string][]string{},
}, },
}, },
@ -304,7 +320,7 @@ func TestConvertVMToCoreInstance(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
instance, err := convertVMToCoreInstance(tc.inVM, tc.inInterfaceIPConfigs) instance, err := convertVMToCoreInstance(tc.inVM, tc.inInterface)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)

View File

@ -0,0 +1,21 @@
package azure
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
)
// getVirtualNetwork return the first virtual network found in the resource group.
func (m *Metadata) getVirtualNetwork(ctx context.Context, resourceGroup string) (*armnetwork.VirtualNetwork, error) {
pager := m.virtualNetworksAPI.List(resourceGroup, nil)
for pager.NextPage(ctx) {
for _, network := range pager.PageResponse().Value {
if network != nil {
return network, nil
}
}
}
return nil, fmt.Errorf("no virtual network found in resource group %s", resourceGroup)
}

View File

@ -8,6 +8,22 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
) )
type virtualNetworksClient struct {
*armnetwork.VirtualNetworksClient
}
func (c *virtualNetworksClient) List(resourceGroupName string, options *armnetwork.VirtualNetworksClientListOptions) virtualNetworksClientListPager {
return c.VirtualNetworksClient.List(resourceGroupName, options)
}
type securityGroupsClient struct {
*armnetwork.SecurityGroupsClient
}
func (c *securityGroupsClient) List(resourceGroupName string, options *armnetwork.SecurityGroupsClientListOptions) securityGroupsClientListPager {
return c.SecurityGroupsClient.List(resourceGroupName, options)
}
type networkInterfacesClient struct { type networkInterfacesClient struct {
*armnetwork.InterfacesClient *armnetwork.InterfacesClient
} }
@ -25,6 +41,32 @@ func (c *networkInterfacesClient) Get(ctx context.Context, resourceGroupName str
return c.InterfacesClient.Get(ctx, resourceGroupName, networkInterfaceName, options) return c.InterfacesClient.Get(ctx, resourceGroupName, networkInterfaceName, options)
} }
type publicIPAddressesClient struct {
*armnetwork.PublicIPAddressesClient
}
func (c *publicIPAddressesClient) GetVirtualMachineScaleSetPublicIPAddress(ctx context.Context, resourceGroupName string,
virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string,
ipConfigurationName string, publicIPAddressName string,
options *armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressOptions,
) (armnetwork.PublicIPAddressesClientGetVirtualMachineScaleSetPublicIPAddressResponse, error) {
return c.PublicIPAddressesClient.GetVirtualMachineScaleSetPublicIPAddress(ctx, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, ipConfigurationName, publicIPAddressName, options)
}
func (c *publicIPAddressesClient) Get(ctx context.Context, resourceGroupName string, publicIPAddressName string,
options *armnetwork.PublicIPAddressesClientGetOptions,
) (armnetwork.PublicIPAddressesClientGetResponse, error) {
return c.PublicIPAddressesClient.Get(ctx, resourceGroupName, publicIPAddressName, options)
}
type loadBalancersClient struct {
*armnetwork.LoadBalancersClient
}
func (c *loadBalancersClient) List(resourceGroupName string, options *armnetwork.LoadBalancersClientListOptions) loadBalancersClientListPager {
return c.LoadBalancersClient.List(resourceGroupName, options)
}
type virtualMachinesClient struct { type virtualMachinesClient struct {
*armcompute.VirtualMachinesClient *armcompute.VirtualMachinesClient
} }

View File

@ -0,0 +1,15 @@
package cloudtypes
import "github.com/edgelesssys/constellation/coordinator/role"
// Instance describes metadata of a peer.
type Instance struct {
Name string
ProviderID string
Role role.Role
PrivateIPs []string
PublicIPs []string
AliasIPRanges []string
// SSHKeys maps usernames to ssh public keys.
SSHKeys map[string][]string
}

View File

@ -16,6 +16,12 @@ type instanceAPI interface {
Close() error Close() error
} }
type subnetworkAPI interface {
List(ctx context.Context, req *computepb.ListSubnetworksRequest, opts ...gax.CallOption) SubnetworkIterator
Get(ctx context.Context, req *computepb.GetSubnetworkRequest, opts ...gax.CallOption) (*computepb.Subnetwork, error)
Close() error
}
type metadataAPI interface { type metadataAPI interface {
InstanceAttributeValue(attr string) (string, error) InstanceAttributeValue(attr string) (string, error)
ProjectID() (string, error) ProjectID() (string, error)
@ -30,3 +36,7 @@ type Operation interface {
type InstanceIterator interface { type InstanceIterator interface {
Next() (*computepb.Instance, error) Next() (*computepb.Instance, error)
} }
type SubnetworkIterator interface {
Next() (*computepb.Subnetwork, error)
}

View File

@ -1,7 +1,7 @@
package gcp package gcp
import ( import (
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
) )
@ -15,7 +15,7 @@ func (a *Autoscaler) Name() string {
} }
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler. // Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (a *Autoscaler) Secrets(instance core.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (a *Autoscaler) Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil return resources.Secrets{}, nil
} }

View File

@ -3,7 +3,7 @@ package gcp
import ( import (
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -12,7 +12,7 @@ func TestTrivialAutoscalerFunctions(t *testing.T) {
autoscaler := Autoscaler{} autoscaler := Autoscaler{}
assert.NotEmpty(autoscaler.Name()) assert.NotEmpty(autoscaler.Name())
assert.Empty(autoscaler.Secrets(core.Instance{}, "")) assert.Empty(autoscaler.Secrets(cloudtypes.Instance{}, ""))
assert.NotEmpty(autoscaler.Volumes()) assert.NotEmpty(autoscaler.Volumes())
assert.NotEmpty(autoscaler.VolumeMounts()) assert.NotEmpty(autoscaler.VolumeMounts())
assert.NotEmpty(autoscaler.Env()) assert.NotEmpty(autoscaler.Env())

View File

@ -1,12 +1,13 @@
package gcp package gcp
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
"github.com/edgelesssys/constellation/coordinator/cloudprovider" "github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -34,14 +35,17 @@ func (c *CloudControllerManager) Name() string {
func (c *CloudControllerManager) ExtraArgs() []string { func (c *CloudControllerManager) ExtraArgs() []string {
return []string{ return []string{
"--use-service-account-credentials", "--use-service-account-credentials",
"--controllers=cloud-node,cloud-node-lifecycle", "--controllers=cloud-node,cloud-node-lifecycle,nodeipam,service,route",
"--cloud-config=/etc/gce/gce.conf", "--cloud-config=/etc/gce/gce.conf",
"--cidr-allocator-type=CloudAllocator",
"--allocate-node-cidrs=true",
"--configure-cloud-routes=false",
} }
} }
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager // ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ . // Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
func (c *CloudControllerManager) ConfigMaps(instance core.Instance) (resources.ConfigMaps, error) { func (c *CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resources.ConfigMaps, error) {
// GCP CCM expects cloud config to contain the GCP project-id and other configuration. // GCP CCM expects cloud config to contain the GCP project-id and other configuration.
// reference: https://github.com/kubernetes/cloud-provider-gcp/blob/master/cluster/gce/gci/configure-helper.sh#L791-L892 // reference: https://github.com/kubernetes/cloud-provider-gcp/blob/master/cluster/gce/gci/configure-helper.sh#L791-L892
var config strings.Builder var config strings.Builder
@ -51,7 +55,10 @@ func (c *CloudControllerManager) ConfigMaps(instance core.Instance) (resources.C
return resources.ConfigMaps{}, err return resources.ConfigMaps{}, err
} }
config.WriteString(fmt.Sprintf("project-id = %s\n", projectID)) config.WriteString(fmt.Sprintf("project-id = %s\n", projectID))
config.WriteString("use-metadata-server = false\n") config.WriteString("use-metadata-server = true\n")
nameParts := strings.Split(instance.Name, "-")
config.WriteString("node-tags = constellation-" + nameParts[len(nameParts)-2] + "\n")
return resources.ConfigMaps{ return resources.ConfigMaps{
&k8s.ConfigMap{ &k8s.ConfigMap{
@ -72,7 +79,7 @@ func (c *CloudControllerManager) ConfigMaps(instance core.Instance) (resources.C
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager. // Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ . // Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
func (c *CloudControllerManager) Secrets(instance core.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (c *CloudControllerManager) Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
serviceAccountKey, err := getServiceAccountKey(cloudServiceAccountURI) serviceAccountKey, err := getServiceAccountKey(cloudServiceAccountURI)
if err != nil { if err != nil {
return resources.Secrets{}, err return resources.Secrets{}, err
@ -150,12 +157,6 @@ func (c *CloudControllerManager) Env() []k8s.EnvVar {
} }
} }
// PrepareInstance is called on every instance before deploying the cloud-controller-manager.
// Allows for cloud-provider specific hooks.
func (c *CloudControllerManager) PrepareInstance(instance core.Instance, vpnIP string) error {
return nil
}
// Supported is used to determine if cloud controller manager is implemented for this cloud provider. // Supported is used to determine if cloud controller manager is implemented for this cloud provider.
func (c *CloudControllerManager) Supported() bool { func (c *CloudControllerManager) Supported() bool {
return true return true

View File

@ -1,11 +1,12 @@
package gcp package gcp
import ( import (
"context"
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/edgelesssys/constellation/cli/gcp/client" "github.com/edgelesssys/constellation/cli/gcp/client"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -15,12 +16,12 @@ import (
func TestConfigMaps(t *testing.T) { func TestConfigMaps(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
instance core.Instance instance cloudtypes.Instance
wantConfigMaps resources.ConfigMaps wantConfigMaps resources.ConfigMaps
wantErr bool wantErr bool
}{ }{
"ConfigMaps works": { "ConfigMaps works": {
instance: core.Instance{ProviderID: "gce://project-id/zone/instance-name"}, instance: cloudtypes.Instance{ProviderID: "gce://project-id/zone/instanceName-UID-0", Name: "instanceName-UID-0"},
wantConfigMaps: resources.ConfigMaps{ wantConfigMaps: resources.ConfigMaps{
&k8s.ConfigMap{ &k8s.ConfigMap{
TypeMeta: v1.TypeMeta{ TypeMeta: v1.TypeMeta{
@ -34,14 +35,15 @@ func TestConfigMaps(t *testing.T) {
Data: map[string]string{ Data: map[string]string{
"gce.conf": `[global] "gce.conf": `[global]
project-id = project-id project-id = project-id
use-metadata-server = false use-metadata-server = true
node-tags = constellation-UID
`, `,
}, },
}, },
}, },
}, },
"invalid providerID fails": { "invalid providerID fails": {
instance: core.Instance{ProviderID: "invalid"}, instance: cloudtypes.Instance{ProviderID: "invalid"},
wantErr: true, wantErr: true,
}, },
} }
@ -80,7 +82,7 @@ func TestSecrets(t *testing.T) {
rawKey, err := json.Marshal(serviceAccountKey) rawKey, err := json.Marshal(serviceAccountKey)
require.NoError(t, err) require.NoError(t, err)
testCases := map[string]struct { testCases := map[string]struct {
instance core.Instance instance cloudtypes.Instance
cloudServiceAccountURI string cloudServiceAccountURI string
wantSecrets resources.Secrets wantSecrets resources.Secrets
wantErr bool wantErr bool
@ -115,7 +117,7 @@ func TestSecrets(t *testing.T) {
require := require.New(t) require := require.New(t)
cloud := CloudControllerManager{} cloud := CloudControllerManager{}
secrets, err := cloud.Secrets(tc.instance, tc.cloudServiceAccountURI) secrets, err := cloud.Secrets(context.Background(), tc.instance, tc.cloudServiceAccountURI)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
@ -137,6 +139,5 @@ func TestTrivialCCMFunctions(t *testing.T) {
assert.NotEmpty(cloud.Volumes()) assert.NotEmpty(cloud.Volumes())
assert.NotEmpty(cloud.VolumeMounts()) assert.NotEmpty(cloud.VolumeMounts())
assert.NotEmpty(cloud.Env()) assert.NotEmpty(cloud.Env())
assert.NoError(cloud.PrepareInstance(core.Instance{}, "192.0.2.0"))
assert.True(cloud.Supported()) assert.True(cloud.Supported())
} }

View File

@ -8,6 +8,7 @@ import (
compute "cloud.google.com/go/compute/apiv1" compute "cloud.google.com/go/compute/apiv1"
"github.com/edgelesssys/constellation/coordinator/cloudprovider" "github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/core"
"google.golang.org/api/iterator" "google.golang.org/api/iterator"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1" computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
@ -16,9 +17,12 @@ import (
const gcpSSHMetadataKey = "ssh-keys" const gcpSSHMetadataKey = "ssh-keys"
var providerIDRegex = regexp.MustCompile(`^gce://([^/]+)/([^/]+)/([^/]+)$`)
// Client implements the gcp.API interface. // Client implements the gcp.API interface.
type Client struct { type Client struct {
instanceAPI instanceAPI
subnetworkAPI
metadataAPI metadataAPI
} }
@ -28,11 +32,15 @@ func NewClient(ctx context.Context) (*Client, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Client{instanceAPI: &instanceClient{insAPI}, metadataAPI: &metadataClient{}}, nil subnetAPI, err := compute.NewSubnetworksRESTClient(ctx)
if err != nil {
return nil, err
}
return &Client{instanceAPI: &instanceClient{insAPI}, subnetworkAPI: &subnetworkClient{subnetAPI}, metadataAPI: &metadataClient{}}, nil
} }
// RetrieveInstances returns list of instances including their ips and metadata. // RetrieveInstances returns list of instances including their ips and metadata.
func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([]core.Instance, error) { func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([]cloudtypes.Instance, error) {
uid, err := c.uid() uid, err := c.uid()
if err != nil { if err != nil {
return nil, err return nil, err
@ -43,7 +51,7 @@ func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([
} }
instanceIterator := c.instanceAPI.List(ctx, req) instanceIterator := c.instanceAPI.List(ctx, req)
instances := []core.Instance{} instances := []cloudtypes.Instance{}
for { for {
resp, err := instanceIterator.Next() resp, err := instanceIterator.Next()
if err == iterator.Done { if err == iterator.Done {
@ -68,10 +76,10 @@ func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([
} }
// RetrieveInstance returns a an instance including ips and metadata. // RetrieveInstance returns a an instance including ips and metadata.
func (c *Client) RetrieveInstance(ctx context.Context, project, zone, instanceName string) (core.Instance, error) { func (c *Client) RetrieveInstance(ctx context.Context, project, zone, instanceName string) (cloudtypes.Instance, error) {
instance, err := c.getComputeInstance(ctx, project, zone, instanceName) instance, err := c.getComputeInstance(ctx, project, zone, instanceName)
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
return convertToCoreInstance(instance, project, zone) return convertToCoreInstance(instance, project, zone)
@ -156,8 +164,45 @@ func (c *Client) UnsetInstanceMetadata(ctx context.Context, project, zone, insta
return nil return nil
} }
// RetrieveSubnetworkAliasCIDR returns the alias CIDR of the subnetwork specified by project, zone and subnetworkName.
func (c *Client) RetrieveSubnetworkAliasCIDR(ctx context.Context, project, zone, instanceName string) (string, error) {
instance, err := c.getComputeInstance(ctx, project, zone, instanceName)
if err != nil {
return "", err
}
if instance == nil || instance.NetworkInterfaces == nil || len(instance.NetworkInterfaces) == 0 || instance.NetworkInterfaces[0].Subnetwork == nil {
return "", fmt.Errorf("retrieving instance network interfaces failed")
}
subnetworkURL := *instance.NetworkInterfaces[0].Subnetwork
subnetworkURLFragments := strings.Split(subnetworkURL, "/")
subnetworkName := subnetworkURLFragments[len(subnetworkURLFragments)-1]
// convert:
// zone --> region
// europe-west3-b --> europe-west3
regionParts := strings.Split(zone, "-")
region := strings.TrimSuffix(zone, "-"+regionParts[len(regionParts)-1])
req := &computepb.GetSubnetworkRequest{
Project: project,
Region: region,
Subnetwork: subnetworkName,
}
subnetwork, err := c.subnetworkAPI.Get(ctx, req)
if err != nil {
return "", fmt.Errorf("retrieving subnetwork alias CIDR failed: %w", err)
}
if subnetwork == nil || subnetwork.IpCidrRange == nil || *subnetwork.IpCidrRange == "" {
return "", fmt.Errorf("retrieving subnetwork alias CIDR returned invalid results")
}
return *subnetwork.IpCidrRange, nil
}
// Close closes the instanceAPI client. // Close closes the instanceAPI client.
func (c *Client) Close() error { func (c *Client) Close() error {
if err := c.subnetworkAPI.Close(); err != nil {
return err
}
return c.instanceAPI.Close() return c.instanceAPI.Close()
} }
@ -199,8 +244,8 @@ func (c *Client) uid() (string, error) {
return uid, nil return uid, nil
} }
// extractIPs extracts private interface IPs from a list of interfaces. // extractPrivateIPs extracts private interface IPs from a list of interfaces.
func extractIPs(interfaces []*computepb.NetworkInterface) []string { func extractPrivateIPs(interfaces []*computepb.NetworkInterface) []string {
ips := []string{} ips := []string{}
for _, interf := range interfaces { for _, interf := range interfaces {
if interf == nil || interf.NetworkIP == nil { if interf == nil || interf.NetworkIP == nil {
@ -211,6 +256,40 @@ func extractIPs(interfaces []*computepb.NetworkInterface) []string {
return ips return ips
} }
// extractPublicIPs extracts public interface IPs from a list of interfaces.
func extractPublicIPs(interfaces []*computepb.NetworkInterface) []string {
ips := []string{}
for _, interf := range interfaces {
if interf == nil || interf.AccessConfigs == nil {
continue
}
for _, accessConfig := range interf.AccessConfigs {
if accessConfig == nil || accessConfig.NatIP == nil {
continue
}
ips = append(ips, *accessConfig.NatIP)
}
}
return ips
}
// extractAliasIPRanges extracts alias interface IPs from a list of interfaces.
func extractAliasIPRanges(interfaces []*computepb.NetworkInterface) []string {
ips := []string{}
for _, interf := range interfaces {
if interf == nil || interf.AliasIpRanges == nil {
continue
}
for _, aliasIP := range interf.AliasIpRanges {
if aliasIP == nil || aliasIP.IpCidrRange == nil {
continue
}
ips = append(ips, *aliasIP.IpCidrRange)
}
}
return ips
}
// extractSSHKeys extracts SSH keys from GCP instance metadata. // extractSSHKeys extracts SSH keys from GCP instance metadata.
// reference: https://cloud.google.com/compute/docs/connect/add-ssh-keys . // reference: https://cloud.google.com/compute/docs/connect/add-ssh-keys .
func extractSSHKeys(metadata map[string]string) map[string][]string { func extractSSHKeys(metadata map[string]string) map[string][]string {
@ -239,17 +318,19 @@ func extractSSHKeys(metadata map[string]string) map[string][]string {
} }
// convertToCoreInstance converts a *computepb.Instance to a core.Instance. // convertToCoreInstance converts a *computepb.Instance to a core.Instance.
func convertToCoreInstance(in *computepb.Instance, project string, zone string) (core.Instance, error) { func convertToCoreInstance(in *computepb.Instance, project string, zone string) (cloudtypes.Instance, error) {
if in.Name == nil { if in.Name == nil {
return core.Instance{}, fmt.Errorf("retrieving instance from compute API client returned invalid instance Name: %v", in.Name) return cloudtypes.Instance{}, fmt.Errorf("retrieving instance from compute API client returned invalid instance Name: %v", in.Name)
} }
metadata := extractInstanceMetadata(in.Metadata, "", false) metadata := extractInstanceMetadata(in.Metadata, "", false)
return core.Instance{ return cloudtypes.Instance{
Name: *in.Name, Name: *in.Name,
ProviderID: joinProviderID(project, zone, *in.Name), ProviderID: joinProviderID(project, zone, *in.Name),
Role: cloudprovider.ExtractRole(metadata), Role: cloudprovider.ExtractRole(metadata),
IPs: extractIPs(in.NetworkInterfaces), PrivateIPs: extractPrivateIPs(in.NetworkInterfaces),
SSHKeys: extractSSHKeys(metadata), PublicIPs: extractPublicIPs(in.NetworkInterfaces),
AliasIPRanges: extractAliasIPRanges(in.NetworkInterfaces),
SSHKeys: extractSSHKeys(metadata),
}, nil }, nil
} }
@ -262,9 +343,7 @@ func joinProviderID(project, zone, instanceName string) string {
// splitProviderID splits a provider's id into core components. // splitProviderID splits a provider's id into core components.
// A providerID is build after the schema 'gce://<project-id>/<zone>/<instance-name>' // A providerID is build after the schema 'gce://<project-id>/<zone>/<instance-name>'
func splitProviderID(providerID string) (project, zone, instance string, err error) { func splitProviderID(providerID string) (project, zone, instance string, err error) {
// providerIDregex is a regex matching a gce providerID with each part of the URI being a submatch. matches := providerIDRegex.FindStringSubmatch(providerID)
providerIDregex := regexp.MustCompile(`^gce://([^/]+)/([^/]+)/([^/]+)$`)
matches := providerIDregex.FindStringSubmatch(providerID)
if len(matches) != 4 { if len(matches) != 4 {
return "", "", "", fmt.Errorf("error splitting providerID: %v", providerID) return "", "", "", fmt.Errorf("error splitting providerID: %v", providerID)
} }

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
compute "cloud.google.com/go/compute/apiv1" compute "cloud.google.com/go/compute/apiv1"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
gax "github.com/googleapis/gax-go/v2" gax "github.com/googleapis/gax-go/v2"
@ -53,7 +54,11 @@ func TestRetrieveInstances(t *testing.T) {
}, },
}, },
NetworkInterfaces: []*computepb.NetworkInterface{ NetworkInterfaces: []*computepb.NetworkInterface{
{NetworkIP: proto.String("192.0.2.0")}, {
NetworkIP: proto.String("192.0.2.0"),
AliasIpRanges: []*computepb.AliasIpRange{{IpCidrRange: proto.String("192.0.2.0/16")}},
AccessConfigs: []*computepb.AccessConfig{{NatIP: proto.String("192.0.2.1")}},
},
}, },
}, },
}, },
@ -65,20 +70,22 @@ func TestRetrieveInstances(t *testing.T) {
metadata stubMetadataClient metadata stubMetadataClient
instanceIter *stubInstanceIterator instanceIter *stubInstanceIterator
instanceIterMutator func(*stubInstanceIterator) instanceIterMutator func(*stubInstanceIterator)
wantInstances []core.Instance wantInstances []cloudtypes.Instance
wantErr bool wantErr bool
}{ }{
"retrieve works": { "retrieve works": {
client: stubInstancesClient{}, client: stubInstancesClient{},
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
wantInstances: []core.Instance{ wantInstances: []cloudtypes.Instance{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
Role: role.Coordinator, Role: role.Coordinator,
IPs: []string{"192.0.2.0"}, AliasIPRanges: []string{"192.0.2.0/16"},
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}}, PublicIPs: []string{"192.0.2.1"},
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}},
}, },
}, },
}, },
@ -94,13 +101,15 @@ func TestRetrieveInstances(t *testing.T) {
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces = nil }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces = nil },
wantInstances: []core.Instance{ wantInstances: []cloudtypes.Instance{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
Role: role.Coordinator, Role: role.Coordinator,
IPs: []string{}, AliasIPRanges: []string{},
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}}, PublicIPs: []string{},
PrivateIPs: []string{},
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}},
}, },
}, },
}, },
@ -109,13 +118,15 @@ func TestRetrieveInstances(t *testing.T) {
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces[0].NetworkIP = nil }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces[0].NetworkIP = nil },
wantInstances: []core.Instance{ wantInstances: []cloudtypes.Instance{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
Role: role.Coordinator, Role: role.Coordinator,
IPs: []string{}, AliasIPRanges: []string{"192.0.2.0/16"},
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}}, PublicIPs: []string{"192.0.2.1"},
PrivateIPs: []string{},
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}},
}, },
}, },
}, },
@ -124,7 +135,7 @@ func TestRetrieveInstances(t *testing.T) {
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Metadata.Items[2].Key = proto.String("") }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Metadata.Items[2].Key = proto.String("") },
wantInstances: []core.Instance{}, wantInstances: []cloudtypes.Instance{},
}, },
"constellation retrieval fails": { "constellation retrieval fails": {
client: stubInstancesClient{}, client: stubInstancesClient{},
@ -137,13 +148,15 @@ func TestRetrieveInstances(t *testing.T) {
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Metadata.Items[3].Key = proto.String("") }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Metadata.Items[3].Key = proto.String("") },
wantInstances: []core.Instance{ wantInstances: []cloudtypes.Instance{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
Role: role.Unknown, Role: role.Unknown,
IPs: []string{"192.0.2.0"}, AliasIPRanges: []string{"192.0.2.0/16"},
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}}, PublicIPs: []string{"192.0.2.1"},
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}},
}, },
}, },
}, },
@ -198,7 +211,11 @@ func TestRetrieveInstance(t *testing.T) {
}, },
}, },
NetworkInterfaces: []*computepb.NetworkInterface{ NetworkInterfaces: []*computepb.NetworkInterface{
{NetworkIP: proto.String("192.0.2.0")}, {
NetworkIP: proto.String("192.0.2.0"),
AliasIpRanges: []*computepb.AliasIpRange{{IpCidrRange: proto.String("192.0.2.0/16")}},
AccessConfigs: []*computepb.AccessConfig{{NatIP: proto.String("192.0.2.1")}},
},
}, },
} }
} }
@ -207,17 +224,19 @@ func TestRetrieveInstance(t *testing.T) {
client stubInstancesClient client stubInstancesClient
clientInstance *computepb.Instance clientInstance *computepb.Instance
clientInstanceMutator func(*computepb.Instance) clientInstanceMutator func(*computepb.Instance)
wantInstance core.Instance wantInstance cloudtypes.Instance
wantErr bool wantErr bool
}{ }{
"retrieve works": { "retrieve works": {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, AliasIPRanges: []string{"192.0.2.0/16"},
SSHKeys: map[string][]string{}, PublicIPs: []string{"192.0.2.1"},
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{},
}, },
}, },
"retrieve with SSH key works": { "retrieve with SSH key works": {
@ -227,11 +246,13 @@ func TestRetrieveInstance(t *testing.T) {
i.Metadata.Items[0].Key = proto.String("ssh-keys") i.Metadata.Items[0].Key = proto.String("ssh-keys")
i.Metadata.Items[0].Value = proto.String("bob:ssh-rsa bobskey") i.Metadata.Items[0].Value = proto.String("bob:ssh-rsa bobskey")
}, },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, AliasIPRanges: []string{"192.0.2.0/16"},
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}}, PublicIPs: []string{"192.0.2.1"},
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}},
}, },
}, },
"retrieve with Role works": { "retrieve with Role works": {
@ -241,12 +262,14 @@ func TestRetrieveInstance(t *testing.T) {
i.Metadata.Items[0].Key = proto.String(core.RoleMetadataKey) i.Metadata.Items[0].Key = proto.String(core.RoleMetadataKey)
i.Metadata.Items[0].Value = proto.String(role.Coordinator.String()) i.Metadata.Items[0].Value = proto.String(role.Coordinator.String())
}, },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
Role: role.Coordinator, AliasIPRanges: []string{"192.0.2.0/16"},
IPs: []string{"192.0.2.0"}, PublicIPs: []string{"192.0.2.1"},
SSHKeys: map[string][]string{}, Role: role.Coordinator,
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{},
}, },
}, },
"retrieve fails": { "retrieve fails": {
@ -260,55 +283,91 @@ func TestRetrieveInstance(t *testing.T) {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0] = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0] = nil },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, AliasIPRanges: []string{"192.0.2.0/16"},
SSHKeys: map[string][]string{}, PublicIPs: []string{"192.0.2.1"},
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{},
}, },
}, },
"metadata key is null": { "metadata key is null": {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0].Key = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0].Key = nil },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, AliasIPRanges: []string{"192.0.2.0/16"},
SSHKeys: map[string][]string{}, PublicIPs: []string{"192.0.2.1"},
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{},
}, },
}, },
"metadata value is null": { "metadata value is null": {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0].Value = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0].Value = nil },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, AliasIPRanges: []string{"192.0.2.0/16"},
SSHKeys: map[string][]string{}, PublicIPs: []string{"192.0.2.1"},
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{},
}, },
}, },
"instance without network ip": { "instance without network ip": {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0] = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0] = nil },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{}, AliasIPRanges: []string{},
SSHKeys: map[string][]string{}, PublicIPs: []string{},
PrivateIPs: []string{},
SSHKeys: map[string][]string{},
}, },
}, },
"network ip is nil": { "network ip is nil": {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0].NetworkIP = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0].NetworkIP = nil },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{}, AliasIPRanges: []string{"192.0.2.0/16"},
SSHKeys: map[string][]string{}, PublicIPs: []string{"192.0.2.1"},
PrivateIPs: []string{},
SSHKeys: map[string][]string{},
},
},
"network alias cidr is nil": {
client: stubInstancesClient{},
clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0].AliasIpRanges[0].IpCidrRange = nil },
wantInstance: cloudtypes.Instance{
Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{},
PublicIPs: []string{"192.0.2.1"},
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{},
},
},
"network public ip is nil": {
client: stubInstancesClient{},
clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0].AccessConfigs[0].NatIP = nil },
wantInstance: cloudtypes.Instance{
Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{"192.0.2.0/16"},
PublicIPs: []string{},
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{},
}, },
}, },
} }
@ -620,15 +679,107 @@ func TestUnsetInstanceMetadata(t *testing.T) {
} }
} }
func TestRetrieveSubnetworkAliasCIDR(t *testing.T) {
aliasCIDR := "192.0.2.1/24"
someErr := errors.New("some error")
testCases := map[string]struct {
stubInstancesClient stubInstancesClient
stubSubnetworksClient stubSubnetworksClient
wantAliasCIDR string
wantErr bool
}{
"RetrieveSubnetworkAliasCIDR works": {
stubInstancesClient: stubInstancesClient{
GetInstance: &computepb.Instance{
NetworkInterfaces: []*computepb.NetworkInterface{
{
Subnetwork: proto.String("projects/project/regions/region/subnetworks/subnetwork"),
},
},
},
},
stubSubnetworksClient: stubSubnetworksClient{
GetSubnetwork: &computepb.Subnetwork{
IpCidrRange: &aliasCIDR,
},
},
wantAliasCIDR: aliasCIDR,
},
"instance has no network interface": {
stubInstancesClient: stubInstancesClient{
GetInstance: &computepb.Instance{
NetworkInterfaces: []*computepb.NetworkInterface{},
},
},
wantErr: true,
},
"cannot get instance": {
stubInstancesClient: stubInstancesClient{
GetErr: someErr,
},
wantErr: true,
},
"cannot get subnetwork": {
stubInstancesClient: stubInstancesClient{
GetInstance: &computepb.Instance{
NetworkInterfaces: []*computepb.NetworkInterface{
{
Subnetwork: proto.String("projects/project/regions/region/subnetworks/subnetwork"),
},
},
},
},
stubSubnetworksClient: stubSubnetworksClient{
GetErr: someErr,
},
wantErr: true,
},
"subnetwork has no cidr range": {
stubInstancesClient: stubInstancesClient{
GetInstance: &computepb.Instance{
NetworkInterfaces: []*computepb.NetworkInterface{
{
Subnetwork: proto.String("projects/project/regions/region/subnetworks/subnetwork"),
},
},
},
},
stubSubnetworksClient: stubSubnetworksClient{
GetSubnetwork: &computepb.Subnetwork{},
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
client := Client{instanceAPI: tc.stubInstancesClient, subnetworkAPI: tc.stubSubnetworksClient}
aliasCIDR, err := client.RetrieveSubnetworkAliasCIDR(context.Background(), "project", "zone", "subnetwork")
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantAliasCIDR, aliasCIDR)
})
}
}
func TestClose(t *testing.T) { func TestClose(t *testing.T) {
someErr := errors.New("failed") someErr := errors.New("failed")
assert := assert.New(t) assert := assert.New(t)
client := Client{instanceAPI: stubInstancesClient{}} client := Client{instanceAPI: stubInstancesClient{}, subnetworkAPI: stubSubnetworksClient{}}
assert.NoError(client.Close()) assert.NoError(client.Close())
client = Client{instanceAPI: stubInstancesClient{CloseErr: someErr}} client = Client{instanceAPI: stubInstancesClient{CloseErr: someErr}, subnetworkAPI: stubSubnetworksClient{}}
assert.Error(client.Close())
client = Client{instanceAPI: stubInstancesClient{}, subnetworkAPI: stubSubnetworksClient{CloseErr: someErr}}
assert.Error(client.Close()) assert.Error(client.Close())
} }
@ -725,6 +876,25 @@ func (s stubInstancesClient) Close() error {
return s.CloseErr return s.CloseErr
} }
type stubSubnetworksClient struct {
GetSubnetwork *computepb.Subnetwork
GetErr error
SubnetworkIterator SubnetworkIterator
CloseErr error
}
func (s stubSubnetworksClient) Get(ctx context.Context, req *computepb.GetSubnetworkRequest, opts ...gax.CallOption) (*computepb.Subnetwork, error) {
return s.GetSubnetwork, s.GetErr
}
func (s stubSubnetworksClient) List(ctx context.Context, req *computepb.ListSubnetworksRequest, opts ...gax.CallOption) SubnetworkIterator {
return s.SubnetworkIterator
}
func (s stubSubnetworksClient) Close() error {
return s.CloseErr
}
type stubMetadataClient struct { type stubMetadataClient struct {
InstanceValue string InstanceValue string
InstanceErr error InstanceErr error

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
) )
@ -11,9 +12,9 @@ import (
// API handles all GCP API requests. // API handles all GCP API requests.
type API interface { type API interface {
// RetrieveInstances retrieves a list of all accessible GCP instances with their metadata. // RetrieveInstances retrieves a list of all accessible GCP instances with their metadata.
RetrieveInstances(ctx context.Context, project, zone string) ([]core.Instance, error) RetrieveInstances(ctx context.Context, project, zone string) ([]cloudtypes.Instance, error)
// RetrieveInstances retrieves a single GCP instances with its metadata. // RetrieveInstances retrieves a single GCP instances with its metadata.
RetrieveInstance(ctx context.Context, project, zone, instanceName string) (core.Instance, error) RetrieveInstance(ctx context.Context, project, zone, instanceName string) (cloudtypes.Instance, error)
// RetrieveInstanceMetadata retrieves the GCP instance metadata of the current instance. // RetrieveInstanceMetadata retrieves the GCP instance metadata of the current instance.
RetrieveInstanceMetadata(attr string) (string, error) RetrieveInstanceMetadata(attr string) (string, error)
// RetrieveProjectID retrieves the GCP projectID containing the current instance. // RetrieveProjectID retrieves the GCP projectID containing the current instance.
@ -22,6 +23,8 @@ type API interface {
RetrieveZone() (string, error) RetrieveZone() (string, error)
// RetrieveInstanceName retrieves the instance name of the current instance. // RetrieveInstanceName retrieves the instance name of the current instance.
RetrieveInstanceName() (string, error) RetrieveInstanceName() (string, error)
// RetrieveSubnetworkAliasCIDR retrieves the subnetwork CIDR of the current instance.
RetrieveSubnetworkAliasCIDR(ctx context.Context, project, zone, instanceName string) (string, error)
// SetInstanceMetadata sets metadata key: value of the instance specified by project, zone and instanceName. // SetInstanceMetadata sets metadata key: value of the instance specified by project, zone and instanceName.
SetInstanceMetadata(ctx context.Context, project, zone, instanceName, key, value string) error SetInstanceMetadata(ctx context.Context, project, zone, instanceName, key, value string) error
// UnsetInstanceMetadata removes a metadata key-value pair of the instance specified by project, zone and instanceName. // UnsetInstanceMetadata removes a metadata key-value pair of the instance specified by project, zone and instanceName.
@ -41,7 +44,7 @@ func New(api API) *Metadata {
} }
// List retrieves all instances belonging to the current constellation. // List retrieves all instances belonging to the current constellation.
func (m *Metadata) List(ctx context.Context) ([]core.Instance, error) { func (m *Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
project, err := m.api.RetrieveProjectID() project, err := m.api.RetrieveProjectID()
if err != nil { if err != nil {
return nil, err return nil, err
@ -58,27 +61,27 @@ func (m *Metadata) List(ctx context.Context) ([]core.Instance, error) {
} }
// Self retrieves the current instance. // Self retrieves the current instance.
func (m *Metadata) Self(ctx context.Context) (core.Instance, error) { func (m *Metadata) Self(ctx context.Context) (cloudtypes.Instance, error) {
project, err := m.api.RetrieveProjectID() project, err := m.api.RetrieveProjectID()
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
zone, err := m.api.RetrieveZone() zone, err := m.api.RetrieveZone()
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
instanceName, err := m.api.RetrieveInstanceName() instanceName, err := m.api.RetrieveInstanceName()
if err != nil { if err != nil {
return core.Instance{}, err return cloudtypes.Instance{}, err
} }
return m.api.RetrieveInstance(ctx, project, zone, instanceName) return m.api.RetrieveInstance(ctx, project, zone, instanceName)
} }
// GetInstance retrieves an instance using its providerID. // GetInstance retrieves an instance using its providerID.
func (m *Metadata) GetInstance(ctx context.Context, providerID string) (core.Instance, error) { func (m *Metadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
project, zone, instanceName, err := splitProviderID(providerID) project, zone, instanceName, err := splitProviderID(providerID)
if err != nil { if err != nil {
return core.Instance{}, fmt.Errorf("invalid providerID: %w", err) return cloudtypes.Instance{}, fmt.Errorf("invalid providerID: %w", err)
} }
return m.api.RetrieveInstance(ctx, project, zone, instanceName) return m.api.RetrieveInstance(ctx, project, zone, instanceName)
} }
@ -117,6 +120,33 @@ func (m *Metadata) SetVPNIP(ctx context.Context, vpnIP string) error {
return m.api.SetInstanceMetadata(ctx, project, zone, instanceName, core.VPNIPMetadataKey, vpnIP) return m.api.SetInstanceMetadata(ctx, project, zone, instanceName, core.VPNIPMetadataKey, vpnIP)
} }
// GetSubnetworkCIDR returns the subnetwork CIDR of the current instance.
func (m *Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) {
project, err := m.api.RetrieveProjectID()
if err != nil {
return "", err
}
zone, err := m.api.RetrieveZone()
if err != nil {
return "", err
}
instanceName, err := m.api.RetrieveInstanceName()
if err != nil {
return "", err
}
return m.api.RetrieveSubnetworkAliasCIDR(ctx, project, zone, instanceName)
}
// SupportsLoadBalancer returns true if the cloud provider supports load balancers.
func (m *Metadata) SupportsLoadBalancer() bool {
return false
}
// GetLoadBalancerIP returns the IP of the load balancer.
func (m *Metadata) GetLoadBalancerIP(ctx context.Context) (string, error) {
return "", nil
}
// Supported is used to determine if metadata API is implemented for this cloud provider. // Supported is used to determine if metadata API is implemented for this cloud provider.
func (m *Metadata) Supported() bool { func (m *Metadata) Supported() bool {
return true return true

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -14,22 +15,22 @@ import (
func TestList(t *testing.T) { func TestList(t *testing.T) {
err := errors.New("some err") err := errors.New("some err")
uid := "1234" uid := "1234"
instancesGenerator := func() *[]core.Instance { instancesGenerator := func() *[]cloudtypes.Instance {
return &[]core.Instance{ return &[]cloudtypes.Instance{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
}, },
} }
} }
testCases := map[string]struct { testCases := map[string]struct {
client stubGCPClient client stubGCPClient
instancesGenerator func() *[]core.Instance instancesGenerator func() *[]cloudtypes.Instance
instancesMutator func(*[]core.Instance) instancesMutator func(*[]cloudtypes.Instance)
wantErr bool wantErr bool
wantInstances []core.Instance wantInstances []cloudtypes.Instance
}{ }{
"retrieve works": { "retrieve works": {
client: stubGCPClient{ client: stubGCPClient{
@ -40,11 +41,11 @@ func TestList(t *testing.T) {
}, },
}, },
instancesGenerator: instancesGenerator, instancesGenerator: instancesGenerator,
wantInstances: []core.Instance{ wantInstances: []cloudtypes.Instance{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
}, },
}, },
}, },
@ -105,22 +106,22 @@ func TestSelf(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
client stubGCPClient client stubGCPClient
wantErr bool wantErr bool
wantInstance core.Instance wantInstance cloudtypes.Instance
}{ }{
"retrieve works": { "retrieve works": {
client: stubGCPClient{ client: stubGCPClient{
projectID: "someProjectID", projectID: "someProjectID",
zone: "someZone", zone: "someZone",
retrieveInstanceValue: core.Instance{ retrieveInstanceValue: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
}, },
}, },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
}, },
}, },
"retrieve error is detected": { "retrieve error is detected": {
@ -179,21 +180,21 @@ func TestGetInstance(t *testing.T) {
providerID string providerID string
client stubGCPClient client stubGCPClient
wantErr bool wantErr bool
wantInstance core.Instance wantInstance cloudtypes.Instance
}{ }{
"retrieve works": { "retrieve works": {
providerID: "gce://someProject/someZone/someInstance", providerID: "gce://someProject/someZone/someInstance",
client: stubGCPClient{ client: stubGCPClient{
retrieveInstanceValue: core.Instance{ retrieveInstanceValue: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
}, },
}, },
wantInstance: core.Instance{ wantInstance: cloudtypes.Instance{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
}, },
}, },
"retrieve error is detected": { "retrieve error is detected": {
@ -357,12 +358,13 @@ func TestTrivialMetadataFunctions(t *testing.T) {
} }
type stubGCPClient struct { type stubGCPClient struct {
retrieveInstanceValue core.Instance retrieveInstanceValue cloudtypes.Instance
retrieveInstanceErr error retrieveInstanceErr error
retrieveInstancesValues []core.Instance retrieveInstancesValues []cloudtypes.Instance
retrieveInstancesErr error retrieveInstancesErr error
retrieveInstanceMetadaValues map[string]string retrieveInstanceMetadaValues map[string]string
retrieveInstanceMetadataErr error retrieveInstanceMetadataErr error
retrieveSubentworkAliasErr error
projectID string projectID string
zone string zone string
instanceName string instanceName string
@ -384,11 +386,11 @@ type stubGCPClient struct {
unsetMetadataKeys []string unsetMetadataKeys []string
} }
func (s *stubGCPClient) RetrieveInstances(ctx context.Context, project, zone string) ([]core.Instance, error) { func (s *stubGCPClient) RetrieveInstances(ctx context.Context, project, zone string) ([]cloudtypes.Instance, error) {
return s.retrieveInstancesValues, s.retrieveInstancesErr return s.retrieveInstancesValues, s.retrieveInstancesErr
} }
func (s *stubGCPClient) RetrieveInstance(ctx context.Context, project, zone string, instanceName string) (core.Instance, error) { func (s *stubGCPClient) RetrieveInstance(ctx context.Context, project, zone string, instanceName string) (cloudtypes.Instance, error) {
return s.retrieveInstanceValue, s.retrieveInstanceErr return s.retrieveInstanceValue, s.retrieveInstanceErr
} }
@ -426,3 +428,7 @@ func (s *stubGCPClient) UnsetInstanceMetadata(ctx context.Context, project, zone
return s.unsetInstanceMetadataErr return s.unsetInstanceMetadataErr
} }
func (s *stubGCPClient) RetrieveSubnetworkAliasCIDR(ctx context.Context, project, zone, instanceName string) (string, error) {
return "", s.retrieveSubentworkAliasErr
}

View File

@ -23,6 +23,26 @@ func (c *instanceClient) List(ctx context.Context, req *computepb.ListInstancesR
return c.InstancesClient.List(ctx, req) return c.InstancesClient.List(ctx, req)
} }
type subnetworkClient struct {
*compute.SubnetworksClient
}
func (c *subnetworkClient) Close() error {
return c.SubnetworksClient.Close()
}
func (c *subnetworkClient) List(ctx context.Context, req *computepb.ListSubnetworksRequest,
opts ...gax.CallOption,
) SubnetworkIterator {
return c.SubnetworksClient.List(ctx, req)
}
func (c *subnetworkClient) Get(ctx context.Context, req *computepb.GetSubnetworkRequest,
opts ...gax.CallOption,
) (*computepb.Subnetwork, error) {
return c.SubnetworksClient.Get(ctx, req)
}
type metadataClient struct{} type metadataClient struct{}
func (c *metadataClient) InstanceAttributeValue(attr string) (string, error) { func (c *metadataClient) InstanceAttributeValue(attr string) (string, error) {

View File

@ -5,9 +5,9 @@ const (
CloudControllerManagerImageAWS = "us.gcr.io/k8s-artifacts-prod/provider-aws/cloud-controller-manager:v1.22.0-alpha.0" CloudControllerManagerImageAWS = "us.gcr.io/k8s-artifacts-prod/provider-aws/cloud-controller-manager:v1.22.0-alpha.0"
// CloudControllerManagerImageGCP is the CCM image used on GCP. // CloudControllerManagerImageGCP is the CCM image used on GCP.
// TODO: use newer "cloud-provider-gcp" from https://github.com/kubernetes/cloud-provider-gcp when newer releases are available. // TODO: use newer "cloud-provider-gcp" from https://github.com/kubernetes/cloud-provider-gcp when newer releases are available.
CloudControllerManagerImageGCP = "ghcr.io/malt3/cloud-provider-gcp:latest" CloudControllerManagerImageGCP = "ghcr.io/edgelesssys/cloud-provider-gcp:sha-2f6a5b07fc2d37f24f8ff725132f87584d627d8f"
// CloudControllerManagerImageAzure is the CCM image used on Azure. // CloudControllerManagerImageAzure is the CCM image used on Azure.
CloudControllerManagerImageAzure = "mcr.microsoft.com/oss/kubernetes/azure-cloud-controller-manager:v1.23.5" CloudControllerManagerImageAzure = "mcr.microsoft.com/oss/kubernetes/azure-cloud-controller-manager:v1.23.11"
// CloudNodeManagerImageAzure is the cloud-node-manager image used on Azure. // CloudNodeManagerImageAzure is the cloud-node-manager image used on Azure.
CloudNodeManagerImageAzure = "mcr.microsoft.com/oss/kubernetes/azure-cloud-node-manager:v1.23.5" CloudNodeManagerImageAzure = "mcr.microsoft.com/oss/kubernetes/azure-cloud-node-manager:v1.23.11"
) )

View File

@ -1,7 +1,7 @@
package qemu package qemu
import ( import (
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
) )
@ -15,7 +15,7 @@ func (a Autoscaler) Name() string {
} }
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler. // Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (a Autoscaler) Secrets(instance core.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (a Autoscaler) Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil return resources.Secrets{}, nil
} }

View File

@ -1,7 +1,9 @@
package qemu package qemu
import ( import (
"github.com/edgelesssys/constellation/coordinator/core" "context"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
) )
@ -31,13 +33,13 @@ func (c CloudControllerManager) ExtraArgs() []string {
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager // ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ . // Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
func (c CloudControllerManager) ConfigMaps(instance core.Instance) (resources.ConfigMaps, error) { func (c CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resources.ConfigMaps, error) {
return resources.ConfigMaps{}, nil return resources.ConfigMaps{}, nil
} }
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager. // Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ . // Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
func (c CloudControllerManager) Secrets(instance core.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (c CloudControllerManager) Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil return resources.Secrets{}, nil
} }
@ -59,7 +61,7 @@ func (c CloudControllerManager) Env() []k8s.EnvVar {
// PrepareInstance is called on every instance before deploying the cloud-controller-manager. // PrepareInstance is called on every instance before deploying the cloud-controller-manager.
// Allows for cloud-provider specific hooks. // Allows for cloud-provider specific hooks.
func (c CloudControllerManager) PrepareInstance(instance core.Instance, vpnIP string) error { func (c CloudControllerManager) PrepareInstance(instance cloudtypes.Instance, vpnIP string) error {
// no specific hook required. // no specific hook required.
return nil return nil
} }

View File

@ -3,7 +3,7 @@ package qemu
import ( import (
"context" "context"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
) )
@ -16,17 +16,17 @@ func (m *Metadata) Supported() bool {
} }
// List retrieves all instances belonging to the current constellation. // List retrieves all instances belonging to the current constellation.
func (m *Metadata) List(ctx context.Context) ([]core.Instance, error) { func (m *Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
panic("function *Metadata.List not implemented") panic("function *Metadata.List not implemented")
} }
// Self retrieves the current instance. // Self retrieves the current instance.
func (m *Metadata) Self(ctx context.Context) (core.Instance, error) { func (m *Metadata) Self(ctx context.Context) (cloudtypes.Instance, error) {
panic("function *Metdata.Self not implemented") panic("function *Metdata.Self not implemented")
} }
// GetInstance retrieves an instance using its providerID. // GetInstance retrieves an instance using its providerID.
func (m Metadata) GetInstance(ctx context.Context, providerID string) (core.Instance, error) { func (m Metadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
panic("function *Metadata.GetInstance not implemented") panic("function *Metadata.GetInstance not implemented")
} }
@ -39,3 +39,18 @@ func (m Metadata) SignalRole(ctx context.Context, role role.Role) error {
func (m Metadata) SetVPNIP(ctx context.Context, vpnIP string) error { func (m Metadata) SetVPNIP(ctx context.Context, vpnIP string) error {
panic("function *Metadata.SetVPNIP not implemented") panic("function *Metadata.SetVPNIP not implemented")
} }
// SupportsLoadBalancer returns true if the cloud provider supports load balancers.
func (m Metadata) SupportsLoadBalancer() bool {
return false
}
// GetLoadBalancerIP returns the IP of the load balancer.
func (m Metadata) GetLoadBalancerIP(ctx context.Context) (string, error) {
panic("function *Metadata.GetLoadBalancerIP not implemented")
}
// GetSubnetworkCIDR retrieves the subnetwork CIDR from cloud provider metadata.
func (m Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) {
panic("function *Metadata.GetSubnetworkCIDR not implemented")
}

View File

@ -42,10 +42,7 @@ func main() {
var bindIP, bindPort, etcdEndpoint string var bindIP, bindPort, etcdEndpoint string
var enforceEtcdTls bool var enforceEtcdTls bool
var kube core.Cluster var kube core.Cluster
var metadata core.ProviderMetadata var coreMetadata core.ProviderMetadata
var cloudControllerManager core.CloudControllerManager
var cloudNodeManager core.CloudNodeManager
var autoscaler core.ClusterAutoscaler
var encryptedDisk core.EncryptedDisk var encryptedDisk core.EncryptedDisk
cfg := zap.NewDevelopmentConfig() cfg := zap.NewDevelopmentConfig()
@ -87,15 +84,13 @@ func main() {
issuer = gcp.NewIssuer() issuer = gcp.NewIssuer()
validator = gcp.NewValidator(pcrs) validator = gcp.NewValidator(pcrs)
kube = kubernetes.New(k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New())
gcpClient, err := gcpcloud.NewClient(context.Background()) gcpClient, err := gcpcloud.NewClient(context.Background())
if err != nil { if err != nil {
log.Fatalf("creating GCP client failed: %v\n", err) log.Fatalf("creating GCP client failed: %v\n", err)
} }
metadata = gcpcloud.New(gcpClient) metadata := gcpcloud.New(gcpClient)
cloudControllerManager = &gcpcloud.CloudControllerManager{} coreMetadata = metadata
cloudNodeManager = &gcpcloud.CloudNodeManager{} kube = kubernetes.New("gcp", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), &gcpcloud.CloudControllerManager{}, &gcpcloud.CloudNodeManager{}, &gcpcloud.Autoscaler{}, metadata)
autoscaler = &gcpcloud.Autoscaler{}
encryptedDisk = diskencryption.New() encryptedDisk = diskencryption.New()
bindIP = defaultIP bindIP = defaultIP
bindPort = defaultPort bindPort = defaultPort
@ -112,14 +107,13 @@ func main() {
issuer = azure.NewIssuer() issuer = azure.NewIssuer()
validator = azure.NewValidator(pcrs) validator = azure.NewValidator(pcrs)
kube = kubernetes.New(k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New()) metadata, err := azurecloud.NewMetadata(context.Background())
metadata, err = azurecloud.NewMetadata(context.Background())
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
cloudControllerManager = &azurecloud.CloudControllerManager{} coreMetadata = metadata
cloudNodeManager = &azurecloud.CloudNodeManager{} kube = kubernetes.New("azure", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), azurecloud.NewCloudControllerManager(metadata), &azurecloud.CloudNodeManager{}, &azurecloud.Autoscaler{}, metadata)
autoscaler = &azurecloud.Autoscaler{}
encryptedDisk = diskencryption.New() encryptedDisk = diskencryption.New()
bindIP = defaultIP bindIP = defaultIP
bindPort = defaultPort bindPort = defaultPort
@ -136,13 +130,10 @@ func main() {
issuer = qemu.NewIssuer() issuer = qemu.NewIssuer()
validator = qemu.NewValidator(pcrs) validator = qemu.NewValidator(pcrs)
kube = kubernetes.New(k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New())
// no support for cloud services in qemu // no support for cloud services in qemu
metadata = &qemucloud.Metadata{} metadata := &qemucloud.Metadata{}
cloudControllerManager = &qemucloud.CloudControllerManager{} kube = kubernetes.New("qemu", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), &qemucloud.CloudControllerManager{}, &qemucloud.CloudNodeManager{}, &qemucloud.Autoscaler{}, metadata)
cloudNodeManager = &qemucloud.CloudNodeManager{} coreMetadata = metadata
autoscaler = &qemucloud.Autoscaler{}
encryptedDisk = diskencryption.New() encryptedDisk = diskencryption.New()
bindIP = defaultIP bindIP = defaultIP
@ -155,10 +146,7 @@ func main() {
issuer = core.NewMockIssuer() issuer = core.NewMockIssuer()
validator = core.NewMockValidator() validator = core.NewMockValidator()
kube = &core.ClusterFake{} kube = &core.ClusterFake{}
metadata = &core.ProviderMetadataFake{} coreMetadata = &core.ProviderMetadataFake{}
cloudControllerManager = &core.CloudControllerManagerFake{}
cloudNodeManager = &core.CloudNodeManagerFake{}
autoscaler = &core.ClusterAutoscalerFake{}
encryptedDisk = &core.EncryptedDiskFake{} encryptedDisk = &core.EncryptedDiskFake{}
bindIP = defaultIP bindIP = defaultIP
bindPort = defaultPort bindPort = defaultPort
@ -174,5 +162,5 @@ func main() {
netDialer := &net.Dialer{} netDialer := &net.Dialer{}
dialer := grpcutil.NewDialer(validator, netDialer) dialer := grpcutil.NewDialer(validator, netDialer)
run(issuer, wg, openTPM, util.GetIPAddr, dialer, fileHandler, kube, run(issuer, wg, openTPM, util.GetIPAddr, dialer, fileHandler, kube,
metadata, cloudControllerManager, cloudNodeManager, autoscaler, encryptedDisk, etcdEndpoint, enforceEtcdTls, bindIP, bindPort, zapLoggerCore, fs) coreMetadata, encryptedDisk, etcdEndpoint, enforceEtcdTls, bindIP, bindPort, zapLoggerCore, fs)
} }

View File

@ -30,7 +30,7 @@ import (
var version = "0.0.0" var version = "0.0.0"
func run(issuer core.QuoteIssuer, vpn core.VPN, openTPM vtpm.TPMOpenFunc, getPublicIPAddr func() (string, error), dialer *grpcutil.Dialer, fileHandler file.Handler, func run(issuer core.QuoteIssuer, vpn core.VPN, openTPM vtpm.TPMOpenFunc, getPublicIPAddr func() (string, error), dialer *grpcutil.Dialer, fileHandler file.Handler,
kube core.Cluster, metadata core.ProviderMetadata, cloudControllerManager core.CloudControllerManager, cloudNodeManager core.CloudNodeManager, clusterAutoscaler core.ClusterAutoscaler, encryptedDisk core.EncryptedDisk, etcdEndpoint string, etcdTLS bool, bindIP, bindPort string, zapLoggerCore *zap.Logger, kube core.Cluster, metadata core.ProviderMetadata, encryptedDisk core.EncryptedDisk, etcdEndpoint string, etcdTLS bool, bindIP, bindPort string, zapLoggerCore *zap.Logger,
fs afero.Fs, fs afero.Fs,
) { ) {
defer zapLoggerCore.Sync() defer zapLoggerCore.Sync()
@ -47,7 +47,7 @@ func run(issuer core.QuoteIssuer, vpn core.VPN, openTPM vtpm.TPMOpenFunc, getPub
Logger: zapLoggerCore.WithOptions(zap.IncreaseLevel(zap.WarnLevel)).Named("etcd"), Logger: zapLoggerCore.WithOptions(zap.IncreaseLevel(zap.WarnLevel)).Named("etcd"),
} }
linuxUserManager := user.NewLinuxUserManager(fs) linuxUserManager := user.NewLinuxUserManager(fs)
core, err := core.NewCore(vpn, kube, metadata, cloudControllerManager, cloudNodeManager, clusterAutoscaler, encryptedDisk, zapLoggerCore, openTPM, etcdStoreFactory, fileHandler, linuxUserManager) core, err := core.NewCore(vpn, kube, metadata, encryptedDisk, zapLoggerCore, openTPM, etcdStoreFactory, fileHandler, linuxUserManager)
if err != nil { if err != nil {
zapLoggerCore.Fatal("failed to create core", zap.Error(err)) zapLoggerCore.Fatal("failed to create core", zap.Error(err))
} }

View File

@ -213,7 +213,7 @@ func TestConcurrent(t *testing.T) {
func spawnPeer(require *require.Assertions, logger *zap.Logger, netDialer *testdialer.BufconnDialer, netw *network, endpoint string) (*grpc.Server, *pubapi.API, *fakeVPN) { func spawnPeer(require *require.Assertions, logger *zap.Logger, netDialer *testdialer.BufconnDialer, netw *network, endpoint string) (*grpc.Server, *pubapi.API, *fakeVPN) {
vpn := newVPN(netw, endpoint) vpn := newVPN(netw, endpoint)
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
cor, err := core.NewCore(vpn, &core.ClusterFake{}, &core.ProviderMetadataFake{}, &core.CloudControllerManagerFake{}, &core.CloudNodeManagerFake{}, &core.ClusterAutoscalerFake{}, &core.EncryptedDiskFake{}, logger, simulator.OpenSimulatedTPM, fakeStoreFactory{}, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) cor, err := core.NewCore(vpn, &core.ClusterFake{}, &core.ProviderMetadataFake{}, &core.EncryptedDiskFake{}, logger, simulator.OpenSimulatedTPM, fakeStoreFactory{}, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
require.NoError(cor.AdvanceState(state.AcceptingInit, nil, nil)) require.NoError(cor.AdvanceState(state.AcceptingInit, nil, nil))

View File

@ -6,9 +6,8 @@ import (
"fmt" "fmt"
"net" "net"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
k8s "k8s.io/api/core/v1"
) )
var ErrUnimplemented = errors.New("unimplemented") var ErrUnimplemented = errors.New("unimplemented")
@ -20,24 +19,12 @@ const (
VPNIPMetadataKey = "constellation-vpn-ip" VPNIPMetadataKey = "constellation-vpn-ip"
) )
// Instance describes a cloud-provider instance including name, providerID, ip addresses and instance metadata.
type Instance struct {
Name string
ProviderID string
Role role.Role
IPs []string
// SSHKeys maps usernames to ssh public keys.
SSHKeys map[string][]string
}
// ProviderMetadata implementers read/write cloud provider metadata. // ProviderMetadata implementers read/write cloud provider metadata.
type ProviderMetadata interface { type ProviderMetadata interface {
// List retrieves all instances belonging to the current constellation. // List retrieves all instances belonging to the current constellation.
List(ctx context.Context) ([]Instance, error) List(ctx context.Context) ([]cloudtypes.Instance, error)
// Self retrieves the current instance. // Self retrieves the current instance.
Self(ctx context.Context) (Instance, error) Self(ctx context.Context) (cloudtypes.Instance, error)
// GetInstance retrieves an instance using its providerID.
GetInstance(ctx context.Context, providerID string) (Instance, error)
// SignalRole signals the constellation role via cloud provider metadata (if supported by the CSP and deployment type, otherwise does nothing). // SignalRole signals the constellation role via cloud provider metadata (if supported by the CSP and deployment type, otherwise does nothing).
SignalRole(ctx context.Context, role role.Role) error SignalRole(ctx context.Context, role role.Role) error
// SetVPNIP stores the internally used VPN IP in cloud provider metadata (if supported and required for autoscaling by the CSP, otherwise does nothing). // SetVPNIP stores the internally used VPN IP in cloud provider metadata (if supported and required for autoscaling by the CSP, otherwise does nothing).
@ -46,121 +33,19 @@ type ProviderMetadata interface {
Supported() bool Supported() bool
} }
// CloudControllerManager implementers provide configuration for the k8s cloud-controller-manager.
type CloudControllerManager interface {
// Image returns the container image used to provide cloud-controller-manager for the cloud-provider.
Image() string
// Path returns the path used by cloud-controller-manager executable within the container image.
Path() string
// Name returns the cloud-provider name as used by k8s cloud-controller-manager (k8s.gcr.io/cloud-controller-manager).
Name() string
// ExtraArgs returns a list of arguments to append to the cloud-controller-manager command.
ExtraArgs() []string
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
ConfigMaps(instance Instance) (resources.ConfigMaps, error)
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
Secrets(instance Instance, cloudServiceAccountURI string) (resources.Secrets, error)
// Volumes returns a list of volumes to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/storage/volumes/ .
Volumes() []k8s.Volume
// VolumeMounts a list of of volume mounts to deploy together with the k8s cloud-controller-manager.
VolumeMounts() []k8s.VolumeMount
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cloud-controller-manager.
Env() []k8s.EnvVar
// PrepareInstance is called on every instance before deploying the cloud-controller-manager.
// Allows for cloud-provider specific hooks.
PrepareInstance(instance Instance, vpnIP string) error
// Supported is used to determine if cloud controller manager is implemented for this cloud provider.
Supported() bool
}
// CloudNodeManager implementers provide configuration for the k8s cloud-node-manager.
type CloudNodeManager interface {
// Image returns the container image used to provide cloud-node-manager for the cloud-provider.
Image() string
// Path returns the path used by cloud-node-manager executable within the container image.
Path() string
// ExtraArgs returns a list of arguments to append to the cloud-node-manager command.
ExtraArgs() []string
// Supported is used to determine if cloud node manager is implemented for this cloud provider.
Supported() bool
}
// ClusterAutoscaler implementers provide configuration for the k8s cluster-autoscaler.
type ClusterAutoscaler interface {
// Name returns the cloud-provider name as used by k8s cluster-autoscaler.
Name() string
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
Secrets(instance Instance, cloudServiceAccountURI string) (resources.Secrets, error)
// Volumes returns a list of volumes to deploy together with the k8s cluster-autoscaler.
Volumes() []k8s.Volume
// VolumeMounts returns a list of volume mounts to deploy together with the k8s cluster-autoscaler.
VolumeMounts() []k8s.VolumeMount
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cluster-autoscaler.
Env() []k8s.EnvVar
// Supported is used to determine if cluster autoscaler is implemented for this cloud provider.
Supported() bool
}
// CoordinatorEndpoints retrieves a list of constellation coordinator endpoint candidates from the cloud provider API.
func CoordinatorEndpoints(ctx context.Context, metadata ProviderMetadata) ([]string, error) {
if !metadata.Supported() {
return nil, errors.New("retrieving instances list from cloud provider is not yet supported")
}
instances, err := metadata.List(ctx)
if err != nil {
return nil, fmt.Errorf("retrieving instances list from cloud provider failed: %w", err)
}
coordinatorEndpoints := []string{}
for _, instance := range instances {
// check if role of instance is "Coordinator"
if instance.Role == role.Coordinator {
for _, ip := range instance.IPs {
coordinatorEndpoints = append(coordinatorEndpoints, net.JoinHostPort(ip, coordinatorPort))
}
}
}
return coordinatorEndpoints, nil
}
// PrepareInstanceForCCM sets the vpn IP in cloud provider metadata.
func PrepareInstanceForCCM(ctx context.Context, metadata ProviderMetadata, cloudControllerManager CloudControllerManager, vpnIP string) error {
if err := metadata.SetVPNIP(ctx, vpnIP); err != nil {
return fmt.Errorf("setting VPN IP for cloud-controller-manager failed: %w", err)
}
instance, err := metadata.Self(ctx)
if err != nil {
return fmt.Errorf("retrieving instance metadata for cloud-controller-manager failed: %w", err)
}
return cloudControllerManager.PrepareInstance(instance, vpnIP)
}
type ProviderMetadataFake struct{} type ProviderMetadataFake struct{}
func (f *ProviderMetadataFake) List(ctx context.Context) ([]Instance, error) { func (f *ProviderMetadataFake) List(ctx context.Context) ([]cloudtypes.Instance, error) {
self, err := f.Self(ctx) self, err := f.Self(ctx)
return []Instance{self}, err return []cloudtypes.Instance{self}, err
} }
func (f *ProviderMetadataFake) Self(ctx context.Context) (Instance, error) { func (f *ProviderMetadataFake) Self(ctx context.Context) (cloudtypes.Instance, error) {
return Instance{ return cloudtypes.Instance{
Name: "instanceName", Name: "instanceName",
ProviderID: "fake://instance-id", ProviderID: "fake://instance-id",
Role: role.Unknown, Role: role.Unknown,
IPs: []string{"192.0.2.1"}, PrivateIPs: []string{"192.0.2.1"},
}, nil
}
func (f *ProviderMetadataFake) GetInstance(ctx context.Context, providerID string) (Instance, error) {
return Instance{
Name: "instanceName",
ProviderID: providerID,
Role: role.Unknown,
IPs: []string{"192.0.2.1"},
}, nil }, nil
} }
@ -176,96 +61,24 @@ func (f *ProviderMetadataFake) Supported() bool {
return true return true
} }
type CloudControllerManagerFake struct{} // CoordinatorEndpoints retrieves a list of constellation coordinator endpoint candidates from the cloud provider API.
func CoordinatorEndpoints(ctx context.Context, metadata ProviderMetadata) ([]string, error) {
if !metadata.Supported() {
return nil, errors.New("retrieving instances list from cloud provider is not yet supported")
}
instances, err := metadata.List(ctx)
if err != nil {
return nil, fmt.Errorf("retrieving instances list from cloud provider failed: %w", err)
}
coordinatorEndpoints := []string{}
for _, instance := range instances {
// check if role of instance is "Coordinator"
if instance.Role == role.Coordinator {
for _, ip := range instance.PrivateIPs {
coordinatorEndpoints = append(coordinatorEndpoints, net.JoinHostPort(ip, coordinatorPort))
}
}
}
func (f *CloudControllerManagerFake) Image() string { return coordinatorEndpoints, nil
return "fake-image:latest"
}
func (f *CloudControllerManagerFake) Path() string {
return "/fake-controller-manager"
}
func (f *CloudControllerManagerFake) Name() string {
return "fake"
}
func (f *CloudControllerManagerFake) ExtraArgs() []string {
return []string{}
}
func (f *CloudControllerManagerFake) ConfigMaps(instance Instance) (resources.ConfigMaps, error) {
return []*k8s.ConfigMap{}, nil
}
func (f *CloudControllerManagerFake) Secrets(instance Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return []*k8s.Secret{}, nil
}
func (f *CloudControllerManagerFake) Volumes() []k8s.Volume {
return []k8s.Volume{}
}
func (f *CloudControllerManagerFake) VolumeMounts() []k8s.VolumeMount {
return []k8s.VolumeMount{}
}
func (f *CloudControllerManagerFake) Env() []k8s.EnvVar {
return []k8s.EnvVar{}
}
func (f *CloudControllerManagerFake) PrepareInstance(instance Instance, vpnIP string) error {
return nil
}
func (f *CloudControllerManagerFake) Supported() bool {
return false
}
type CloudNodeManagerFake struct{}
func (f *CloudNodeManagerFake) Image() string {
return "fake-image:latest"
}
func (f *CloudNodeManagerFake) Path() string {
return "/fake-cloud-node-manager"
}
func (f *CloudNodeManagerFake) ExtraArgs() []string {
return []string{}
}
func (f *CloudNodeManagerFake) Supported() bool {
return false
}
type ClusterAutoscalerFake struct{}
func (f *ClusterAutoscalerFake) Name() string {
return "fake"
}
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (f *ClusterAutoscalerFake) Secrets(instance Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil
}
// Volumes returns a list of volumes to deploy together with the k8s cluster-autoscaler.
func (f *ClusterAutoscalerFake) Volumes() []k8s.Volume {
return []k8s.Volume{}
}
// VolumeMounts returns a list of volume mounts to deploy together with the k8s cluster-autoscaler.
func (f *ClusterAutoscalerFake) VolumeMounts() []k8s.VolumeMount {
return []k8s.VolumeMount{}
}
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cluster-autoscaler.
func (f *ClusterAutoscalerFake) Env() []k8s.EnvVar {
return []k8s.EnvVar{}
}
func (f *ClusterAutoscalerFake) Supported() bool {
return false
} }

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -20,18 +21,18 @@ func TestCoordinatorEndpoints(t *testing.T) {
}{ }{
"getting coordinator endpoints works and role is checked": { "getting coordinator endpoints works and role is checked": {
metadata: stubMetadata{ metadata: stubMetadata{
listRes: []Instance{ listRes: []cloudtypes.Instance{
{ {
Name: "someInstanceA", Name: "someInstanceA",
Role: role.Coordinator, Role: role.Coordinator,
ProviderID: "provider://somePath/someInstanceA", ProviderID: "provider://somePath/someInstanceA",
IPs: []string{"192.0.2.1"}, PrivateIPs: []string{"192.0.2.1"},
}, },
{ {
Name: "someInstanceB", Name: "someInstanceB",
Role: role.Node, Role: role.Node,
ProviderID: "provider://somePath/someInstanceB", ProviderID: "provider://somePath/someInstanceB",
IPs: []string{"192.0.2.2"}, PrivateIPs: []string{"192.0.2.2"},
}, },
}, },
supportedRes: true, supportedRes: true,
@ -70,64 +71,27 @@ func TestCoordinatorEndpoints(t *testing.T) {
} }
} }
func TestPrepareInstanceForCCM(t *testing.T) {
err := errors.New("some err")
testCases := map[string]struct {
metadata stubMetadata
vpnIP string
wantErr bool
}{
"updating role works": {
metadata: stubMetadata{},
vpnIP: "192.0.2.1",
wantErr: false,
},
"setting VPN IP fails": {
metadata: stubMetadata{
setVPNIPErr: err,
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
err := PrepareInstanceForCCM(context.Background(), &tc.metadata, &CloudControllerManagerFake{}, tc.vpnIP)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
})
}
}
type stubMetadata struct { type stubMetadata struct {
listRes []Instance listRes []cloudtypes.Instance
listErr error listErr error
selfRes Instance selfRes cloudtypes.Instance
selfErr error selfErr error
getInstanceRes Instance getInstanceRes cloudtypes.Instance
getInstanceErr error getInstanceErr error
signalRoleErr error signalRoleErr error
setVPNIPErr error setVPNIPErr error
supportedRes bool supportedRes bool
} }
func (m *stubMetadata) List(ctx context.Context) ([]Instance, error) { func (m *stubMetadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
return m.listRes, m.listErr return m.listRes, m.listErr
} }
func (m *stubMetadata) Self(ctx context.Context) (Instance, error) { func (m *stubMetadata) Self(ctx context.Context) (cloudtypes.Instance, error) {
return m.selfRes, m.selfErr return m.selfRes, m.selfErr
} }
func (m *stubMetadata) GetInstance(ctx context.Context, providerID string) (Instance, error) { func (m *stubMetadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
return m.getInstanceRes, m.getInstanceErr return m.getInstanceRes, m.getInstanceErr
} }
@ -142,3 +106,11 @@ func (m *stubMetadata) SetVPNIP(ctx context.Context, vpnIP string) error {
func (m *stubMetadata) Supported() bool { func (m *stubMetadata) Supported() bool {
return m.supportedRes return m.supportedRes
} }
func (m *stubMetadata) GetSubnetworkCIDR(ctx context.Context) (string, error) {
return "", nil
}
func (m *stubMetadata) GetLoadBalancerIP(ctx context.Context) (string, error) {
return "", nil
}

View File

@ -2,11 +2,8 @@ package core
import ( import (
"context" "context"
"strings"
"time" "time"
"github.com/edgelesssys/constellation/coordinator/kubernetes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/constants" "github.com/edgelesssys/constellation/internal/constants"
"go.uber.org/zap" "go.uber.org/zap"
@ -14,93 +11,24 @@ import (
) )
// GetK8sJoinArgs returns the args needed by a Node to join the cluster. // GetK8sJoinArgs returns the args needed by a Node to join the cluster.
func (c *Core) GetK8sJoinArgs() (*kubeadm.BootstrapTokenDiscovery, error) { func (c *Core) GetK8sJoinArgs(ctx context.Context) (*kubeadm.BootstrapTokenDiscovery, error) {
return c.kube.GetJoinToken(constants.KubernetesJoinTokenTTL) return c.kube.GetJoinToken(ctx, constants.KubernetesJoinTokenTTL)
} }
// GetK8SCertificateKey returns the key needed by a Coordinator to join the cluster. // GetK8SCertificateKey returns the key needed by a Coordinator to join the cluster.
func (c *Core) GetK8SCertificateKey() (string, error) { func (c *Core) GetK8SCertificateKey(ctx context.Context) (string, error) {
return c.kube.GetKubeadmCertificateKey() return c.kube.GetKubeadmCertificateKey(ctx)
} }
// InitCluster initializes the cluster, stores the join args, and returns the kubeconfig. // InitCluster initializes the cluster, stores the join args, and returns the kubeconfig.
func (c *Core) InitCluster(autoscalingNodeGroups []string, cloudServiceAccountURI string, masterSecret []byte) ([]byte, error) { func (c *Core) InitCluster(ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI string, masterSecret []byte) ([]byte, error) {
var nodeName string
var providerID string
var instance Instance
var ccmConfigMaps resources.ConfigMaps
var ccmSecrets resources.Secrets
var caSecrets resources.Secrets
var err error
nodeIP := coordinatorVPNIP.String()
if c.metadata.Supported() {
instance, err = c.metadata.Self(context.TODO())
if err != nil {
c.zaplogger.Error("Retrieving own instance metadata failed", zap.Error(err))
return nil, err
}
nodeName = instance.Name
providerID = instance.ProviderID
if len(instance.IPs) > 0 {
nodeIP = instance.IPs[0]
}
} else {
nodeName = coordinatorVPNIP.String()
}
if c.cloudControllerManager.Supported() && c.metadata.Supported() {
c.zaplogger.Info("Preparing node for cloud-controller-manager")
if err := PrepareInstanceForCCM(context.TODO(), c.metadata, c.cloudControllerManager, coordinatorVPNIP.String()); err != nil {
c.zaplogger.Error("Preparing node for CCM failed", zap.Error(err))
return nil, err
}
ccmConfigMaps, err = c.cloudControllerManager.ConfigMaps(instance)
if err != nil {
c.zaplogger.Error("Defining ConfigMaps for CCM failed", zap.Error(err))
return nil, err
}
ccmSecrets, err = c.cloudControllerManager.Secrets(instance, cloudServiceAccountURI)
if err != nil {
c.zaplogger.Error("Defining Secrets for CCM failed", zap.Error(err))
return nil, err
}
}
if c.clusterAutoscaler.Supported() {
caSecrets, err = c.clusterAutoscaler.Secrets(instance, cloudServiceAccountURI)
if err != nil {
c.zaplogger.Error("Defining Secrets for cluster-autoscaler failed", zap.Error(err))
return nil, err
}
}
c.zaplogger.Info("Initializing cluster") c.zaplogger.Info("Initializing cluster")
if err := c.kube.InitCluster(kubernetes.InitClusterInput{ vpnIP, err := c.GetVPNIP()
APIServerAdvertiseIP: coordinatorVPNIP.String(), if err != nil {
NodeIP: nodeIP, c.zaplogger.Error("Retrieving vpn ip failed", zap.Error(err))
NodeName: k8sCompliantHostname(nodeName), return nil, err
ProviderID: providerID, }
SupportClusterAutoscaler: c.clusterAutoscaler.Supported(), if err := c.kube.InitCluster(ctx, autoscalingNodeGroups, cloudServiceAccountURI, vpnIP, masterSecret); err != nil {
AutoscalingCloudprovider: c.clusterAutoscaler.Name(),
AutoscalingSecrets: caSecrets,
AutoscalingVolumes: c.clusterAutoscaler.Volumes(),
AutoscalingVolumeMounts: c.clusterAutoscaler.VolumeMounts(),
AutoscalingEnv: c.clusterAutoscaler.Env(),
AutoscalingNodeGroups: autoscalingNodeGroups,
SupportsCloudControllerManager: c.cloudControllerManager.Supported(),
CloudControllerManagerName: c.cloudControllerManager.Name(),
CloudControllerManagerImage: c.cloudControllerManager.Image(),
CloudControllerManagerPath: c.cloudControllerManager.Path(),
CloudControllerManagerExtraArgs: c.cloudControllerManager.ExtraArgs(),
CloudControllerManagerConfigMaps: ccmConfigMaps,
CloudControllerManagerSecrets: ccmSecrets,
CloudControllerManagerVolumes: c.cloudControllerManager.Volumes(),
CloudControllerManagerVolumeMounts: c.cloudControllerManager.VolumeMounts(),
CloudControllerManagerEnv: c.cloudControllerManager.Env(),
SupportsCloudNodeManager: c.cloudNodeManager.Supported(),
CloudNodeManagerImage: c.cloudNodeManager.Image(),
CloudNodeManagerPath: c.cloudNodeManager.Path(),
CloudNodeManagerExtraArgs: c.cloudNodeManager.ExtraArgs(),
MasterSecret: masterSecret,
}); err != nil {
c.zaplogger.Error("Initializing cluster failed", zap.Error(err)) c.zaplogger.Error("Initializing cluster failed", zap.Error(err))
return nil, err return nil, err
} }
@ -125,41 +53,16 @@ func (c *Core) InitCluster(autoscalingNodeGroups []string, cloudServiceAccountUR
} }
// JoinCluster lets a Node join the cluster. // JoinCluster lets a Node join the cluster.
func (c *Core) JoinCluster(args *kubeadm.BootstrapTokenDiscovery, certKey string, peerRole role.Role) error { func (c *Core) JoinCluster(ctx context.Context, args *kubeadm.BootstrapTokenDiscovery, certKey string, peerRole role.Role) error {
c.zaplogger.Info("Joining Kubernetes cluster") c.zaplogger.Info("Joining Kubernetes cluster")
nodeVPNIP, err := c.vpn.GetInterfaceIP() nodeVPNIP, err := c.vpn.GetInterfaceIP()
if err != nil { if err != nil {
c.zaplogger.Error("Retrieving vpn ip failed", zap.Error(err)) c.zaplogger.Error("Retrieving vpn ip failed", zap.Error(err))
return err return err
} }
var nodeName string
var providerID string
nodeIP := nodeVPNIP
if c.metadata.Supported() {
instance, err := c.metadata.Self(context.TODO())
if err != nil {
c.zaplogger.Error("Retrieving own instance metadata failed", zap.Error(err))
return err
}
providerID = instance.ProviderID
nodeName = instance.Name
if len(instance.IPs) > 0 {
nodeIP = instance.IPs[0]
}
} else {
nodeName = nodeVPNIP
}
if c.cloudControllerManager.Supported() && c.metadata.Supported() {
c.zaplogger.Info("Preparing node for cloud-controller-manager")
if err := PrepareInstanceForCCM(context.TODO(), c.metadata, c.cloudControllerManager, nodeVPNIP); err != nil {
c.zaplogger.Error("Preparing node for CCM failed", zap.Error(err))
return err
}
}
c.zaplogger.Info("k8s Join data", zap.String("nodename", nodeName), zap.String("nodeIP", nodeIP), zap.String("nodeVPNIP", nodeVPNIP), zap.String("provid", providerID))
// we need to pass the VPNIP for another control-plane, otherwise etcd will bind itself to the wrong IP address and fails // we need to pass the VPNIP for another control-plane, otherwise etcd will bind itself to the wrong IP address and fails
if err := c.kube.JoinCluster(args, k8sCompliantHostname(nodeName), nodeIP, nodeVPNIP, providerID, certKey, c.cloudControllerManager.Supported(), peerRole); err != nil { if err := c.kube.JoinCluster(ctx, args, nodeVPNIP, certKey, peerRole); err != nil {
c.zaplogger.Error("Joining Kubernetes cluster failed", zap.Error(err)) c.zaplogger.Error("Joining Kubernetes cluster failed", zap.Error(err))
return err return err
} }
@ -177,15 +80,15 @@ func (c *Core) JoinCluster(args *kubeadm.BootstrapTokenDiscovery, certKey string
// Cluster manages the overall cluster lifecycle (init, join). // Cluster manages the overall cluster lifecycle (init, join).
type Cluster interface { type Cluster interface {
// InitCluster bootstraps a new cluster with the current node being the master, returning the arguments required to join the cluster. // InitCluster bootstraps a new cluster with the current node being the master, returning the arguments required to join the cluster.
InitCluster(kubernetes.InitClusterInput) error InitCluster(ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI, vpnIP string, masterSecret []byte) error
// JoinCluster will join the current node to an existing cluster. // JoinCluster will join the current node to an existing cluster.
JoinCluster(args *kubeadm.BootstrapTokenDiscovery, nodeName, nodeIP, nodeVPNIP, providerID, certKey string, ccmSupported bool, peerRole role.Role) error JoinCluster(ctx context.Context, args *kubeadm.BootstrapTokenDiscovery, nodeVPNIP, certKey string, peerRole role.Role) error
// GetKubeconfig reads the kubeconfig from the filesystem. Only succeeds after cluster is initialized. // GetKubeconfig reads the kubeconfig from the filesystem. Only succeeds after cluster is initialized.
GetKubeconfig() ([]byte, error) GetKubeconfig() ([]byte, error)
// GetKubeadmCertificateKey returns the 64-byte hex string key needed to join the cluster as control-plane. This function must be executed on a control-plane. // GetKubeadmCertificateKey returns the 64-byte hex string key needed to join the cluster as control-plane. This function must be executed on a control-plane.
GetKubeadmCertificateKey() (string, error) GetKubeadmCertificateKey(ctx context.Context) (string, error)
// GetJoinToken returns a bootstrap (join) token. // GetJoinToken returns a bootstrap (join) token.
GetJoinToken(ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) GetJoinToken(ctx context.Context, ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error)
// StartKubelet starts the kubelet service. // StartKubelet starts the kubelet service.
StartKubelet() error StartKubelet() error
} }
@ -194,12 +97,12 @@ type Cluster interface {
type ClusterFake struct{} type ClusterFake struct{}
// InitCluster fakes bootstrapping a new cluster with the current node being the master, returning the arguments required to join the cluster. // InitCluster fakes bootstrapping a new cluster with the current node being the master, returning the arguments required to join the cluster.
func (c *ClusterFake) InitCluster(kubernetes.InitClusterInput) error { func (c *ClusterFake) InitCluster(ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI, vpnIP string, masterSecret []byte) error {
return nil return nil
} }
// JoinCluster will fake joining the current node to an existing cluster. // JoinCluster will fake joining the current node to an existing cluster.
func (c *ClusterFake) JoinCluster(args *kubeadm.BootstrapTokenDiscovery, nodeName, nodeIP, nodeVPNIP, providerID, certKey string, _ bool, _ role.Role) error { func (c *ClusterFake) JoinCluster(ctx context.Context, args *kubeadm.BootstrapTokenDiscovery, nodeVPNIP, certKey string, peerRole role.Role) error {
return nil return nil
} }
@ -209,12 +112,12 @@ func (c *ClusterFake) GetKubeconfig() ([]byte, error) {
} }
// GetKubeadmCertificateKey fakes generating a certificateKey. // GetKubeadmCertificateKey fakes generating a certificateKey.
func (c *ClusterFake) GetKubeadmCertificateKey() (string, error) { func (c *ClusterFake) GetKubeadmCertificateKey(context.Context) (string, error) {
return "controlPlaneCertficateKey", nil return "controlPlaneCertficateKey", nil
} }
// GetJoinToken returns a bootstrap (join) token. // GetJoinToken returns a bootstrap (join) token.
func (c *ClusterFake) GetJoinToken(_ time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) { func (c *ClusterFake) GetJoinToken(ctx context.Context, _ time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) {
return &kubeadm.BootstrapTokenDiscovery{ return &kubeadm.BootstrapTokenDiscovery{
APIServerEndpoint: "0.0.0.0", APIServerEndpoint: "0.0.0.0",
Token: "kube-fake-token", Token: "kube-fake-token",
@ -226,12 +129,3 @@ func (c *ClusterFake) GetJoinToken(_ time.Duration) (*kubeadm.BootstrapTokenDisc
func (c *ClusterFake) StartKubelet() error { func (c *ClusterFake) StartKubelet() error {
return nil return nil
} }
// k8sCompliantHostname transforms a hostname to an RFC 1123 compliant, lowercase subdomain as required by Kubernetes node names.
// The following regex is used by k8s for validation: /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/ .
// Only a simple heuristic is used for now (to lowercase, replace underscores).
func k8sCompliantHostname(in string) string {
hostname := strings.ToLower(in)
hostname = strings.ReplaceAll(hostname, "_", "-")
return hostname
}

View File

@ -1,14 +1,12 @@
package core package core
import ( import (
"context"
"errors" "errors"
"regexp"
"testing" "testing"
"time" "time"
"github.com/edgelesssys/constellation/coordinator/attestation/simulator" "github.com/edgelesssys/constellation/coordinator/attestation/simulator"
"github.com/edgelesssys/constellation/coordinator/kubernetes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/deploy/user" "github.com/edgelesssys/constellation/internal/deploy/user"
"github.com/edgelesssys/constellation/internal/file" "github.com/edgelesssys/constellation/internal/file"
@ -16,165 +14,65 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap" "go.uber.org/zap"
k8s "k8s.io/api/core/v1"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
) )
func TestInitCluster(t *testing.T) { func TestInitCluster(t *testing.T) {
someErr := errors.New("someErr") someErr := errors.New("someErr")
kubeconfigContent := []byte("kubeconfig")
testMS := []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8} testMS := []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}
testCases := map[string]struct { testCases := map[string]struct {
cluster clusterStub cluster Cluster
metadata stubMetadata vpn VPN
cloudControllerManager stubCloudControllerManager metadata ProviderMetadata
cloudNodeManager stubCloudNodeManager masterSecret []byte
clusterAutoscaler stubClusterAutoscaler autoscalingNodeGroups []string
masterSecret []byte wantErr bool
autoscalingNodeGroups []string
wantErr bool
wantInitClusterInput kubernetes.InitClusterInput
}{ }{
"InitCluster works": { "InitCluster works": {
cluster: clusterStub{ cluster: &clusterStub{
kubeconfig: []byte("kubeconfig"), kubeconfig: kubeconfigContent,
}, },
vpn: &stubVPN{interfaceIP: "192.0.2.1"},
metadata: &stubMetadata{supportedRes: true},
autoscalingNodeGroups: []string{"someNodeGroup"},
},
"InitCluster works even if signal role fails": {
cluster: &clusterStub{
kubeconfig: kubeconfigContent,
},
vpn: &stubVPN{interfaceIP: "192.0.2.1"},
metadata: &stubMetadata{supportedRes: true, signalRoleErr: someErr},
autoscalingNodeGroups: []string{"someNodeGroup"}, autoscalingNodeGroups: []string{"someNodeGroup"},
masterSecret: testMS, masterSecret: testMS,
wantInitClusterInput: kubernetes.InitClusterInput{
APIServerAdvertiseIP: "10.118.0.1",
NodeIP: "10.118.0.1",
NodeName: "10.118.0.1",
SupportsCloudControllerManager: false,
SupportClusterAutoscaler: false,
AutoscalingNodeGroups: []string{"someNodeGroup"},
MasterSecret: testMS,
},
wantErr: false,
}, },
"Instance metadata is retrieved": { "cannot get VPN IP": {
cluster: clusterStub{ cluster: &clusterStub{
kubeconfig: []byte("kubeconfig"), kubeconfig: kubeconfigContent,
}, },
masterSecret: testMS, vpn: &stubVPN{getInterfaceIPErr: someErr},
metadata: stubMetadata{
selfRes: Instance{
Name: "some-name",
ProviderID: "fake://providerid",
},
supportedRes: true,
},
wantInitClusterInput: kubernetes.InitClusterInput{
APIServerAdvertiseIP: "10.118.0.1",
NodeIP: "10.118.0.1",
NodeName: "some-name",
ProviderID: "fake://providerid",
SupportsCloudControllerManager: false,
SupportClusterAutoscaler: false,
MasterSecret: testMS,
},
wantErr: false,
},
"metadata of self retrieval error is checked": {
cluster: clusterStub{
kubeconfig: []byte("kubeconfig"),
},
metadata: stubMetadata{
supportedRes: true,
selfErr: errors.New("metadata retrieval error"),
},
wantErr: true,
},
"Autoscaler is prepared when supported": {
cluster: clusterStub{
kubeconfig: []byte("kubeconfig"),
},
clusterAutoscaler: stubClusterAutoscaler{
nameRes: "some-name",
supportedRes: true,
},
masterSecret: testMS,
autoscalingNodeGroups: []string{"someNodeGroup"}, autoscalingNodeGroups: []string{"someNodeGroup"},
wantInitClusterInput: kubernetes.InitClusterInput{ wantErr: true,
APIServerAdvertiseIP: "10.118.0.1",
NodeIP: "10.118.0.1",
NodeName: "10.118.0.1",
SupportsCloudControllerManager: false,
SupportClusterAutoscaler: true,
AutoscalingCloudprovider: "some-name",
AutoscalingNodeGroups: []string{"someNodeGroup"},
MasterSecret: testMS,
},
wantErr: false,
}, },
"Node is prepared for CCM if supported": { "cannot init kubernetes": {
cluster: clusterStub{ cluster: &clusterStub{
kubeconfig: []byte("kubeconfig"),
},
masterSecret: testMS,
cloudControllerManager: stubCloudControllerManager{
supportedRes: true,
nameRes: "some-name",
imageRes: "someImage",
pathRes: "/some/path",
},
wantInitClusterInput: kubernetes.InitClusterInput{
APIServerAdvertiseIP: "10.118.0.1",
NodeIP: "10.118.0.1",
NodeName: "10.118.0.1",
SupportsCloudControllerManager: true,
SupportClusterAutoscaler: false,
CloudControllerManagerName: "some-name",
CloudControllerManagerImage: "someImage",
CloudControllerManagerPath: "/some/path",
MasterSecret: testMS,
},
wantErr: false,
},
"Node preparation for CCM can fail": {
cluster: clusterStub{
kubeconfig: []byte("kubeconfig"),
},
metadata: stubMetadata{
supportedRes: true,
},
cloudControllerManager: stubCloudControllerManager{
supportedRes: true,
nameRes: "some-name",
imageRes: "someImage",
pathRes: "/some/path",
prepareInstanceRes: errors.New("preparing node for CCM failed"),
},
wantErr: true,
},
"updating role fails without error": {
cluster: clusterStub{
kubeconfig: []byte("kubeconfig"),
},
masterSecret: testMS,
metadata: stubMetadata{
signalRoleErr: errors.New("updating role fails"),
supportedRes: true,
},
wantErr: false,
wantInitClusterInput: kubernetes.InitClusterInput{
APIServerAdvertiseIP: "10.118.0.1",
NodeIP: "10.118.0.1",
MasterSecret: testMS,
},
},
"getting kubeconfig fail detected": {
cluster: clusterStub{
getKubeconfigErr: errors.New("getting kubeconfig fails"),
},
wantErr: true,
},
"InitCluster fail detected": {
cluster: clusterStub{
initErr: someErr, initErr: someErr,
}, },
wantErr: true, vpn: &stubVPN{interfaceIP: "192.0.2.1"},
metadata: &stubMetadata{supportedRes: true},
autoscalingNodeGroups: []string{"someNodeGroup"},
wantErr: true,
},
"cannot get kubeconfig": {
cluster: &clusterStub{
getKubeconfigErr: someErr,
},
vpn: &stubVPN{interfaceIP: "192.0.2.1"},
metadata: &stubMetadata{supportedRes: true},
autoscalingNodeGroups: []string{"someNodeGroup"},
wantErr: true,
}, },
} }
@ -186,19 +84,17 @@ func TestInitCluster(t *testing.T) {
zapLogger, err := zap.NewDevelopment() zapLogger, err := zap.NewDevelopment()
require.NoError(err) require.NoError(err)
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&stubVPN{}, &tc.cluster, &tc.metadata, &tc.cloudControllerManager, &tc.cloudNodeManager, &tc.clusterAutoscaler, nil, zapLogger, simulator.OpenSimulatedTPM, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(tc.vpn, tc.cluster, tc.metadata, nil, zapLogger, simulator.OpenSimulatedTPM, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
kubeconfig, err := core.InitCluster(tc.autoscalingNodeGroups, "cloud-service-account-uri", tc.masterSecret) kubeconfig, err := core.InitCluster(context.Background(), tc.autoscalingNodeGroups, "cloud-service-account-uri", tc.masterSecret)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
} }
require.NoError(err) require.NoError(err)
require.Len(tc.cluster.initInputs, 1) assert.Equal(kubeconfigContent, kubeconfig)
assert.Equal(tc.wantInitClusterInput, tc.cluster.initInputs[0])
assert.Equal(tc.cluster.kubeconfig, kubeconfig)
}) })
} }
} }
@ -207,90 +103,38 @@ func TestJoinCluster(t *testing.T) {
someErr := errors.New("someErr") someErr := errors.New("someErr")
testCases := map[string]struct { testCases := map[string]struct {
cluster clusterStub cluster Cluster
metadata stubMetadata metadata ProviderMetadata
cloudControllerManager stubCloudControllerManager vpn VPN
cloudNodeManager stubCloudNodeManager wantErr bool
clusterAutoscaler stubClusterAutoscaler
vpn stubVPN
wantErr bool
wantJoinClusterArgs joinClusterArgs
}{ }{
"JoinCluster works": { "JoinCluster works": {
vpn: stubVPN{ vpn: &stubVPN{
interfaceIP: "192.0.2.0", interfaceIP: "192.0.2.0",
}, },
wantJoinClusterArgs: joinClusterArgs{ cluster: &clusterStub{},
args: &kubeadm.BootstrapTokenDiscovery{ metadata: &stubMetadata{supportedRes: true},
APIServerEndpoint: "192.0.2.0:6443",
Token: "someToken",
CACertHashes: []string{"someHash"},
},
nodeName: "192.0.2.0",
nodeIP: "192.0.2.0",
},
}, },
"JoinCluster fail detected": { "JoinCluster works even if signal role fails": {
cluster: clusterStub{ vpn: &stubVPN{
joinErr: someErr, interfaceIP: "192.0.2.0",
}, },
wantErr: true, cluster: &clusterStub{},
metadata: &stubMetadata{supportedRes: true, signalRoleErr: someErr},
}, },
"retrieving vpn ip failure detected": { "cannot get VPN IP": {
vpn: stubVPN{ vpn: &stubVPN{getInterfaceIPErr: someErr},
getInterfaceIPErr: errors.New("retrieving interface ip error"), cluster: &clusterStub{},
}, metadata: &stubMetadata{supportedRes: true},
wantErr: true, wantErr: true,
}, },
"Instance metadata is retrieved": { "joining kuberentes fails": {
metadata: stubMetadata{ vpn: &stubVPN{
selfRes: Instance{ interfaceIP: "192.0.2.0",
Name: "some-name",
ProviderID: "fake://providerid",
},
supportedRes: true,
},
wantJoinClusterArgs: joinClusterArgs{
args: &kubeadm.BootstrapTokenDiscovery{
APIServerEndpoint: "192.0.2.0:6443",
Token: "someToken",
CACertHashes: []string{"someHash"},
},
nodeName: "some-name",
providerID: "fake://providerid",
},
wantErr: false,
},
"Instance metadata retrieval can fail": {
metadata: stubMetadata{
supportedRes: true,
selfErr: errors.New("metadata retrieval error"),
},
wantErr: true,
},
"CCM preparation failure is detected": {
metadata: stubMetadata{
supportedRes: true,
},
cloudControllerManager: stubCloudControllerManager{
supportedRes: true,
prepareInstanceRes: errors.New("ccm prepare fails"),
},
wantErr: true,
},
"updating role fails without error": {
metadata: stubMetadata{
signalRoleErr: errors.New("updating role fails"),
supportedRes: true,
},
wantErr: false,
wantJoinClusterArgs: joinClusterArgs{
args: &kubeadm.BootstrapTokenDiscovery{
APIServerEndpoint: "192.0.2.0:6443",
Token: "someToken",
CACertHashes: []string{"someHash"},
},
}, },
cluster: &clusterStub{joinErr: someErr},
metadata: &stubMetadata{supportedRes: true},
wantErr: true,
}, },
} }
@ -302,7 +146,7 @@ func TestJoinCluster(t *testing.T) {
zapLogger, err := zap.NewDevelopment() zapLogger, err := zap.NewDevelopment()
require.NoError(err) require.NoError(err)
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&tc.vpn, &tc.cluster, &tc.metadata, &tc.cloudControllerManager, &tc.cloudNodeManager, &tc.clusterAutoscaler, nil, zapLogger, simulator.OpenSimulatedTPM, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(tc.vpn, tc.cluster, tc.metadata, nil, zapLogger, simulator.OpenSimulatedTPM, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
joinReq := &kubeadm.BootstrapTokenDiscovery{ joinReq := &kubeadm.BootstrapTokenDiscovery{
@ -310,47 +154,13 @@ func TestJoinCluster(t *testing.T) {
Token: "someToken", Token: "someToken",
CACertHashes: []string{"someHash"}, CACertHashes: []string{"someHash"},
} }
err = core.JoinCluster(joinReq, "", role.Node) err = core.JoinCluster(context.Background(), joinReq, "", role.Node)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
} }
require.NoError(err) require.NoError(err)
require.Len(tc.cluster.joinClusterArgs, 1)
assert.Equal(tc.wantJoinClusterArgs, tc.cluster.joinClusterArgs[0])
})
}
}
func TestK8sCompliantHostname(t *testing.T) {
compliantHostname := regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
testCases := map[string]struct {
hostname string
wantHostname string
}{
"azure scale set names work": {
hostname: "constellation-scale-set-coordinators-name_0",
wantHostname: "constellation-scale-set-coordinators-name-0",
},
"compliant hostname is not modified": {
hostname: "abcd-123",
wantHostname: "abcd-123",
},
"uppercase hostnames are lowercased": {
hostname: "ABCD",
wantHostname: "abcd",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
hostname := k8sCompliantHostname(tc.hostname)
assert.Equal(tc.wantHostname, hostname)
assert.Regexp(compliantHostname, hostname)
}) })
} }
} }
@ -364,24 +174,20 @@ type clusterStub struct {
getJoinTokenErr error getJoinTokenErr error
startKubeletErr error startKubeletErr error
initInputs []kubernetes.InitClusterInput inAutoscalingNodeGroups []string
joinClusterArgs []joinClusterArgs inCloudServiceAccountURI string
inVpnIP string
} }
func (c *clusterStub) InitCluster(in kubernetes.InitClusterInput) error { func (c *clusterStub) InitCluster(ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI string, vpnIP string, masterSecret []byte) error {
c.initInputs = append(c.initInputs, in) c.inAutoscalingNodeGroups = autoscalingNodeGroups
c.inCloudServiceAccountURI = cloudServiceAccountURI
c.inVpnIP = vpnIP
return c.initErr return c.initErr
} }
func (c *clusterStub) JoinCluster(args *kubeadm.BootstrapTokenDiscovery, nodeName, nodeIP, nodeVPNIP, providerID, certKey string, _ bool, _ role.Role) error { func (c *clusterStub) JoinCluster(ctx context.Context, args *kubeadm.BootstrapTokenDiscovery, nodeVPNIP string, certKey string, peerRole role.Role) error {
c.joinClusterArgs = append(c.joinClusterArgs, joinClusterArgs{
args: args,
nodeName: nodeName,
nodeIP: nodeIP,
providerID: providerID,
})
return c.joinErr return c.joinErr
} }
@ -389,149 +195,14 @@ func (c *clusterStub) GetKubeconfig() ([]byte, error) {
return c.kubeconfig, c.getKubeconfigErr return c.kubeconfig, c.getKubeconfigErr
} }
func (c *clusterStub) GetKubeadmCertificateKey() (string, error) { func (c *clusterStub) GetKubeadmCertificateKey(context.Context) (string, error) {
return "dummy", nil return "dummy", nil
} }
func (c *clusterStub) GetJoinToken(ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) { func (c *clusterStub) GetJoinToken(ctx context.Context, ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) {
return c.getJoinTokenResponse, c.getJoinTokenErr return c.getJoinTokenResponse, c.getJoinTokenErr
} }
func (c *clusterStub) StartKubelet() error { func (c *clusterStub) StartKubelet() error {
return c.startKubeletErr return c.startKubeletErr
} }
type prepareInstanceRequest struct {
instance Instance
vpnIP string
}
type stubCloudControllerManager struct {
imageRes string
pathRes string
nameRes string
prepareInstanceRes error
extraArgsRes []string
configMapsRes resources.ConfigMaps
configMapsErr error
secretsRes resources.Secrets
secretsErr error
volumesRes []k8s.Volume
volumeMountRes []k8s.VolumeMount
envRes []k8s.EnvVar
supportedRes bool
prepareInstanceRequests []prepareInstanceRequest
}
func (s *stubCloudControllerManager) Image() string {
return s.imageRes
}
func (s *stubCloudControllerManager) Path() string {
return s.pathRes
}
func (s *stubCloudControllerManager) Name() string {
return s.nameRes
}
func (s *stubCloudControllerManager) PrepareInstance(instance Instance, vpnIP string) error {
s.prepareInstanceRequests = append(s.prepareInstanceRequests, prepareInstanceRequest{
instance: instance,
vpnIP: vpnIP,
})
return s.prepareInstanceRes
}
func (s *stubCloudControllerManager) ExtraArgs() []string {
return s.extraArgsRes
}
func (s *stubCloudControllerManager) ConfigMaps(instance Instance) (resources.ConfigMaps, error) {
return s.configMapsRes, s.configMapsErr
}
func (s *stubCloudControllerManager) Secrets(instance Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return s.secretsRes, s.secretsErr
}
func (s *stubCloudControllerManager) Volumes() []k8s.Volume {
return s.volumesRes
}
func (s *stubCloudControllerManager) VolumeMounts() []k8s.VolumeMount {
return s.volumeMountRes
}
func (s *stubCloudControllerManager) Env() []k8s.EnvVar {
return s.envRes
}
func (s *stubCloudControllerManager) Supported() bool {
return s.supportedRes
}
type stubCloudNodeManager struct {
imageRes string
pathRes string
extraArgsRes []string
supportedRes bool
}
func (s *stubCloudNodeManager) Image() string {
return s.imageRes
}
func (s *stubCloudNodeManager) Path() string {
return s.pathRes
}
func (s *stubCloudNodeManager) ExtraArgs() []string {
return s.extraArgsRes
}
func (s *stubCloudNodeManager) Supported() bool {
return s.supportedRes
}
type stubClusterAutoscaler struct {
nameRes string
supportedRes bool
secretsRes resources.Secrets
secretsErr error
volumesRes []k8s.Volume
volumeMountRes []k8s.VolumeMount
envRes []k8s.EnvVar
}
func (s *stubClusterAutoscaler) Name() string {
return s.nameRes
}
func (s *stubClusterAutoscaler) Secrets(instance Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return s.secretsRes, s.secretsErr
}
func (s *stubClusterAutoscaler) Volumes() []k8s.Volume {
return s.volumesRes
}
func (s *stubClusterAutoscaler) VolumeMounts() []k8s.VolumeMount {
return s.volumeMountRes
}
func (s *stubClusterAutoscaler) Env() []k8s.EnvVar {
return s.envRes
}
func (s *stubClusterAutoscaler) Supported() bool {
return s.supportedRes
}
type joinClusterArgs struct {
args *kubeadm.BootstrapTokenDiscovery
nodeName string
nodeIP string
providerID string
}

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/netip"
"sync" "sync"
"time" "time"
@ -25,8 +24,6 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
) )
var coordinatorVPNIP = netip.AddrFrom4([4]byte{10, 118, 0, 1})
type Core struct { type Core struct {
state state.State state state.State
openTPM vtpm.TPMOpenFunc openTPM vtpm.TPMOpenFunc
@ -35,9 +32,6 @@ type Core struct {
vpn VPN vpn VPN
kube Cluster kube Cluster
metadata ProviderMetadata metadata ProviderMetadata
cloudControllerManager CloudControllerManager
cloudNodeManager CloudNodeManager
clusterAutoscaler ClusterAutoscaler
encryptedDisk EncryptedDisk encryptedDisk EncryptedDisk
kms kms.CloudKMS kms kms.CloudKMS
zaplogger *zap.Logger zaplogger *zap.Logger
@ -50,8 +44,7 @@ type Core struct {
// NewCore creates and initializes a new Core object. // NewCore creates and initializes a new Core object.
func NewCore(vpn VPN, kube Cluster, func NewCore(vpn VPN, kube Cluster,
metadata ProviderMetadata, cloudControllerManager CloudControllerManager, cloudNodeManager CloudNodeManager, clusterAutoscaler ClusterAutoscaler, metadata ProviderMetadata, encryptedDisk EncryptedDisk, zapLogger *zap.Logger, openTPM vtpm.TPMOpenFunc, persistentStoreFactory PersistentStoreFactory, fileHandler file.Handler, linuxUserManager user.LinuxUserManager,
encryptedDisk EncryptedDisk, zapLogger *zap.Logger, openTPM vtpm.TPMOpenFunc, persistentStoreFactory PersistentStoreFactory, fileHandler file.Handler, linuxUserManager user.LinuxUserManager,
) (*Core, error) { ) (*Core, error) {
stor := store.NewStdStore() stor := store.NewStdStore()
c := &Core{ c := &Core{
@ -60,9 +53,6 @@ func NewCore(vpn VPN, kube Cluster,
vpn: vpn, vpn: vpn,
kube: kube, kube: kube,
metadata: metadata, metadata: metadata,
cloudNodeManager: cloudNodeManager,
cloudControllerManager: cloudControllerManager,
clusterAutoscaler: clusterAutoscaler,
encryptedDisk: encryptedDisk, encryptedDisk: encryptedDisk,
zaplogger: zapLogger, zaplogger: zapLogger,
kms: nil, // KMS is set up during init phase kms: nil, // KMS is set up during init phase

View File

@ -38,7 +38,7 @@ func TestGetNextNodeIP(t *testing.T) {
require := require.New(t) require := require.New(t)
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&stubVPN{}, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
require.NoError(core.InitializeStoreIPs()) require.NoError(core.InitializeStoreIPs())
@ -82,7 +82,7 @@ func TestSwitchToPersistentStore(t *testing.T) {
storeFactory := &fakeStoreFactory{} storeFactory := &fakeStoreFactory{}
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, storeFactory, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&stubVPN{}, nil, nil, nil, zaptest.NewLogger(t), nil, storeFactory, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(core.store.Put("test", []byte("test"))) require.NoError(core.store.Put("test", []byte("test")))
require.NoError(err) require.NoError(err)
@ -97,7 +97,7 @@ func TestGetIDs(t *testing.T) {
require := require.New(t) require := require.New(t)
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&stubVPN{}, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
_, _, err = core.GetIDs(nil) _, _, err = core.GetIDs(nil)
@ -122,7 +122,7 @@ func TestNotifyNodeHeartbeat(t *testing.T) {
require := require.New(t) require := require.New(t)
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&stubVPN{}, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
const ip = "192.0.2.1" const ip = "192.0.2.1"
@ -136,7 +136,7 @@ func TestDeriveKey(t *testing.T) {
require := require.New(t) require := require.New(t)
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&stubVPN{}, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
// error when no kms is set up // error when no kms is set up
@ -214,7 +214,7 @@ func TestInitialize(t *testing.T) {
VPNPrivKey: []byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, VPNPrivKey: []byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7},
}).ToFile(fileHandler)) }).ToFile(fileHandler))
} }
core, err := NewCore(&stubVPN{}, &clusterStub{}, &ProviderMetadataFake{}, nil, nil, nil, nil, zaptest.NewLogger(t), openTPM, &fakeStoreFactory{}, fileHandler, user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&stubVPN{}, &clusterStub{}, &ProviderMetadataFake{}, nil, zaptest.NewLogger(t), openTPM, &fakeStoreFactory{}, fileHandler, user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
core.initialVPNPeersRetriever = fakeInitializeVPNPeersRetriever core.initialVPNPeersRetriever = fakeInitializeVPNPeersRetriever
// prepare store to emulate initialized KMS // prepare store to emulate initialized KMS
@ -272,7 +272,7 @@ func TestPersistNodeState(t *testing.T) {
require.NoError(err) require.NoError(err)
require.NoError(file.Close()) require.NoError(file.Close())
} }
core, err := NewCore(tc.vpn, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, fileHandler, user.NewLinuxUserManagerFake(fs)) core, err := NewCore(tc.vpn, nil, nil, nil, zaptest.NewLogger(t), nil, nil, fileHandler, user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
err = core.PersistNodeState(role.Coordinator, "192.0.2.1", []byte("owner-id"), []byte("cluster-id")) err = core.PersistNodeState(role.Coordinator, "192.0.2.1", []byte("owner-id"), []byte("cluster-id"))
if tc.wantErr { if tc.wantErr {

View File

@ -45,7 +45,7 @@ func TestGetDiskUUID(t *testing.T) {
uuid: tc.wantUUID, uuid: tc.wantUUID,
} }
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, &diskStub, zapLogger, nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&stubVPN{}, nil, nil, &diskStub, zapLogger, nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
uuid, err := core.GetDiskUUID() uuid, err := core.GetDiskUUID()
if tc.wantErr { if tc.wantErr {
@ -88,7 +88,7 @@ func TestUpdateDiskPassphrase(t *testing.T) {
updatePassphraseErr: tc.updatePassphraseErr, updatePassphraseErr: tc.updatePassphraseErr,
} }
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, &diskStub, zapLogger, nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&stubVPN{}, nil, nil, &diskStub, zapLogger, nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
err = core.UpdateDiskPassphrase("passphrase") err = core.UpdateDiskPassphrase("passphrase")
if tc.wantErr { if tc.wantErr {

View File

@ -124,16 +124,13 @@ func newMockCoreWithDialer(bufDialer *bufconnDialer) (*Core, *pubapi.API, error)
vpn := &stubVPN{} vpn := &stubVPN{}
kubeFake := &ClusterFake{} kubeFake := &ClusterFake{}
metadataFake := &ProviderMetadataFake{} metadataFake := &ProviderMetadataFake{}
ccmFake := &CloudControllerManagerFake{}
cnmFake := &CloudNodeManagerFake{}
autoscalerFake := &ClusterAutoscalerFake{}
encryptedDiskFake := &EncryptedDiskFake{} encryptedDiskFake := &EncryptedDiskFake{}
getPublicAddr := func() (string, error) { getPublicAddr := func() (string, error) {
return "192.0.2.1", nil return "192.0.2.1", nil
} }
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(vpn, kubeFake, metadataFake, ccmFake, cnmFake, autoscalerFake, encryptedDiskFake, zapLogger, simulator.OpenSimulatedTPM, &fakeStoreFactory{}, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(vpn, kubeFake, metadataFake, encryptedDiskFake, zapLogger, simulator.OpenSimulatedTPM, &fakeStoreFactory{}, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -55,7 +55,7 @@ func TestGetPeers(t *testing.T) {
require := require.New(t) require := require.New(t)
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&stubVPN{}, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
// prepare store // prepare store
@ -116,7 +116,7 @@ func TestAddPeer(t *testing.T) {
require := require.New(t) require := require.New(t)
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&tc.vpn, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&tc.vpn, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
err = core.AddPeer(tc.peer) err = core.AddPeer(tc.peer)

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/atls" "github.com/edgelesssys/constellation/coordinator/atls"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/peer" "github.com/edgelesssys/constellation/coordinator/peer"
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto" "github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
@ -71,7 +72,7 @@ func TestReinitializeAsNode(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
coordinators := []Instance{{IPs: []string{"192.0.2.1"}, Role: role.Coordinator}} coordinators := []cloudtypes.Instance{{PrivateIPs: []string{"192.0.2.1"}, Role: role.Coordinator}}
netDialer := testdialer.NewBufconnDialer() netDialer := testdialer.NewBufconnDialer()
dialer := grpcutil.NewDialer(&MockValidator{}, netDialer) dialer := grpcutil.NewDialer(&MockValidator{}, netDialer)
server := newPubAPIServer() server := newPubAPIServer()
@ -81,7 +82,7 @@ func TestReinitializeAsNode(t *testing.T) {
defer server.Stop() defer server.Stop()
vpn := &stubVPN{} vpn := &stubVPN{}
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(vpn, nil, &stubMetadata{listRes: coordinators, supportedRes: true}, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(vpn, nil, &stubMetadata{listRes: coordinators, supportedRes: true}, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
err = core.ReinitializeAsNode(context.Background(), dialer, vpnIP, &stubPubAPI{}, 0) err = core.ReinitializeAsNode(context.Background(), dialer, vpnIP, &stubPubAPI{}, 0)
@ -144,7 +145,7 @@ func TestReinitializeAsCoordinator(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
coordinators := []Instance{{IPs: []string{"192.0.2.1"}, Role: role.Coordinator}} coordinators := []cloudtypes.Instance{{PrivateIPs: []string{"192.0.2.1"}, Role: role.Coordinator}}
netDialer := testdialer.NewBufconnDialer() netDialer := testdialer.NewBufconnDialer()
dialer := grpcutil.NewDialer(&MockValidator{}, netDialer) dialer := grpcutil.NewDialer(&MockValidator{}, netDialer)
server := newPubAPIServer() server := newPubAPIServer()
@ -154,7 +155,7 @@ func TestReinitializeAsCoordinator(t *testing.T) {
defer server.Stop() defer server.Stop()
vpn := &stubVPN{} vpn := &stubVPN{}
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(vpn, nil, &stubMetadata{listRes: coordinators, supportedRes: true}, nil, nil, nil, nil, zaptest.NewLogger(t), nil, &fakeStoreFactory{}, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(vpn, nil, &stubMetadata{listRes: coordinators, supportedRes: true}, nil, zaptest.NewLogger(t), nil, &fakeStoreFactory{}, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
// prepare store to emulate initialized KMS // prepare store to emulate initialized KMS
require.NoError(core.data().PutKMSData(kms.KMSInformation{StorageUri: kms.NoStoreURI, KmsUri: kms.ClusterKMSURI})) require.NoError(core.data().PutKMSData(kms.KMSInformation{StorageUri: kms.NoStoreURI, KmsUri: kms.ClusterKMSURI}))
@ -224,10 +225,10 @@ func TestGetInitialVPNPeers(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
coordinators := func(ips []string) []Instance { coordinators := func(ips []string) []cloudtypes.Instance {
instances := []Instance{} instances := []cloudtypes.Instance{}
for _, ip := range ips { for _, ip := range ips {
instances = append(instances, Instance{IPs: []string{ip}, Role: role.Coordinator}) instances = append(instances, cloudtypes.Instance{PrivateIPs: []string{ip}, Role: role.Coordinator})
} }
return instances return instances
}(tc.coordinatorIPs) }(tc.coordinatorIPs)

View File

@ -67,7 +67,7 @@ func TestAdvanceState(t *testing.T) {
} }
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), openTPM, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs)) core, err := NewCore(&stubVPN{}, nil, nil, nil, zaptest.NewLogger(t), openTPM, nil, file.NewHandler(fs), user.NewLinuxUserManagerFake(fs))
require.NoError(err) require.NoError(err)
assert.Equal(state.Uninitialized, core.GetState()) assert.Equal(state.Uninitialized, core.GetState())
core.state = tc.initialState core.state = tc.initialState

View File

@ -0,0 +1,246 @@
package kubernetes
import (
"context"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/coordinator/role"
k8s "k8s.io/api/core/v1"
)
// ProviderMetadata implementers read/write cloud provider metadata.
type ProviderMetadata interface {
// List retrieves all instances belonging to the current Constellation.
List(ctx context.Context) ([]cloudtypes.Instance, error)
// Self retrieves the current instance.
Self(ctx context.Context) (cloudtypes.Instance, error)
// GetSubnetworkCIDR retrieves the subnetwork CIDR for the current instance.
GetSubnetworkCIDR(ctx context.Context) (string, error)
// SupportsLoadBalancer returns true if the cloud provider supports load balancers.
SupportsLoadBalancer() bool
// GetLoadBalancerIP retrieves the load balancer IP.
GetLoadBalancerIP(ctx context.Context) (string, error)
// GetInstance retrieves an instance using its providerID.
GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error)
// SignalRole signals the constellation role via cloud provider metadata (if supported by the CSP and deployment type, otherwise does nothing).
SignalRole(ctx context.Context, role role.Role) error
// SetVPNIP stores the internally used VPN IP in cloud provider metadata (if supported and required for autoscaling by the CSP, otherwise does nothing).
SetVPNIP(ctx context.Context, vpnIP string) error
// Supported is used to determine if metadata API is implemented for this cloud provider.
Supported() bool
}
// CloudControllerManager implementers provide configuration for the k8s cloud-controller-manager.
type CloudControllerManager interface {
// Image returns the container image used to provide cloud-controller-manager for the cloud-provider.
Image() string
// Path returns the path used by cloud-controller-manager executable within the container image.
Path() string
// Name returns the cloud-provider name as used by k8s cloud-controller-manager (k8s.gcr.io/cloud-controller-manager).
Name() string
// ExtraArgs returns a list of arguments to append to the cloud-controller-manager command.
ExtraArgs() []string
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
ConfigMaps(instance cloudtypes.Instance) (resources.ConfigMaps, error)
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error)
// Volumes returns a list of volumes to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/storage/volumes/ .
Volumes() []k8s.Volume
// VolumeMounts a list of of volume mounts to deploy together with the k8s cloud-controller-manager.
VolumeMounts() []k8s.VolumeMount
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cloud-controller-manager.
Env() []k8s.EnvVar
// Supported is used to determine if cloud controller manager is implemented for this cloud provider.
Supported() bool
}
// CloudNodeManager implementers provide configuration for the k8s cloud-node-manager.
type CloudNodeManager interface {
// Image returns the container image used to provide cloud-node-manager for the cloud-provider.
Image() string
// Path returns the path used by cloud-node-manager executable within the container image.
Path() string
// ExtraArgs returns a list of arguments to append to the cloud-node-manager command.
ExtraArgs() []string
// Supported is used to determine if cloud node manager is implemented for this cloud provider.
Supported() bool
}
// ClusterAutoscaler implementers provide configuration for the k8s cluster-autoscaler.
type ClusterAutoscaler interface {
// Name returns the cloud-provider name as used by k8s cluster-autoscaler.
Name() string
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error)
// Volumes returns a list of volumes to deploy together with the k8s cluster-autoscaler.
Volumes() []k8s.Volume
// VolumeMounts returns a list of volume mounts to deploy together with the k8s cluster-autoscaler.
VolumeMounts() []k8s.VolumeMount
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cluster-autoscaler.
Env() []k8s.EnvVar
// Supported is used to determine if cluster autoscaler is implemented for this cloud provider.
Supported() bool
}
type stubProviderMetadata struct {
GetLoadBalancerIPErr error
GetLoadBalancerIPResp string
GetSubnetworkCIDRErr error
GetSubnetworkCIDRResp string
ListErr error
ListResp []cloudtypes.Instance
SignalRoleErr error
SetVPNIPErr error
SelfErr error
SelfResp cloudtypes.Instance
GetInstanceErr error
GetInstanceResp cloudtypes.Instance
SupportedResp bool
SupportsLoadBalancerResp bool
}
func (m *stubProviderMetadata) GetLoadBalancerIP(ctx context.Context) (string, error) {
return m.GetLoadBalancerIPResp, m.GetLoadBalancerIPErr
}
func (m *stubProviderMetadata) GetSubnetworkCIDR(ctx context.Context) (string, error) {
return m.GetSubnetworkCIDRResp, m.GetSubnetworkCIDRErr
}
func (m *stubProviderMetadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
return m.ListResp, m.ListErr
}
func (m *stubProviderMetadata) Self(ctx context.Context) (cloudtypes.Instance, error) {
return m.SelfResp, m.SelfErr
}
func (m *stubProviderMetadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
return m.GetInstanceResp, m.GetInstanceErr
}
func (m *stubProviderMetadata) SignalRole(ctx context.Context, role role.Role) error {
return m.SignalRoleErr
}
func (m *stubProviderMetadata) SetVPNIP(ctx context.Context, vpnIP string) error {
return m.SetVPNIPErr
}
func (m *stubProviderMetadata) Supported() bool {
return m.SupportedResp
}
func (m *stubProviderMetadata) SupportsLoadBalancer() bool {
return m.SupportsLoadBalancerResp
}
type stubCloudControllerManager struct {
SupportedResp bool
}
func (m *stubCloudControllerManager) Image() string {
return "stub-image:latest"
}
func (m *stubCloudControllerManager) Path() string {
return "/stub-controller-manager"
}
func (m *stubCloudControllerManager) Name() string {
return "stub"
}
func (m *stubCloudControllerManager) ExtraArgs() []string {
return []string{}
}
func (m *stubCloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resources.ConfigMaps, error) {
return []*k8s.ConfigMap{}, nil
}
func (m *stubCloudControllerManager) Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return []*k8s.Secret{}, nil
}
func (m *stubCloudControllerManager) Volumes() []k8s.Volume {
return []k8s.Volume{}
}
func (m *stubCloudControllerManager) VolumeMounts() []k8s.VolumeMount {
return []k8s.VolumeMount{}
}
func (m *stubCloudControllerManager) Env() []k8s.EnvVar {
return []k8s.EnvVar{}
}
func (m *stubCloudControllerManager) Supported() bool {
return m.SupportedResp
}
type stubCloudNodeManager struct {
SupportedResp bool
ImageResp string
PathResp string
ExtraArgsResp []string
}
func (m *stubCloudNodeManager) Image() string {
return m.ImageResp
}
func (m *stubCloudNodeManager) Path() string {
return m.PathResp
}
func (m *stubCloudNodeManager) ExtraArgs() []string {
return m.ExtraArgsResp
}
func (m *stubCloudNodeManager) Supported() bool {
return m.SupportedResp
}
type stubClusterAutoscaler struct {
SupportedResp bool
}
func (a *stubClusterAutoscaler) Name() string {
return "stub"
}
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (a *stubClusterAutoscaler) Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil
}
// Volumes returns a list of volumes to deploy together with the k8s cluster-autoscaler.
func (a *stubClusterAutoscaler) Volumes() []k8s.Volume {
return []k8s.Volume{}
}
// VolumeMounts returns a list of volume mounts to deploy together with the k8s cluster-autoscaler.
func (a *stubClusterAutoscaler) VolumeMounts() []k8s.VolumeMount {
return []k8s.VolumeMount{}
}
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cluster-autoscaler.
func (a *stubClusterAutoscaler) Env() []k8s.EnvVar {
return []k8s.EnvVar{}
}
func (a *stubClusterAutoscaler) Supported() bool {
return a.SupportedResp
}

View File

@ -1,36 +0,0 @@
package kubernetes
import (
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1"
)
// InitClusterInput collects the arguments to initialize a new cluster.
type InitClusterInput struct {
APIServerAdvertiseIP string
NodeIP string
NodeName string
ProviderID string
SupportClusterAutoscaler bool
AutoscalingCloudprovider string
AutoscalingNodeGroups []string
AutoscalingSecrets resources.Secrets
AutoscalingVolumes []k8s.Volume
AutoscalingVolumeMounts []k8s.VolumeMount
AutoscalingEnv []k8s.EnvVar
SupportsCloudControllerManager bool
CloudControllerManagerName string
CloudControllerManagerImage string
CloudControllerManagerPath string
CloudControllerManagerExtraArgs []string
CloudControllerManagerConfigMaps resources.ConfigMaps
CloudControllerManagerSecrets resources.Secrets
CloudControllerManagerVolumes []k8s.Volume
CloudControllerManagerVolumeMounts []k8s.VolumeMount
CloudControllerManagerEnv []k8s.EnvVar
SupportsCloudNodeManager bool
CloudNodeManagerImage string
CloudNodeManagerPath string
CloudNodeManagerExtraArgs []string
MasterSecret []byte
}

View File

@ -104,7 +104,6 @@ func (c *CoreOSConfiguration) InitConfiguration(externalCloudProvider bool) Kube
"profiling": "false", "profiling": "false",
}, },
}, },
ControlPlaneEndpoint: "127.0.0.1:16443",
}, },
// warning: this config is applied to every node in the cluster! // warning: this config is applied to every node in the cluster!
KubeletConfiguration: kubeletconf.KubeletConfiguration{ KubeletConfiguration: kubeletconf.KubeletConfiguration{
@ -123,6 +122,18 @@ func (c *CoreOSConfiguration) InitConfiguration(externalCloudProvider bool) Kube
APIVersion: kubeletconf.SchemeGroupVersion.String(), APIVersion: kubeletconf.SchemeGroupVersion.String(),
Kind: "KubeletConfiguration", Kind: "KubeletConfiguration",
}, },
RegisterWithTaints: []corev1.Taint{
{
Key: "node.cloudprovider.kubernetes.io/uninitialized",
Value: "true",
Effect: corev1.TaintEffectPreferNoSchedule,
},
{
Key: "node.cilium.io/agent-not-ready",
Value: "true",
Effect: corev1.TaintEffectPreferNoSchedule,
},
},
}, },
} }
} }
@ -157,63 +168,6 @@ func (c *CoreOSConfiguration) JoinConfiguration(externalCloudProvider bool) Kube
} }
} }
type AWSConfiguration struct{}
func (a *AWSConfiguration) InitConfiguration() KubeadmInitYAML {
return KubeadmInitYAML{
InitConfiguration: kubeadm.InitConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: kubeadm.SchemeGroupVersion.String(),
Kind: "InitConfiguration",
},
NodeRegistration: kubeadm.NodeRegistrationOptions{
CRISocket: "/run/containerd/containerd.sock",
IgnorePreflightErrors: []string{"SystemVerification"},
},
LocalAPIEndpoint: kubeadm.APIEndpoint{BindPort: bindPort},
},
ClusterConfiguration: kubeadm.ClusterConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: kubeadm.SchemeGroupVersion.String(),
Kind: "ClusterConfiguration",
},
APIServer: kubeadm.APIServer{
CertSANs: []string{"10.118.0.1"},
},
},
KubeletConfiguration: kubeletconf.KubeletConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: kubeletconf.SchemeGroupVersion.String(),
Kind: "KubeletConfiguration",
},
},
}
}
func (a *AWSConfiguration) JoinConfiguration() KubeadmJoinYAML {
return KubeadmJoinYAML{
JoinConfiguration: kubeadm.JoinConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: kubeadm.SchemeGroupVersion.String(),
Kind: "JoinConfiguration",
},
NodeRegistration: kubeadm.NodeRegistrationOptions{
CRISocket: "/run/containerd/containerd.sock",
IgnorePreflightErrors: []string{"SystemVerification"},
},
Discovery: kubeadm.Discovery{
BootstrapToken: &kubeadm.BootstrapTokenDiscovery{},
},
},
KubeletConfiguration: kubeletconf.KubeletConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: kubeletconf.SchemeGroupVersion.String(),
Kind: "KubeletConfiguration",
},
},
}
}
type KubeadmJoinYAML struct { type KubeadmJoinYAML struct {
JoinConfiguration kubeadm.JoinConfiguration JoinConfiguration kubeadm.JoinConfiguration
KubeletConfiguration kubeletconf.KubeletConfiguration KubeletConfiguration kubeletconf.KubeletConfiguration
@ -276,10 +230,27 @@ func (k *KubeadmInitYAML) SetNodeName(nodeName string) {
k.InitConfiguration.NodeRegistration.Name = nodeName k.InitConfiguration.NodeRegistration.Name = nodeName
} }
// SetCertSANs sets the SANs for the certificate.
func (k *KubeadmInitYAML) SetCertSANs(certSANs []string) {
for _, certSAN := range certSANs {
if certSAN == "" {
continue
}
k.ClusterConfiguration.APIServer.CertSANs = append(k.ClusterConfiguration.APIServer.CertSANs, certSAN)
}
}
func (k *KubeadmInitYAML) SetApiServerAdvertiseAddress(apiServerAdvertiseAddress string) { func (k *KubeadmInitYAML) SetApiServerAdvertiseAddress(apiServerAdvertiseAddress string) {
k.InitConfiguration.LocalAPIEndpoint.AdvertiseAddress = apiServerAdvertiseAddress k.InitConfiguration.LocalAPIEndpoint.AdvertiseAddress = apiServerAdvertiseAddress
} }
// SetControlPlaneEndpoint sets the control plane endpoint if controlPlaneEndpoint is not empty.
func (k *KubeadmInitYAML) SetControlPlaneEndpoint(controlPlaneEndpoint string) {
if controlPlaneEndpoint != "" {
k.ClusterConfiguration.ControlPlaneEndpoint = controlPlaneEndpoint
}
}
func (k *KubeadmInitYAML) SetServiceCIDR(serviceCIDR string) { func (k *KubeadmInitYAML) SetServiceCIDR(serviceCIDR string) {
k.ClusterConfiguration.Networking.ServiceSubnet = serviceCIDR k.ClusterConfiguration.Networking.ServiceSubnet = serviceCIDR
} }

View File

@ -13,27 +13,11 @@ func TestMain(m *testing.M) {
} }
func TestInitConfiguration(t *testing.T) { func TestInitConfiguration(t *testing.T) {
awsConfig := AWSConfiguration{}
coreOSConfig := CoreOSConfiguration{} coreOSConfig := CoreOSConfiguration{}
testCases := map[string]struct { testCases := map[string]struct {
config KubeadmInitYAML config KubeadmInitYAML
}{ }{
"AWS init config can be created": {
config: awsConfig.InitConfiguration(),
},
"AWS init config with all fields can be created": {
config: func() KubeadmInitYAML {
c := awsConfig.InitConfiguration()
c.SetApiServerAdvertiseAddress("192.0.2.0")
c.SetNodeIP("192.0.2.0")
c.SetNodeName("node")
c.SetPodNetworkCIDR("10.244.0.0/16")
c.SetServiceCIDR("10.245.0.0/24")
c.SetProviderID("somecloudprovider://instance-id")
return c
}(),
},
"CoreOS init config can be created": { "CoreOS init config can be created": {
config: coreOSConfig.InitConfiguration(true), config: coreOSConfig.InitConfiguration(true),
}, },
@ -67,27 +51,11 @@ func TestInitConfiguration(t *testing.T) {
} }
func TestJoinConfiguration(t *testing.T) { func TestJoinConfiguration(t *testing.T) {
awsConfig := AWSConfiguration{}
coreOSConfig := CoreOSConfiguration{} coreOSConfig := CoreOSConfiguration{}
testCases := map[string]struct { testCases := map[string]struct {
config KubeadmJoinYAML config KubeadmJoinYAML
}{ }{
"AWS join config can be created": {
config: awsConfig.JoinConfiguration(),
},
"AWS join config with all fields can be created": {
config: func() KubeadmJoinYAML {
c := awsConfig.JoinConfiguration()
c.SetApiServerEndpoint("192.0.2.0:6443")
c.SetNodeIP("192.0.2.0")
c.SetNodeName("node")
c.SetToken("token")
c.AppendDiscoveryTokenCaCertHash("discovery-token-ca-cert-hash")
c.SetProviderID("somecloudprovider://instance-id")
return c
}(),
},
"CoreOS join config can be created": { "CoreOS join config can be created": {
config: coreOSConfig.JoinConfiguration(true), config: coreOSConfig.JoinConfiguration(true),
}, },

View File

@ -239,18 +239,13 @@ func TestGetObjects(t *testing.T) {
resourcesYAML string resourcesYAML string
wantErr bool wantErr bool
}{ }{
"GetObjects works on flannel deployment": {
wantResources: resources.NewDefaultFlannelDeployment(),
resourcesYAML: string(nginxDeplYAML),
wantErr: false,
},
"GetObjects works on cluster-autoscaler deployment": { "GetObjects works on cluster-autoscaler deployment": {
wantResources: resources.NewDefaultFlannelDeployment(), wantResources: resources.NewDefaultAutoscalerDeployment(nil, nil, nil),
resourcesYAML: string(nginxDeplYAML), resourcesYAML: string(nginxDeplYAML),
wantErr: false, wantErr: false,
}, },
"GetObjects works on cloud-controller-manager deployment": { "GetObjects works on cloud-controller-manager deployment": {
wantResources: resources.NewDefaultCloudControllerManagerDeployment("someProvider", "someImage", "somePath", nil, nil, nil, nil), wantResources: resources.NewDefaultCloudControllerManagerDeployment("someProvider", "someImage", "somePath", "someCIDR", nil, nil, nil, nil),
resourcesYAML: string(nginxDeplYAML), resourcesYAML: string(nginxDeplYAML),
wantErr: false, wantErr: false,
}, },

View File

@ -9,8 +9,6 @@ import (
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
const defaultCIDR = "10.244.0.0/16"
type cloudControllerManagerDeployment struct { type cloudControllerManagerDeployment struct {
ServiceAccount k8s.ServiceAccount ServiceAccount k8s.ServiceAccount
ClusterRoleBinding rbac.ClusterRoleBinding ClusterRoleBinding rbac.ClusterRoleBinding
@ -22,14 +20,12 @@ type cloudControllerManagerDeployment struct {
// https://kubernetes.io/docs/tasks/administer-cluster/running-cloud-controller/#cloud-controller-manager // https://kubernetes.io/docs/tasks/administer-cluster/running-cloud-controller/#cloud-controller-manager
// NewDefaultCloudControllerManagerDeployment creates a new *cloudControllerManagerDeployment, customized for the CSP. // NewDefaultCloudControllerManagerDeployment creates a new *cloudControllerManagerDeployment, customized for the CSP.
func NewDefaultCloudControllerManagerDeployment(cloudProvider, image, path string, extraArgs []string, extraVolumes []k8s.Volume, extraVolumeMounts []k8s.VolumeMount, env []k8s.EnvVar) *cloudControllerManagerDeployment { func NewDefaultCloudControllerManagerDeployment(cloudProvider, image, path, podCIDR string, extraArgs []string, extraVolumes []k8s.Volume, extraVolumeMounts []k8s.VolumeMount, env []k8s.EnvVar) *cloudControllerManagerDeployment {
command := []string{ command := []string{
path, path,
fmt.Sprintf("--cloud-provider=%s", cloudProvider), fmt.Sprintf("--cloud-provider=%s", cloudProvider),
"--leader-elect=true", "--leader-elect=true",
"--allocate-node-cidrs=false", fmt.Sprintf("--cluster-cidr=%s", podCIDR),
"--configure-cloud-routes=false",
fmt.Sprintf("--cluster-cidr=%s", defaultCIDR),
"-v=2", "-v=2",
} }
command = append(command, extraArgs...) command = append(command, extraArgs...)
@ -151,6 +147,10 @@ func NewDefaultCloudControllerManagerDeployment(cloudProvider, image, path strin
Key: "node-role.kubernetes.io/master", Key: "node-role.kubernetes.io/master",
Effect: k8s.TaintEffectNoSchedule, Effect: k8s.TaintEffectNoSchedule,
}, },
{
Key: "node.kubernetes.io/not-ready",
Effect: k8s.TaintEffectNoSchedule,
},
}, },
NodeSelector: map[string]string{ NodeSelector: map[string]string{
"node-role.kubernetes.io/master": "", "node-role.kubernetes.io/master": "",

View File

@ -12,7 +12,7 @@ func TestCloudControllerMarshalUnmarshal(t *testing.T) {
require := require.New(t) require := require.New(t)
assert := assert.New(t) assert := assert.New(t)
cloudControllerManagerDepl := NewDefaultCloudControllerManagerDeployment("dummy-cloudprovider", "some-image:latest", "/dummy_path", []string{}, []k8s.Volume{}, []k8s.VolumeMount{}, nil) cloudControllerManagerDepl := NewDefaultCloudControllerManagerDeployment("dummy-cloudprovider", "some-image:latest", "/dummy_path", "192.0.2.0/24", []string{}, []k8s.Volume{}, []k8s.VolumeMount{}, nil)
data, err := cloudControllerManagerDepl.Marshal() data, err := cloudControllerManagerDepl.Marshal()
require.NoError(err) require.NoError(err)

View File

@ -1,339 +0,0 @@
package resources
import (
"google.golang.org/protobuf/proto"
apps "k8s.io/api/apps/v1"
k8s "k8s.io/api/core/v1"
policy "k8s.io/api/policy/v1beta1"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/resource"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
cniConfJSON = `{"name":"cbr0","cniVersion":"0.3.1","plugins":[{"type":"flannel","delegate":{"hairpinMode":true,"isDefaultGateway":true}},{"type":"portmap","capabilities":{"portMappings":true}}]}`
netConfJSON = `{"Network":"10.244.0.0/16","Backend":{"Type":"vxlan"}}`
)
// Reference: https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
// Changes compared to the reference: added the wireguard interface "wg0" to the args of the "kube-flannel" container of the DaemonSet.
type FlannelDeployment struct {
PodSecurityPolicy policy.PodSecurityPolicy
ClusterRole rbac.ClusterRole
ClusterRoleBinding rbac.ClusterRoleBinding
ServiceAccount k8s.ServiceAccount
ConfigMap k8s.ConfigMap
DaemonSet apps.DaemonSet
}
func NewDefaultFlannelDeployment() *FlannelDeployment {
return &FlannelDeployment{
PodSecurityPolicy: policy.PodSecurityPolicy{
TypeMeta: v1.TypeMeta{
APIVersion: "policy/v1beta1",
Kind: "PodSecurityPolicy",
},
ObjectMeta: v1.ObjectMeta{
Name: "psp.flannel.unprivileged",
Annotations: map[string]string{
"seccomp.security.alpha.kubernetes.io/allowedProfileNames": "docker/default",
"seccomp.security.alpha.kubernetes.io/defaultProfileName": "docker/default",
"apparmor.security.beta.kubernetes.io/allowedProfileNames": "runtime/default",
"apparmor.security.beta.kubernetes.io/defaultProfileName": "runtime/default",
},
},
Spec: policy.PodSecurityPolicySpec{
Privileged: false,
Volumes: []policy.FSType{
policy.FSType("configMap"),
policy.FSType("secret"),
policy.FSType("emptyDir"),
policy.FSType("hostPath"),
},
AllowedHostPaths: []policy.AllowedHostPath{
{PathPrefix: "/etc/cni/net.d"},
{PathPrefix: "/etc/kube-flannel"},
{PathPrefix: "/run/flannel"},
},
ReadOnlyRootFilesystem: false,
RunAsUser: policy.RunAsUserStrategyOptions{
Rule: policy.RunAsUserStrategyRunAsAny,
},
SupplementalGroups: policy.SupplementalGroupsStrategyOptions{
Rule: policy.SupplementalGroupsStrategyRunAsAny,
},
FSGroup: policy.FSGroupStrategyOptions{
Rule: policy.FSGroupStrategyRunAsAny,
},
AllowPrivilegeEscalation: proto.Bool(false),
DefaultAllowPrivilegeEscalation: proto.Bool(false),
AllowedCapabilities: []k8s.Capability{
k8s.Capability("NET_ADMIN"),
k8s.Capability("NET_RAW"),
},
HostPID: false,
HostIPC: false,
HostNetwork: true,
HostPorts: []policy.HostPortRange{
{Min: 0, Max: 65535},
},
SELinux: policy.SELinuxStrategyOptions{
Rule: policy.SELinuxStrategyRunAsAny,
},
},
},
ClusterRole: rbac.ClusterRole{
TypeMeta: v1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1",
Kind: "ClusterRole",
},
ObjectMeta: v1.ObjectMeta{
Name: "flannel",
},
Rules: []rbac.PolicyRule{
{
APIGroups: []string{"extensions"},
Resources: []string{"podsecuritypolicies"},
Verbs: []string{"use"},
ResourceNames: []string{"psp.flannel.unprivileged"},
},
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get"},
},
{
APIGroups: []string{""},
Resources: []string{"nodes"},
Verbs: []string{"list", "watch"},
},
{
APIGroups: []string{""},
Resources: []string{"nodes/status"},
Verbs: []string{"patch"},
},
},
},
ClusterRoleBinding: rbac.ClusterRoleBinding{
TypeMeta: v1.TypeMeta{
Kind: "ClusterRoleBinding",
APIVersion: "rbac.authorization.k8s.io/v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "flannel",
},
RoleRef: rbac.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "flannel",
},
Subjects: []rbac.Subject{
{
Kind: "ServiceAccount",
Name: "flannel",
Namespace: "kube-system",
},
},
},
ServiceAccount: k8s.ServiceAccount{
TypeMeta: v1.TypeMeta{
APIVersion: "v1",
Kind: "ServiceAccount",
},
ObjectMeta: v1.ObjectMeta{
Name: "flannel",
Namespace: "kube-system",
},
},
ConfigMap: k8s.ConfigMap{
TypeMeta: v1.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "kube-flannel-cfg",
Namespace: "kube-system",
Labels: map[string]string{
"tier": "node",
"app": "flannel",
},
},
Data: map[string]string{
"cni-conf.json": cniConfJSON,
"net-conf.json": netConfJSON,
},
},
DaemonSet: apps.DaemonSet{
TypeMeta: v1.TypeMeta{
APIVersion: "apps/v1",
Kind: "DaemonSet",
},
ObjectMeta: v1.ObjectMeta{
Name: "kube-flannel-ds",
Namespace: "kube-system",
Labels: map[string]string{
"tier": "node",
"app": "flannel",
},
},
Spec: apps.DaemonSetSpec{
Selector: &v1.LabelSelector{
MatchLabels: map[string]string{"app": "flannel"},
},
Template: k8s.PodTemplateSpec{
ObjectMeta: v1.ObjectMeta{
Labels: map[string]string{
"tier": "node",
"app": "flannel",
},
},
Spec: k8s.PodSpec{
Affinity: &k8s.Affinity{
NodeAffinity: &k8s.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &k8s.NodeSelector{
NodeSelectorTerms: []k8s.NodeSelectorTerm{
{MatchExpressions: []k8s.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: k8s.NodeSelectorOpIn,
Values: []string{"linux"},
},
}},
},
},
},
},
HostNetwork: true,
PriorityClassName: "system-node-critical",
Tolerations: []k8s.Toleration{
{
Operator: k8s.TolerationOpExists,
Effect: k8s.TaintEffectNoSchedule,
},
},
ServiceAccountName: "flannel",
InitContainers: []k8s.Container{
{
Name: "install-cni-plugin",
Image: "rancher/mirrored-flannelcni-flannel-cni-plugin:v1.0.0",
Command: []string{"cp"},
Args: []string{"-f", "/flannel", "/opt/cni/bin/flannel"},
VolumeMounts: []k8s.VolumeMount{
{
Name: "cni-plugin",
MountPath: "/opt/cni/bin",
},
},
},
{
Name: "install-cni",
Image: "quay.io/coreos/flannel:v0.15.1",
Command: []string{"cp"},
Args: []string{"-f", "/etc/kube-flannel/cni-conf.json", "/etc/cni/net.d/10-flannel.conflist"},
VolumeMounts: []k8s.VolumeMount{
{
Name: "cni",
MountPath: "/etc/cni/net.d",
},
{
Name: "flannel-cfg",
MountPath: "/etc/kube-flannel/",
},
},
},
},
Containers: []k8s.Container{
{
Name: "kube-flannel",
Image: "quay.io/coreos/flannel:v0.15.1",
Command: []string{"/opt/bin/flanneld"},
Args: []string{"--ip-masq", "--kube-subnet-mgr", "--iface", "wg0"},
Resources: k8s.ResourceRequirements{
Requests: k8s.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("50Mi"),
},
Limits: k8s.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("50Mi"),
},
},
SecurityContext: &k8s.SecurityContext{
Privileged: proto.Bool(false),
Capabilities: &k8s.Capabilities{
Add: []k8s.Capability{k8s.Capability("NET_ADMIN"), k8s.Capability("NET_RAW")},
},
},
Env: []k8s.EnvVar{
{
Name: "POD_NAME",
ValueFrom: &k8s.EnvVarSource{
FieldRef: &k8s.ObjectFieldSelector{FieldPath: "metadata.name"},
},
},
{
Name: "POD_NAMESPACE",
ValueFrom: &k8s.EnvVarSource{
FieldRef: &k8s.ObjectFieldSelector{FieldPath: "metadata.namespace"},
},
},
},
VolumeMounts: []k8s.VolumeMount{
{
Name: "run",
MountPath: "/run/flannel",
},
{
Name: "flannel-cfg",
MountPath: "/etc/kube-flannel/",
},
},
},
},
Volumes: []k8s.Volume{
{
Name: "run",
VolumeSource: k8s.VolumeSource{
HostPath: &k8s.HostPathVolumeSource{
Path: "/run/flannel",
},
},
},
{
Name: "cni-plugin",
VolumeSource: k8s.VolumeSource{
HostPath: &k8s.HostPathVolumeSource{
Path: "/opt/cni/bin",
},
},
},
{
Name: "cni",
VolumeSource: k8s.VolumeSource{
HostPath: &k8s.HostPathVolumeSource{
Path: "/etc/cni/net.d",
},
},
},
{
Name: "flannel-cfg",
VolumeSource: k8s.VolumeSource{
ConfigMap: &k8s.ConfigMapVolumeSource{
LocalObjectReference: k8s.LocalObjectReference{
Name: "kube-flannel-cfg",
},
},
},
},
},
},
},
},
},
}
}
func (f *FlannelDeployment) Marshal() ([]byte, error) {
return MarshalK8SResources(f)
}

View File

@ -1,21 +0,0 @@
package resources
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFlannelDeployment(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
flannelDeployment := NewDefaultFlannelDeployment()
data, err := flannelDeployment.Marshal()
require.NoError(err)
var recreated FlannelDeployment
require.NoError(UnmarshalK8SResources(data, &recreated))
assert.Equal(flannelDeployment, &recreated)
}

View File

@ -20,6 +20,11 @@ const (
kubeletStartTimeout = 10 * time.Minute kubeletStartTimeout = 10 * time.Minute
) )
var (
kubernetesKeyRegexp = regexp.MustCompile("[a-f0-9]{64}")
providerIDRegex = regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachineScaleSets/([^/]+)/virtualMachines/([^/]+)$`)
)
// Client provides the functionality of `kubectl apply`. // Client provides the functionality of `kubectl apply`.
type Client interface { type Client interface {
Apply(resources resources.Marshaler, forceConflicts bool) error Apply(resources resources.Marshaler, forceConflicts bool) error
@ -27,21 +32,6 @@ type Client interface {
// TODO: add tolerations // TODO: add tolerations
} }
type ClusterUtil interface {
InstallComponents(ctx context.Context, version string) error
InitCluster(initConfig []byte) error
JoinCluster(joinConfig []byte) error
SetupPodNetwork(kubectl Client, podNetworkConfiguration resources.Marshaler) error
SetupAutoscaling(kubectl Client, clusterAutoscalerConfiguration resources.Marshaler, secrets resources.Marshaler) error
SetupCloudControllerManager(kubectl Client, cloudControllerManagerConfiguration resources.Marshaler, configMaps resources.Marshaler, secrets resources.Marshaler) error
SetupCloudNodeManager(kubectl Client, cloudNodeManagerConfiguration resources.Marshaler) error
SetupKMS(kubectl Client, kmsConfiguration resources.Marshaler) error
StartKubelet() error
RestartKubelet() error
GetControlPlaneJoinCertificateKey() (string, error)
CreateJoinToken(ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error)
}
// KubernetesUtil provides low level management of the kubernetes cluster. // KubernetesUtil provides low level management of the kubernetes cluster.
type KubernetesUtil struct { type KubernetesUtil struct {
inst installer inst installer
@ -68,7 +58,7 @@ func (k *KubernetesUtil) InstallComponents(ctx context.Context, version string)
return enableSystemdUnit(ctx, kubeletServiceEtcPath) return enableSystemdUnit(ctx, kubeletServiceEtcPath)
} }
func (k *KubernetesUtil) InitCluster(initConfig []byte) error { func (k *KubernetesUtil) InitCluster(ctx context.Context, initConfig []byte) error {
// TODO: audit policy should be user input // TODO: audit policy should be user input
auditPolicy, err := resources.NewDefaultAuditPolicy().Marshal() auditPolicy, err := resources.NewDefaultAuditPolicy().Marshal()
if err != nil { if err != nil {
@ -88,7 +78,7 @@ func (k *KubernetesUtil) InitCluster(initConfig []byte) error {
return fmt.Errorf("writing kubeadm init yaml config %v failed: %w", initConfigFile.Name(), err) return fmt.Errorf("writing kubeadm init yaml config %v failed: %w", initConfigFile.Name(), err)
} }
cmd := exec.Command(kubeadmPath, "init", "--config", initConfigFile.Name()) cmd := exec.CommandContext(ctx, kubeadmPath, "init", "--config", initConfigFile.Name())
_, err = cmd.Output() _, err = cmd.Output()
if err != nil { if err != nil {
var exitErr *exec.ExitError var exitErr *exec.ExitError
@ -100,18 +90,84 @@ func (k *KubernetesUtil) InitCluster(initConfig []byte) error {
return nil return nil
} }
// SetupPodNetwork sets up the flannel pod network. type SetupPodNetworkInput struct {
func (k *KubernetesUtil) SetupPodNetwork(kubectl Client, podNetworkConfiguration resources.Marshaler) error { CloudProvider string
if err := kubectl.Apply(podNetworkConfiguration, true); err != nil { NodeName string
FirstNodePodCIDR string
SubnetworkPodCIDR string
ProviderID string
}
// SetupPodNetwork sets up the cilium pod network.
func (k *KubernetesUtil) SetupPodNetwork(ctx context.Context, in SetupPodNetworkInput) error {
switch in.CloudProvider {
case "gcp":
return k.setupGCPPodNetwork(ctx, in.NodeName, in.FirstNodePodCIDR, in.SubnetworkPodCIDR)
case "azure":
return k.setupAzurePodNetwork(ctx, in.ProviderID, in.SubnetworkPodCIDR)
case "qemu":
return k.setupQemuPodNetwork(ctx)
default:
return fmt.Errorf("unsupported cloud provider %q", in.CloudProvider)
}
}
func (k *KubernetesUtil) setupAzurePodNetwork(ctx context.Context, providerID, subnetworkPodCIDR string) error {
matches := providerIDRegex.FindStringSubmatch(providerID)
if len(matches) != 5 {
return fmt.Errorf("error splitting providerID %q", providerID)
}
ciliumInstall := exec.CommandContext(ctx, "cilium", "install", "--azure-resource-group", matches[2], "--encryption", "wireguard", "--ipam", "azure",
"--helm-set",
"tunnel=disabled,enableIPv4Masquerade=true,azure.enabled=true,debug.enabled=true,ipv4NativeRoutingCIDR="+subnetworkPodCIDR+
",endpointRoutes.enabled=true,encryption.enabled=true,encryption.type=wireguard,l7Proxy=false,egressMasqueradeInterfaces=eth0")
ciliumInstall.Env = append(os.Environ(), "KUBECONFIG="+kubeConfig)
out, err := ciliumInstall.CombinedOutput()
if err != nil {
err = errors.New(string(out))
return err
}
return nil
}
func (k *KubernetesUtil) setupGCPPodNetwork(ctx context.Context, nodeName, nodePodCIDR, subnetworkPodCIDR string) error {
out, err := exec.CommandContext(ctx, kubectlPath, "--kubeconfig", kubeConfig, "patch", "node", nodeName, "-p", "{\"spec\":{\"podCIDR\": \""+nodePodCIDR+"\"}}").CombinedOutput()
if err != nil {
err = errors.New(string(out))
return err return err
} }
// allow coredns to run on uninitialized nodes (required by cloud-controller-manager) // allow coredns to run on uninitialized nodes (required by cloud-controller-manager)
err := exec.Command(kubectlPath, "--kubeconfig", kubeConfig, "-n", "kube-system", "patch", "deployment", "coredns", "--type", "json", "-p", "[{\"op\":\"add\",\"path\":\"/spec/template/spec/tolerations/-\",\"value\":{\"key\":\"node.cloudprovider.kubernetes.io/uninitialized\",\"value\":\"true\",\"effect\":\"NoSchedule\"}}]").Run() err = exec.CommandContext(ctx, kubectlPath, "--kubeconfig", kubeConfig, "-n", "kube-system", "patch", "deployment", "coredns", "--type", "json", "-p", "[{\"op\":\"add\",\"path\":\"/spec/template/spec/tolerations/-\",\"value\":{\"key\":\"node.cloudprovider.kubernetes.io/uninitialized\",\"value\":\"true\",\"effect\":\"NoSchedule\"}},{\"op\":\"add\",\"path\":\"/spec/template/spec/nodeSelector\",\"value\":{\"node-role.kubernetes.io/control-plane\":\"\"}}]").Run()
if err != nil { if err != nil {
return err return err
} }
return exec.Command(kubectlPath, "--kubeconfig", kubeConfig, "-n", "kube-system", "patch", "deployment", "coredns", "--type", "json", "-p", "[{\"op\":\"add\",\"path\":\"/spec/template/spec/tolerations/-\",\"value\":{\"key\":\"node.kubernetes.io/network-unavailable\",\"value\":\"\",\"effect\":\"NoSchedule\"}}]").Run()
err = exec.CommandContext(ctx, kubectlPath, "--kubeconfig", kubeConfig, "-n", "kube-system", "patch", "deployment", "coredns", "--type", "json", "-p", "[{\"op\":\"add\",\"path\":\"/spec/template/spec/tolerations/-\",\"value\":{\"key\":\"node.kubernetes.io/network-unavailable\",\"value\":\"\",\"effect\":\"NoSchedule\"}}]").Run()
if err != nil {
return err
}
ciliumInstall := exec.CommandContext(ctx, "cilium", "install", "--encryption", "wireguard", "--ipam", "kubernetes", "--ipv4-native-routing-cidr", subnetworkPodCIDR, "--helm-set", "endpointRoutes.enabled=true,tunnel=disabled")
ciliumInstall.Env = append(os.Environ(), "KUBECONFIG="+kubeConfig)
out, err = ciliumInstall.CombinedOutput()
if err != nil {
err = errors.New(string(out))
return err
}
return nil
}
func (k *KubernetesUtil) setupQemuPodNetwork(ctx context.Context) error {
ciliumInstall := exec.CommandContext(ctx, "cilium", "install", "--encryption", "wireguard", "--helm-set", "ipam.operator.clusterPoolIPv4PodCIDRList=10.244.0.0/16,endpointRoutes.enabled=true")
ciliumInstall.Env = append(os.Environ(), "KUBECONFIG="+kubeConfig)
out, err := ciliumInstall.CombinedOutput()
if err != nil {
err = errors.New(string(out))
return err
}
return nil
} }
// SetupAutoscaling deploys the k8s cluster autoscaler. // SetupAutoscaling deploys the k8s cluster autoscaler.
@ -142,7 +198,7 @@ func (k *KubernetesUtil) SetupCloudNodeManager(kubectl Client, cloudNodeManagerC
} }
// JoinCluster joins existing Kubernetes cluster using kubeadm join. // JoinCluster joins existing Kubernetes cluster using kubeadm join.
func (k *KubernetesUtil) JoinCluster(joinConfig []byte) error { func (k *KubernetesUtil) JoinCluster(ctx context.Context, joinConfig []byte) error {
// TODO: audit policy should be user input // TODO: audit policy should be user input
auditPolicy, err := resources.NewDefaultAuditPolicy().Marshal() auditPolicy, err := resources.NewDefaultAuditPolicy().Marshal()
if err != nil { if err != nil {
@ -163,7 +219,7 @@ func (k *KubernetesUtil) JoinCluster(joinConfig []byte) error {
} }
// run `kubeadm join` to join a worker node to an existing Kubernetes cluster // run `kubeadm join` to join a worker node to an existing Kubernetes cluster
cmd := exec.Command(kubeadmPath, "join", "--config", joinConfigFile.Name()) cmd := exec.CommandContext(ctx, kubeadmPath, "join", "--config", joinConfigFile.Name())
if _, err := cmd.Output(); err != nil { if _, err := cmd.Output(); err != nil {
var exitErr *exec.ExitError var exitErr *exec.ExitError
if errors.As(err, &exitErr) { if errors.As(err, &exitErr) {
@ -201,10 +257,10 @@ func (k *KubernetesUtil) RestartKubelet() error {
// GetControlPlaneJoinCertificateKey return the key which can be used in combination with the joinArgs // GetControlPlaneJoinCertificateKey return the key which can be used in combination with the joinArgs
// to join the Cluster as control-plane. // to join the Cluster as control-plane.
func (k *KubernetesUtil) GetControlPlaneJoinCertificateKey() (string, error) { func (k *KubernetesUtil) GetControlPlaneJoinCertificateKey(ctx context.Context) (string, error) {
// Key will be valid for 1h (no option to reduce the duration). // Key will be valid for 1h (no option to reduce the duration).
// https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init-phase/#cmd-phase-upload-certs // https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init-phase/#cmd-phase-upload-certs
output, err := exec.Command(kubeadmPath, "init", "phase", "upload-certs", "--upload-certs").Output() output, err := exec.CommandContext(ctx, kubeadmPath, "init", "phase", "upload-certs", "--upload-certs").Output()
if err != nil { if err != nil {
var exitErr *exec.ExitError var exitErr *exec.ExitError
if errors.As(err, &exitErr) { if errors.As(err, &exitErr) {
@ -218,7 +274,7 @@ func (k *KubernetesUtil) GetControlPlaneJoinCertificateKey() (string, error) {
[upload-certs] Using certificate key: [upload-certs] Using certificate key:
9555b74008f24687eb964bd90a164ecb5760a89481d9c55a77c129b7db438168 9555b74008f24687eb964bd90a164ecb5760a89481d9c55a77c129b7db438168
*/ */
key := regexp.MustCompile("[a-f0-9]{64}").FindString(string(output)) key := kubernetesKeyRegexp.FindString(string(output))
if key == "" { if key == "" {
return "", fmt.Errorf("failed to parse kubeadm output: %s", string(output)) return "", fmt.Errorf("failed to parse kubeadm output: %s", string(output))
} }
@ -226,8 +282,8 @@ func (k *KubernetesUtil) GetControlPlaneJoinCertificateKey() (string, error) {
} }
// CreateJoinToken creates a new bootstrap (join) token. // CreateJoinToken creates a new bootstrap (join) token.
func (k *KubernetesUtil) CreateJoinToken(ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) { func (k *KubernetesUtil) CreateJoinToken(ctx context.Context, ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) {
output, err := exec.Command(kubeadmPath, "token", "create", "--ttl", ttl.String(), "--print-join-command").Output() output, err := exec.CommandContext(ctx, kubeadmPath, "token", "create", "--ttl", ttl.String(), "--print-join-command").Output()
if err != nil { if err != nil {
return nil, fmt.Errorf("kubeadm token create failed: %w", err) return nil, fmt.Errorf("kubeadm token create failed: %w", err)
} }

View File

@ -0,0 +1,25 @@
package kubernetes
import (
"context"
"time"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
)
type clusterUtil interface {
InstallComponents(ctx context.Context, version string) error
InitCluster(ctx context.Context, initConfig []byte) error
JoinCluster(ctx context.Context, joinConfig []byte) error
SetupPodNetwork(context.Context, k8sapi.SetupPodNetworkInput) error
SetupAutoscaling(kubectl k8sapi.Client, clusterAutoscalerConfiguration resources.Marshaler, secrets resources.Marshaler) error
SetupCloudControllerManager(kubectl k8sapi.Client, cloudControllerManagerConfiguration resources.Marshaler, configMaps resources.Marshaler, secrets resources.Marshaler) error
SetupCloudNodeManager(kubectl k8sapi.Client, cloudNodeManagerConfiguration resources.Marshaler) error
SetupKMS(kubectl k8sapi.Client, kmsConfiguration resources.Marshaler) error
StartKubelet() error
RestartKubelet() error
GetControlPlaneJoinCertificateKey(ctx context.Context) (string, error)
CreateJoinToken(ctx context.Context, ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error)
}

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
@ -13,12 +14,6 @@ import (
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
) )
// k8s pod network cidr. This value was chosen to match the default flannel pod network.
const (
podNetworkCidr = "10.244.0.0/16"
serviceCidr = "10.245.0.0/24"
)
// configReader provides kubeconfig as []byte. // configReader provides kubeconfig as []byte.
type configReader interface { type configReader interface {
ReadKubeconfig() ([]byte, error) ReadKubeconfig() ([]byte, error)
@ -30,43 +25,96 @@ type configurationProvider interface {
JoinConfiguration(externalCloudProvider bool) k8sapi.KubeadmJoinYAML JoinConfiguration(externalCloudProvider bool) k8sapi.KubeadmJoinYAML
} }
// KubeWrapper implements ClusterWrapper interface. // KubeWrapper implements Cluster interface.
type KubeWrapper struct { type KubeWrapper struct {
clusterUtil k8sapi.ClusterUtil cloudProvider string
configProvider configurationProvider clusterUtil clusterUtil
client k8sapi.Client configProvider configurationProvider
kubeconfigReader configReader client k8sapi.Client
kubeconfigReader configReader
cloudControllerManager CloudControllerManager
cloudNodeManager CloudNodeManager
clusterAutoscaler ClusterAutoscaler
providerMetadata ProviderMetadata
} }
// New creates a new KubeWrapper with real values. // New creates a new KubeWrapper with real values.
func New(clusterUtil k8sapi.ClusterUtil, configProvider configurationProvider, client k8sapi.Client) *KubeWrapper { func New(cloudProvider string, clusterUtil clusterUtil, configProvider configurationProvider, client k8sapi.Client, cloudControllerManager CloudControllerManager,
cloudNodeManager CloudNodeManager, clusterAutoscaler ClusterAutoscaler, providerMetadata ProviderMetadata,
) *KubeWrapper {
return &KubeWrapper{ return &KubeWrapper{
clusterUtil: clusterUtil, cloudProvider: cloudProvider,
configProvider: configProvider, clusterUtil: clusterUtil,
client: client, configProvider: configProvider,
kubeconfigReader: &KubeconfigReader{fs: afero.Afero{Fs: afero.NewOsFs()}}, client: client,
kubeconfigReader: &KubeconfigReader{fs: afero.Afero{Fs: afero.NewOsFs()}},
cloudControllerManager: cloudControllerManager,
cloudNodeManager: cloudNodeManager,
clusterAutoscaler: clusterAutoscaler,
providerMetadata: providerMetadata,
} }
} }
// InitCluster initializes a new Kubernetes cluster and applies pod network provider. // InitCluster initializes a new Kubernetes cluster and applies pod network provider.
func (k *KubeWrapper) InitCluster(in InitClusterInput) error { func (k *KubeWrapper) InitCluster(ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI, vpnIP string, masterSecret []byte) error {
// TODO: k8s version should be user input // TODO: k8s version should be user input
if err := k.clusterUtil.InstallComponents(context.TODO(), "1.23.6"); err != nil { if err := k.clusterUtil.InstallComponents(context.TODO(), "1.23.6"); err != nil {
return err return err
} }
initConfig := k.configProvider.InitConfiguration(in.SupportsCloudControllerManager) nodeName := vpnIP
initConfig.SetApiServerAdvertiseAddress(in.APIServerAdvertiseIP) var providerID string
initConfig.SetNodeIP(in.NodeIP) var instance cloudtypes.Instance
initConfig.SetNodeName(in.NodeName) var publicIP string
initConfig.SetPodNetworkCIDR(podNetworkCidr) var nodePodCIDR string
initConfig.SetServiceCIDR(serviceCidr) var subnetworkPodCIDR string
initConfig.SetProviderID(in.ProviderID) // this is the IP in "kubeadm init --control-plane-endpoint=<IP/DNS>:<port>" hence the unfortunate name
var controlPlaneEndpointIP string
var nodeIP string
var err error
// Step 1: retrieve cloud metadata for Kubernetes configuration
if k.providerMetadata.Supported() {
instance, err = k.providerMetadata.Self(context.TODO())
if err != nil {
return fmt.Errorf("retrieving own instance metadata failed: %w", err)
}
nodeName = k8sCompliantHostname(instance.Name)
providerID = instance.ProviderID
if len(instance.PrivateIPs) > 0 {
nodeIP = instance.PrivateIPs[0]
}
if len(instance.PublicIPs) > 0 {
publicIP = instance.PublicIPs[0]
}
if len(instance.AliasIPRanges) > 0 {
nodePodCIDR = instance.AliasIPRanges[0]
}
subnetworkPodCIDR, err = k.providerMetadata.GetSubnetworkCIDR(context.TODO())
if err != nil {
return fmt.Errorf("retrieving subnetwork CIDR failed: %w", err)
}
controlPlaneEndpointIP = publicIP
if k.providerMetadata.SupportsLoadBalancer() {
controlPlaneEndpointIP, err = k.providerMetadata.GetLoadBalancerIP(context.TODO())
if err != nil {
return fmt.Errorf("retrieving load balancer IP failed: %w", err)
}
}
}
// Step 2: configure kubeadm init config
initConfig := k.configProvider.InitConfiguration(k.cloudControllerManager.Supported())
initConfig.SetNodeIP(nodeIP)
initConfig.SetCertSANs([]string{publicIP, nodeIP})
initConfig.SetNodeName(nodeName)
initConfig.SetProviderID(providerID)
initConfig.SetControlPlaneEndpoint(controlPlaneEndpointIP)
initConfigYAML, err := initConfig.Marshal() initConfigYAML, err := initConfig.Marshal()
if err != nil { if err != nil {
return fmt.Errorf("encoding kubeadm init configuration as YAML failed: %w", err) return fmt.Errorf("encoding kubeadm init configuration as YAML failed: %w", err)
} }
if err := k.clusterUtil.InitCluster(initConfigYAML); err != nil { if err := k.clusterUtil.InitCluster(ctx, initConfigYAML); err != nil {
return fmt.Errorf("kubeadm init failed: %w", err) return fmt.Errorf("kubeadm init failed: %w", err)
} }
kubeConfig, err := k.GetKubeconfig() kubeConfig, err := k.GetKubeconfig()
@ -74,54 +122,72 @@ func (k *KubeWrapper) InitCluster(in InitClusterInput) error {
return fmt.Errorf("reading kubeconfig after cluster initialization failed: %w", err) return fmt.Errorf("reading kubeconfig after cluster initialization failed: %w", err)
} }
k.client.SetKubeconfig(kubeConfig) k.client.SetKubeconfig(kubeConfig)
flannel := resources.NewDefaultFlannelDeployment()
if err = k.clusterUtil.SetupPodNetwork(k.client, flannel); err != nil { // Step 3: configure & start kubernetes controllers
setupPodNetworkInput := k8sapi.SetupPodNetworkInput{
CloudProvider: k.cloudProvider,
NodeName: nodeName,
FirstNodePodCIDR: nodePodCIDR,
SubnetworkPodCIDR: subnetworkPodCIDR,
ProviderID: providerID,
}
if err = k.clusterUtil.SetupPodNetwork(ctx, setupPodNetworkInput); err != nil {
return fmt.Errorf("setup of pod network failed: %w", err) return fmt.Errorf("setup of pod network failed: %w", err)
} }
kms := resources.NewKMSDeployment(in.MasterSecret) kms := resources.NewKMSDeployment(masterSecret)
if err = k.clusterUtil.SetupKMS(k.client, kms); err != nil { if err = k.clusterUtil.SetupKMS(k.client, kms); err != nil {
return fmt.Errorf("setup of kms failed: %w", err) return fmt.Errorf("setup of kms failed: %w", err)
} }
if in.SupportsCloudControllerManager { if err := k.setupCCM(context.TODO(), vpnIP, subnetworkPodCIDR, cloudServiceAccountURI, instance); err != nil {
cloudControllerManagerConfiguration := resources.NewDefaultCloudControllerManagerDeployment( return fmt.Errorf("setting up cloud controller manager failed: %w", err)
in.CloudControllerManagerName, in.CloudControllerManagerImage, in.CloudControllerManagerPath, in.CloudControllerManagerExtraArgs, }
in.CloudControllerManagerVolumes, in.CloudControllerManagerVolumeMounts, in.CloudControllerManagerEnv, if err := k.setupCloudNodeManager(); err != nil {
) return fmt.Errorf("setting up cloud node manager failed: %w", err)
if err := k.clusterUtil.SetupCloudControllerManager(k.client, cloudControllerManagerConfiguration, in.CloudControllerManagerConfigMaps, in.CloudControllerManagerSecrets); err != nil {
return fmt.Errorf("failed to setup cloud-controller-manager: %w", err)
}
} }
if in.SupportsCloudNodeManager { if err := k.setupClusterAutoscaler(instance, cloudServiceAccountURI, autoscalingNodeGroups); err != nil {
cloudNodeManagerConfiguration := resources.NewDefaultCloudNodeManagerDeployment( return fmt.Errorf("setting up cluster autoscaler failed: %w", err)
in.CloudNodeManagerImage, in.CloudNodeManagerPath, in.CloudNodeManagerExtraArgs,
)
if err := k.clusterUtil.SetupCloudNodeManager(k.client, cloudNodeManagerConfiguration); err != nil {
return fmt.Errorf("failed to setup cloud-node-manager: %w", err)
}
}
if in.SupportClusterAutoscaler {
clusterAutoscalerConfiguration := resources.NewDefaultAutoscalerDeployment(in.AutoscalingVolumes, in.AutoscalingVolumeMounts, in.AutoscalingEnv)
clusterAutoscalerConfiguration.SetAutoscalerCommand(in.AutoscalingCloudprovider, in.AutoscalingNodeGroups)
if err := k.clusterUtil.SetupAutoscaling(k.client, clusterAutoscalerConfiguration, in.AutoscalingSecrets); err != nil {
return fmt.Errorf("failed to setup cluster-autoscaler: %w", err)
}
} }
return nil return nil
} }
// JoinCluster joins existing Kubernetes cluster. // JoinCluster joins existing Kubernetes cluster.
func (k *KubeWrapper) JoinCluster(args *kubeadm.BootstrapTokenDiscovery, nodeName, nodeInternalIP, nodeVPNIP, providerID, certKey string, ccmSupported bool, peerRole role.Role) error { func (k *KubeWrapper) JoinCluster(ctx context.Context, args *kubeadm.BootstrapTokenDiscovery, nodeVPNIP, certKey string, peerRole role.Role) error {
// TODO: k8s version should be user input // TODO: k8s version should be user input
if err := k.clusterUtil.InstallComponents(context.TODO(), "1.23.6"); err != nil { if err := k.clusterUtil.InstallComponents(context.TODO(), "1.23.6"); err != nil {
return err return err
} }
joinConfig := k.configProvider.JoinConfiguration(ccmSupported) // Step 1: retrieve cloud metadata for Kubernetes configuration
var providerID string
nodeName := nodeVPNIP
nodeInternalIP := nodeVPNIP
if k.providerMetadata.Supported() {
instance, err := k.providerMetadata.Self(context.TODO())
if err != nil {
return fmt.Errorf("retrieving own instance metadata failed: %w", err)
}
providerID = instance.ProviderID
nodeName = instance.Name
if len(instance.PrivateIPs) > 0 {
nodeInternalIP = instance.PrivateIPs[0]
}
}
nodeName = k8sCompliantHostname(nodeName)
if k.cloudControllerManager.Supported() && k.providerMetadata.Supported() {
if err := k.prepareInstanceForCCM(context.TODO(), nodeVPNIP); err != nil {
return fmt.Errorf("preparing node for CCM failed: %w", err)
}
}
// Step 2: configure kubeadm join config
joinConfig := k.configProvider.JoinConfiguration(k.cloudControllerManager.Supported())
joinConfig.SetApiServerEndpoint(args.APIServerEndpoint) joinConfig.SetApiServerEndpoint(args.APIServerEndpoint)
joinConfig.SetToken(args.Token) joinConfig.SetToken(args.Token)
joinConfig.AppendDiscoveryTokenCaCertHash(args.CACertHashes[0]) joinConfig.AppendDiscoveryTokenCaCertHash(args.CACertHashes[0])
@ -129,13 +195,13 @@ func (k *KubeWrapper) JoinCluster(args *kubeadm.BootstrapTokenDiscovery, nodeNam
joinConfig.SetNodeName(nodeName) joinConfig.SetNodeName(nodeName)
joinConfig.SetProviderID(providerID) joinConfig.SetProviderID(providerID)
if peerRole == role.Coordinator { if peerRole == role.Coordinator {
joinConfig.SetControlPlane(nodeVPNIP, certKey) joinConfig.SetControlPlane(nodeInternalIP, certKey)
} }
joinConfigYAML, err := joinConfig.Marshal() joinConfigYAML, err := joinConfig.Marshal()
if err != nil { if err != nil {
return fmt.Errorf("encoding kubeadm join configuration as YAML failed: %w", err) return fmt.Errorf("encoding kubeadm join configuration as YAML failed: %w", err)
} }
if err := k.clusterUtil.JoinCluster(joinConfigYAML); err != nil { if err := k.clusterUtil.JoinCluster(ctx, joinConfigYAML); err != nil {
return fmt.Errorf("joining cluster failed: %v %w ", string(joinConfigYAML), err) return fmt.Errorf("joining cluster failed: %v %w ", string(joinConfigYAML), err)
} }
@ -156,13 +222,89 @@ func (k *KubeWrapper) GetKubeconfig() ([]byte, error) {
} }
// GetKubeadmCertificateKey return the key needed to join the Cluster as Control-Plane (has to be executed on a control-plane; errors otherwise). // GetKubeadmCertificateKey return the key needed to join the Cluster as Control-Plane (has to be executed on a control-plane; errors otherwise).
func (k *KubeWrapper) GetKubeadmCertificateKey() (string, error) { func (k *KubeWrapper) GetKubeadmCertificateKey(ctx context.Context) (string, error) {
return k.clusterUtil.GetControlPlaneJoinCertificateKey() return k.clusterUtil.GetControlPlaneJoinCertificateKey(ctx)
} }
// GetJoinToken returns a bootstrap (join) token. // GetJoinToken returns a bootstrap (join) token.
func (k *KubeWrapper) GetJoinToken(ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) { func (k *KubeWrapper) GetJoinToken(ctx context.Context, ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) {
return k.clusterUtil.CreateJoinToken(ttl) return k.clusterUtil.CreateJoinToken(ctx, ttl)
}
func (k *KubeWrapper) setupCCM(ctx context.Context, vpnIP, subnetworkPodCIDR, cloudServiceAccountURI string, instance cloudtypes.Instance) error {
if !k.cloudControllerManager.Supported() {
return nil
}
if err := k.prepareInstanceForCCM(context.TODO(), vpnIP); err != nil {
return fmt.Errorf("preparing node for CCM failed: %w", err)
}
ccmConfigMaps, err := k.cloudControllerManager.ConfigMaps(instance)
if err != nil {
return fmt.Errorf("defining ConfigMaps for CCM failed: %w", err)
}
ccmSecrets, err := k.cloudControllerManager.Secrets(ctx, instance, cloudServiceAccountURI)
if err != nil {
return fmt.Errorf("defining Secrets for CCM failed: %w", err)
}
cloudControllerManagerConfiguration := resources.NewDefaultCloudControllerManagerDeployment(
k.cloudControllerManager.Name(), k.cloudControllerManager.Image(), k.cloudControllerManager.Path(), subnetworkPodCIDR,
k.cloudControllerManager.ExtraArgs(), k.cloudControllerManager.Volumes(), k.cloudControllerManager.VolumeMounts(), k.cloudControllerManager.Env(),
)
if err := k.clusterUtil.SetupCloudControllerManager(k.client, cloudControllerManagerConfiguration, ccmConfigMaps, ccmSecrets); err != nil {
return fmt.Errorf("failed to setup cloud-controller-manager: %w", err)
}
return nil
}
func (k *KubeWrapper) setupCloudNodeManager() error {
if !k.cloudNodeManager.Supported() {
return nil
}
cloudNodeManagerConfiguration := resources.NewDefaultCloudNodeManagerDeployment(
k.cloudNodeManager.Image(), k.cloudNodeManager.Path(), k.cloudNodeManager.ExtraArgs(),
)
if err := k.clusterUtil.SetupCloudNodeManager(k.client, cloudNodeManagerConfiguration); err != nil {
return fmt.Errorf("failed to setup cloud-node-manager: %w", err)
}
return nil
}
func (k *KubeWrapper) setupClusterAutoscaler(instance cloudtypes.Instance, cloudServiceAccountURI string, autoscalingNodeGroups []string) error {
if !k.clusterAutoscaler.Supported() {
return nil
}
caSecrets, err := k.clusterAutoscaler.Secrets(instance, cloudServiceAccountURI)
if err != nil {
return fmt.Errorf("defining Secrets for cluster-autoscaler failed: %w", err)
}
clusterAutoscalerConfiguration := resources.NewDefaultAutoscalerDeployment(k.clusterAutoscaler.Volumes(), k.clusterAutoscaler.VolumeMounts(), k.clusterAutoscaler.Env())
clusterAutoscalerConfiguration.SetAutoscalerCommand(k.clusterAutoscaler.Name(), autoscalingNodeGroups)
if err := k.clusterUtil.SetupAutoscaling(k.client, clusterAutoscalerConfiguration, caSecrets); err != nil {
return fmt.Errorf("failed to setup cluster-autoscaler: %w", err)
}
return nil
}
// prepareInstanceForCCM sets the vpn IP in cloud provider metadata.
func (k *KubeWrapper) prepareInstanceForCCM(ctx context.Context, vpnIP string) error {
if err := k.providerMetadata.SetVPNIP(ctx, vpnIP); err != nil {
return fmt.Errorf("setting VPN IP for cloud-controller-manager failed: %w", err)
}
return nil
}
// k8sCompliantHostname transforms a hostname to an RFC 1123 compliant, lowercase subdomain as required by Kubernetes node names.
// The following regex is used by k8s for validation: /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/ .
// Only a simple heuristic is used for now (to lowercase, replace underscores).
func k8sCompliantHostname(in string) string {
hostname := strings.ToLower(in)
hostname = strings.ReplaceAll(hostname, "_", "-")
return hostname
} }
// StartKubelet starts the kubelet service. // StartKubelet starts the kubelet service.

View File

@ -3,9 +3,11 @@ package kubernetes
import ( import (
"context" "context"
"errors" "errors"
"regexp"
"testing" "testing"
"time" "time"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
@ -13,13 +15,484 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/goleak" "go.uber.org/goleak"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
"sigs.k8s.io/yaml"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
goleak.VerifyTestMain(m) goleak.VerifyTestMain(m)
} }
func TestInitCluster(t *testing.T) {
someErr := errors.New("failed")
coordinatorVPNIP := "192.0.2.0"
serviceAccountUri := "some-service-account-uri"
masterSecret := []byte("some-master-secret")
autoscalingNodeGroups := []string{"0,10,autoscaling_group_0"}
nodeName := "node-name"
providerID := "provider-id"
privateIP := "192.0.2.1"
publicIP := "192.0.2.2"
loadbalancerIP := "192.0.2.3"
aliasIPRange := "192.0.2.0/24"
testCases := map[string]struct {
clusterUtil stubClusterUtil
kubeCTL stubKubeCTL
providerMetadata ProviderMetadata
CloudControllerManager CloudControllerManager
CloudNodeManager CloudNodeManager
ClusterAutoscaler ClusterAutoscaler
kubeconfigReader configReader
wantConfig k8sapi.KubeadmInitYAML
wantErr bool
}{
"kubeadm init works without metadata": {
clusterUtil: stubClusterUtil{},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{SupportedResp: false},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{SupportedResp: false},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantConfig: k8sapi.KubeadmInitYAML{
InitConfiguration: kubeadm.InitConfiguration{
NodeRegistration: kubeadm.NodeRegistrationOptions{
KubeletExtraArgs: map[string]string{
"node-ip": "",
"provider-id": "",
},
Name: coordinatorVPNIP,
},
},
ClusterConfiguration: kubeadm.ClusterConfiguration{},
},
},
"kubeadm init works with metadata and loadbalancer": {
clusterUtil: stubClusterUtil{},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{
SupportedResp: true,
SelfResp: cloudtypes.Instance{
Name: nodeName,
ProviderID: providerID,
PrivateIPs: []string{privateIP},
PublicIPs: []string{publicIP},
AliasIPRanges: []string{aliasIPRange},
},
GetLoadBalancerIPResp: loadbalancerIP,
SupportsLoadBalancerResp: true,
},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{SupportedResp: false},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantConfig: k8sapi.KubeadmInitYAML{
InitConfiguration: kubeadm.InitConfiguration{
NodeRegistration: kubeadm.NodeRegistrationOptions{
KubeletExtraArgs: map[string]string{
"node-ip": privateIP,
"provider-id": providerID,
},
Name: nodeName,
},
},
ClusterConfiguration: kubeadm.ClusterConfiguration{
ControlPlaneEndpoint: loadbalancerIP,
APIServer: kubeadm.APIServer{
CertSANs: []string{publicIP, privateIP},
},
},
},
wantErr: false,
},
"kubeadm init fails when retrieving metadata self": {
clusterUtil: stubClusterUtil{},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{
SelfErr: someErr,
SupportedResp: true,
},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantErr: true,
},
"kubeadm init fails when retrieving metadata subnetwork cidr": {
clusterUtil: stubClusterUtil{},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{
GetSubnetworkCIDRErr: someErr,
SupportedResp: true,
},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantErr: true,
},
"kubeadm init fails when retrieving metadata loadbalancer ip": {
clusterUtil: stubClusterUtil{},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{
GetLoadBalancerIPErr: someErr,
SupportsLoadBalancerResp: true,
SupportedResp: true,
},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantErr: true,
},
"kubeadm init fails when applying the init config": {
clusterUtil: stubClusterUtil{initClusterErr: someErr},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantErr: true,
},
"kubeadm init fails when setting up the pod network": {
clusterUtil: stubClusterUtil{setupPodNetworkErr: someErr},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantErr: true,
},
"kubeadm init fails when setting the cloud contoller manager": {
clusterUtil: stubClusterUtil{setupCloudControllerManagerError: someErr},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{},
CloudControllerManager: &stubCloudControllerManager{SupportedResp: true},
CloudNodeManager: &stubCloudNodeManager{},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantErr: true,
},
"kubeadm init fails when setting the cloud node manager": {
clusterUtil: stubClusterUtil{setupCloudNodeManagerError: someErr},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{SupportedResp: true},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantErr: true,
},
"kubeadm init fails when setting the cluster autoscaler": {
clusterUtil: stubClusterUtil{setupAutoscalingError: someErr},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{},
ClusterAutoscaler: &stubClusterAutoscaler{SupportedResp: true},
wantErr: true,
},
"kubeadm init fails when reading kubeconfig": {
clusterUtil: stubClusterUtil{},
kubeconfigReader: &stubKubeconfigReader{
ReadErr: someErr,
},
providerMetadata: &stubProviderMetadata{},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantErr: true,
},
"kubeadm init fails when setting up the kms": {
clusterUtil: stubClusterUtil{setupKMSError: someErr},
kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
providerMetadata: &stubProviderMetadata{SupportedResp: false},
CloudControllerManager: &stubCloudControllerManager{},
CloudNodeManager: &stubCloudNodeManager{SupportedResp: false},
ClusterAutoscaler: &stubClusterAutoscaler{},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
kube := KubeWrapper{
clusterUtil: &tc.clusterUtil,
providerMetadata: tc.providerMetadata,
cloudControllerManager: tc.CloudControllerManager,
cloudNodeManager: tc.CloudNodeManager,
clusterAutoscaler: tc.ClusterAutoscaler,
configProvider: &stubConfigProvider{InitConfig: k8sapi.KubeadmInitYAML{}},
client: &tc.kubeCTL,
kubeconfigReader: tc.kubeconfigReader,
}
err := kube.InitCluster(context.Background(), autoscalingNodeGroups, serviceAccountUri, coordinatorVPNIP, masterSecret)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
var kubeadmConfig k8sapi.KubeadmInitYAML
require.NoError(resources.UnmarshalK8SResources(tc.clusterUtil.initConfigs[0], &kubeadmConfig))
require.Equal(tc.wantConfig.ClusterConfiguration, kubeadmConfig.ClusterConfiguration)
require.Equal(tc.wantConfig.InitConfiguration, kubeadmConfig.InitConfiguration)
})
}
}
func TestJoinCluster(t *testing.T) {
someErr := errors.New("failed")
joinCommand := &kubeadm.BootstrapTokenDiscovery{
APIServerEndpoint: "192.0.2.0:6443",
Token: "kube-fake-token",
CACertHashes: []string{"sha256:a60ebe9b0879090edd83b40a4df4bebb20506bac1e51d518ff8f4505a721930f"},
}
nodeVPNIP := "192.0.2.0"
certKey := "cert-key"
testCases := map[string]struct {
clusterUtil stubClusterUtil
providerMetadata ProviderMetadata
CloudControllerManager CloudControllerManager
wantConfig kubeadm.JoinConfiguration
role role.Role
wantErr bool
}{
"kubeadm join worker works without metadata": {
clusterUtil: stubClusterUtil{},
providerMetadata: &stubProviderMetadata{},
CloudControllerManager: &stubCloudControllerManager{},
role: role.Node,
wantConfig: kubeadm.JoinConfiguration{
Discovery: kubeadm.Discovery{
BootstrapToken: joinCommand,
},
NodeRegistration: kubeadm.NodeRegistrationOptions{
Name: nodeVPNIP,
KubeletExtraArgs: map[string]string{"node-ip": "192.0.2.0"},
},
},
},
"kubeadm join worker works with metadata": {
clusterUtil: stubClusterUtil{},
providerMetadata: &stubProviderMetadata{
SupportedResp: true,
SelfResp: cloudtypes.Instance{
ProviderID: "provider-id",
Name: "metadata-name",
PrivateIPs: []string{"192.0.2.1"},
},
},
CloudControllerManager: &stubCloudControllerManager{},
role: role.Node,
wantConfig: kubeadm.JoinConfiguration{
Discovery: kubeadm.Discovery{
BootstrapToken: joinCommand,
},
NodeRegistration: kubeadm.NodeRegistrationOptions{
Name: "metadata-name",
KubeletExtraArgs: map[string]string{"node-ip": "192.0.2.1"},
},
},
},
"kubeadm join worker works with metadata and cloud controller manager": {
clusterUtil: stubClusterUtil{},
providerMetadata: &stubProviderMetadata{
SupportedResp: true,
SelfResp: cloudtypes.Instance{
ProviderID: "provider-id",
Name: "metadata-name",
PrivateIPs: []string{"192.0.2.1"},
},
},
CloudControllerManager: &stubCloudControllerManager{
SupportedResp: true,
},
role: role.Node,
wantConfig: kubeadm.JoinConfiguration{
Discovery: kubeadm.Discovery{
BootstrapToken: joinCommand,
},
NodeRegistration: kubeadm.NodeRegistrationOptions{
Name: "metadata-name",
KubeletExtraArgs: map[string]string{"node-ip": "192.0.2.1"},
},
},
},
"kubeadm join control-plane node works with metadata": {
clusterUtil: stubClusterUtil{},
providerMetadata: &stubProviderMetadata{
SupportedResp: true,
SelfResp: cloudtypes.Instance{
ProviderID: "provider-id",
Name: "metadata-name",
PrivateIPs: []string{"192.0.2.1"},
},
},
CloudControllerManager: &stubCloudControllerManager{},
role: role.Coordinator,
wantConfig: kubeadm.JoinConfiguration{
Discovery: kubeadm.Discovery{
BootstrapToken: joinCommand,
},
NodeRegistration: kubeadm.NodeRegistrationOptions{
Name: "metadata-name",
KubeletExtraArgs: map[string]string{"node-ip": "192.0.2.1"},
},
ControlPlane: &kubeadm.JoinControlPlane{
LocalAPIEndpoint: kubeadm.APIEndpoint{
AdvertiseAddress: "192.0.2.1",
BindPort: 6443,
},
CertificateKey: certKey,
},
},
},
"kubeadm join worker fails when retrieving self metadata": {
clusterUtil: stubClusterUtil{},
providerMetadata: &stubProviderMetadata{
SupportedResp: true,
SelfErr: someErr,
},
CloudControllerManager: &stubCloudControllerManager{},
role: role.Node,
wantErr: true,
},
"kubeadm join worker fails when applying the join config": {
clusterUtil: stubClusterUtil{joinClusterErr: someErr},
providerMetadata: &stubProviderMetadata{},
CloudControllerManager: &stubCloudControllerManager{},
role: role.Node,
wantErr: true,
},
"kubeadm join worker works fails when setting the metadata for the cloud controller manager": {
clusterUtil: stubClusterUtil{},
providerMetadata: &stubProviderMetadata{
SupportedResp: true,
SelfResp: cloudtypes.Instance{
ProviderID: "provider-id",
Name: "metadata-name",
PrivateIPs: []string{"192.0.2.1"},
},
SetVPNIPErr: someErr,
},
CloudControllerManager: &stubCloudControllerManager{
SupportedResp: true,
},
role: role.Node,
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
kube := KubeWrapper{
clusterUtil: &tc.clusterUtil,
providerMetadata: tc.providerMetadata,
cloudControllerManager: tc.CloudControllerManager,
configProvider: &stubConfigProvider{},
}
err := kube.JoinCluster(context.Background(), joinCommand, nodeVPNIP, certKey, tc.role)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
var joinYaml k8sapi.KubeadmJoinYAML
joinYaml, err = joinYaml.Unmarshal(tc.clusterUtil.joinConfigs[0])
require.NoError(err)
assert.Equal(tc.wantConfig, joinYaml.JoinConfiguration)
})
}
}
func TestGetKubeconfig(t *testing.T) {
testCases := map[string]struct {
Kubewrapper KubeWrapper
wantErr bool
}{
"check single replacement": {
Kubewrapper: KubeWrapper{kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("127.0.0.1:16443"),
}},
},
"check multiple replacement": {
Kubewrapper: KubeWrapper{kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("127.0.0.1:16443...127.0.0.1:16443"),
}},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
data, err := tc.Kubewrapper.GetKubeconfig()
require.NoError(err)
assert.NotContains(string(data), "127.0.0.1:16443")
assert.Contains(string(data), "10.118.0.1:6443")
})
}
}
func TestK8sCompliantHostname(t *testing.T) {
compliantHostname := regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
testCases := map[string]struct {
hostname string
wantHostname string
}{
"azure scale set names work": {
hostname: "constellation-scale-set-coordinators-name_0",
wantHostname: "constellation-scale-set-coordinators-name-0",
},
"compliant hostname is not modified": {
hostname: "abcd-123",
wantHostname: "abcd-123",
},
"uppercase hostnames are lowercased": {
hostname: "ABCD",
wantHostname: "abcd",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
hostname := k8sCompliantHostname(tc.hostname)
assert.Equal(tc.wantHostname, hostname)
assert.Regexp(compliantHostname, hostname)
})
}
}
type stubClusterUtil struct { type stubClusterUtil struct {
installComponentsErr error installComponentsErr error
initClusterErr error initClusterErr error
@ -42,12 +515,12 @@ func (s *stubClusterUtil) InstallComponents(ctx context.Context, version string)
return s.installComponentsErr return s.installComponentsErr
} }
func (s *stubClusterUtil) InitCluster(initConfig []byte) error { func (s *stubClusterUtil) InitCluster(ctx context.Context, initConfig []byte) error {
s.initConfigs = append(s.initConfigs, initConfig) s.initConfigs = append(s.initConfigs, initConfig)
return s.initClusterErr return s.initClusterErr
} }
func (s *stubClusterUtil) SetupPodNetwork(kubectl k8sapi.Client, podNetworkConfiguration resources.Marshaler) error { func (s *stubClusterUtil) SetupPodNetwork(context.Context, k8sapi.SetupPodNetworkInput) error {
return s.setupPodNetworkErr return s.setupPodNetworkErr
} }
@ -67,7 +540,7 @@ func (s *stubClusterUtil) SetupCloudNodeManager(kubectl k8sapi.Client, cloudNode
return s.setupCloudNodeManagerError return s.setupCloudNodeManagerError
} }
func (s *stubClusterUtil) JoinCluster(joinConfig []byte) error { func (s *stubClusterUtil) JoinCluster(ctx context.Context, joinConfig []byte) error {
s.joinConfigs = append(s.joinConfigs, joinConfig) s.joinConfigs = append(s.joinConfigs, joinConfig)
return s.joinClusterErr return s.joinClusterErr
} }
@ -80,11 +553,11 @@ func (s *stubClusterUtil) RestartKubelet() error {
return s.restartKubeletErr return s.restartKubeletErr
} }
func (s *stubClusterUtil) GetControlPlaneJoinCertificateKey() (string, error) { func (s *stubClusterUtil) GetControlPlaneJoinCertificateKey(context.Context) (string, error) {
return "", nil return "", nil
} }
func (s *stubClusterUtil) CreateJoinToken(ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) { func (s *stubClusterUtil) CreateJoinToken(ctx context.Context, ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) {
return s.createJoinTokenResponse, s.createJoinTokenErr return s.createJoinTokenResponse, s.createJoinTokenErr
} }
@ -132,163 +605,3 @@ type stubKubeconfigReader struct {
func (s *stubKubeconfigReader) ReadKubeconfig() ([]byte, error) { func (s *stubKubeconfigReader) ReadKubeconfig() ([]byte, error) {
return s.Kubeconfig, s.ReadErr return s.Kubeconfig, s.ReadErr
} }
func TestInitCluster(t *testing.T) {
someErr := errors.New("failed")
coordinatorVPNIP := "192.0.2.0"
coordinatorProviderID := "somecloudprovider://instance-id"
instanceName := "instance-id"
supportsClusterAutoscaler := false
cloudprovider := "some-cloudprovider"
cloudControllerManagerImage := "some-image:latest"
cloudControllerManagerPath := "/some_path"
autoscalingNodeGroups := []string{"0,10,autoscaling_group_0"}
testCases := map[string]struct {
clusterUtil stubClusterUtil
kubeCTL stubKubeCTL
kubeconfigReader stubKubeconfigReader
initConfig k8sapi.KubeadmInitYAML
joinConfig k8sapi.KubeadmJoinYAML
wantErr bool
}{
"kubeadm init works": {
clusterUtil: stubClusterUtil{},
kubeconfigReader: stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
wantErr: false,
},
"kubeadm init errors": {
clusterUtil: stubClusterUtil{initClusterErr: someErr},
kubeconfigReader: stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
wantErr: true,
},
"pod network setup errors": {
clusterUtil: stubClusterUtil{setupPodNetworkErr: someErr},
kubeconfigReader: stubKubeconfigReader{
Kubeconfig: []byte("someKubeconfig"),
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
kube := KubeWrapper{
clusterUtil: &tc.clusterUtil,
configProvider: &stubConfigProvider{InitConfig: k8sapi.KubeadmInitYAML{}},
client: &tc.kubeCTL,
kubeconfigReader: &tc.kubeconfigReader,
}
err := kube.InitCluster(
InitClusterInput{
APIServerAdvertiseIP: coordinatorVPNIP,
NodeName: instanceName,
ProviderID: coordinatorProviderID,
SupportClusterAutoscaler: supportsClusterAutoscaler,
AutoscalingCloudprovider: cloudprovider,
AutoscalingNodeGroups: autoscalingNodeGroups,
CloudControllerManagerName: cloudprovider,
CloudControllerManagerImage: cloudControllerManagerImage,
CloudControllerManagerPath: cloudControllerManagerPath,
},
)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
var kubeadmConfig k8sapi.KubeadmInitYAML
require.NoError(resources.UnmarshalK8SResources(tc.clusterUtil.initConfigs[0], &kubeadmConfig))
assert.Equal(kubeadmConfig.InitConfiguration.LocalAPIEndpoint.AdvertiseAddress, "192.0.2.0")
assert.Equal(kubeadmConfig.ClusterConfiguration.Networking.PodSubnet, "10.244.0.0/16")
})
}
}
func TestJoinCluster(t *testing.T) {
someErr := errors.New("failed")
joinCommand := &kubeadm.BootstrapTokenDiscovery{
APIServerEndpoint: "192.0.2.0:6443",
Token: "kube-fake-token",
CACertHashes: []string{"sha256:a60ebe9b0879090edd83b40a4df4bebb20506bac1e51d518ff8f4505a721930f"},
}
nodeVPNIP := "192.0.2.0"
coordinatorProviderID := "somecloudprovider://instance-id"
instanceName := "instance-id"
client := fakeK8SClient{}
testCases := map[string]struct {
clusterUtil stubClusterUtil
wantErr bool
}{
"kubeadm join works": {
clusterUtil: stubClusterUtil{},
wantErr: false,
},
"kubeadm join errors": {
clusterUtil: stubClusterUtil{joinClusterErr: someErr},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
kube := New(&tc.clusterUtil, &stubConfigProvider{}, &client)
err := kube.JoinCluster(joinCommand, instanceName, nodeVPNIP, nodeVPNIP, coordinatorProviderID, "", true, role.Node)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
var joinConfig kubeadm.JoinConfiguration
require.NoError(yaml.Unmarshal(tc.clusterUtil.joinConfigs[0], &joinConfig))
assert.Equal("192.0.2.0:6443", joinConfig.Discovery.BootstrapToken.APIServerEndpoint)
assert.Equal("kube-fake-token", joinConfig.Discovery.BootstrapToken.Token)
assert.Equal([]string{"sha256:a60ebe9b0879090edd83b40a4df4bebb20506bac1e51d518ff8f4505a721930f"}, joinConfig.Discovery.BootstrapToken.CACertHashes)
assert.Equal(map[string]string{"node-ip": "192.0.2.0"}, joinConfig.NodeRegistration.KubeletExtraArgs)
})
}
}
func TestGetKubeconfig(t *testing.T) {
testCases := map[string]struct {
Kubewrapper KubeWrapper
wantErr bool
}{
"check single replacement": {
Kubewrapper: KubeWrapper{kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("127.0.0.1:16443"),
}},
},
"check multiple replacement": {
Kubewrapper: KubeWrapper{kubeconfigReader: &stubKubeconfigReader{
Kubeconfig: []byte("127.0.0.1:16443...127.0.0.1:16443"),
}},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
data, err := tc.Kubewrapper.GetKubeconfig()
require.NoError(err)
assert.NotContains(string(data), "127.0.0.1:16443")
assert.Contains(string(data), "10.118.0.1:6443")
})
}
}

View File

@ -99,7 +99,7 @@ func (a *API) ActivateAsCoordinator(in *pubproto.ActivateAsCoordinatorRequest, s
} }
logToCLI("Initializing Kubernetes ...") logToCLI("Initializing Kubernetes ...")
kubeconfig, err := a.core.InitCluster(in.AutoscalingNodeGroups, in.CloudServiceAccountUri, in.MasterSecret) kubeconfig, err := a.core.InitCluster(context.TODO(), in.AutoscalingNodeGroups, in.CloudServiceAccountUri, in.MasterSecret)
if err != nil { if err != nil {
return status.Errorf(codes.Internal, "initializing Kubernetes cluster failed: %v", err) return status.Errorf(codes.Internal, "initializing Kubernetes cluster failed: %v", err)
} }

View File

@ -39,6 +39,6 @@ type Core interface {
CreateSSHUsers([]ssh.UserKey) error CreateSSHUsers([]ssh.UserKey) error
InitCluster(autoscalingNodeGroups []string, cloudServiceAccountURI string, masterSecret []byte) ([]byte, error) InitCluster(ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI string, masterSecret []byte) ([]byte, error)
JoinCluster(joinToken *kubeadm.BootstrapTokenDiscovery, certificateKey string, role role.Role) error JoinCluster(ctx context.Context, joinToken *kubeadm.BootstrapTokenDiscovery, certificateKey string, role role.Role) error
} }

View File

@ -122,12 +122,12 @@ func (c *fakeCore) UpdatePeers(peers []peer.Peer) error {
return c.UpdatePeersErr return c.UpdatePeersErr
} }
func (c *fakeCore) InitCluster(autoscalingNodeGroups []string, cloudServiceAccountURI string, masterSecret []byte) ([]byte, error) { func (c *fakeCore) InitCluster(ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI string, masterSecret []byte) ([]byte, error) {
c.autoscalingNodeGroups = autoscalingNodeGroups c.autoscalingNodeGroups = autoscalingNodeGroups
return c.kubeconfig, nil return c.kubeconfig, nil
} }
func (c *fakeCore) JoinCluster(args *kubeadm.BootstrapTokenDiscovery, _ string, _ role.Role) error { func (c *fakeCore) JoinCluster(ctx context.Context, args *kubeadm.BootstrapTokenDiscovery, _ string, _ role.Role) error {
c.joinArgs = append(c.joinArgs, *args) c.joinArgs = append(c.joinArgs, *args)
return c.joinClusterErr return c.joinClusterErr
} }

View File

@ -74,7 +74,7 @@ func (a *API) ActivateAsAdditionalCoordinator(ctx context.Context, in *pubproto.
return nil, status.Errorf(codes.Internal, "add peers to vpn: %v", err) return nil, status.Errorf(codes.Internal, "add peers to vpn: %v", err)
} }
a.logger.Info("about to join the k8s cluster") a.logger.Info("about to join the k8s cluster")
err = a.core.JoinCluster(joinArgs, certKey, role.Coordinator) err = a.core.JoinCluster(context.TODO(), joinArgs, certKey, role.Coordinator)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "%v", err) return nil, status.Errorf(codes.Internal, "%v", err)
} }

View File

@ -186,7 +186,7 @@ func (a *API) JoinCluster(ctx context.Context, in *pubproto.JoinClusterRequest)
return nil, status.Errorf(codes.Internal, "request K8s join string: %v", err) return nil, status.Errorf(codes.Internal, "request K8s join string: %v", err)
} }
err = a.core.JoinCluster(&kubeadm.BootstrapTokenDiscovery{ err = a.core.JoinCluster(context.TODO(), &kubeadm.BootstrapTokenDiscovery{
APIServerEndpoint: resp.ApiServerEndpoint, APIServerEndpoint: resp.ApiServerEndpoint,
Token: resp.Token, Token: resp.Token,
CACertHashes: []string{resp.DiscoveryTokenCaCertHash}, CACertHashes: []string{resp.DiscoveryTokenCaCertHash},

View File

@ -47,7 +47,7 @@ func (a *API) GetUpdate(ctx context.Context, in *vpnproto.GetUpdateRequest) (*vp
// GetK8SJoinArgs is the RPC call to get the K8s join args. // GetK8SJoinArgs is the RPC call to get the K8s join args.
func (a *API) GetK8SJoinArgs(ctx context.Context, in *vpnproto.GetK8SJoinArgsRequest) (*vpnproto.GetK8SJoinArgsResponse, error) { func (a *API) GetK8SJoinArgs(ctx context.Context, in *vpnproto.GetK8SJoinArgsRequest) (*vpnproto.GetK8SJoinArgsResponse, error) {
args, err := a.core.GetK8sJoinArgs() args, err := a.core.GetK8sJoinArgs(context.TODO())
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "%v", err) return nil, status.Errorf(codes.Internal, "%v", err)
} }
@ -60,7 +60,7 @@ func (a *API) GetK8SJoinArgs(ctx context.Context, in *vpnproto.GetK8SJoinArgsReq
// GetK8SCertificateKey is the RPC call to get the K8s certificateKey necessary for control-plane join. // GetK8SCertificateKey is the RPC call to get the K8s certificateKey necessary for control-plane join.
func (a *API) GetK8SCertificateKey(ctx context.Context, in *vpnproto.GetK8SCertificateKeyRequest) (*vpnproto.GetK8SCertificateKeyResponse, error) { func (a *API) GetK8SCertificateKey(ctx context.Context, in *vpnproto.GetK8SCertificateKeyRequest) (*vpnproto.GetK8SCertificateKeyResponse, error) {
certKey, err := a.core.GetK8SCertificateKey() certKey, err := a.core.GetK8SCertificateKey(context.TODO())
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "%v", err) return nil, status.Errorf(codes.Internal, "%v", err)
} }
@ -79,7 +79,7 @@ func (a *API) GetDataKey(ctx context.Context, in *vpnproto.GetDataKeyRequest) (*
type Core interface { type Core interface {
GetPeers(resourceVersion int) (int, []peer.Peer, error) GetPeers(resourceVersion int) (int, []peer.Peer, error)
NotifyNodeHeartbeat(net.Addr) NotifyNodeHeartbeat(net.Addr)
GetK8sJoinArgs() (*kubeadm.BootstrapTokenDiscovery, error) GetK8sJoinArgs(ctx context.Context) (*kubeadm.BootstrapTokenDiscovery, error)
GetK8SCertificateKey() (string, error) GetK8SCertificateKey(ctx context.Context) (string, error)
GetDataKey(ctx context.Context, dataKeyID string, length int) ([]byte, error) GetDataKey(ctx context.Context, dataKeyID string, length int) ([]byte, error)
} }

View File

@ -202,11 +202,11 @@ func (c *stubCore) NotifyNodeHeartbeat(addr net.Addr) {
c.heartbeats = append(c.heartbeats, addr) c.heartbeats = append(c.heartbeats, addr)
} }
func (c *stubCore) GetK8sJoinArgs() (*kubeadm.BootstrapTokenDiscovery, error) { func (c *stubCore) GetK8sJoinArgs(context.Context) (*kubeadm.BootstrapTokenDiscovery, error) {
return &c.joinArgs, nil return &c.joinArgs, nil
} }
func (c *stubCore) GetK8SCertificateKey() (string, error) { func (c *stubCore) GetK8SCertificateKey(context.Context) (string, error) {
return c.kubeadmCertificateKey, c.getCertKeyErr return c.kubeadmCertificateKey, c.getCertKeyErr
} }

View File

@ -179,7 +179,19 @@ func getIPsFromConfig(stat statec.ConstellationState, config configc.Config) ([]
if err != nil { if err != nil {
return nil, err return nil, err
} }
return append(coordinators.PublicIPs(), nodes.PublicIPs()...), nil
var ips []string
// only deploy to non empty public IPs
for _, ip := range append(coordinators.PublicIPs(), nodes.PublicIPs()...) {
if ip != "" {
ips = append(ips, ip)
}
}
if len(ips) == 0 {
return nil, fmt.Errorf("no public IPs found in statefile")
}
return ips, nil
} }
func init() { func init() {

View File

@ -5,16 +5,16 @@ import (
"fmt" "fmt"
azurecloud "github.com/edgelesssys/constellation/coordinator/cloudprovider/azure" azurecloud "github.com/edgelesssys/constellation/coordinator/cloudprovider/azure"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
gcpcloud "github.com/edgelesssys/constellation/coordinator/cloudprovider/gcp" gcpcloud "github.com/edgelesssys/constellation/coordinator/cloudprovider/gcp"
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/internal/deploy/ssh" "github.com/edgelesssys/constellation/internal/deploy/ssh"
) )
type providerMetadata interface { type providerMetadata interface {
// List retrieves all instances belonging to the current constellation. // List retrieves all instances belonging to the current constellation.
List(ctx context.Context) ([]core.Instance, error) List(ctx context.Context) ([]cloudtypes.Instance, error)
// Self retrieves the current instance. // Self retrieves the current instance.
Self(ctx context.Context) (core.Instance, error) Self(ctx context.Context) (cloudtypes.Instance, error)
} }
// Fetcher checks the metadata service to search for instances that were set up for debugging and cloud provider specific SSH keys. // Fetcher checks the metadata service to search for instances that were set up for debugging and cloud provider specific SSH keys.
@ -66,7 +66,7 @@ func (f *Fetcher) DiscoverDebugdIPs(ctx context.Context) ([]string, error) {
} }
var ips []string var ips []string
for _, instance := range instances { for _, instance := range instances {
ips = append(ips, instance.IPs...) ips = append(ips, instance.PrivateIPs...)
} }
return ips, nil return ips, nil
} }

View File

@ -5,7 +5,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/internal/deploy/ssh" "github.com/edgelesssys/constellation/internal/deploy/ssh"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -21,15 +21,15 @@ func TestDiscoverDebugIPs(t *testing.T) {
}{ }{
"disovery works": { "disovery works": {
meta: stubMetadata{ meta: stubMetadata{
listRes: []core.Instance{ listRes: []cloudtypes.Instance{
{ {
IPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
}, },
{ {
IPs: []string{"192.0.2.1"}, PrivateIPs: []string{"192.0.2.1"},
}, },
{ {
IPs: []string{"192.0.2.2"}, PrivateIPs: []string{"192.0.2.2"},
}, },
}, },
}, },
@ -75,7 +75,7 @@ func TestFetchSSHKeys(t *testing.T) {
}{ }{
"fetch works": { "fetch works": {
meta: stubMetadata{ meta: stubMetadata{
selfRes: core.Instance{ selfRes: cloudtypes.Instance{
Name: "name", Name: "name",
ProviderID: "provider-id", ProviderID: "provider-id",
SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}}, SSHKeys: map[string][]string{"bob": {"ssh-rsa bobskey"}},
@ -117,24 +117,24 @@ func TestFetchSSHKeys(t *testing.T) {
} }
type stubMetadata struct { type stubMetadata struct {
listRes []core.Instance listRes []cloudtypes.Instance
listErr error listErr error
selfRes core.Instance selfRes cloudtypes.Instance
selfErr error selfErr error
getInstanceRes core.Instance getInstanceRes cloudtypes.Instance
getInstanceErr error getInstanceErr error
supportedRes bool supportedRes bool
} }
func (m *stubMetadata) List(ctx context.Context) ([]core.Instance, error) { func (m *stubMetadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
return m.listRes, m.listErr return m.listRes, m.listErr
} }
func (m *stubMetadata) Self(ctx context.Context) (core.Instance, error) { func (m *stubMetadata) Self(ctx context.Context) (cloudtypes.Instance, error) {
return m.selfRes, m.selfErr return m.selfRes, m.selfErr
} }
func (m *stubMetadata) GetInstance(ctx context.Context, providerID string) (core.Instance, error) { func (m *stubMetadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
return m.getInstanceRes, m.getInstanceErr return m.getInstanceRes, m.getInstanceErr
} }

9
go.mod
View File

@ -32,7 +32,10 @@ replace (
k8s.io/sample-controller => k8s.io/sample-controller v0.24.0 k8s.io/sample-controller => k8s.io/sample-controller v0.24.0
) )
replace github.com/nmiculinic/wg-quick-go v0.1.3 => github.com/katexochen/wg-quick-go v0.1.3-beta.1 replace (
github.com/martinjungblut/go-cryptsetup => github.com/daniel-weisse/go-cryptsetup v0.0.0-20220511084044-b537356aa24b
github.com/nmiculinic/wg-quick-go v0.1.3 => github.com/katexochen/wg-quick-go v0.1.3-beta.1
)
require ( require (
cloud.google.com/go/compute v1.5.0 cloud.google.com/go/compute v1.5.0
@ -105,7 +108,6 @@ require (
k8s.io/kubernetes v1.24.0 k8s.io/kubernetes v1.24.0
k8s.io/mount-utils v0.24.0 k8s.io/mount-utils v0.24.0
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
sigs.k8s.io/yaml v1.3.0
) )
require ( require (
@ -223,6 +225,5 @@ require (
sigs.k8s.io/kustomize/api v0.11.4 // indirect sigs.k8s.io/kustomize/api v0.11.4 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
) )
replace github.com/martinjungblut/go-cryptsetup => github.com/daniel-weisse/go-cryptsetup v0.0.0-20220511084044-b537356aa24b

View File

@ -11,6 +11,8 @@ import (
"github.com/spf13/cobra/doc" "github.com/spf13/cobra/doc"
) )
var seeAlsoRegexp = regexp.MustCompile(`(?s)### SEE ALSO\n.+?\n\n`)
func main() { func main() {
cobra.EnableCommandSorting = false cobra.EnableCommandSorting = false
rootCmd := cmd.NewRootCmd() rootCmd := cmd.NewRootCmd()
@ -28,7 +30,7 @@ func main() {
} }
// Remove "see also" sections. They list parent and child commands, which is not interesting for us. // Remove "see also" sections. They list parent and child commands, which is not interesting for us.
cleanedBody := regexp.MustCompile(`(?s)### SEE ALSO\n.+?\n\n`).ReplaceAll(body.Bytes(), nil) cleanedBody := seeAlsoRegexp.ReplaceAll(body.Bytes(), nil)
fmt.Printf("Commands:\n\n%s\n%s", cmdList, cleanedBody) fmt.Printf("Commands:\n\n%s\n%s", cmdList, cleanedBody)
} }

View File

@ -197,6 +197,13 @@ func Default() *Config {
FromPort: constants.NodePortFrom, FromPort: constants.NodePortFrom,
ToPort: constants.NodePortTo, ToPort: constants.NodePortTo,
}, },
{
Name: "kubernetes",
Description: "Kubernetes",
Protocol: "tcp",
IPRange: "0.0.0.0/0",
FromPort: constants.KubernetesPort,
},
}, },
Provider: ProviderConfig{ Provider: ProviderConfig{
// TODO remove our subscriptions from the default config // TODO remove our subscriptions from the default config
@ -204,7 +211,7 @@ func Default() *Config {
SubscriptionID: "0d202bbb-4fa7-4af8-8125-58c269a05435", SubscriptionID: "0d202bbb-4fa7-4af8-8125-58c269a05435",
TenantID: "adb650a8-5da3-4b15-b4b0-3daf65ff7626", TenantID: "adb650a8-5da3-4b15-b4b0-3daf65ff7626",
Location: "North Europe", Location: "North Europe",
Image: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/CONSTELLATION-IMAGES/providers/Microsoft.Compute/galleries/Constellation/images/constellation-coreos/versions/0.0.1654096948", Image: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/CONSTELLATION-IMAGES/providers/Microsoft.Compute/galleries/Constellation/images/constellation-coreos/versions/0.0.1654162332",
Measurements: azurePCRs, Measurements: azurePCRs,
UserAssignedIdentity: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/constellation-images/providers/Microsoft.ManagedIdentity/userAssignedIdentities/constellation-dev-identity", UserAssignedIdentity: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/constellation-images/providers/Microsoft.ManagedIdentity/userAssignedIdentities/constellation-dev-identity",
}, },
@ -212,7 +219,7 @@ func Default() *Config {
Project: "constellation-331613", Project: "constellation-331613",
Region: "europe-west3", Region: "europe-west3",
Zone: "europe-west3-b", Zone: "europe-west3-b",
Image: "projects/constellation-images/global/images/constellation-coreos-1654096948", Image: "projects/constellation-images/global/images/constellation-coreos-1654162332",
ServiceAccountRoles: []string{ ServiceAccountRoles: []string{
"roles/compute.instanceAdmin.v1", "roles/compute.instanceAdmin.v1",
"roles/compute.networkAdmin", "roles/compute.networkAdmin",

View File

@ -29,8 +29,9 @@ const (
NVMEOverTCPPort = 8009 NVMEOverTCPPort = 8009
// Default NodePort Range // Default NodePort Range
// https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport // https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
NodePortFrom = 30000 NodePortFrom = 30000
NodePortTo = 32767 NodePortTo = 32767
KubernetesPort = 6443
// //
// Filenames. // Filenames.

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/edgelesssys/constellation/coordinator/atls" "github.com/edgelesssys/constellation/coordinator/atls"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core" "github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto" "github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
@ -20,33 +21,33 @@ import (
) )
func TestRequestKeyLoop(t *testing.T) { func TestRequestKeyLoop(t *testing.T) {
defaultInstance := core.Instance{ defaultInstance := cloudtypes.Instance{
Name: "test-instance", Name: "test-instance",
ProviderID: "/test/provider", ProviderID: "/test/provider",
Role: role.Coordinator, Role: role.Coordinator,
IPs: []string{"192.0.2.1"}, PrivateIPs: []string{"192.0.2.1"},
} }
testCases := map[string]struct { testCases := map[string]struct {
server *stubAPIServer server *stubAPIServer
wantCalls int wantCalls int
listResponse []core.Instance listResponse []cloudtypes.Instance
dontStartServer bool dontStartServer bool
}{ }{
"success": { "success": {
server: &stubAPIServer{requestStateDiskKeyResp: &pubproto.RequestStateDiskKeyResponse{}}, server: &stubAPIServer{requestStateDiskKeyResp: &pubproto.RequestStateDiskKeyResponse{}},
listResponse: []core.Instance{defaultInstance}, listResponse: []cloudtypes.Instance{defaultInstance},
}, },
"no error if server throws an error": { "no error if server throws an error": {
server: &stubAPIServer{ server: &stubAPIServer{
requestStateDiskKeyResp: &pubproto.RequestStateDiskKeyResponse{}, requestStateDiskKeyResp: &pubproto.RequestStateDiskKeyResponse{},
requestStateDiskKeyErr: errors.New("error"), requestStateDiskKeyErr: errors.New("error"),
}, },
listResponse: []core.Instance{defaultInstance}, listResponse: []cloudtypes.Instance{defaultInstance},
}, },
"no error if the server can not be reached": { "no error if the server can not be reached": {
server: &stubAPIServer{requestStateDiskKeyResp: &pubproto.RequestStateDiskKeyResponse{}}, server: &stubAPIServer{requestStateDiskKeyResp: &pubproto.RequestStateDiskKeyResponse{}},
listResponse: []core.Instance{defaultInstance}, listResponse: []cloudtypes.Instance{defaultInstance},
dontStartServer: true, dontStartServer: true,
}, },
"no error if no endpoint is available": { "no error if no endpoint is available": {
@ -54,13 +55,13 @@ func TestRequestKeyLoop(t *testing.T) {
}, },
"works for multiple endpoints": { "works for multiple endpoints": {
server: &stubAPIServer{requestStateDiskKeyResp: &pubproto.RequestStateDiskKeyResponse{}}, server: &stubAPIServer{requestStateDiskKeyResp: &pubproto.RequestStateDiskKeyResponse{}},
listResponse: []core.Instance{ listResponse: []cloudtypes.Instance{
defaultInstance, defaultInstance,
{ {
Name: "test-instance-2", Name: "test-instance-2",
ProviderID: "/test/provider", ProviderID: "/test/provider",
Role: role.Coordinator, Role: role.Coordinator,
IPs: []string{"192.0.2.2"}, PrivateIPs: []string{"192.0.2.2"},
}, },
}, },
}, },
@ -192,19 +193,15 @@ func (s *stubAPIServer) RequestStateDiskKey(ctx context.Context, in *pubproto.Re
} }
type stubMetadata struct { type stubMetadata struct {
listResponse []core.Instance listResponse []cloudtypes.Instance
} }
func (s stubMetadata) List(ctx context.Context) ([]core.Instance, error) { func (s stubMetadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
return s.listResponse, nil return s.listResponse, nil
} }
func (s stubMetadata) Self(ctx context.Context) (core.Instance, error) { func (s stubMetadata) Self(ctx context.Context) (cloudtypes.Instance, error) {
return core.Instance{}, nil return cloudtypes.Instance{}, nil
}
func (s stubMetadata) GetInstance(ctx context.Context, providerID string) (core.Instance, error) {
return core.Instance{}, nil
} }
func (s stubMetadata) SignalRole(ctx context.Context, role role.Role) error { func (s stubMetadata) SignalRole(ctx context.Context, role role.Role) error {