blob: 27a64ab3f55978be643961cd4e5a5bcea62b4b47 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This is Google plugin of credentialfetcher.
package plugin
import (
"fmt"
"os"
"strings"
"sync"
"time"
)
import (
"cloud.google.com/go/compute/metadata"
"istio.io/pkg/log"
)
import (
"github.com/apache/dubbo-go-pixiu/security/pkg/util"
)
var gcecredLog = log.RegisterScope("gcecred", "GCE credential fetcher for istio agent", 0)
// Token refresh frequency is default to 5 minutes.
var rotationInterval = 5 * time.Minute
// GCE VM credential needs refresh if remaining life time is below 25 minutes.
var gracePeriod = 25 * time.Minute
// rotateToken determines whether to start periodic token rotation job.
// This is enabled by default.
var rotateToken = true
// SetTokenRotation enable/disable periodic token rotation job.
// This is only for testing purpose, not thread safe.
func SetTokenRotation(enable bool) {
rotateToken = enable
}
// GCEPlugin is the plugin object.
type GCEPlugin struct {
// aud is the unique URI agreed upon by both the instance and the system verifying the instance's identity.
// For more info: https://cloud.google.com/compute/docs/instances/verifying-instance-identity
aud string
// The location to save the identity token
jwtPath string
// identity provider
identityProvider string
// token refresh
rotationTicker *time.Ticker
closing chan bool
tokenCache string
// mutex lock is required to avoid race condition when updating token file and token cache.
tokenMutex sync.RWMutex
}
// CreateGCEPlugin creates a Google credential fetcher plugin. Return the pointer to the created plugin.
func CreateGCEPlugin(audience, jwtPath, identityProvider string) *GCEPlugin {
p := &GCEPlugin{
aud: audience,
jwtPath: jwtPath,
identityProvider: identityProvider,
closing: make(chan bool),
}
if rotateToken {
go p.startTokenRotationJob()
}
return p
}
func (p *GCEPlugin) Stop() {
close(p.closing)
}
func (p *GCEPlugin) startTokenRotationJob() {
// Wake up once in a while and refresh GCE VM credential.
p.rotationTicker = time.NewTicker(rotationInterval)
for {
select {
case <-p.rotationTicker.C:
p.rotate()
case <-p.closing:
if p.rotationTicker != nil {
p.rotationTicker.Stop()
}
return
}
}
}
func (p *GCEPlugin) rotate() {
if p.shouldRotate(time.Now()) {
if _, err := p.GetPlatformCredential(); err != nil {
gcecredLog.Errorf("credential refresh failed: %+v", err)
}
}
}
func (p *GCEPlugin) shouldRotate(now time.Time) bool {
p.tokenMutex.RLock()
defer p.tokenMutex.RUnlock()
if p.tokenCache == "" {
return true
}
exp, err := util.GetExp(p.tokenCache)
// When fails to get expiration time from token, always refresh the token.
if err != nil || exp.IsZero() {
return true
}
rotate := now.After(exp.Add(-gracePeriod))
gcecredLog.Debugf("credential expiration: %s, grace period: %s, should rotate: %t",
exp.String(), gracePeriod.String(), rotate)
return rotate
}
// GetPlatformCredential fetches the GCE VM identity jwt token from its metadata server,
// and write it to jwtPath. The local copy of the token in jwtPath is used by both
// Envoy STS client and istio agent to fetch certificate and access token.
// Note: this function only works in a GCE VM environment.
func (p *GCEPlugin) GetPlatformCredential() (string, error) {
p.tokenMutex.Lock()
defer p.tokenMutex.Unlock()
if p.jwtPath == "" {
return "", fmt.Errorf("jwtPath is unset")
}
uri := fmt.Sprintf("instance/service-accounts/default/identity?audience=%s&format=full", p.aud)
token, err := metadata.Get(uri)
if err != nil {
gcecredLog.Errorf("Failed to get vm identity token from metadata server: %v", err)
return "", err
}
// Update token cache.
p.tokenCache = token
gcecredLog.Debugf("Got GCE identity token: %d", len(token))
tokenbytes := []byte(token)
err = os.WriteFile(p.jwtPath, tokenbytes, 0o640)
if err != nil {
gcecredLog.Errorf("Encountered error when writing vm identity token: %v", err)
return "", err
}
return strings.TrimSpace(token), nil
}
// GetIdentityProvider returns the name of the identity provider that can authenticate the workload credential.
// GCE identity provider is named "GoogleComputeEngine".
func (p *GCEPlugin) GetIdentityProvider() string {
return p.identityProvider
}