2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-03-22 11:03:15 -04:00
|
|
|
package azure
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2022-08-29 05:54:30 -04:00
|
|
|
"errors"
|
2022-10-06 06:14:41 -04:00
|
|
|
"fmt"
|
2022-03-22 11:03:15 -04:00
|
|
|
"io"
|
|
|
|
"net/http"
|
2022-08-29 05:54:30 -04:00
|
|
|
"time"
|
2022-10-06 06:14:41 -04:00
|
|
|
|
2022-10-24 10:58:21 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
2022-10-06 06:14:41 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/role"
|
2022-03-22 11:03:15 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// subset of azure imds API: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=linux
|
|
|
|
// this is not yet available through the azure sdk (see https://github.com/Azure/azure-rest-api-specs/issues/4408)
|
|
|
|
|
|
|
|
const (
|
|
|
|
imdsURL = "http://169.254.169.254/metadata/instance"
|
|
|
|
imdsAPIVersion = "2021-02-01"
|
2022-08-29 05:54:30 -04:00
|
|
|
maxCacheAge = 12 * time.Hour
|
2022-03-22 11:03:15 -04:00
|
|
|
)
|
|
|
|
|
2023-03-21 07:46:49 -04:00
|
|
|
// IMDSClient is a client for the Azure Instance Metadata Service.
|
|
|
|
type IMDSClient struct {
|
2022-03-22 11:03:15 -04:00
|
|
|
client *http.Client
|
2022-08-29 05:54:30 -04:00
|
|
|
|
|
|
|
cache metadataResponse
|
|
|
|
cacheTime time.Time
|
|
|
|
}
|
|
|
|
|
2023-03-21 07:46:49 -04:00
|
|
|
// NewIMDSClient creates a new IMDSClient.
|
|
|
|
func NewIMDSClient() *IMDSClient {
|
|
|
|
// 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
|
|
|
|
return &IMDSClient{
|
|
|
|
client: &http.Client{Transport: &http.Transport{Proxy: nil}},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tags returns the tags of the instance the function is called from.
|
|
|
|
func (c *IMDSClient) Tags(ctx context.Context) (map[string]string, error) {
|
|
|
|
if c.timeForUpdate() || len(c.cache.Compute.Tags) == 0 {
|
|
|
|
if err := c.update(ctx); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tags := make(map[string]string, len(c.cache.Compute.Tags))
|
|
|
|
for _, tag := range c.cache.Compute.Tags {
|
|
|
|
tags[tag.Name] = tag.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
return tags, nil
|
|
|
|
}
|
|
|
|
|
2022-11-15 03:08:18 -05:00
|
|
|
// providerID returns the provider ID of the instance the function is called from.
|
2023-03-21 07:46:49 -04:00
|
|
|
func (c *IMDSClient) providerID(ctx context.Context) (string, error) {
|
2022-08-29 05:54:30 -04:00
|
|
|
if c.timeForUpdate() || c.cache.Compute.ResourceID == "" {
|
|
|
|
if err := c.update(ctx); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.cache.Compute.ResourceID == "" {
|
|
|
|
return "", errors.New("unable to get provider id")
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.cache.Compute.ResourceID, nil
|
|
|
|
}
|
|
|
|
|
2023-03-21 07:46:49 -04:00
|
|
|
func (c *IMDSClient) name(ctx context.Context) (string, error) {
|
2022-11-15 03:08:18 -05:00
|
|
|
if c.timeForUpdate() || c.cache.Compute.OSProfile.ComputerName == "" {
|
|
|
|
if err := c.update(ctx); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.cache.Compute.OSProfile.ComputerName == "" {
|
|
|
|
return "", errors.New("unable to get name")
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.cache.Compute.OSProfile.ComputerName, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// subscriptionID returns the subscription ID of the instance the function
|
2022-08-29 05:54:30 -04:00
|
|
|
// is called from.
|
2023-03-21 07:46:49 -04:00
|
|
|
func (c *IMDSClient) subscriptionID(ctx context.Context) (string, error) {
|
2022-08-29 05:54:30 -04:00
|
|
|
if c.timeForUpdate() || c.cache.Compute.SubscriptionID == "" {
|
|
|
|
if err := c.update(ctx); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.cache.Compute.SubscriptionID == "" {
|
|
|
|
return "", errors.New("unable to get subscription id")
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.cache.Compute.SubscriptionID, nil
|
|
|
|
}
|
|
|
|
|
2022-11-15 03:08:18 -05:00
|
|
|
// resourceGroup returns the resource group of the instance the function
|
2022-08-29 05:54:30 -04:00
|
|
|
// is called from.
|
2023-03-21 07:46:49 -04:00
|
|
|
func (c *IMDSClient) resourceGroup(ctx context.Context) (string, error) {
|
2022-08-29 05:54:30 -04:00
|
|
|
if c.timeForUpdate() || c.cache.Compute.ResourceGroup == "" {
|
|
|
|
if err := c.update(ctx); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.cache.Compute.ResourceGroup == "" {
|
|
|
|
return "", errors.New("unable to get resource group")
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.cache.Compute.ResourceGroup, nil
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
2022-11-15 03:08:18 -05:00
|
|
|
// uid returns the UID of the cluster, based on the tags on the instance
|
|
|
|
// the function is called from, which are inherited from the scale set.
|
2023-03-21 07:46:49 -04:00
|
|
|
func (c *IMDSClient) uid(ctx context.Context) (string, error) {
|
2022-08-29 05:54:30 -04:00
|
|
|
if c.timeForUpdate() || len(c.cache.Compute.Tags) == 0 {
|
|
|
|
if err := c.update(ctx); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-06 06:14:41 -04:00
|
|
|
for _, tag := range c.cache.Compute.Tags {
|
2022-10-24 10:58:21 -04:00
|
|
|
if tag.Name == cloud.TagUID {
|
2022-10-06 06:14:41 -04:00
|
|
|
return tag.Value, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", fmt.Errorf("unable to get uid from metadata tags %v", c.cache.Compute.Tags)
|
|
|
|
}
|
|
|
|
|
2022-11-26 13:44:34 -05:00
|
|
|
// initSecretHash returns the hash of the init secret of the cluster, based on the tags on the instance
|
|
|
|
// the function is called from, which are inherited from the scale set.
|
2023-03-21 07:46:49 -04:00
|
|
|
func (c *IMDSClient) initSecretHash(ctx context.Context) (string, error) {
|
2022-11-26 13:44:34 -05:00
|
|
|
if c.timeForUpdate() || len(c.cache.Compute.Tags) == 0 {
|
|
|
|
if err := c.update(ctx); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tag := range c.cache.Compute.Tags {
|
|
|
|
if tag.Name == cloud.TagInitSecretHash {
|
|
|
|
return tag.Value, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", fmt.Errorf("unable to get tag %s from metadata tags %v", cloud.TagInitSecretHash, c.cache.Compute.Tags)
|
|
|
|
}
|
|
|
|
|
2022-11-15 03:08:18 -05:00
|
|
|
// role returns the role of the instance the function is called from.
|
2023-03-21 07:46:49 -04:00
|
|
|
func (c *IMDSClient) role(ctx context.Context) (role.Role, error) {
|
2022-10-06 06:14:41 -04:00
|
|
|
if c.timeForUpdate() || len(c.cache.Compute.Tags) == 0 {
|
|
|
|
if err := c.update(ctx); err != nil {
|
|
|
|
return role.Unknown, err
|
|
|
|
}
|
2022-08-29 05:54:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tag := range c.cache.Compute.Tags {
|
2022-10-24 10:58:21 -04:00
|
|
|
if tag.Name == cloud.TagRole {
|
2022-10-06 06:14:41 -04:00
|
|
|
return role.FromString(tag.Value), nil
|
2022-08-29 05:54:30 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-06 06:14:41 -04:00
|
|
|
return role.Unknown, fmt.Errorf("unable to get role from metadata tags %v", c.cache.Compute.Tags)
|
2022-08-29 05:54:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// timeForUpdate checks whether an update is needed due to cache age.
|
2023-03-21 07:46:49 -04:00
|
|
|
func (c *IMDSClient) timeForUpdate() bool {
|
2022-08-29 05:54:30 -04:00
|
|
|
return time.Since(c.cacheTime) > maxCacheAge
|
|
|
|
}
|
|
|
|
|
|
|
|
// update updates instance metadata from the azure imds API.
|
2023-03-21 07:46:49 -04:00
|
|
|
func (c *IMDSClient) update(ctx context.Context) error {
|
2022-08-29 05:54:30 -04:00
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, imdsURL, http.NoBody)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
2022-08-29 05:54:30 -04:00
|
|
|
return err
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
req.Header.Add("Metadata", "True")
|
|
|
|
query := req.URL.Query()
|
|
|
|
query.Add("format", "json")
|
|
|
|
query.Add("api-version", imdsAPIVersion)
|
|
|
|
req.URL.RawQuery = query.Encode()
|
|
|
|
resp, err := c.client.Do(req)
|
|
|
|
if err != nil {
|
2022-08-29 05:54:30 -04:00
|
|
|
return err
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
2022-08-29 05:54:30 -04:00
|
|
|
return err
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
var res metadataResponse
|
|
|
|
if err := json.Unmarshal(body, &res); err != nil {
|
2022-08-29 05:54:30 -04:00
|
|
|
return err
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2022-08-29 05:54:30 -04:00
|
|
|
|
|
|
|
c.cache = res
|
|
|
|
c.cacheTime = time.Now()
|
|
|
|
return nil
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// metadataResponse contains metadataResponse with only the required values.
|
|
|
|
type metadataResponse struct {
|
2022-08-29 05:54:30 -04:00
|
|
|
Compute metadataResponseCompute `json:"compute,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type metadataResponseCompute struct {
|
|
|
|
ResourceID string `json:"resourceId,omitempty"`
|
|
|
|
SubscriptionID string `json:"subscriptionId,omitempty"`
|
|
|
|
ResourceGroup string `json:"resourceGroupName,omitempty"`
|
|
|
|
Tags []metadataTag `json:"tagsList,omitempty"`
|
2022-11-15 03:08:18 -05:00
|
|
|
OSProfile struct {
|
|
|
|
ComputerName string `json:"computerName,omitempty"`
|
|
|
|
} `json:"osProfile,omitempty"`
|
2022-08-29 05:54:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
type metadataTag struct {
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
Value string `json:"value,omitempty"`
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|