Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fabaf55

Browse files
committed
Metadata package: add Azure provider
Signed-off-by: Michael Schnerring <[email protected]>
1 parent 73b4f50 commit fabaf55

File tree

4 files changed

+513
-4
lines changed

4 files changed

+513
-4
lines changed

pkg/metadata/main.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"flag"
66
"fmt"
77
"io/ioutil"
8+
"net/http"
89
"os"
910
"path"
1011
"strconv"
@@ -80,7 +81,19 @@ func main() {
8081
log.SetLevel(log.DebugLevel)
8182
}
8283

83-
providers := []string{"aws", "gcp", "hetzner", "openstack", "scaleway", "vultr", "digitalocean", "packet", "metaldata", "cdrom"}
84+
providers := []string{
85+
"aws",
86+
"gcp",
87+
"hetzner",
88+
"openstack",
89+
"scaleway",
90+
"vultr",
91+
"digitalocean",
92+
"packet",
93+
"cdrom",
94+
"azure-imds",
95+
"azure-ovf",
96+
}
8497
args := flag.Args()
8598
if len(args) > 0 {
8699
providers = args
@@ -105,6 +118,14 @@ func main() {
105118
netProviders = append(netProviders, NewDigitalOcean())
106119
case p == "metaldata":
107120
netProviders = append(netProviders, NewMetalData())
121+
case p == "azure-imds":
122+
netProviders = append(netProviders, NewAzureIMDS())
123+
case p == "azure-ovf":
124+
// TODO not every provider should create a separate http client
125+
client := &http.Client{
126+
Timeout: time.Second * 2,
127+
}
128+
netProviders = append(netProviders, NewAzureOVF(client))
108129
case p == "cdrom":
109130
cdromProviders = ListCDROMs()
110131
case strings.HasPrefix(p, "file="):
@@ -207,7 +228,7 @@ func processUserData(basePath string, data []byte) error {
207228
var root ConfigFile
208229
if err := json.Unmarshal(data, &root); err != nil {
209230
// Userdata is no JSON, presumably...
210-
log.Printf("Could not unmarshall userdata: %s", err)
231+
log.Printf("Could not unmarshal userdata: %s", err)
211232
// This is not an error
212233
return nil
213234
}
@@ -291,7 +312,7 @@ func retry(attempts int, sleep time.Duration, f func() error) (err error) {
291312

292313
time.Sleep(sleep)
293314

294-
log.Printf("retrying after error:", err)
315+
log.Debugf("Retrying after error: %s", err)
295316
}
296-
return fmt.Errorf("after %d attempts, last error: %s", attempts, err)
317+
return fmt.Errorf("After %d attempts, last error: %s", attempts, err)
297318
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"net/http"
7+
"os"
8+
"path"
9+
"strings"
10+
"time"
11+
12+
log "github.com/sirupsen/logrus"
13+
)
14+
15+
// ProviderAzureIMDS reads from Azure's Instance Metadata Service (IMDS) API.
16+
type ProviderAzureIMDS struct {
17+
client *http.Client
18+
providerOVF *ProviderAzureOVF
19+
}
20+
21+
// NewAzureIMDS factory
22+
func NewAzureIMDS() *ProviderAzureIMDS {
23+
client := &http.Client{
24+
Timeout: time.Second * 2,
25+
}
26+
return &ProviderAzureIMDS{
27+
client: client,
28+
providerOVF: NewAzureOVF(client),
29+
}
30+
}
31+
32+
func (p *ProviderAzureIMDS) String() string {
33+
return "Azure-IMDS"
34+
}
35+
36+
// Probe checks if Azure IMDS API is available
37+
func (p *ProviderAzureIMDS) Probe() bool {
38+
// "Poll" VM Unique ID
39+
// See: https://azure.microsoft.com/en-us/blog/accessing-and-using-azure-vm-unique-id/
40+
pollVMID := func() error {
41+
_, err := p.imdsGet("compute/vmId")
42+
return err
43+
}
44+
if err := retry(6, 5*time.Second, pollVMID); err != nil {
45+
log.Debugf("%s: Probe failed: %s", p.String(), err)
46+
return false
47+
}
48+
// Probe fallback provider
49+
return p.providerOVF.Probe()
50+
}
51+
52+
// Extract user data via Azure IMDS.
53+
func (p *ProviderAzureIMDS) Extract() ([]byte, error) {
54+
if err := p.saveHostname(); err != nil {
55+
return nil, fmt.Errorf("%s: %s", p.String(), err)
56+
}
57+
58+
if err := p.saveSSHKeys(); err != nil {
59+
log.Warnf("%s: Saving SSH keys failed: %s", p.String(), err)
60+
}
61+
62+
p.imdsSave("network/interface/0/ipv4/ipAddress/0/publicIpAddress")
63+
p.imdsSave("network/interface/0/ipv4/ipAddress/0/privateIpAddress")
64+
p.imdsSave("compute/zone")
65+
p.imdsSave("compute/vmId")
66+
67+
userData, err := p.getUserData()
68+
if err != nil {
69+
return nil, fmt.Errorf("%s: %s", p.String(), err)
70+
}
71+
return userData, nil
72+
}
73+
74+
func (p *ProviderAzureIMDS) saveHostname() error {
75+
hostname, err := p.imdsGet("compute/name")
76+
if err != nil {
77+
return err
78+
}
79+
err = ioutil.WriteFile(path.Join(ConfigPath, Hostname), hostname, 0644)
80+
if err != nil {
81+
return fmt.Errorf("%s: Failed to write hostname: %s", p.String(), err)
82+
}
83+
log.Debugf("%s: Saved hostname: %s", p.String(), string(hostname))
84+
return nil
85+
}
86+
87+
func (p *ProviderAzureIMDS) saveSSHKeys() error {
88+
// TODO support multiple keys
89+
sshKey, err := p.imdsGet("compute/publicKeys/0/keyData")
90+
if err != nil {
91+
return fmt.Errorf("Getting SSH key failed: %s", err)
92+
}
93+
if err := os.Mkdir(path.Join(ConfigPath, SSH), 0755); err != nil {
94+
return fmt.Errorf("Creating directory %s failed: %s", SSH, err)
95+
}
96+
err = ioutil.WriteFile(path.Join(ConfigPath, SSH, "authorized_keys"), sshKey, 0600)
97+
if err != nil {
98+
return fmt.Errorf("Writing SSH key failed: %s", err)
99+
}
100+
log.Debugf("%s: Saved authorized_keys", p.String())
101+
return nil
102+
}
103+
104+
// Get resource value from IMDS and write to file in ConfigPath
105+
func (p *ProviderAzureIMDS) imdsSave(resourceName string) {
106+
if value, err := p.imdsGet(resourceName); err == nil {
107+
fileName := strings.Replace(resourceName, "/", "_", -1)
108+
err = ioutil.WriteFile(path.Join(ConfigPath, fileName), value, 0644)
109+
if err != nil {
110+
log.Warnf("%s: Failed to write file %s:%s %s", p.String(), fileName, value, err)
111+
}
112+
log.Debugf("%s: Saved resource %s: %s", p.String(), resourceName, string(value))
113+
} else {
114+
log.Warnf("%s: Failed to get resource %s: %s", p.String(), resourceName, err)
115+
}
116+
}
117+
118+
// Get IMDS resource value
119+
func (p *ProviderAzureIMDS) imdsGet(resourceName string) ([]byte, error) {
120+
req, err := http.NewRequest("GET", imdsURL(resourceName), nil)
121+
if err != nil {
122+
return nil, fmt.Errorf("http.NewRequest failed: %s", err)
123+
}
124+
req.Header.Set("Metadata", "true")
125+
126+
resp, err := p.client.Do(req)
127+
if err != nil {
128+
return nil, fmt.Errorf("IMDS unavailable: %s", err)
129+
}
130+
defer resp.Body.Close()
131+
if resp.StatusCode != 200 {
132+
return nil, fmt.Errorf("IMDS returned status code: %d", resp.StatusCode)
133+
}
134+
135+
body, err := ioutil.ReadAll(resp.Body)
136+
if err != nil {
137+
return nil, fmt.Errorf("Reading HTTP response failed: %s", err)
138+
}
139+
140+
return body, nil
141+
}
142+
143+
// Build Azure Instance Metadata Service (IMDS) URL
144+
// For available nodes, see: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
145+
func imdsURL(node string) string {
146+
const (
147+
baseURL = "http://169.254.169.254/metadata/instance"
148+
// TODO Version 2020-10-01 might not yet be available in every region
149+
//apiVersion = "2020-10-01"
150+
apiVersion = "2020-09-01"
151+
// For leaf nodes in /metadata/instance, the format=json doesn't work.
152+
// For these queries, format=text needs to be explicitly specified
153+
// because the default format is JSON.
154+
params = "?api-version=" + apiVersion + "&format=text"
155+
)
156+
if len(node) > 0 {
157+
return baseURL + "/" + node + params
158+
}
159+
return baseURL + params
160+
}
161+
162+
func (p *ProviderAzureIMDS) getUserData() ([]byte, error) {
163+
userData, err := p.imdsGet("compute/customData")
164+
if err != nil {
165+
log.Errorf("Failed to get user data: %s", err)
166+
return nil, err
167+
}
168+
169+
// TODO
170+
// Fallback OVF provider will report ready
171+
// defer ReportReady(p.client)
172+
173+
if len(userData) > 0 { // Always false
174+
log.Warnf("%s: Unexpectedly received user data: \n%s", p.String(), string(userData))
175+
// TODO
176+
// Getting user data via IMDS is disabled. See upstream issue:
177+
// * https://github.com/MicrosoftDocs/azure-docs/issues/64154
178+
// * https://github.com/MicrosoftDocs/azure-docs/issues/30370 (OP)
179+
// return userData, nil
180+
}
181+
182+
// log.Debugf("%s: user data is empty", p.String())
183+
// return nil, nil
184+
185+
// Fallback
186+
log.Debugf(
187+
"%s: user data retrieval is disabled for this provider.\n"+
188+
"Falling back to %s provider",
189+
p.String(),
190+
p.providerOVF.String())
191+
return p.providerOVF.Extract()
192+
193+
}

0 commit comments

Comments
 (0)