mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
2d8fcd9bf4
Co-authored-by: Malte Poll <mp@edgeless.systems> Co-authored-by: katexochen <katexochen@users.noreply.github.com> Co-authored-by: Daniel Weiße <dw@edgeless.systems> Co-authored-by: Thomas Tendyck <tt@edgeless.systems> Co-authored-by: Benedict Schlueter <bs@edgeless.systems> Co-authored-by: leongross <leon.gross@rub.de> Co-authored-by: Moritz Eckert <m1gh7ym0@gmail.com>
183 lines
6.3 KiB
Go
183 lines
6.3 KiB
Go
package azure
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
|
"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"
|
|
"github.com/edgelesssys/constellation/coordinator/core"
|
|
"github.com/edgelesssys/constellation/coordinator/role"
|
|
)
|
|
|
|
// Metadata implements azure metadata APIs.
|
|
type Metadata struct {
|
|
imdsAPI
|
|
networkInterfacesAPI
|
|
scaleSetsAPI
|
|
virtualMachinesAPI
|
|
virtualMachineScaleSetVMsAPI
|
|
tagsAPI
|
|
}
|
|
|
|
// 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
|
|
}
|
|
subscriptionID, _, err := extractBasicsFromProviderID("azure://" + instanceMetadata.Compute.ResourceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
networkInterfacesAPI := armnetwork.NewInterfacesClient(subscriptionID, cred, nil)
|
|
scaleSetsAPI := armcompute.NewVirtualMachineScaleSetsClient(subscriptionID, cred, nil)
|
|
virtualMachinesAPI := armcompute.NewVirtualMachinesClient(subscriptionID, cred, nil)
|
|
virtualMachineScaleSetVMsAPI := armcompute.NewVirtualMachineScaleSetVMsClient(subscriptionID, cred, nil)
|
|
tagsAPI := armresources.NewTagsClient(subscriptionID, cred, nil)
|
|
|
|
return &Metadata{
|
|
imdsAPI: &imdsAPI,
|
|
networkInterfacesAPI: &networkInterfacesClient{networkInterfacesAPI},
|
|
scaleSetsAPI: &scaleSetsClient{scaleSetsAPI},
|
|
virtualMachinesAPI: &virtualMachinesClient{virtualMachinesAPI},
|
|
virtualMachineScaleSetVMsAPI: &virtualMachineScaleSetVMsClient{virtualMachineScaleSetVMsAPI},
|
|
tagsAPI: &tagsClient{tagsAPI},
|
|
}, nil
|
|
}
|
|
|
|
// List retrieves all instances belonging to the current constellation.
|
|
func (m *Metadata) List(ctx context.Context) ([]core.Instance, error) {
|
|
providerID, err := m.providerID(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, resourceGroup, err := extractBasicsFromProviderID(providerID)
|
|
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
|
|
}
|
|
instances := make([]core.Instance, 0, len(singleInstances)+len(scaleSetInstances))
|
|
instances = append(instances, singleInstances...)
|
|
instances = append(instances, scaleSetInstances...)
|
|
return instances, nil
|
|
}
|
|
|
|
// Self retrieves the current instance.
|
|
func (m *Metadata) Self(ctx context.Context) (core.Instance, error) {
|
|
providerID, err := m.providerID(ctx)
|
|
if err != nil {
|
|
return core.Instance{}, err
|
|
}
|
|
return m.GetInstance(ctx, providerID)
|
|
}
|
|
|
|
// GetInstance retrieves an instance using its providerID.
|
|
func (m *Metadata) GetInstance(ctx context.Context, providerID string) (core.Instance, error) {
|
|
instance, singleErr := m.getVM(ctx, providerID)
|
|
if singleErr == nil {
|
|
return instance, nil
|
|
}
|
|
instance, scaleSetErr := m.getScaleSetVM(ctx, providerID)
|
|
if scaleSetErr == 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)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
if _, _, _, _, err := splitScaleSetProviderID(providerID); err == nil {
|
|
// 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())
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// extractBasicsFromProviderID extracts subscriptionID and resourceGroup from both types of valid azure providerID.
|
|
func extractBasicsFromProviderID(providerID string) (subscriptionID, resourceGroup string, err error) {
|
|
subscriptionID, resourceGroup, _, err = splitVMProviderID(providerID)
|
|
if err == nil {
|
|
return subscriptionID, resourceGroup, nil
|
|
}
|
|
subscriptionID, resourceGroup, _, _, err = splitScaleSetProviderID(providerID)
|
|
if err == nil {
|
|
return subscriptionID, resourceGroup, nil
|
|
}
|
|
return "", "", fmt.Errorf("providerID %v is malformatted", providerID)
|
|
}
|
|
|
|
// 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 {
|
|
keyPathRegexp := regexp.MustCompile(`^\/home\/([^\/]+)\/\.ssh\/authorized_keys$`)
|
|
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
|
|
}
|