forked from linuxkit/linuxkit
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathprovider_gcp.go
127 lines (112 loc) · 3.17 KB
/
provider_gcp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"path"
"strings"
"time"
)
const (
project = "http://metadata.google.internal/computeMetadata/v1/project/"
instance = "http://metadata.google.internal/computeMetadata/v1/instance/"
)
// ProviderGCP is the type implementing the Provider interface for GCP
type ProviderGCP struct {
}
// NewGCP returns a new ProviderGCP
func NewGCP() *ProviderGCP {
return &ProviderGCP{}
}
func (p *ProviderGCP) String() string {
return "GCP"
}
// Probe checks if we are running on GCP
func (p *ProviderGCP) Probe() bool {
// Getting the hostname should always work...
_, err := gcpGet(instance + "hostname")
return err == nil
}
// Extract gets both the GCP specific and generic userdata
func (p *ProviderGCP) Extract() ([]byte, error) {
// Get host name. This must not fail
hostname, err := gcpGet(instance + "hostname")
if err != nil {
return nil, err
}
err = os.WriteFile(path.Join(ConfigPath, Hostname), hostname, 0644)
if err != nil {
return nil, fmt.Errorf("GCP: Failed to write hostname: %s", err)
}
if err := p.handleSSH(); err != nil {
log.Printf("GCP: Failed to get ssh data: %s", err)
}
// Generic userdata
userData, err := gcpGet(instance + "attributes/user-data")
if err != nil {
log.Printf("GCP: Failed to get user-data: %s", err)
// This is not an error
return nil, nil
}
return userData, nil
}
// gcpGet requests and extracts the requested URL
func gcpGet(url string) ([]byte, error) {
var client = &http.Client{
Timeout: time.Second * 2,
}
req, err := http.NewRequest("", url, nil)
if err != nil {
return nil, fmt.Errorf("GCP: http.NewRequest failed: %s", err)
}
req.Header.Set("Metadata-Flavor", "Google")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("GCP: Could not contact metadata service: %s", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("GCP: Status not ok: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("GCP: Failed to read http response: %s", err)
}
return body, nil
}
// SSH keys:
// TODO also retrieve the instance keys and respect block
//
// project keys see:
// https://cloud.google.com/compute/docs/instances/ssh-keys
//
// The keys have usernames attached, but as a simplification
// we are going to add them all to one root file
// TODO split them into individual user files and make the ssh
//
// container construct those users
func (p *ProviderGCP) handleSSH() error {
sshKeys, err := gcpGet(project + "attributes/ssh-keys")
if err != nil {
return fmt.Errorf("Failed to get sshKeys: %s", err)
}
if _, err := os.Stat(path.Join(ConfigPath, SSH)); os.IsNotExist(err) {
if err := os.Mkdir(path.Join(ConfigPath, SSH), 0755); err != nil {
return fmt.Errorf("Failed to create %s: %s", SSH, err)
}
}
rootKeys := ""
for _, line := range strings.Split(string(sshKeys), "\n") {
parts := strings.SplitN(line, ":", 2)
// ignoring username for now
if len(parts) == 2 {
rootKeys = rootKeys + parts[1] + "\n"
}
}
err = os.WriteFile(path.Join(ConfigPath, SSH, "authorized_keys"), []byte(rootKeys), 0600)
if err != nil {
return fmt.Errorf("Failed to write ssh keys: %s", err)
}
return nil
}