chore: migrate to gitea
This commit is contained in:
392
vendor/cloud.google.com/go/auth/internal/trustboundary/trust_boundary.go
generated
vendored
Normal file
392
vendor/cloud.google.com/go/auth/internal/trustboundary/trust_boundary.go
generated
vendored
Normal file
@@ -0,0 +1,392 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
package trustboundary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/retry"
|
||||
"cloud.google.com/go/auth/internal/transport/headers"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// serviceAccountAllowedLocationsEndpoint is the URL for fetching allowed locations for a given service account email.
|
||||
serviceAccountAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/projects/-/serviceAccounts/%s/allowedLocations"
|
||||
)
|
||||
|
||||
// isEnabled wraps isTrustBoundaryEnabled with sync.OnceValues to ensure it's
|
||||
// called only once.
|
||||
var isEnabled = sync.OnceValues(isTrustBoundaryEnabled)
|
||||
|
||||
// IsEnabled returns if the trust boundary feature is enabled and an error if
|
||||
// the configuration is invalid. The underlying check is performed only once.
|
||||
func IsEnabled() (bool, error) {
|
||||
return isEnabled()
|
||||
}
|
||||
|
||||
// isTrustBoundaryEnabled checks if the trust boundary feature is enabled via
|
||||
// GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED environment variable.
|
||||
//
|
||||
// If the environment variable is not set, it is considered false.
|
||||
//
|
||||
// The environment variable is interpreted as a boolean with the following
|
||||
// (case-insensitive) rules:
|
||||
// - "true", "1" are considered true.
|
||||
// - "false", "0" are considered false.
|
||||
//
|
||||
// Any other values will return an error.
|
||||
func isTrustBoundaryEnabled() (bool, error) {
|
||||
const envVar = "GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED"
|
||||
val, ok := os.LookupEnv(envVar)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
val = strings.ToLower(val)
|
||||
switch val {
|
||||
case "true", "1":
|
||||
return true, nil
|
||||
case "false", "0":
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf(`invalid value for %s: %q. Must be one of "true", "false", "1", or "0"`, envVar, val)
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigProvider provides specific configuration for trust boundary lookups.
|
||||
type ConfigProvider interface {
|
||||
// GetTrustBoundaryEndpoint returns the endpoint URL for the trust boundary lookup.
|
||||
GetTrustBoundaryEndpoint(ctx context.Context) (url string, err error)
|
||||
// GetUniverseDomain returns the universe domain associated with the credential.
|
||||
// It may return an error if the universe domain cannot be determined.
|
||||
GetUniverseDomain(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
// AllowedLocationsResponse is the structure of the response from the Trust Boundary API.
|
||||
type AllowedLocationsResponse struct {
|
||||
// Locations is the list of allowed locations.
|
||||
Locations []string `json:"locations"`
|
||||
// EncodedLocations is the encoded representation of the allowed locations.
|
||||
EncodedLocations string `json:"encodedLocations"`
|
||||
}
|
||||
|
||||
// fetchTrustBoundaryData fetches the trust boundary data from the API.
|
||||
func fetchTrustBoundaryData(ctx context.Context, client *http.Client, url string, token *auth.Token, logger *slog.Logger) (*internal.TrustBoundaryData, error) {
|
||||
if logger == nil {
|
||||
logger = slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
}
|
||||
if client == nil {
|
||||
return nil, errors.New("trustboundary: HTTP client is required")
|
||||
}
|
||||
|
||||
if url == "" {
|
||||
return nil, errors.New("trustboundary: URL cannot be empty")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to create trust boundary request: %w", err)
|
||||
}
|
||||
|
||||
if token == nil || token.Value == "" {
|
||||
return nil, errors.New("trustboundary: access token required for lookup API authentication")
|
||||
}
|
||||
headers.SetAuthHeader(token, req)
|
||||
logger.DebugContext(ctx, "trust boundary request", "request", internallog.HTTPRequest(req, nil))
|
||||
|
||||
retryer := retry.New()
|
||||
var response *http.Response
|
||||
for {
|
||||
response, err = client.Do(req)
|
||||
|
||||
var statusCode int
|
||||
if response != nil {
|
||||
statusCode = response.StatusCode
|
||||
}
|
||||
pause, shouldRetry := retryer.Retry(statusCode, err)
|
||||
|
||||
if !shouldRetry {
|
||||
break
|
||||
}
|
||||
|
||||
if response != nil {
|
||||
// Drain and close the body to reuse the connection
|
||||
io.Copy(io.Discard, response.Body)
|
||||
response.Body.Close()
|
||||
}
|
||||
|
||||
if err := retry.Sleep(ctx, pause); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to fetch trust boundary: %w", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to read trust boundary response: %w", err)
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "trust boundary response", "response", internallog.HTTPResponse(response, body))
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("trustboundary: trust boundary request failed with status: %s, body: %s", response.Status, string(body))
|
||||
}
|
||||
|
||||
apiResponse := AllowedLocationsResponse{}
|
||||
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to unmarshal trust boundary response: %w", err)
|
||||
}
|
||||
|
||||
if apiResponse.EncodedLocations == "" {
|
||||
return nil, errors.New("trustboundary: invalid API response: encodedLocations is empty")
|
||||
}
|
||||
|
||||
return internal.NewTrustBoundaryData(apiResponse.Locations, apiResponse.EncodedLocations), nil
|
||||
}
|
||||
|
||||
// serviceAccountConfig holds configuration for SA trust boundary lookups.
|
||||
// It implements the ConfigProvider interface.
|
||||
type serviceAccountConfig struct {
|
||||
ServiceAccountEmail string
|
||||
UniverseDomain string
|
||||
}
|
||||
|
||||
// NewServiceAccountConfigProvider creates a new config for service accounts.
|
||||
func NewServiceAccountConfigProvider(saEmail, universeDomain string) ConfigProvider {
|
||||
return &serviceAccountConfig{
|
||||
ServiceAccountEmail: saEmail,
|
||||
UniverseDomain: universeDomain,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTrustBoundaryEndpoint returns the formatted URL for fetching allowed locations
|
||||
// for the configured service account and universe domain.
|
||||
func (sac *serviceAccountConfig) GetTrustBoundaryEndpoint(ctx context.Context) (url string, err error) {
|
||||
if sac.ServiceAccountEmail == "" {
|
||||
return "", errors.New("trustboundary: service account email cannot be empty for config")
|
||||
}
|
||||
ud := sac.UniverseDomain
|
||||
if ud == "" {
|
||||
ud = internal.DefaultUniverseDomain
|
||||
}
|
||||
return fmt.Sprintf(serviceAccountAllowedLocationsEndpoint, ud, sac.ServiceAccountEmail), nil
|
||||
}
|
||||
|
||||
// GetUniverseDomain returns the configured universe domain, defaulting to
|
||||
// [internal.DefaultUniverseDomain] if not explicitly set.
|
||||
func (sac *serviceAccountConfig) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
if sac.UniverseDomain == "" {
|
||||
return internal.DefaultUniverseDomain, nil
|
||||
}
|
||||
return sac.UniverseDomain, nil
|
||||
}
|
||||
|
||||
// DataProvider fetches and caches trust boundary Data.
|
||||
// It implements the DataProvider interface and uses a ConfigProvider
|
||||
// to get type-specific details for the lookup.
|
||||
type DataProvider struct {
|
||||
client *http.Client
|
||||
configProvider ConfigProvider
|
||||
data *internal.TrustBoundaryData
|
||||
logger *slog.Logger
|
||||
base auth.TokenProvider
|
||||
}
|
||||
|
||||
// NewProvider wraps the provided base [auth.TokenProvider] to create a new
|
||||
// provider that injects tokens with trust boundary data. It uses the provided
|
||||
// HTTP client and configProvider to fetch the data and attach it to the token's
|
||||
// metadata.
|
||||
func NewProvider(client *http.Client, configProvider ConfigProvider, logger *slog.Logger, base auth.TokenProvider) (*DataProvider, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("trustboundary: HTTP client cannot be nil for DataProvider")
|
||||
}
|
||||
if configProvider == nil {
|
||||
return nil, errors.New("trustboundary: ConfigProvider cannot be nil for DataProvider")
|
||||
}
|
||||
p := &DataProvider{
|
||||
client: client,
|
||||
configProvider: configProvider,
|
||||
logger: internallog.New(logger),
|
||||
base: base,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Token retrieves a token from the base provider and injects it with trust
|
||||
// boundary data.
|
||||
func (p *DataProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
// Get the original token.
|
||||
token, err := p.base.Token(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tbData, err := p.GetTrustBoundaryData(ctx, token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: error fetching the trust boundary data: %w", err)
|
||||
}
|
||||
if tbData != nil {
|
||||
if token.Metadata == nil {
|
||||
token.Metadata = make(map[string]interface{})
|
||||
}
|
||||
token.Metadata[internal.TrustBoundaryDataKey] = *tbData
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// GetTrustBoundaryData retrieves the trust boundary data.
|
||||
// It first checks the universe domain: if it's non-default, a NoOp is returned.
|
||||
// Otherwise, it checks a local cache. If the data is not cached as NoOp,
|
||||
// it fetches new data from the endpoint provided by its ConfigProvider,
|
||||
// using the given accessToken for authentication. Results are cached.
|
||||
// If fetching fails, it returns previously cached data if available, otherwise the fetch error.
|
||||
func (p *DataProvider) GetTrustBoundaryData(ctx context.Context, token *auth.Token) (*internal.TrustBoundaryData, error) {
|
||||
// Check the universe domain.
|
||||
uniDomain, err := p.configProvider.GetUniverseDomain(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: error getting universe domain: %w", err)
|
||||
}
|
||||
if uniDomain != "" && uniDomain != internal.DefaultUniverseDomain {
|
||||
if p.data == nil || p.data.EncodedLocations != internal.TrustBoundaryNoOp {
|
||||
p.data = internal.NewNoOpTrustBoundaryData()
|
||||
}
|
||||
return p.data, nil
|
||||
}
|
||||
|
||||
// Check cache for a no-op result from a previous API call.
|
||||
cachedData := p.data
|
||||
if cachedData != nil && cachedData.EncodedLocations == internal.TrustBoundaryNoOp {
|
||||
return cachedData, nil
|
||||
}
|
||||
|
||||
// Get the endpoint
|
||||
url, err := p.configProvider.GetTrustBoundaryEndpoint(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: error getting the lookup endpoint: %w", err)
|
||||
}
|
||||
|
||||
// Proceed to fetch new data.
|
||||
newData, fetchErr := fetchTrustBoundaryData(ctx, p.client, url, token, p.logger)
|
||||
|
||||
if fetchErr != nil {
|
||||
// Fetch failed. Fallback to cachedData if available.
|
||||
if cachedData != nil {
|
||||
return cachedData, nil // Successful fallback
|
||||
}
|
||||
// No cache to fallback to.
|
||||
return nil, fmt.Errorf("trustboundary: failed to fetch trust boundary data for endpoint %s and no cache available: %w", url, fetchErr)
|
||||
}
|
||||
|
||||
// Fetch successful. Update cache.
|
||||
p.data = newData
|
||||
return newData, nil
|
||||
}
|
||||
|
||||
// GCEConfigProvider implements ConfigProvider for GCE environments.
|
||||
// It lazily fetches and caches the necessary metadata (service account email, universe domain)
|
||||
// from the GCE metadata server.
|
||||
type GCEConfigProvider struct {
|
||||
// universeDomainProvider provides the universe domain and underlying metadata client.
|
||||
universeDomainProvider *internal.ComputeUniverseDomainProvider
|
||||
|
||||
// Caching for service account email
|
||||
saOnce sync.Once
|
||||
saEmail string
|
||||
saEmailErr error
|
||||
|
||||
// Caching for universe domain
|
||||
udOnce sync.Once
|
||||
ud string
|
||||
udErr error
|
||||
}
|
||||
|
||||
// NewGCEConfigProvider creates a new GCEConfigProvider
|
||||
// which uses the provided gceUDP to interact with the GCE metadata server.
|
||||
func NewGCEConfigProvider(gceUDP *internal.ComputeUniverseDomainProvider) *GCEConfigProvider {
|
||||
// The validity of gceUDP and its internal MetadataClient will be checked
|
||||
// within the GetTrustBoundaryEndpoint and GetUniverseDomain methods.
|
||||
return &GCEConfigProvider{
|
||||
universeDomainProvider: gceUDP,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GCEConfigProvider) fetchSA(ctx context.Context) {
|
||||
if g.universeDomainProvider == nil || g.universeDomainProvider.MetadataClient == nil {
|
||||
g.saEmailErr = errors.New("trustboundary: GCEConfigProvider not properly initialized (missing ComputeUniverseDomainProvider or MetadataClient)")
|
||||
return
|
||||
}
|
||||
mdClient := g.universeDomainProvider.MetadataClient
|
||||
saEmail, err := mdClient.EmailWithContext(ctx, "default")
|
||||
if err != nil {
|
||||
g.saEmailErr = fmt.Errorf("trustboundary: GCE config: failed to get service account email: %w", err)
|
||||
return
|
||||
}
|
||||
g.saEmail = saEmail
|
||||
}
|
||||
|
||||
func (g *GCEConfigProvider) fetchUD(ctx context.Context) {
|
||||
if g.universeDomainProvider == nil || g.universeDomainProvider.MetadataClient == nil {
|
||||
g.udErr = errors.New("trustboundary: GCEConfigProvider not properly initialized (missing ComputeUniverseDomainProvider or MetadataClient)")
|
||||
return
|
||||
}
|
||||
ud, err := g.universeDomainProvider.GetProperty(ctx)
|
||||
if err != nil {
|
||||
g.udErr = fmt.Errorf("trustboundary: GCE config: failed to get universe domain: %w", err)
|
||||
return
|
||||
}
|
||||
if ud == "" {
|
||||
ud = internal.DefaultUniverseDomain
|
||||
}
|
||||
g.ud = ud
|
||||
}
|
||||
|
||||
// GetTrustBoundaryEndpoint constructs the trust boundary lookup URL for a GCE environment.
|
||||
// It uses cached metadata (service account email, universe domain) after the first call.
|
||||
func (g *GCEConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
g.saOnce.Do(func() { g.fetchSA(ctx) })
|
||||
if g.saEmailErr != nil {
|
||||
return "", g.saEmailErr
|
||||
}
|
||||
g.udOnce.Do(func() { g.fetchUD(ctx) })
|
||||
if g.udErr != nil {
|
||||
return "", g.udErr
|
||||
}
|
||||
return fmt.Sprintf(serviceAccountAllowedLocationsEndpoint, g.ud, g.saEmail), nil
|
||||
}
|
||||
|
||||
// GetUniverseDomain retrieves the universe domain from the GCE metadata server.
|
||||
// It uses a cached value after the first call.
|
||||
func (g *GCEConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
g.udOnce.Do(func() { g.fetchUD(ctx) })
|
||||
if g.udErr != nil {
|
||||
return "", g.udErr
|
||||
}
|
||||
return g.ud, nil
|
||||
}
|
||||
Reference in New Issue
Block a user