istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/credentialfetcher/plugin/gce.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // This is Google plugin of credentialfetcher. 16 17 package plugin 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "strings" 24 "sync" 25 "time" 26 27 "cloud.google.com/go/compute/metadata" 28 29 "istio.io/istio/pkg/log" 30 "istio.io/istio/security/pkg/util" 31 ) 32 33 var gcecredLog = log.RegisterScope("gcecred", "GCE credential fetcher for istio agent") 34 35 // Token refresh frequency is default to 5 minutes. 36 var rotationInterval = 5 * time.Minute 37 38 // GCE VM credential needs refresh if remaining life time is below 25 minutes. 39 var gracePeriod = 25 * time.Minute 40 41 // rotateToken determines whether to start periodic token rotation job. 42 // This is enabled by default. 43 var rotateToken = true 44 45 // SetTokenRotation enable/disable periodic token rotation job. 46 // This is only for testing purpose, not thread safe. 47 func SetTokenRotation(enable bool) { 48 rotateToken = enable 49 } 50 51 // GCEPlugin is the plugin object. 52 type GCEPlugin struct { 53 // aud is the unique URI agreed upon by both the instance and the system verifying the instance's identity. 54 // For more info: https://cloud.google.com/compute/docs/instances/verifying-instance-identity 55 aud string 56 57 // The location to save the identity token 58 jwtPath string 59 60 // identity provider 61 identityProvider string 62 63 // token refresh 64 rotationTicker *time.Ticker 65 closing chan bool 66 tokenCache string 67 // mutex lock is required to avoid race condition when updating token file and token cache. 68 tokenMutex sync.RWMutex 69 } 70 71 // CreateGCEPlugin creates a Google credential fetcher plugin. Return the pointer to the created plugin. 72 func CreateGCEPlugin(audience, jwtPath, identityProvider string) *GCEPlugin { 73 p := &GCEPlugin{ 74 aud: audience, 75 jwtPath: jwtPath, 76 identityProvider: identityProvider, 77 closing: make(chan bool), 78 } 79 if rotateToken { 80 go p.startTokenRotationJob() 81 } 82 return p 83 } 84 85 func (p *GCEPlugin) Stop() { 86 close(p.closing) 87 } 88 89 func (p *GCEPlugin) startTokenRotationJob() { 90 // Wake up once in a while and refresh GCE VM credential. 91 p.rotationTicker = time.NewTicker(rotationInterval) 92 for { 93 select { 94 case <-p.rotationTicker.C: 95 p.rotate() 96 case <-p.closing: 97 if p.rotationTicker != nil { 98 p.rotationTicker.Stop() 99 } 100 return 101 } 102 } 103 } 104 105 func (p *GCEPlugin) rotate() { 106 if p.shouldRotate(time.Now()) { 107 if _, err := p.GetPlatformCredential(); err != nil { 108 gcecredLog.Errorf("credential refresh failed: %+v", err) 109 } 110 } 111 } 112 113 func (p *GCEPlugin) shouldRotate(now time.Time) bool { 114 p.tokenMutex.RLock() 115 defer p.tokenMutex.RUnlock() 116 117 if p.tokenCache == "" { 118 return true 119 } 120 exp, err := util.GetExp(p.tokenCache) 121 // When fails to get expiration time from token, always refresh the token. 122 if err != nil || exp.IsZero() { 123 return true 124 } 125 rotate := now.After(exp.Add(-gracePeriod)) 126 gcecredLog.Debugf("credential expiration: %s, grace period: %s, should rotate: %t", 127 exp.String(), gracePeriod.String(), rotate) 128 return rotate 129 } 130 131 // GetPlatformCredential fetches the GCE VM identity jwt token from its metadata server, 132 // and write it to jwtPath. The local copy of the token in jwtPath is used by both 133 // Envoy STS client and istio agent to fetch certificate and access token. 134 // Note: this function only works in a GCE VM environment. 135 func (p *GCEPlugin) GetPlatformCredential() (string, error) { 136 p.tokenMutex.Lock() 137 defer p.tokenMutex.Unlock() 138 139 if p.jwtPath == "" { 140 return "", fmt.Errorf("jwtPath is unset") 141 } 142 uri := fmt.Sprintf("instance/service-accounts/default/identity?audience=%s&format=full", p.aud) 143 token, err := metadata.GetWithContext(context.TODO(), uri) 144 if err != nil { 145 gcecredLog.Errorf("Failed to get vm identity token from metadata server: %v", err) 146 return "", err 147 } 148 // Update token cache. 149 p.tokenCache = token 150 gcecredLog.Debugf("Got GCE identity token: %d", len(token)) 151 tokenbytes := []byte(token) 152 err = os.WriteFile(p.jwtPath, tokenbytes, 0o640) 153 if err != nil { 154 gcecredLog.Errorf("Encountered error when writing vm identity token: %v", err) 155 return "", err 156 } 157 return strings.TrimSpace(token), nil 158 } 159 160 // GetIdentityProvider returns the name of the identity provider that can authenticate the workload credential. 161 // GCE identity provider is named "GoogleComputeEngine". 162 func (p *GCEPlugin) GetIdentityProvider() string { 163 return p.identityProvider 164 }