1
16
17 package gcp
18
19 import (
20 "encoding/json"
21 "net/http"
22 "os"
23 "os/exec"
24 "runtime"
25 "strings"
26 "sync"
27 "time"
28
29 utilnet "k8s.io/apimachinery/pkg/util/net"
30 "k8s.io/cloud-provider/credentialconfig"
31 "k8s.io/klog/v2"
32 "k8s.io/kubernetes/pkg/credentialprovider"
33 "k8s.io/legacy-cloud-providers/gce/gcpcredential"
34 )
35
36 const (
37 metadataURL = "http://metadata.google.internal./computeMetadata/v1/"
38 metadataAttributes = metadataURL + "instance/attributes/"
39
40 DockerConfigKey = metadataAttributes + "google-dockercfg"
41
42 DockerConfigURLKey = metadataAttributes + "google-dockercfg-url"
43 serviceAccounts = metadataURL + "instance/service-accounts/"
44 metadataScopes = metadataURL + "instance/service-accounts/default/scopes"
45
46 StorageScopePrefix = "https://www.googleapis.com/auth/devstorage"
47 cloudPlatformScopePrefix = "https://www.googleapis.com/auth/cloud-platform"
48 defaultServiceAccount = "default/"
49 )
50
51
52
53 var gceProductNameFile = "/sys/class/dmi/id/product_name"
54
55 var metadataHeader = &http.Header{
56 "Metadata-Flavor": []string{"Google"},
57 }
58
59 var warnOnce sync.Once
60
61
62
63 func init() {
64 tr := utilnet.SetTransportDefaults(&http.Transport{})
65 metadataHTTPClientTimeout := time.Second * 10
66 httpClient := &http.Client{
67 Transport: tr,
68 Timeout: metadataHTTPClientTimeout,
69 }
70 credentialprovider.RegisterCredentialProvider("google-dockercfg",
71 &credentialprovider.CachingDockerConfigProvider{
72 Provider: &DockerConfigKeyProvider{
73 MetadataProvider: MetadataProvider{Client: httpClient},
74 },
75 Lifetime: 60 * time.Second,
76 })
77
78 credentialprovider.RegisterCredentialProvider("google-dockercfg-url",
79 &credentialprovider.CachingDockerConfigProvider{
80 Provider: &DockerConfigURLKeyProvider{
81 MetadataProvider: MetadataProvider{Client: httpClient},
82 },
83 Lifetime: 60 * time.Second,
84 })
85
86 credentialprovider.RegisterCredentialProvider("google-container-registry",
87
88
89 &ContainerRegistryProvider{
90 MetadataProvider: MetadataProvider{Client: httpClient},
91 })
92 }
93
94
95
96 type MetadataProvider struct {
97 Client *http.Client
98 }
99
100
101
102 type DockerConfigKeyProvider struct {
103 MetadataProvider
104 }
105
106
107
108 type DockerConfigURLKeyProvider struct {
109 MetadataProvider
110 }
111
112
113
114
115
116 type ContainerRegistryProvider struct {
117 MetadataProvider
118 }
119
120
121
122 func onGCEVM() bool {
123 var name string
124
125 if runtime.GOOS == "windows" {
126 data, err := exec.Command("wmic", "computersystem", "get", "model").Output()
127 if err != nil {
128 return false
129 }
130 fields := strings.Split(strings.TrimSpace(string(data)), "\r\n")
131 if len(fields) != 2 {
132 klog.V(2).Infof("Received unexpected value retrieving system model: %q", string(data))
133 return false
134 }
135 name = fields[1]
136 } else {
137 data, err := os.ReadFile(gceProductNameFile)
138 if err != nil {
139 klog.V(2).Infof("Error while reading product_name: %v", err)
140 return false
141 }
142 name = strings.TrimSpace(string(data))
143 }
144 return name == "Google" || name == "Google Compute Engine"
145 }
146
147
148 func (g *MetadataProvider) Enabled() bool {
149 onGCE := onGCEVM()
150 if !onGCE {
151 return false
152 }
153 if credentialprovider.AreLegacyCloudCredentialProvidersDisabled() {
154 warnOnce.Do(func() {
155 klog.V(4).Infof("GCP credential provider is now disabled. Please refer to sig-cloud-provider for guidance on external credential provider integration for GCP")
156 })
157 return false
158 }
159 return true
160 }
161
162
163 func (g *DockerConfigKeyProvider) Provide(image string) credentialprovider.DockerConfig {
164 return registryToDocker(gcpcredential.ProvideConfigKey(g.Client, image))
165 }
166
167
168 func (g *DockerConfigURLKeyProvider) Provide(image string) credentialprovider.DockerConfig {
169 return registryToDocker(gcpcredential.ProvideURLKey(g.Client, image))
170 }
171
172
173
174 func runWithBackoff(f func() ([]byte, error)) []byte {
175 var backoff = 100 * time.Millisecond
176 const maxBackoff = time.Minute
177 for {
178 value, err := f()
179 if err == nil {
180 return value
181 }
182 time.Sleep(backoff)
183 backoff = backoff * 2
184 if backoff > maxBackoff {
185 backoff = maxBackoff
186 }
187 }
188 }
189
190
191
192
193
194
195
196
197
198
199
200 func (g *ContainerRegistryProvider) Enabled() bool {
201 if !onGCEVM() {
202 return false
203 }
204
205 if credentialprovider.AreLegacyCloudCredentialProvidersDisabled() {
206 warnOnce.Do(func() {
207 klog.V(4).Infof("GCP credential provider is now disabled. Please refer to sig-cloud-provider for guidance on external credential provider integration for GCP")
208 })
209 return false
210 }
211
212
213 value := runWithBackoff(func() ([]byte, error) {
214 value, err := gcpcredential.ReadURL(serviceAccounts, g.Client, metadataHeader)
215 if err != nil {
216 klog.V(2).Infof("Failed to Get service accounts from gce metadata server: %v", err)
217 }
218 return value, err
219 })
220
221
222
223
224 defaultServiceAccountExists := false
225 for _, sa := range strings.Split(string(value), "\n") {
226 if strings.TrimSpace(sa) == defaultServiceAccount {
227 defaultServiceAccountExists = true
228 break
229 }
230 }
231 if !defaultServiceAccountExists {
232 klog.V(2).Infof("'default' service account does not exist. Found following service accounts: %q", string(value))
233 return false
234 }
235 url := metadataScopes + "?alt=json"
236 value = runWithBackoff(func() ([]byte, error) {
237 value, err := gcpcredential.ReadURL(url, g.Client, metadataHeader)
238 if err != nil {
239 klog.V(2).Infof("Failed to Get scopes in default service account from gce metadata server: %v", err)
240 }
241 return value, err
242 })
243 var scopes []string
244 if err := json.Unmarshal(value, &scopes); err != nil {
245 klog.Errorf("Failed to unmarshal scopes: %v", err)
246 return false
247 }
248 for _, v := range scopes {
249
250 if strings.HasPrefix(v, StorageScopePrefix) || strings.HasPrefix(v, cloudPlatformScopePrefix) {
251 return true
252 }
253 }
254 klog.Warningf("Google container registry is disabled, no storage scope is available: %s", value)
255 return false
256 }
257
258
259 func (g *ContainerRegistryProvider) Provide(image string) credentialprovider.DockerConfig {
260 return registryToDocker(gcpcredential.ProvideContainerRegistry(g.Client, image))
261 }
262
263 func registryToDocker(registryConfig credentialconfig.RegistryConfig) credentialprovider.DockerConfig {
264 dockerConfig := credentialprovider.DockerConfig{}
265 for k, v := range registryConfig {
266 dockerConfig[k] = credentialprovider.DockerConfigEntry{
267 Username: v.Username,
268 Password: v.Password,
269 Email: v.Email,
270 }
271 }
272 return dockerConfig
273 }
274
View as plain text