2022-03-22 11:03:15 -04:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
2022-06-10 07:18:30 -04:00
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
|
2022-07-27 16:02:33 -04:00
|
|
|
armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2"
|
2022-03-22 11:03:15 -04:00
|
|
|
"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/services/graphrbac/1.6/graphrbac"
|
|
|
|
"github.com/Azure/go-autorest/autorest"
|
|
|
|
"github.com/Azure/go-autorest/autorest/azure/auth"
|
2022-06-07 05:08:44 -04:00
|
|
|
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
2022-06-08 02:17:52 -04:00
|
|
|
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
|
2022-03-22 11:03:15 -04:00
|
|
|
"github.com/edgelesssys/constellation/internal/state"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
graphAPIResource = "https://graph.windows.net"
|
|
|
|
managementAPIResource = "https://management.azure.com"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Client is a client for Azure.
|
|
|
|
type Client struct {
|
|
|
|
networksAPI
|
|
|
|
networkSecurityGroupsAPI
|
|
|
|
resourceGroupAPI
|
|
|
|
scaleSetsAPI
|
|
|
|
publicIPAddressesAPI
|
|
|
|
networkInterfacesAPI
|
2022-05-24 04:04:42 -04:00
|
|
|
loadBalancersAPI
|
2022-03-22 11:03:15 -04:00
|
|
|
virtualMachinesAPI
|
|
|
|
applicationsAPI
|
|
|
|
servicePrincipalsAPI
|
|
|
|
roleAssignmentsAPI
|
2022-06-10 07:18:30 -04:00
|
|
|
applicationInsightsAPI
|
2022-03-22 11:03:15 -04:00
|
|
|
|
2022-08-01 03:47:39 -04:00
|
|
|
pollFrequency time.Duration
|
2022-03-22 11:03:15 -04:00
|
|
|
adReplicationLagCheckInterval time.Duration
|
|
|
|
adReplicationLagCheckMaxRetries int
|
|
|
|
|
2022-06-29 09:26:29 -04:00
|
|
|
workers cloudtypes.Instances
|
|
|
|
controlPlanes cloudtypes.Instances
|
2022-03-22 11:03:15 -04:00
|
|
|
|
|
|
|
name string
|
|
|
|
uid string
|
|
|
|
resourceGroup string
|
|
|
|
location string
|
|
|
|
subscriptionID string
|
|
|
|
tenantID string
|
|
|
|
subnetID string
|
2022-06-29 09:26:29 -04:00
|
|
|
controlPlaneScaleSet string
|
|
|
|
workerScaleSet string
|
2022-05-24 04:04:42 -04:00
|
|
|
loadBalancerName string
|
|
|
|
loadBalancerPubIP string
|
2022-03-22 11:03:15 -04:00
|
|
|
networkSecurityGroup string
|
|
|
|
adAppObjectID string
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewFromDefault creates a client with initialized clients.
|
|
|
|
func NewFromDefault(subscriptionID, tenantID string) (*Client, error) {
|
|
|
|
cred, err := azidentity.NewDefaultAzureCredential(nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
graphAuthorizer, err := getAuthorizer(graphAPIResource)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
managementAuthorizer, err := getAuthorizer(managementAPIResource)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-07-27 16:02:33 -04:00
|
|
|
netAPI, err := armnetwork.NewVirtualNetworksClient(subscriptionID, cred, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
netSecGrpAPI, err := armnetwork.NewSecurityGroupsClient(subscriptionID, cred, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
resGroupAPI, err := armresources.NewResourceGroupsClient(subscriptionID, cred, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
scaleSetAPI, err := armcomputev2.NewVirtualMachineScaleSetsClient(subscriptionID, cred, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
publicIPAddressesAPI, err := armnetwork.NewPublicIPAddressesClient(subscriptionID, cred, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
networkInterfacesAPI, err := armnetwork.NewInterfacesClient(subscriptionID, cred, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
loadBalancersAPI, err := armnetwork.NewLoadBalancersClient(subscriptionID, cred, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
virtualMachinesAPI, err := armcomputev2.NewVirtualMachinesClient(subscriptionID, cred, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
applicationInsightsAPI, err := armapplicationinsights.NewComponentsClient(subscriptionID, cred, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-22 11:03:15 -04:00
|
|
|
applicationsAPI := graphrbac.NewApplicationsClient(tenantID)
|
|
|
|
applicationsAPI.Authorizer = graphAuthorizer
|
|
|
|
servicePrincipalsAPI := graphrbac.NewServicePrincipalsClient(tenantID)
|
|
|
|
servicePrincipalsAPI.Authorizer = graphAuthorizer
|
|
|
|
roleAssignmentsAPI := authorization.NewRoleAssignmentsClient(subscriptionID)
|
|
|
|
roleAssignmentsAPI.Authorizer = managementAuthorizer
|
|
|
|
|
|
|
|
return &Client{
|
2022-07-27 16:02:33 -04:00
|
|
|
networksAPI: netAPI,
|
|
|
|
networkSecurityGroupsAPI: netSecGrpAPI,
|
|
|
|
resourceGroupAPI: resGroupAPI,
|
|
|
|
scaleSetsAPI: scaleSetAPI,
|
|
|
|
publicIPAddressesAPI: publicIPAddressesAPI,
|
|
|
|
networkInterfacesAPI: networkInterfacesAPI,
|
|
|
|
loadBalancersAPI: loadBalancersAPI,
|
|
|
|
applicationsAPI: applicationsAPI,
|
|
|
|
servicePrincipalsAPI: servicePrincipalsAPI,
|
|
|
|
roleAssignmentsAPI: roleAssignmentsAPI,
|
|
|
|
virtualMachinesAPI: virtualMachinesAPI,
|
2022-06-10 07:18:30 -04:00
|
|
|
applicationInsightsAPI: applicationInsightsAPI,
|
2022-03-22 11:03:15 -04:00
|
|
|
subscriptionID: subscriptionID,
|
|
|
|
tenantID: tenantID,
|
2022-06-29 09:26:29 -04:00
|
|
|
workers: cloudtypes.Instances{},
|
|
|
|
controlPlanes: cloudtypes.Instances{},
|
2022-08-01 03:47:39 -04:00
|
|
|
pollFrequency: time.Second * 5,
|
2022-03-22 11:03:15 -04:00
|
|
|
adReplicationLagCheckInterval: adReplicationLagCheckInterval,
|
|
|
|
adReplicationLagCheckMaxRetries: adReplicationLagCheckMaxRetries,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewInitialized creates and initializes client by setting the subscriptionID, location and name
|
|
|
|
// of the Constellation.
|
|
|
|
func NewInitialized(subscriptionID, tenantID, name, location string) (*Client, error) {
|
|
|
|
client, err := NewFromDefault(subscriptionID, tenantID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = client.init(location, name)
|
|
|
|
return client, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// init initializes the client.
|
|
|
|
func (c *Client) init(location, name string) error {
|
|
|
|
c.location = location
|
|
|
|
c.name = name
|
|
|
|
uid, err := c.generateUID()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.uid = uid
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetState returns the state of the client as ConstellationState.
|
|
|
|
func (c *Client) GetState() (state.ConstellationState, error) {
|
|
|
|
var stat state.ConstellationState
|
|
|
|
stat.CloudProvider = cloudprovider.Azure.String()
|
|
|
|
if len(c.resourceGroup) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no resource group")
|
|
|
|
}
|
|
|
|
stat.AzureResourceGroup = c.resourceGroup
|
|
|
|
if c.name == "" {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no name")
|
|
|
|
}
|
|
|
|
stat.Name = c.name
|
|
|
|
if len(c.uid) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no uid")
|
|
|
|
}
|
|
|
|
stat.UID = c.uid
|
2022-07-08 04:59:59 -04:00
|
|
|
if len(c.loadBalancerPubIP) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no load balancer public IP")
|
|
|
|
}
|
|
|
|
stat.BootstrapperHost = c.loadBalancerPubIP
|
2022-03-22 11:03:15 -04:00
|
|
|
if len(c.location) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no location")
|
|
|
|
}
|
|
|
|
stat.AzureLocation = c.location
|
|
|
|
if len(c.subscriptionID) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no subscription")
|
|
|
|
}
|
|
|
|
stat.AzureSubscription = c.subscriptionID
|
|
|
|
if len(c.tenantID) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no tenant")
|
|
|
|
}
|
|
|
|
stat.AzureTenant = c.tenantID
|
|
|
|
if len(c.subnetID) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no subnet")
|
|
|
|
}
|
|
|
|
stat.AzureSubnet = c.subnetID
|
|
|
|
if len(c.networkSecurityGroup) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no network security group")
|
|
|
|
}
|
|
|
|
stat.AzureNetworkSecurityGroup = c.networkSecurityGroup
|
2022-06-29 09:26:29 -04:00
|
|
|
if len(c.workerScaleSet) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no worker scale set")
|
2022-03-29 07:30:50 -04:00
|
|
|
}
|
2022-07-29 02:10:51 -04:00
|
|
|
stat.AzureWorkerScaleSet = c.workerScaleSet
|
2022-06-29 09:26:29 -04:00
|
|
|
if len(c.controlPlaneScaleSet) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no control plane scale set")
|
2022-03-29 07:30:50 -04:00
|
|
|
}
|
2022-07-29 02:10:51 -04:00
|
|
|
stat.AzureControlPlaneScaleSet = c.controlPlaneScaleSet
|
2022-06-29 09:26:29 -04:00
|
|
|
if len(c.workers) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no workers")
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2022-07-29 02:10:51 -04:00
|
|
|
stat.AzureWorkerInstances = c.workers
|
2022-06-29 09:26:29 -04:00
|
|
|
if len(c.controlPlanes) == 0 {
|
|
|
|
return state.ConstellationState{}, errors.New("client has no control planes")
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2022-07-29 02:10:51 -04:00
|
|
|
stat.AzureControlPlaneInstances = c.controlPlanes
|
2022-03-22 11:03:15 -04:00
|
|
|
// AD App Object ID does not have to be set at all times
|
|
|
|
stat.AzureADAppObjectID = c.adAppObjectID
|
|
|
|
|
|
|
|
return stat, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetState sets the state of the client to the handed ConstellationState.
|
|
|
|
func (c *Client) SetState(stat state.ConstellationState) error {
|
|
|
|
if stat.CloudProvider != cloudprovider.Azure.String() {
|
|
|
|
return errors.New("state is not azure state")
|
|
|
|
}
|
|
|
|
if len(stat.AzureResourceGroup) == 0 {
|
|
|
|
return errors.New("state has no resource group")
|
|
|
|
}
|
|
|
|
c.resourceGroup = stat.AzureResourceGroup
|
|
|
|
if stat.Name == "" {
|
|
|
|
return errors.New("state has no name")
|
|
|
|
}
|
|
|
|
c.name = stat.Name
|
|
|
|
if len(stat.UID) == 0 {
|
|
|
|
return errors.New("state has no uuid")
|
|
|
|
}
|
|
|
|
c.uid = stat.UID
|
2022-07-08 04:59:59 -04:00
|
|
|
if len(stat.BootstrapperHost) == 0 {
|
|
|
|
return errors.New("state has no bootstrapper host")
|
|
|
|
}
|
|
|
|
c.loadBalancerPubIP = stat.BootstrapperHost
|
2022-03-22 11:03:15 -04:00
|
|
|
if len(stat.AzureLocation) == 0 {
|
|
|
|
return errors.New("state has no location")
|
|
|
|
}
|
|
|
|
c.location = stat.AzureLocation
|
|
|
|
if len(stat.AzureSubscription) == 0 {
|
|
|
|
return errors.New("state has no subscription")
|
|
|
|
}
|
|
|
|
c.subscriptionID = stat.AzureSubscription
|
|
|
|
if len(stat.AzureTenant) == 0 {
|
|
|
|
return errors.New("state has no tenant")
|
|
|
|
}
|
|
|
|
c.tenantID = stat.AzureTenant
|
|
|
|
if len(stat.AzureSubnet) == 0 {
|
|
|
|
return errors.New("state has no subnet")
|
|
|
|
}
|
|
|
|
c.subnetID = stat.AzureSubnet
|
|
|
|
if len(stat.AzureNetworkSecurityGroup) == 0 {
|
|
|
|
return errors.New("state has no subnet")
|
|
|
|
}
|
|
|
|
c.networkSecurityGroup = stat.AzureNetworkSecurityGroup
|
2022-07-29 02:10:51 -04:00
|
|
|
if len(stat.AzureWorkerScaleSet) == 0 {
|
2022-06-29 09:26:29 -04:00
|
|
|
return errors.New("state has no worker scale set")
|
2022-03-29 07:30:50 -04:00
|
|
|
}
|
2022-07-29 02:10:51 -04:00
|
|
|
c.workerScaleSet = stat.AzureWorkerScaleSet
|
|
|
|
if len(stat.AzureControlPlaneScaleSet) == 0 {
|
2022-06-29 09:26:29 -04:00
|
|
|
return errors.New("state has no worker scale set")
|
2022-03-29 07:30:50 -04:00
|
|
|
}
|
2022-07-29 02:10:51 -04:00
|
|
|
c.controlPlaneScaleSet = stat.AzureControlPlaneScaleSet
|
|
|
|
if len(stat.AzureWorkerInstances) == 0 {
|
2022-06-29 09:26:29 -04:00
|
|
|
return errors.New("state has no workers")
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2022-07-29 02:10:51 -04:00
|
|
|
c.workers = stat.AzureWorkerInstances
|
|
|
|
if len(stat.AzureControlPlaneInstances) == 0 {
|
2022-06-29 09:26:29 -04:00
|
|
|
return errors.New("state has no control planes")
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2022-07-29 02:10:51 -04:00
|
|
|
c.controlPlanes = stat.AzureControlPlaneInstances
|
2022-03-22 11:03:15 -04:00
|
|
|
// AD App Object ID does not have to be set at all times
|
|
|
|
c.adAppObjectID = stat.AzureADAppObjectID
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) generateUID() (string, error) {
|
|
|
|
letters := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
|
|
|
|
|
|
|
const uidLen = 5
|
|
|
|
uid := make([]byte, uidLen)
|
|
|
|
for i := 0; i < uidLen; i++ {
|
|
|
|
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
uid[i] = letters[n.Int64()]
|
|
|
|
}
|
|
|
|
return string(uid), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getAuthorizer creates an autorest.Authorizer for different Azure AD APIs using either environment variables or azure cli credentials.
|
|
|
|
func getAuthorizer(resource string) (autorest.Authorizer, error) {
|
|
|
|
authorizer, cliErr := auth.NewAuthorizerFromCLIWithResource(resource)
|
|
|
|
if cliErr == nil {
|
|
|
|
return authorizer, nil
|
|
|
|
}
|
|
|
|
authorizer, envErr := auth.NewAuthorizerFromEnvironmentWithResource(resource)
|
|
|
|
if envErr == nil {
|
|
|
|
return authorizer, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("unable to create authorizer from env or cli: %v %v", envErr, cliErr)
|
|
|
|
}
|