2022-03-22 11:03:15 -04:00
package azure
import (
"context"
"fmt"
"net/http"
"regexp"
"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-03-22 11:03:15 -04:00
"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/resources/armresources"
2022-05-24 04:04:42 -04:00
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
2022-03-22 11:03:15 -04:00
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role"
2022-06-10 07:18:30 -04:00
"github.com/edgelesssys/constellation/internal/azureshared"
2022-03-22 11:03:15 -04:00
)
2022-05-24 04:04:42 -04:00
var (
publicIPAddressRegexp = regexp . MustCompile ( ` /subscriptions/[^/]+/resourceGroups/[^/]+/providers/Microsoft.Network/publicIPAddresses/(?P<IPname>[^/]+) ` )
keyPathRegexp = regexp . MustCompile ( ` ^\/home\/([^\/]+)\/\.ssh\/authorized_keys$ ` )
)
2022-03-22 11:03:15 -04:00
// Metadata implements azure metadata APIs.
type Metadata struct {
imdsAPI
2022-05-24 04:04:42 -04:00
virtualNetworksAPI
securityGroupsAPI
2022-03-22 11:03:15 -04:00
networkInterfacesAPI
2022-05-24 04:04:42 -04:00
publicIPAddressesAPI
2022-03-22 11:03:15 -04:00
scaleSetsAPI
2022-05-24 04:04:42 -04:00
loadBalancerAPI
2022-03-22 11:03:15 -04:00
virtualMachinesAPI
virtualMachineScaleSetVMsAPI
tagsAPI
2022-06-10 07:18:30 -04:00
applicationInsightsAPI
2022-03-22 11:03:15 -04:00
}
// NewMetadata creates a new Metadata.
func NewMetadata ( ctx context . Context ) ( * Metadata , error ) {
cred , err := azidentity . NewDefaultAzureCredential ( nil )
if err != nil {
return nil , err
}
// The default http client may use a system-wide proxy and it is recommended to disable the proxy explicitly:
// https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=linux#proxies
// See also: https://github.com/microsoft/azureimds/blob/master/imdssample.go#L10
imdsAPI := imdsClient {
client : & http . Client { Transport : & http . Transport { Proxy : nil } } ,
}
instanceMetadata , err := imdsAPI . Retrieve ( ctx )
if err != nil {
return nil , err
}
2022-06-10 07:18:30 -04:00
subscriptionID , _ , err := azureshared . BasicsFromProviderID ( "azure://" + instanceMetadata . Compute . ResourceID )
2022-03-22 11:03:15 -04:00
if err != nil {
return nil , err
}
2022-05-24 04:04:42 -04:00
virtualNetworksAPI := armnetwork . NewVirtualNetworksClient ( subscriptionID , cred , nil )
2022-03-22 11:03:15 -04:00
networkInterfacesAPI := armnetwork . NewInterfacesClient ( subscriptionID , cred , nil )
2022-05-24 04:04:42 -04:00
publicIPAddressesAPI := armnetwork . NewPublicIPAddressesClient ( subscriptionID , cred , nil )
securityGroupsAPI := armnetwork . NewSecurityGroupsClient ( subscriptionID , cred , nil )
2022-03-22 11:03:15 -04:00
scaleSetsAPI := armcompute . NewVirtualMachineScaleSetsClient ( subscriptionID , cred , nil )
2022-05-24 04:04:42 -04:00
loadBalancerAPI := armnetwork . NewLoadBalancersClient ( subscriptionID , cred , nil )
2022-03-22 11:03:15 -04:00
virtualMachinesAPI := armcompute . NewVirtualMachinesClient ( subscriptionID , cred , nil )
virtualMachineScaleSetVMsAPI := armcompute . NewVirtualMachineScaleSetVMsClient ( subscriptionID , cred , nil )
tagsAPI := armresources . NewTagsClient ( subscriptionID , cred , nil )
2022-06-10 07:18:30 -04:00
applicationInsightsAPI := armapplicationinsights . NewComponentsClient ( subscriptionID , cred , nil )
2022-03-22 11:03:15 -04:00
return & Metadata {
imdsAPI : & imdsAPI ,
2022-05-24 04:04:42 -04:00
virtualNetworksAPI : & virtualNetworksClient { virtualNetworksAPI } ,
2022-03-22 11:03:15 -04:00
networkInterfacesAPI : & networkInterfacesClient { networkInterfacesAPI } ,
2022-05-24 04:04:42 -04:00
securityGroupsAPI : & securityGroupsClient { securityGroupsAPI } ,
publicIPAddressesAPI : & publicIPAddressesClient { publicIPAddressesAPI } ,
loadBalancerAPI : & loadBalancersClient { loadBalancerAPI } ,
2022-03-22 11:03:15 -04:00
scaleSetsAPI : & scaleSetsClient { scaleSetsAPI } ,
virtualMachinesAPI : & virtualMachinesClient { virtualMachinesAPI } ,
virtualMachineScaleSetVMsAPI : & virtualMachineScaleSetVMsClient { virtualMachineScaleSetVMsAPI } ,
tagsAPI : & tagsClient { tagsAPI } ,
2022-06-10 07:18:30 -04:00
applicationInsightsAPI : & applicationInsightsClient { applicationInsightsAPI } ,
2022-03-22 11:03:15 -04:00
} , nil
}
// List retrieves all instances belonging to the current constellation.
2022-05-24 04:04:42 -04:00
func ( m * Metadata ) List ( ctx context . Context ) ( [ ] cloudtypes . Instance , error ) {
2022-03-22 11:03:15 -04:00
providerID , err := m . providerID ( ctx )
if err != nil {
return nil , err
}
2022-06-10 07:18:30 -04:00
_ , resourceGroup , err := azureshared . BasicsFromProviderID ( providerID )
2022-03-22 11:03:15 -04:00
if err != nil {
return nil , err
}
singleInstances , err := m . listVMs ( ctx , resourceGroup )
if err != nil {
return nil , err
}
scaleSetInstances , err := m . listScaleSetVMs ( ctx , resourceGroup )
if err != nil {
return nil , err
}
2022-05-24 04:04:42 -04:00
instances := make ( [ ] cloudtypes . Instance , 0 , len ( singleInstances ) + len ( scaleSetInstances ) )
2022-03-22 11:03:15 -04:00
instances = append ( instances , singleInstances ... )
instances = append ( instances , scaleSetInstances ... )
return instances , nil
}
// Self retrieves the current instance.
2022-05-24 04:04:42 -04:00
func ( m * Metadata ) Self ( ctx context . Context ) ( cloudtypes . Instance , error ) {
2022-03-22 11:03:15 -04:00
providerID , err := m . providerID ( ctx )
if err != nil {
2022-05-24 04:04:42 -04:00
return cloudtypes . Instance { } , err
2022-03-22 11:03:15 -04:00
}
return m . GetInstance ( ctx , providerID )
}
// GetInstance retrieves an instance using its providerID.
2022-05-24 04:04:42 -04:00
func ( m * Metadata ) GetInstance ( ctx context . Context , providerID string ) ( cloudtypes . Instance , error ) {
2022-03-22 11:03:15 -04:00
instance , singleErr := m . getVM ( ctx , providerID )
if singleErr == nil {
return instance , nil
}
instance , scaleSetErr := m . getScaleSetVM ( ctx , providerID )
if scaleSetErr == nil {
return instance , nil
}
2022-05-24 04:04:42 -04:00
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 )
2022-03-22 11:03:15 -04:00
}
// SignalRole signals the constellation role via cloud provider metadata.
// On single VMs, the role is stored in tags, on scale set VMs, the role is inferred from the scale set and not signalied explicitly.
func ( m * Metadata ) SignalRole ( ctx context . Context , role role . Role ) error {
providerID , err := m . providerID ( ctx )
if err != nil {
return err
}
2022-06-10 07:18:30 -04:00
if _ , _ , _ , _ , err := azureshared . ScaleSetInformationFromProviderID ( providerID ) ; err == nil {
2022-03-22 11:03:15 -04:00
// scale set instances cannot store tags and role can be inferred from scale set name.
return nil
}
return m . setTag ( ctx , core . RoleMetadataKey , role . String ( ) )
}
2022-05-24 04:04:42 -04:00
// 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
}
2022-06-10 07:18:30 -04:00
_ , resourceGroup , err := azureshared . BasicsFromProviderID ( providerID )
2022-05-24 04:04:42 -04:00
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
}
2022-06-10 07:18:30 -04:00
_ , resourceGroup , err := azureshared . BasicsFromProviderID ( providerID )
2022-05-24 04:04:42 -04:00
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
}
2022-06-10 07:18:30 -04:00
_ , resourceGroup , err := azureshared . BasicsFromProviderID ( providerID )
2022-05-24 04:04:42 -04:00
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
}
2022-06-10 07:18:30 -04:00
_ , resourceGroup , err := azureshared . BasicsFromProviderID ( providerID )
2022-05-24 04:04:42 -04:00
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
}
2022-03-22 11:03:15 -04:00
// 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 {
return nil
}
// Supported is used to determine if metadata API is implemented for this cloud provider.
func ( m * Metadata ) Supported ( ) bool {
return true
}
// providerID retrieves the current instances providerID.
func ( m * Metadata ) providerID ( ctx context . Context ) ( string , error ) {
instanceMetadata , err := m . imdsAPI . Retrieve ( ctx )
if err != nil {
return "" , err
}
return "azure://" + instanceMetadata . Compute . ResourceID , nil
}
// extractInstanceTags converts azure tags into metadata key-value pairs.
func extractInstanceTags ( tags map [ string ] * string ) map [ string ] string {
metadataMap := map [ string ] string { }
for key , value := range tags {
if value == nil {
continue
}
metadataMap [ key ] = * value
}
return metadataMap
}
// extractSSHKeys extracts SSH public keys from azure instance OS Profile.
func extractSSHKeys ( sshConfig armcompute . SSHConfiguration ) map [ string ] [ ] string {
sshKeys := map [ string ] [ ] string { }
for _ , key := range sshConfig . PublicKeys {
if key == nil || key . Path == nil || key . KeyData == nil {
continue
}
matches := keyPathRegexp . FindStringSubmatch ( * key . Path )
if len ( matches ) != 2 {
continue
}
sshKeys [ matches [ 1 ] ] = append ( sshKeys [ matches [ 1 ] ] , * key . KeyData )
}
return sshKeys
}