2018-03-28 02:15:52 +00:00
|
|
|
package azuremonitor
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// VirtualMachineMetadata contains information about a VM from the metadata service
|
|
|
|
type VirtualMachineMetadata struct {
|
|
|
|
Raw string
|
|
|
|
AzureResourceID string
|
|
|
|
Compute struct {
|
|
|
|
Location string `json:"location"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Offer string `json:"offer"`
|
|
|
|
OsType string `json:"osType"`
|
|
|
|
PlacementGroupID string `json:"placementGroupId"`
|
|
|
|
PlatformFaultDomain string `json:"platformFaultDomain"`
|
|
|
|
PlatformUpdateDomain string `json:"platformUpdateDomain"`
|
|
|
|
Publisher string `json:"publisher"`
|
|
|
|
ResourceGroupName string `json:"resourceGroupName"`
|
|
|
|
Sku string `json:"sku"`
|
|
|
|
SubscriptionID string `json:"subscriptionId"`
|
|
|
|
Tags string `json:"tags"`
|
|
|
|
Version string `json:"version"`
|
|
|
|
VMID string `json:"vmId"`
|
|
|
|
VMScaleSetName string `json:"vmScaleSetName"`
|
|
|
|
VMSize string `json:"vmSize"`
|
|
|
|
Zone string `json:"zone"`
|
|
|
|
} `json:"compute"`
|
|
|
|
Network struct {
|
|
|
|
Interface []struct {
|
|
|
|
Ipv4 struct {
|
|
|
|
IPAddress []struct {
|
|
|
|
PrivateIPAddress string `json:"privateIpAddress"`
|
|
|
|
PublicIPAddress string `json:"publicIpAddress"`
|
|
|
|
} `json:"ipAddress"`
|
|
|
|
Subnet []struct {
|
|
|
|
Address string `json:"address"`
|
|
|
|
Prefix string `json:"prefix"`
|
|
|
|
} `json:"subnet"`
|
|
|
|
} `json:"ipv4"`
|
|
|
|
Ipv6 struct {
|
|
|
|
IPAddress []interface{} `json:"ipAddress"`
|
|
|
|
} `json:"ipv6"`
|
|
|
|
MacAddress string `json:"macAddress"`
|
|
|
|
} `json:"interface"`
|
|
|
|
} `json:"network"`
|
|
|
|
}
|
|
|
|
|
2018-04-29 07:31:24 +00:00
|
|
|
// msiToken is the managed service identity token
|
|
|
|
type msiToken struct {
|
2018-03-28 02:15:52 +00:00
|
|
|
AccessToken string `json:"access_token"`
|
|
|
|
RefreshToken string `json:"refresh_token"`
|
|
|
|
ExpiresIn string `json:"expires_in"`
|
|
|
|
ExpiresOn string `json:"expires_on"`
|
|
|
|
NotBefore string `json:"not_before"`
|
|
|
|
Resource string `json:"resource"`
|
|
|
|
TokenType string `json:"token_type"`
|
|
|
|
|
|
|
|
expiresAt time.Time
|
|
|
|
notBefore time.Time
|
|
|
|
raw string
|
|
|
|
}
|
|
|
|
|
2018-04-29 07:31:24 +00:00
|
|
|
func (m *msiToken) parseTimes() {
|
2018-03-28 02:15:52 +00:00
|
|
|
val, err := strconv.ParseInt(m.ExpiresOn, 10, 64)
|
|
|
|
if err == nil {
|
|
|
|
m.expiresAt = time.Unix(val, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
val, err = strconv.ParseInt(m.NotBefore, 10, 64)
|
|
|
|
if err == nil {
|
|
|
|
m.notBefore = time.Unix(val, 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExpiresInDuration returns the duration until the token expires
|
2018-05-07 16:52:54 +00:00
|
|
|
func (m *msiToken) expiresInDuration() time.Duration {
|
2018-03-28 02:15:52 +00:00
|
|
|
expiresDuration := m.expiresAt.Sub(time.Now().UTC())
|
|
|
|
return expiresDuration
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetMsiToken retrieves a managed service identity token from the specified port on the local VM
|
2018-05-07 16:52:54 +00:00
|
|
|
func (a *AzureMonitor) getMsiToken(clientID string) (*msiToken, error) {
|
2018-03-28 02:15:52 +00:00
|
|
|
// Acquire an MSI token. Documented at:
|
|
|
|
// https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/how-to-use-vm-token
|
|
|
|
//
|
|
|
|
//GET http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F&client_id=712eac09-e943-418c-9be6-9fd5c91078bl HTTP/1.1 Metadata: true
|
|
|
|
|
|
|
|
// Create HTTP request for MSI token to access Azure Resource Manager
|
|
|
|
var msiEndpoint *url.URL
|
|
|
|
msiEndpoint, err := url.Parse(msiInstanceMetadataURL)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
msiParameters := url.Values{}
|
2018-05-07 16:52:54 +00:00
|
|
|
// Resource ID defaults to https://management.azure.com
|
|
|
|
msiParameters.Add("resource", defaultMSIResource)
|
2018-03-28 02:15:52 +00:00
|
|
|
msiParameters.Add("api-version", "2018-02-01")
|
|
|
|
|
|
|
|
// Client id is optional
|
|
|
|
if clientID != "" {
|
|
|
|
msiParameters.Add("client_id", clientID)
|
|
|
|
}
|
|
|
|
|
|
|
|
msiEndpoint.RawQuery = msiParameters.Encode()
|
|
|
|
req, err := http.NewRequest("GET", msiEndpoint.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.Header.Add("Metadata", "true")
|
|
|
|
|
2018-05-03 09:11:31 +00:00
|
|
|
resp, err := a.client.Do(req)
|
2018-03-28 02:15:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
reply, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.StatusCode >= 300 || resp.StatusCode < 200 {
|
2018-05-07 16:52:54 +00:00
|
|
|
return nil, fmt.Errorf("E! Get Error. %d HTTP response: %s response body: %s",
|
2018-03-28 02:15:52 +00:00
|
|
|
resp.StatusCode, resp.Status, reply)
|
|
|
|
}
|
|
|
|
|
2018-04-29 07:31:24 +00:00
|
|
|
var token msiToken
|
2018-03-28 02:15:52 +00:00
|
|
|
if err := json.Unmarshal(reply, &token); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
token.parseTimes()
|
|
|
|
token.raw = string(reply)
|
|
|
|
return &token, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetInstanceMetadata retrieves metadata about the current Azure VM
|
2018-05-07 16:52:54 +00:00
|
|
|
func (a *AzureMonitor) GetInstanceMetadata() error {
|
2018-03-28 02:15:52 +00:00
|
|
|
req, err := http.NewRequest("GET", vmInstanceMetadataURL, nil)
|
|
|
|
if err != nil {
|
2018-05-07 16:52:54 +00:00
|
|
|
return fmt.Errorf("Error creating HTTP request")
|
2018-03-28 02:15:52 +00:00
|
|
|
}
|
|
|
|
req.Header.Set("Metadata", "true")
|
|
|
|
|
2018-05-03 09:11:31 +00:00
|
|
|
resp, err := a.client.Do(req)
|
2018-03-28 02:15:52 +00:00
|
|
|
if err != nil {
|
2018-05-07 16:52:54 +00:00
|
|
|
return err
|
2018-03-28 02:15:52 +00:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
reply, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
2018-05-07 16:52:54 +00:00
|
|
|
return err
|
2018-03-28 02:15:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if resp.StatusCode >= 300 || resp.StatusCode < 200 {
|
2018-05-07 16:52:54 +00:00
|
|
|
return fmt.Errorf("Post Error. HTTP response code:%d message:%s reply:\n%s",
|
2018-03-28 02:15:52 +00:00
|
|
|
resp.StatusCode, resp.Status, reply)
|
|
|
|
}
|
|
|
|
|
|
|
|
var metadata VirtualMachineMetadata
|
|
|
|
if err := json.Unmarshal(reply, &metadata); err != nil {
|
2018-05-07 16:52:54 +00:00
|
|
|
return err
|
2018-03-28 02:15:52 +00:00
|
|
|
}
|
|
|
|
|
2018-05-07 16:52:54 +00:00
|
|
|
if a.ResourceID == "" {
|
|
|
|
a.ResourceID = fmt.Sprintf(resourceIDTemplate,
|
|
|
|
metadata.Compute.SubscriptionID, metadata.Compute.ResourceGroupName, metadata.Compute.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
if a.Region == "" {
|
|
|
|
a.Region = metadata.Compute.Location
|
|
|
|
}
|
|
|
|
|
|
|
|
a.url = fmt.Sprintf(urlTemplate, a.Region, a.ResourceID)
|
|
|
|
|
|
|
|
return nil
|
2018-03-28 02:15:52 +00:00
|
|
|
}
|