chore: migrate to gitea
This commit is contained in:
63
vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.go
generated
vendored
Normal file
63
vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2023 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 credsfile is meant to hide implementation details from the pubic
|
||||
// surface of the detect package. It should not import any other packages in
|
||||
// this module. It is located under the main internal package so other
|
||||
// sub-packages can use these parsed types as well.
|
||||
package credsfile
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
// GoogleAppCredsEnvVar is the environment variable for setting the
|
||||
// application default credentials.
|
||||
GoogleAppCredsEnvVar = "GOOGLE_APPLICATION_CREDENTIALS"
|
||||
userCredsFilename = "application_default_credentials.json"
|
||||
)
|
||||
|
||||
// GetFileNameFromEnv returns the override if provided or detects a filename
|
||||
// from the environment.
|
||||
func GetFileNameFromEnv(override string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
return os.Getenv(GoogleAppCredsEnvVar)
|
||||
}
|
||||
|
||||
// GetWellKnownFileName tries to locate the filepath for the user credential
|
||||
// file based on the environment.
|
||||
func GetWellKnownFileName() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return filepath.Join(os.Getenv("APPDATA"), "gcloud", userCredsFilename)
|
||||
}
|
||||
return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", userCredsFilename)
|
||||
}
|
||||
|
||||
// guessUnixHomeDir default to checking for HOME, but not all unix systems have
|
||||
// this set, do have a fallback.
|
||||
func guessUnixHomeDir() string {
|
||||
if v := os.Getenv("HOME"); v != "" {
|
||||
return v
|
||||
}
|
||||
if u, err := user.Current(); err == nil {
|
||||
return u.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
159
vendor/cloud.google.com/go/auth/internal/credsfile/filetype.go
generated
vendored
Normal file
159
vendor/cloud.google.com/go/auth/internal/credsfile/filetype.go
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright 2023 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 credsfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Config3LO is the internals of a client creds file.
|
||||
type Config3LO struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectURIs []string `json:"redirect_uris"`
|
||||
AuthURI string `json:"auth_uri"`
|
||||
TokenURI string `json:"token_uri"`
|
||||
}
|
||||
|
||||
// ClientCredentialsFile representation.
|
||||
type ClientCredentialsFile struct {
|
||||
Web *Config3LO `json:"web"`
|
||||
Installed *Config3LO `json:"installed"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// ServiceAccountFile representation.
|
||||
type ServiceAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
ProjectID string `json:"project_id"`
|
||||
PrivateKeyID string `json:"private_key_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
ClientEmail string `json:"client_email"`
|
||||
ClientID string `json:"client_id"`
|
||||
AuthURL string `json:"auth_uri"`
|
||||
TokenURL string `json:"token_uri"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// UserCredentialsFile representation.
|
||||
type UserCredentialsFile struct {
|
||||
Type string `json:"type"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
QuotaProjectID string `json:"quota_project_id"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// ExternalAccountFile representation.
|
||||
type ExternalAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
Audience string `json:"audience"`
|
||||
SubjectTokenType string `json:"subject_token_type"`
|
||||
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
|
||||
TokenURL string `json:"token_url"`
|
||||
CredentialSource *CredentialSource `json:"credential_source,omitempty"`
|
||||
TokenInfoURL string `json:"token_info_url"`
|
||||
ServiceAccountImpersonation *ServiceAccountImpersonationInfo `json:"service_account_impersonation,omitempty"`
|
||||
QuotaProjectID string `json:"quota_project_id"`
|
||||
WorkforcePoolUserProject string `json:"workforce_pool_user_project"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// ExternalAccountAuthorizedUserFile representation.
|
||||
type ExternalAccountAuthorizedUserFile struct {
|
||||
Type string `json:"type"`
|
||||
Audience string `json:"audience"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenURL string `json:"token_url"`
|
||||
TokenInfoURL string `json:"token_info_url"`
|
||||
RevokeURL string `json:"revoke_url"`
|
||||
QuotaProjectID string `json:"quota_project_id"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// CredentialSource stores the information necessary to retrieve the credentials for the STS exchange.
|
||||
//
|
||||
// One field amongst File, URL, Certificate, and Executable should be filled, depending on the kind of credential in question.
|
||||
// The EnvironmentID should start with AWS if being used for an AWS credential.
|
||||
type CredentialSource struct {
|
||||
File string `json:"file"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Executable *ExecutableConfig `json:"executable,omitempty"`
|
||||
Certificate *CertificateConfig `json:"certificate"`
|
||||
EnvironmentID string `json:"environment_id"` // TODO: Make type for this
|
||||
RegionURL string `json:"region_url"`
|
||||
RegionalCredVerificationURL string `json:"regional_cred_verification_url"`
|
||||
CredVerificationURL string `json:"cred_verification_url"`
|
||||
IMDSv2SessionTokenURL string `json:"imdsv2_session_token_url"`
|
||||
Format *Format `json:"format,omitempty"`
|
||||
}
|
||||
|
||||
// Format describes the format of a [CredentialSource].
|
||||
type Format struct {
|
||||
// Type is either "text" or "json". When not provided "text" type is assumed.
|
||||
Type string `json:"type"`
|
||||
// SubjectTokenFieldName is only required for JSON format. This would be "access_token" for azure.
|
||||
SubjectTokenFieldName string `json:"subject_token_field_name"`
|
||||
}
|
||||
|
||||
// ExecutableConfig represents the command to run for an executable
|
||||
// [CredentialSource].
|
||||
type ExecutableConfig struct {
|
||||
Command string `json:"command"`
|
||||
TimeoutMillis int `json:"timeout_millis"`
|
||||
OutputFile string `json:"output_file"`
|
||||
}
|
||||
|
||||
// CertificateConfig represents the options used to set up X509 based workload
|
||||
// [CredentialSource]
|
||||
type CertificateConfig struct {
|
||||
UseDefaultCertificateConfig bool `json:"use_default_certificate_config"`
|
||||
CertificateConfigLocation string `json:"certificate_config_location"`
|
||||
TrustChainPath string `json:"trust_chain_path"`
|
||||
}
|
||||
|
||||
// ServiceAccountImpersonationInfo has impersonation configuration.
|
||||
type ServiceAccountImpersonationInfo struct {
|
||||
TokenLifetimeSeconds int `json:"token_lifetime_seconds"`
|
||||
}
|
||||
|
||||
// ImpersonatedServiceAccountFile representation.
|
||||
type ImpersonatedServiceAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
|
||||
Delegates []string `json:"delegates"`
|
||||
Scopes []string `json:"scopes"`
|
||||
CredSource json.RawMessage `json:"source_credentials"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// GDCHServiceAccountFile represents the Google Distributed Cloud Hosted (GDCH) service identity file.
|
||||
type GDCHServiceAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
FormatVersion string `json:"format_version"`
|
||||
Project string `json:"project"`
|
||||
Name string `json:"name"`
|
||||
CertPath string `json:"ca_cert_path"`
|
||||
PrivateKeyID string `json:"private_key_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
TokenURL string `json:"token_uri"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
99
vendor/cloud.google.com/go/auth/internal/credsfile/parse.go
generated
vendored
Normal file
99
vendor/cloud.google.com/go/auth/internal/credsfile/parse.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2023 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 credsfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// ParseServiceAccount parses bytes into a [ServiceAccountFile].
|
||||
func ParseServiceAccount(b []byte) (*ServiceAccountFile, error) {
|
||||
var f *ServiceAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseClientCredentials parses bytes into a
|
||||
// [credsfile.ClientCredentialsFile].
|
||||
func ParseClientCredentials(b []byte) (*ClientCredentialsFile, error) {
|
||||
var f *ClientCredentialsFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseUserCredentials parses bytes into a [UserCredentialsFile].
|
||||
func ParseUserCredentials(b []byte) (*UserCredentialsFile, error) {
|
||||
var f *UserCredentialsFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseExternalAccount parses bytes into a [ExternalAccountFile].
|
||||
func ParseExternalAccount(b []byte) (*ExternalAccountFile, error) {
|
||||
var f *ExternalAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseExternalAccountAuthorizedUser parses bytes into a
|
||||
// [ExternalAccountAuthorizedUserFile].
|
||||
func ParseExternalAccountAuthorizedUser(b []byte) (*ExternalAccountAuthorizedUserFile, error) {
|
||||
var f *ExternalAccountAuthorizedUserFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseImpersonatedServiceAccount parses bytes into a
|
||||
// [ImpersonatedServiceAccountFile].
|
||||
func ParseImpersonatedServiceAccount(b []byte) (*ImpersonatedServiceAccountFile, error) {
|
||||
var f *ImpersonatedServiceAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseGDCHServiceAccount parses bytes into a [GDCHServiceAccountFile].
|
||||
func ParseGDCHServiceAccount(b []byte) (*GDCHServiceAccountFile, error) {
|
||||
var f *GDCHServiceAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
type fileTypeChecker struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ParseFileType determines the [CredentialType] based on bytes provided.
|
||||
// Only returns error for json.Unmarshal.
|
||||
func ParseFileType(b []byte) (string, error) {
|
||||
var f fileTypeChecker
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return f.Type, nil
|
||||
}
|
||||
285
vendor/cloud.google.com/go/auth/internal/internal.go
generated
vendored
Normal file
285
vendor/cloud.google.com/go/auth/internal/internal.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
// Copyright 2023 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
// TokenTypeBearer is the auth header prefix for bearer tokens.
|
||||
TokenTypeBearer = "Bearer"
|
||||
|
||||
// QuotaProjectEnvVar is the environment variable for setting the quota
|
||||
// project.
|
||||
QuotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
|
||||
// UniverseDomainEnvVar is the environment variable for setting the default
|
||||
// service domain for a given Cloud universe.
|
||||
UniverseDomainEnvVar = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"
|
||||
projectEnvVar = "GOOGLE_CLOUD_PROJECT"
|
||||
maxBodySize = 1 << 20
|
||||
|
||||
// DefaultUniverseDomain is the default value for universe domain.
|
||||
// Universe domain is the default service domain for a given Cloud universe.
|
||||
DefaultUniverseDomain = "googleapis.com"
|
||||
|
||||
// TrustBoundaryNoOp is a constant indicating no trust boundary is enforced.
|
||||
TrustBoundaryNoOp = "0x0"
|
||||
|
||||
// TrustBoundaryDataKey is the key used to store trust boundary data in a token's metadata.
|
||||
TrustBoundaryDataKey = "google.auth.trust_boundary_data"
|
||||
)
|
||||
|
||||
type clonableTransport interface {
|
||||
Clone() *http.Transport
|
||||
}
|
||||
|
||||
// DefaultClient returns an [http.Client] with some defaults set. If
|
||||
// the current [http.DefaultTransport] is a [clonableTransport], as
|
||||
// is the case for an [*http.Transport], the clone will be used.
|
||||
// Otherwise the [http.DefaultTransport] is used directly.
|
||||
func DefaultClient() *http.Client {
|
||||
if transport, ok := http.DefaultTransport.(clonableTransport); ok {
|
||||
return &http.Client{
|
||||
Transport: transport.Clone(),
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: http.DefaultTransport,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseKey converts the binary contents of a private key file
|
||||
// to an crypto.Signer. It detects whether the private key is in a
|
||||
// PEM container or not. If so, it extracts the the private key
|
||||
// from PEM container before conversion. It only supports PEM
|
||||
// containers with no passphrase.
|
||||
func ParseKey(key []byte) (crypto.Signer, error) {
|
||||
block, _ := pem.Decode(key)
|
||||
if block != nil {
|
||||
key = block.Bytes
|
||||
}
|
||||
var parsedKey crypto.PrivateKey
|
||||
|
||||
var errPKCS8, errPKCS1, errEC error
|
||||
if parsedKey, errPKCS8 = x509.ParsePKCS8PrivateKey(key); errPKCS8 != nil {
|
||||
if parsedKey, errPKCS1 = x509.ParsePKCS1PrivateKey(key); errPKCS1 != nil {
|
||||
if parsedKey, errEC = x509.ParseECPrivateKey(key); errEC != nil {
|
||||
return nil, fmt.Errorf("failed to parse private key. Tried PKCS8, PKCS1, and EC formats. Errors: [PKCS8: %v], [PKCS1: %v], [EC: %v]", errPKCS8, errPKCS1, errEC)
|
||||
}
|
||||
}
|
||||
}
|
||||
parsed, ok := parsedKey.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil, errors.New("private key is not a signer")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// GetQuotaProject retrieves quota project with precedence being: override,
|
||||
// environment variable, creds json file.
|
||||
func GetQuotaProject(b []byte, override string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
if env := os.Getenv(QuotaProjectEnvVar); env != "" {
|
||||
return env
|
||||
}
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
var v struct {
|
||||
QuotaProject string `json:"quota_project_id"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return ""
|
||||
}
|
||||
return v.QuotaProject
|
||||
}
|
||||
|
||||
// GetProjectID retrieves project with precedence being: override,
|
||||
// environment variable, creds json file.
|
||||
func GetProjectID(b []byte, override string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
if env := os.Getenv(projectEnvVar); env != "" {
|
||||
return env
|
||||
}
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
var v struct {
|
||||
ProjectID string `json:"project_id"` // standard service account key
|
||||
Project string `json:"project"` // gdch key
|
||||
}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return ""
|
||||
}
|
||||
if v.ProjectID != "" {
|
||||
return v.ProjectID
|
||||
}
|
||||
return v.Project
|
||||
}
|
||||
|
||||
// DoRequest executes the provided req with the client. It reads the response
|
||||
// body, closes it, and returns it.
|
||||
func DoRequest(client *http.Client, req *http.Request) (*http.Response, []byte, error) {
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ReadAll(io.LimitReader(resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
// ReadAll consumes the whole reader and safely reads the content of its body
|
||||
// with some overflow protection.
|
||||
func ReadAll(r io.Reader) ([]byte, error) {
|
||||
return io.ReadAll(io.LimitReader(r, maxBodySize))
|
||||
}
|
||||
|
||||
// StaticCredentialsProperty is a helper for creating static credentials
|
||||
// properties.
|
||||
func StaticCredentialsProperty(s string) StaticProperty {
|
||||
return StaticProperty(s)
|
||||
}
|
||||
|
||||
// StaticProperty always returns that value of the underlying string.
|
||||
type StaticProperty string
|
||||
|
||||
// GetProperty loads the properly value provided the given context.
|
||||
func (p StaticProperty) GetProperty(context.Context) (string, error) {
|
||||
return string(p), nil
|
||||
}
|
||||
|
||||
// ComputeUniverseDomainProvider fetches the credentials universe domain from
|
||||
// the google cloud metadata service.
|
||||
type ComputeUniverseDomainProvider struct {
|
||||
MetadataClient *metadata.Client
|
||||
universeDomainOnce sync.Once
|
||||
universeDomain string
|
||||
universeDomainErr error
|
||||
}
|
||||
|
||||
// GetProperty fetches the credentials universe domain from the google cloud
|
||||
// metadata service.
|
||||
func (c *ComputeUniverseDomainProvider) GetProperty(ctx context.Context) (string, error) {
|
||||
c.universeDomainOnce.Do(func() {
|
||||
c.universeDomain, c.universeDomainErr = getMetadataUniverseDomain(ctx, c.MetadataClient)
|
||||
})
|
||||
if c.universeDomainErr != nil {
|
||||
return "", c.universeDomainErr
|
||||
}
|
||||
return c.universeDomain, nil
|
||||
}
|
||||
|
||||
// httpGetMetadataUniverseDomain is a package var for unit test substitution.
|
||||
var httpGetMetadataUniverseDomain = func(ctx context.Context, client *metadata.Client) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
||||
defer cancel()
|
||||
return client.GetWithContext(ctx, "universe/universe-domain")
|
||||
}
|
||||
|
||||
func getMetadataUniverseDomain(ctx context.Context, client *metadata.Client) (string, error) {
|
||||
universeDomain, err := httpGetMetadataUniverseDomain(ctx, client)
|
||||
if err == nil {
|
||||
return universeDomain, nil
|
||||
}
|
||||
if _, ok := err.(metadata.NotDefinedError); ok {
|
||||
// http.StatusNotFound (404)
|
||||
return DefaultUniverseDomain, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// FormatIAMServiceAccountResource sets a service account name in an IAM resource
|
||||
// name.
|
||||
func FormatIAMServiceAccountResource(name string) string {
|
||||
return fmt.Sprintf("projects/-/serviceAccounts/%s", name)
|
||||
}
|
||||
|
||||
// TrustBoundaryData represents the trust boundary data associated with a token.
|
||||
// It contains information about the regions or environments where the token is valid.
|
||||
type TrustBoundaryData struct {
|
||||
// Locations is the list of locations that the token is allowed to be used in.
|
||||
Locations []string
|
||||
// EncodedLocations represents the locations in an encoded format.
|
||||
EncodedLocations string
|
||||
}
|
||||
|
||||
// NewTrustBoundaryData returns a new TrustBoundaryData with the specified locations and encoded locations.
|
||||
func NewTrustBoundaryData(locations []string, encodedLocations string) *TrustBoundaryData {
|
||||
// Ensure consistency by treating a nil slice as an empty slice.
|
||||
if locations == nil {
|
||||
locations = []string{}
|
||||
}
|
||||
locationsCopy := make([]string, len(locations))
|
||||
copy(locationsCopy, locations)
|
||||
return &TrustBoundaryData{
|
||||
Locations: locationsCopy,
|
||||
EncodedLocations: encodedLocations,
|
||||
}
|
||||
}
|
||||
|
||||
// NewNoOpTrustBoundaryData returns a new TrustBoundaryData with no restrictions.
|
||||
func NewNoOpTrustBoundaryData() *TrustBoundaryData {
|
||||
return &TrustBoundaryData{
|
||||
Locations: []string{},
|
||||
EncodedLocations: TrustBoundaryNoOp,
|
||||
}
|
||||
}
|
||||
|
||||
// TrustBoundaryHeader returns the value for the x-allowed-locations header and a bool
|
||||
// indicating if the header should be set. The return values are structured to
|
||||
// handle three distinct states required by the backend:
|
||||
// 1. Header not set: (value="", present=false) -> data is empty.
|
||||
// 2. Header set to an empty string: (value="", present=true) -> data is a no-op.
|
||||
// 3. Header set to a value: (value="...", present=true) -> data has locations.
|
||||
func (t TrustBoundaryData) TrustBoundaryHeader() (value string, present bool) {
|
||||
if t.EncodedLocations == "" {
|
||||
// If the data is empty, the header should not be present.
|
||||
return "", false
|
||||
}
|
||||
|
||||
// If data is not empty, the header should always be present.
|
||||
present = true
|
||||
value = ""
|
||||
if t.EncodedLocations != TrustBoundaryNoOp {
|
||||
value = t.EncodedLocations
|
||||
}
|
||||
// For a no-op, the backend requires an empty string.
|
||||
return value, present
|
||||
}
|
||||
171
vendor/cloud.google.com/go/auth/internal/jwt/jwt.go
generated
vendored
Normal file
171
vendor/cloud.google.com/go/auth/internal/jwt/jwt.go
generated
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright 2023 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 jwt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// HeaderAlgRSA256 is the RS256 [Header.Algorithm].
|
||||
HeaderAlgRSA256 = "RS256"
|
||||
// HeaderAlgES256 is the ES256 [Header.Algorithm].
|
||||
HeaderAlgES256 = "ES256"
|
||||
// HeaderType is the standard [Header.Type].
|
||||
HeaderType = "JWT"
|
||||
)
|
||||
|
||||
// Header represents a JWT header.
|
||||
type Header struct {
|
||||
Algorithm string `json:"alg"`
|
||||
Type string `json:"typ"`
|
||||
KeyID string `json:"kid"`
|
||||
}
|
||||
|
||||
func (h *Header) encode() (string, error) {
|
||||
b, err := json.Marshal(h)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// Claims represents the claims set of a JWT.
|
||||
type Claims struct {
|
||||
// Iss is the issuer JWT claim.
|
||||
Iss string `json:"iss"`
|
||||
// Scope is the scope JWT claim.
|
||||
Scope string `json:"scope,omitempty"`
|
||||
// Exp is the expiry JWT claim. If unset, default is in one hour from now.
|
||||
Exp int64 `json:"exp"`
|
||||
// Iat is the subject issued at claim. If unset, default is now.
|
||||
Iat int64 `json:"iat"`
|
||||
// Aud is the audience JWT claim. Optional.
|
||||
Aud string `json:"aud"`
|
||||
// Sub is the subject JWT claim. Optional.
|
||||
Sub string `json:"sub,omitempty"`
|
||||
// AdditionalClaims contains any additional non-standard JWT claims. Optional.
|
||||
AdditionalClaims map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
func (c *Claims) encode() (string, error) {
|
||||
// Compensate for skew
|
||||
now := time.Now().Add(-10 * time.Second)
|
||||
if c.Iat == 0 {
|
||||
c.Iat = now.Unix()
|
||||
}
|
||||
if c.Exp == 0 {
|
||||
c.Exp = now.Add(time.Hour).Unix()
|
||||
}
|
||||
if c.Exp < c.Iat {
|
||||
return "", fmt.Errorf("jwt: invalid Exp = %d; must be later than Iat = %d", c.Exp, c.Iat)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(c.AdditionalClaims) == 0 {
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// Marshal private claim set and then append it to b.
|
||||
prv, err := json.Marshal(c.AdditionalClaims)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid map of additional claims %v: %w", c.AdditionalClaims, err)
|
||||
}
|
||||
|
||||
// Concatenate public and private claim JSON objects.
|
||||
if !bytes.HasSuffix(b, []byte{'}'}) {
|
||||
return "", fmt.Errorf("invalid JSON %s", b)
|
||||
}
|
||||
if !bytes.HasPrefix(prv, []byte{'{'}) {
|
||||
return "", fmt.Errorf("invalid JSON %s", prv)
|
||||
}
|
||||
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
|
||||
b = append(b, prv[1:]...) // Append private claims.
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// EncodeJWS encodes the data using the provided key as a JSON web signature.
|
||||
func EncodeJWS(header *Header, c *Claims, signer crypto.Signer) (string, error) {
|
||||
head, err := header.encode()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
claims, err := c.encode()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ss := fmt.Sprintf("%s.%s", head, claims)
|
||||
h := sha256.New()
|
||||
h.Write([]byte(ss))
|
||||
sig, err := signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA256)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", ss, base64.RawURLEncoding.EncodeToString(sig)), nil
|
||||
}
|
||||
|
||||
// DecodeJWS decodes a claim set from a JWS payload.
|
||||
func DecodeJWS(payload string) (*Claims, error) {
|
||||
// decode returned id token to get expiry
|
||||
s := strings.Split(payload, ".")
|
||||
if len(s) < 2 {
|
||||
return nil, errors.New("invalid token received")
|
||||
}
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Claims{}
|
||||
if err := json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(bytes.NewBuffer(decoded)).Decode(&c.AdditionalClaims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
// VerifyJWS tests whether the provided JWT token's signature was produced by
|
||||
// the private key associated with the provided public key.
|
||||
func VerifyJWS(token string, key *rsa.PublicKey) error {
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) != 3 {
|
||||
return errors.New("jwt: invalid token received, token must have 3 parts")
|
||||
}
|
||||
|
||||
signedContent := parts[0] + "." + parts[1]
|
||||
signatureString, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(signedContent))
|
||||
return rsa.VerifyPKCS1v15(key, crypto.SHA256, h.Sum(nil), signatureString)
|
||||
}
|
||||
117
vendor/cloud.google.com/go/auth/internal/retry/retry.go
generated
vendored
Normal file
117
vendor/cloud.google.com/go/auth/internal/retry/retry.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
// 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 retry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetryAttempts = 5
|
||||
)
|
||||
|
||||
var (
|
||||
syscallRetryable = func(error) bool { return false }
|
||||
)
|
||||
|
||||
// defaultBackoff is basically equivalent to gax.Backoff without the need for
|
||||
// the dependency.
|
||||
type defaultBackoff struct {
|
||||
max time.Duration
|
||||
mul float64
|
||||
cur time.Duration
|
||||
}
|
||||
|
||||
func (b *defaultBackoff) Pause() time.Duration {
|
||||
d := time.Duration(1 + rand.Int63n(int64(b.cur)))
|
||||
b.cur = time.Duration(float64(b.cur) * b.mul)
|
||||
if b.cur > b.max {
|
||||
b.cur = b.max
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Sleep is the equivalent of gax.Sleep without the need for the dependency.
|
||||
func Sleep(ctx context.Context, d time.Duration) error {
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return ctx.Err()
|
||||
case <-t.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new Retryer with the default backoff strategy.
|
||||
func New() *Retryer {
|
||||
return &Retryer{bo: &defaultBackoff{
|
||||
cur: 100 * time.Millisecond,
|
||||
max: 30 * time.Second,
|
||||
mul: 2,
|
||||
}}
|
||||
}
|
||||
|
||||
type backoff interface {
|
||||
Pause() time.Duration
|
||||
}
|
||||
|
||||
// Retryer is a retryer for HTTP requests.
|
||||
type Retryer struct {
|
||||
bo backoff
|
||||
attempts int
|
||||
}
|
||||
|
||||
// Retry determines if a request should be retried.
|
||||
func (r *Retryer) Retry(status int, err error) (time.Duration, bool) {
|
||||
if status == http.StatusOK {
|
||||
return 0, false
|
||||
}
|
||||
retryOk := shouldRetry(status, err)
|
||||
if !retryOk {
|
||||
return 0, false
|
||||
}
|
||||
if r.attempts == maxRetryAttempts {
|
||||
return 0, false
|
||||
}
|
||||
r.attempts++
|
||||
return r.bo.Pause(), true
|
||||
}
|
||||
|
||||
func shouldRetry(status int, err error) bool {
|
||||
if 500 <= status && status <= 599 {
|
||||
return true
|
||||
}
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
return true
|
||||
}
|
||||
// Transient network errors should be retried.
|
||||
if syscallRetryable(err) {
|
||||
return true
|
||||
}
|
||||
if err, ok := err.(interface{ Temporary() bool }); ok {
|
||||
if err.Temporary() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if err, ok := err.(interface{ Unwrap() error }); ok {
|
||||
return shouldRetry(status, err.Unwrap())
|
||||
}
|
||||
return false
|
||||
}
|
||||
361
vendor/cloud.google.com/go/auth/internal/transport/cba.go
generated
vendored
Normal file
361
vendor/cloud.google.com/go/auth/internal/transport/cba.go
generated
vendored
Normal file
@@ -0,0 +1,361 @@
|
||||
// Copyright 2023 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 transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/transport/cert"
|
||||
"github.com/google/s2a-go"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
mTLSModeAlways = "always"
|
||||
mTLSModeNever = "never"
|
||||
mTLSModeAuto = "auto"
|
||||
|
||||
// Experimental: if true, the code will try MTLS with S2A as the default for transport security. Default value is false.
|
||||
googleAPIUseS2AEnv = "EXPERIMENTAL_GOOGLE_API_USE_S2A"
|
||||
googleAPIUseCertSource = "GOOGLE_API_USE_CLIENT_CERTIFICATE"
|
||||
googleAPIUseMTLS = "GOOGLE_API_USE_MTLS_ENDPOINT"
|
||||
googleAPIUseMTLSOld = "GOOGLE_API_USE_MTLS"
|
||||
|
||||
universeDomainPlaceholder = "UNIVERSE_DOMAIN"
|
||||
|
||||
mtlsMDSRoot = "/run/google-mds-mtls/root.crt"
|
||||
mtlsMDSKey = "/run/google-mds-mtls/client.key"
|
||||
)
|
||||
|
||||
// Type represents the type of transport used.
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// TransportTypeUnknown represents an unknown transport type and is the default option.
|
||||
TransportTypeUnknown Type = iota
|
||||
// TransportTypeMTLSS2A represents the mTLS transport type using S2A.
|
||||
TransportTypeMTLSS2A
|
||||
)
|
||||
|
||||
// Options is a struct that is duplicated information from the individual
|
||||
// transport packages in order to avoid cyclic deps. It correlates 1:1 with
|
||||
// fields on httptransport.Options and grpctransport.Options.
|
||||
type Options struct {
|
||||
Endpoint string
|
||||
DefaultEndpointTemplate string
|
||||
DefaultMTLSEndpoint string
|
||||
ClientCertProvider cert.Provider
|
||||
Client *http.Client
|
||||
UniverseDomain string
|
||||
EnableDirectPath bool
|
||||
EnableDirectPathXds bool
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// getUniverseDomain returns the default service domain for a given Cloud
|
||||
// universe.
|
||||
func (o *Options) getUniverseDomain() string {
|
||||
if o.UniverseDomain == "" {
|
||||
return internal.DefaultUniverseDomain
|
||||
}
|
||||
return o.UniverseDomain
|
||||
}
|
||||
|
||||
// isUniverseDomainGDU returns true if the universe domain is the default Google
|
||||
// universe.
|
||||
func (o *Options) isUniverseDomainGDU() bool {
|
||||
return o.getUniverseDomain() == internal.DefaultUniverseDomain
|
||||
}
|
||||
|
||||
// defaultEndpoint returns the DefaultEndpointTemplate merged with the
|
||||
// universe domain if the DefaultEndpointTemplate is set, otherwise returns an
|
||||
// empty string.
|
||||
func (o *Options) defaultEndpoint() string {
|
||||
if o.DefaultEndpointTemplate == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.Replace(o.DefaultEndpointTemplate, universeDomainPlaceholder, o.getUniverseDomain(), 1)
|
||||
}
|
||||
|
||||
// defaultMTLSEndpoint returns the DefaultMTLSEndpointTemplate merged with the
|
||||
// universe domain if the DefaultMTLSEndpointTemplate is set, otherwise returns an
|
||||
// empty string.
|
||||
func (o *Options) defaultMTLSEndpoint() string {
|
||||
if o.DefaultMTLSEndpoint == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.Replace(o.DefaultMTLSEndpoint, universeDomainPlaceholder, o.getUniverseDomain(), 1)
|
||||
}
|
||||
|
||||
// mergedEndpoint merges a user-provided Endpoint of format host[:port] with the
|
||||
// default endpoint.
|
||||
func (o *Options) mergedEndpoint() (string, error) {
|
||||
defaultEndpoint := o.defaultEndpoint()
|
||||
u, err := url.Parse(fixScheme(defaultEndpoint))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Replace(defaultEndpoint, u.Host, o.Endpoint, 1), nil
|
||||
}
|
||||
|
||||
func fixScheme(baseURL string) string {
|
||||
if !strings.Contains(baseURL, "://") {
|
||||
baseURL = "https://" + baseURL
|
||||
}
|
||||
return baseURL
|
||||
}
|
||||
|
||||
// GRPCTransportCredentials embeds interface TransportCredentials with additional data.
|
||||
type GRPCTransportCredentials struct {
|
||||
credentials.TransportCredentials
|
||||
Endpoint string
|
||||
TransportType Type
|
||||
}
|
||||
|
||||
// GetGRPCTransportCredsAndEndpoint returns an instance of
|
||||
// [google.golang.org/grpc/credentials.TransportCredentials], and the
|
||||
// corresponding endpoint and transport type to use for GRPC client.
|
||||
func GetGRPCTransportCredsAndEndpoint(opts *Options) (*GRPCTransportCredentials, error) {
|
||||
config, err := getTransportConfig(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaultTransportCreds := credentials.NewTLS(&tls.Config{
|
||||
GetClientCertificate: config.clientCertSource,
|
||||
})
|
||||
|
||||
var s2aAddr string
|
||||
var transportCredsForS2A credentials.TransportCredentials
|
||||
|
||||
if config.mtlsS2AAddress != "" {
|
||||
s2aAddr = config.mtlsS2AAddress
|
||||
transportCredsForS2A, err = loadMTLSMDSTransportCreds(mtlsMDSRoot, mtlsMDSKey)
|
||||
if err != nil {
|
||||
log.Printf("Loading MTLS MDS credentials failed: %v", err)
|
||||
if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return &GRPCTransportCredentials{defaultTransportCreds, config.endpoint, TransportTypeUnknown}, nil
|
||||
}
|
||||
}
|
||||
} else if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return &GRPCTransportCredentials{defaultTransportCreds, config.endpoint, TransportTypeUnknown}, nil
|
||||
}
|
||||
|
||||
s2aTransportCreds, err := s2a.NewClientCreds(&s2a.ClientOptions{
|
||||
S2AAddress: s2aAddr,
|
||||
TransportCreds: transportCredsForS2A,
|
||||
})
|
||||
if err != nil {
|
||||
// Use default if we cannot initialize S2A client transport credentials.
|
||||
return &GRPCTransportCredentials{defaultTransportCreds, config.endpoint, TransportTypeUnknown}, nil
|
||||
}
|
||||
return &GRPCTransportCredentials{s2aTransportCreds, config.s2aMTLSEndpoint, TransportTypeMTLSS2A}, nil
|
||||
}
|
||||
|
||||
// GetHTTPTransportConfig returns a client certificate source and a function for
|
||||
// dialing MTLS with S2A.
|
||||
func GetHTTPTransportConfig(opts *Options) (cert.Provider, func(context.Context, string, string) (net.Conn, error), error) {
|
||||
config, err := getTransportConfig(opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var s2aAddr string
|
||||
var transportCredsForS2A credentials.TransportCredentials
|
||||
|
||||
if config.mtlsS2AAddress != "" {
|
||||
s2aAddr = config.mtlsS2AAddress
|
||||
transportCredsForS2A, err = loadMTLSMDSTransportCreds(mtlsMDSRoot, mtlsMDSKey)
|
||||
if err != nil {
|
||||
log.Printf("Loading MTLS MDS credentials failed: %v", err)
|
||||
if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return config.clientCertSource, nil, nil
|
||||
}
|
||||
}
|
||||
} else if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return config.clientCertSource, nil, nil
|
||||
}
|
||||
|
||||
dialTLSContextFunc := s2a.NewS2ADialTLSContextFunc(&s2a.ClientOptions{
|
||||
S2AAddress: s2aAddr,
|
||||
TransportCreds: transportCredsForS2A,
|
||||
})
|
||||
return nil, dialTLSContextFunc, nil
|
||||
}
|
||||
|
||||
func loadMTLSMDSTransportCreds(mtlsMDSRootFile, mtlsMDSKeyFile string) (credentials.TransportCredentials, error) {
|
||||
rootPEM, err := os.ReadFile(mtlsMDSRootFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
ok := caCertPool.AppendCertsFromPEM(rootPEM)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to load MTLS MDS root certificate")
|
||||
}
|
||||
// The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain
|
||||
// followed by a PEM-encoded private key. For this reason, the concatenation is passed in to the
|
||||
// tls.X509KeyPair function as both the certificate chain and private key arguments.
|
||||
cert, err := tls.LoadX509KeyPair(mtlsMDSKeyFile, mtlsMDSKeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
return credentials.NewTLS(&tlsConfig), nil
|
||||
}
|
||||
|
||||
func getTransportConfig(opts *Options) (*transportConfig, error) {
|
||||
clientCertSource, err := GetClientCertificateProvider(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoint, err := getEndpoint(opts, clientCertSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaultTransportConfig := transportConfig{
|
||||
clientCertSource: clientCertSource,
|
||||
endpoint: endpoint,
|
||||
}
|
||||
|
||||
if !shouldUseS2A(clientCertSource, opts) {
|
||||
return &defaultTransportConfig, nil
|
||||
}
|
||||
|
||||
s2aAddress := GetS2AAddress(opts.Logger)
|
||||
mtlsS2AAddress := GetMTLSS2AAddress(opts.Logger)
|
||||
if s2aAddress == "" && mtlsS2AAddress == "" {
|
||||
return &defaultTransportConfig, nil
|
||||
}
|
||||
return &transportConfig{
|
||||
clientCertSource: clientCertSource,
|
||||
endpoint: endpoint,
|
||||
s2aAddress: s2aAddress,
|
||||
mtlsS2AAddress: mtlsS2AAddress,
|
||||
s2aMTLSEndpoint: opts.defaultMTLSEndpoint(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetClientCertificateProvider returns a default client certificate source, if
|
||||
// not provided by the user.
|
||||
//
|
||||
// A nil default source can be returned if the source does not exist. Any exceptions
|
||||
// encountered while initializing the default source will be reported as client
|
||||
// error (ex. corrupt metadata file).
|
||||
func GetClientCertificateProvider(opts *Options) (cert.Provider, error) {
|
||||
if !isClientCertificateEnabled(opts) {
|
||||
return nil, nil
|
||||
} else if opts.ClientCertProvider != nil {
|
||||
return opts.ClientCertProvider, nil
|
||||
}
|
||||
return cert.DefaultProvider()
|
||||
|
||||
}
|
||||
|
||||
// isClientCertificateEnabled returns true by default for all GDU universe domain, unless explicitly overridden by env var
|
||||
func isClientCertificateEnabled(opts *Options) bool {
|
||||
if value, ok := os.LookupEnv(googleAPIUseCertSource); ok {
|
||||
// error as false is OK
|
||||
b, _ := strconv.ParseBool(value)
|
||||
return b
|
||||
}
|
||||
return opts.isUniverseDomainGDU()
|
||||
}
|
||||
|
||||
type transportConfig struct {
|
||||
// The client certificate source.
|
||||
clientCertSource cert.Provider
|
||||
// The corresponding endpoint to use based on client certificate source.
|
||||
endpoint string
|
||||
// The plaintext S2A address if it can be used, otherwise an empty string.
|
||||
s2aAddress string
|
||||
// The MTLS S2A address if it can be used, otherwise an empty string.
|
||||
mtlsS2AAddress string
|
||||
// The MTLS endpoint to use with S2A.
|
||||
s2aMTLSEndpoint string
|
||||
}
|
||||
|
||||
// getEndpoint returns the endpoint for the service, taking into account the
|
||||
// user-provided endpoint override "settings.Endpoint".
|
||||
//
|
||||
// If no endpoint override is specified, we will either return the default
|
||||
// endpoint or the default mTLS endpoint if a client certificate is available.
|
||||
//
|
||||
// You can override the default endpoint choice (mTLS vs. regular) by setting
|
||||
// the GOOGLE_API_USE_MTLS_ENDPOINT environment variable.
|
||||
//
|
||||
// If the endpoint override is an address (host:port) rather than full base
|
||||
// URL (ex. https://...), then the user-provided address will be merged into
|
||||
// the default endpoint. For example, WithEndpoint("myhost:8000") and
|
||||
// DefaultEndpointTemplate("https://UNIVERSE_DOMAIN/bar/baz") will return
|
||||
// "https://myhost:8080/bar/baz". Note that this does not apply to the mTLS
|
||||
// endpoint.
|
||||
func getEndpoint(opts *Options, clientCertSource cert.Provider) (string, error) {
|
||||
if opts.Endpoint == "" {
|
||||
mtlsMode := getMTLSMode()
|
||||
if mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto) {
|
||||
return opts.defaultMTLSEndpoint(), nil
|
||||
}
|
||||
return opts.defaultEndpoint(), nil
|
||||
}
|
||||
if strings.Contains(opts.Endpoint, "://") {
|
||||
// User passed in a full URL path, use it verbatim.
|
||||
return opts.Endpoint, nil
|
||||
}
|
||||
if opts.defaultEndpoint() == "" {
|
||||
// If DefaultEndpointTemplate is not configured,
|
||||
// use the user provided endpoint verbatim. This allows a naked
|
||||
// "host[:port]" URL to be used with GRPC Direct Path.
|
||||
return opts.Endpoint, nil
|
||||
}
|
||||
|
||||
// Assume user-provided endpoint is host[:port], merge it with the default endpoint.
|
||||
return opts.mergedEndpoint()
|
||||
}
|
||||
|
||||
func getMTLSMode() string {
|
||||
mode := os.Getenv(googleAPIUseMTLS)
|
||||
if mode == "" {
|
||||
mode = os.Getenv(googleAPIUseMTLSOld) // Deprecated.
|
||||
}
|
||||
if mode == "" {
|
||||
return mTLSModeAuto
|
||||
}
|
||||
return strings.ToLower(mode)
|
||||
}
|
||||
65
vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.go
generated
vendored
Normal file
65
vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 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 cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// defaultCertData holds all the variables pertaining to
|
||||
// the default certificate provider created by [DefaultProvider].
|
||||
//
|
||||
// A singleton model is used to allow the provider to be reused
|
||||
// by the transport layer. As mentioned in [DefaultProvider] (provider nil, nil)
|
||||
// may be returned to indicate a default provider could not be found, which
|
||||
// will skip extra tls config in the transport layer .
|
||||
type defaultCertData struct {
|
||||
once sync.Once
|
||||
provider Provider
|
||||
err error
|
||||
}
|
||||
|
||||
var (
|
||||
defaultCert defaultCertData
|
||||
)
|
||||
|
||||
// Provider is a function that can be passed into crypto/tls.Config.GetClientCertificate.
|
||||
type Provider func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
|
||||
|
||||
// errSourceUnavailable is a sentinel error to indicate certificate source is unavailable.
|
||||
var errSourceUnavailable = errors.New("certificate source is unavailable")
|
||||
|
||||
// DefaultProvider returns a certificate source using the preferred EnterpriseCertificateProxySource.
|
||||
// If EnterpriseCertificateProxySource is not available, fall back to the legacy SecureConnectSource.
|
||||
//
|
||||
// If neither source is available (due to missing configurations), a nil Source and a nil Error are
|
||||
// returned to indicate that a default certificate source is unavailable.
|
||||
func DefaultProvider() (Provider, error) {
|
||||
defaultCert.once.Do(func() {
|
||||
defaultCert.provider, defaultCert.err = NewWorkloadX509CertProvider("")
|
||||
if errors.Is(defaultCert.err, errSourceUnavailable) {
|
||||
defaultCert.provider, defaultCert.err = NewEnterpriseCertificateProxyProvider("")
|
||||
if errors.Is(defaultCert.err, errSourceUnavailable) {
|
||||
defaultCert.provider, defaultCert.err = NewSecureConnectProvider("")
|
||||
if errors.Is(defaultCert.err, errSourceUnavailable) {
|
||||
defaultCert.provider, defaultCert.err = nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return defaultCert.provider, defaultCert.err
|
||||
}
|
||||
54
vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.go
generated
vendored
Normal file
54
vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2023 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 cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/googleapis/enterprise-certificate-proxy/client"
|
||||
)
|
||||
|
||||
type ecpSource struct {
|
||||
key *client.Key
|
||||
}
|
||||
|
||||
// NewEnterpriseCertificateProxyProvider creates a certificate source
|
||||
// using the Enterprise Certificate Proxy client, which delegates
|
||||
// certifcate related operations to an OS-specific "signer binary"
|
||||
// that communicates with the native keystore (ex. keychain on MacOS).
|
||||
//
|
||||
// The configFilePath points to a config file containing relevant parameters
|
||||
// such as the certificate issuer and the location of the signer binary.
|
||||
// If configFilePath is empty, the client will attempt to load the config from
|
||||
// a well-known gcloud location.
|
||||
func NewEnterpriseCertificateProxyProvider(configFilePath string) (Provider, error) {
|
||||
key, err := client.Cred(configFilePath)
|
||||
if err != nil {
|
||||
// TODO(codyoss): once this is fixed upstream can handle this error a
|
||||
// little better here. But be safe for now and assume unavailable.
|
||||
return nil, errSourceUnavailable
|
||||
}
|
||||
|
||||
return (&ecpSource{
|
||||
key: key,
|
||||
}).getClientCertificate, nil
|
||||
}
|
||||
|
||||
func (s *ecpSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
var cert tls.Certificate
|
||||
cert.PrivateKey = s.key
|
||||
cert.Certificate = s.key.CertificateChain()
|
||||
return &cert, nil
|
||||
}
|
||||
124
vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.go
generated
vendored
Normal file
124
vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2023 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 cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
metadataPath = ".secureConnect"
|
||||
metadataFile = "context_aware_metadata.json"
|
||||
)
|
||||
|
||||
type secureConnectSource struct {
|
||||
metadata secureConnectMetadata
|
||||
|
||||
// Cache the cert to avoid executing helper command repeatedly.
|
||||
cachedCertMutex sync.Mutex
|
||||
cachedCert *tls.Certificate
|
||||
}
|
||||
|
||||
type secureConnectMetadata struct {
|
||||
Cmd []string `json:"cert_provider_command"`
|
||||
}
|
||||
|
||||
// NewSecureConnectProvider creates a certificate source using
|
||||
// the Secure Connect Helper and its associated metadata file.
|
||||
//
|
||||
// The configFilePath points to the location of the context aware metadata file.
|
||||
// If configFilePath is empty, use the default context aware metadata location.
|
||||
func NewSecureConnectProvider(configFilePath string) (Provider, error) {
|
||||
if configFilePath == "" {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
// Error locating the default config means Secure Connect is not supported.
|
||||
return nil, errSourceUnavailable
|
||||
}
|
||||
configFilePath = filepath.Join(user.HomeDir, metadataPath, metadataFile)
|
||||
}
|
||||
|
||||
file, err := os.ReadFile(configFilePath)
|
||||
if err != nil {
|
||||
// Config file missing means Secure Connect is not supported.
|
||||
// There are non-os.ErrNotExist errors that may be returned.
|
||||
// (e.g. if the home directory is /dev/null, *nix systems will
|
||||
// return ENOTDIR instead of ENOENT)
|
||||
return nil, errSourceUnavailable
|
||||
}
|
||||
|
||||
var metadata secureConnectMetadata
|
||||
if err := json.Unmarshal(file, &metadata); err != nil {
|
||||
return nil, fmt.Errorf("cert: could not parse JSON in %q: %w", configFilePath, err)
|
||||
}
|
||||
if err := validateMetadata(metadata); err != nil {
|
||||
return nil, fmt.Errorf("cert: invalid config in %q: %w", configFilePath, err)
|
||||
}
|
||||
return (&secureConnectSource{
|
||||
metadata: metadata,
|
||||
}).getClientCertificate, nil
|
||||
}
|
||||
|
||||
func validateMetadata(metadata secureConnectMetadata) error {
|
||||
if len(metadata.Cmd) == 0 {
|
||||
return errors.New("empty cert_provider_command")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureConnectSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
s.cachedCertMutex.Lock()
|
||||
defer s.cachedCertMutex.Unlock()
|
||||
if s.cachedCert != nil && !isCertificateExpired(s.cachedCert) {
|
||||
return s.cachedCert, nil
|
||||
}
|
||||
// Expand OS environment variables in the cert provider command such as "$HOME".
|
||||
for i := 0; i < len(s.metadata.Cmd); i++ {
|
||||
s.metadata.Cmd[i] = os.ExpandEnv(s.metadata.Cmd[i])
|
||||
}
|
||||
command := s.metadata.Cmd
|
||||
data, err := exec.Command(command[0], command[1:]...).Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err := tls.X509KeyPair(data, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.cachedCert = &cert
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// isCertificateExpired returns true if the given cert is expired or invalid.
|
||||
func isCertificateExpired(cert *tls.Certificate) bool {
|
||||
if len(cert.Certificate) == 0 {
|
||||
return true
|
||||
}
|
||||
parsed, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return time.Now().After(parsed.NotAfter)
|
||||
}
|
||||
138
vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.go
generated
vendored
Normal file
138
vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2024 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 cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/googleapis/enterprise-certificate-proxy/client/util"
|
||||
)
|
||||
|
||||
type certConfigs struct {
|
||||
Workload *workloadSource `json:"workload"`
|
||||
}
|
||||
|
||||
type workloadSource struct {
|
||||
CertPath string `json:"cert_path"`
|
||||
KeyPath string `json:"key_path"`
|
||||
}
|
||||
|
||||
type certificateConfig struct {
|
||||
CertConfigs certConfigs `json:"cert_configs"`
|
||||
}
|
||||
|
||||
// getconfigFilePath determines the path to the certificate configuration file.
|
||||
// It first checks for the presence of an environment variable that specifies
|
||||
// the file path. If the environment variable is not set, it falls back to
|
||||
// a default configuration file path.
|
||||
func getconfigFilePath() string {
|
||||
envFilePath := util.GetConfigFilePathFromEnv()
|
||||
if envFilePath != "" {
|
||||
return envFilePath
|
||||
}
|
||||
return util.GetDefaultConfigFilePath()
|
||||
|
||||
}
|
||||
|
||||
// GetCertificatePath retrieves the certificate file path from the provided
|
||||
// configuration file. If the configFilePath is empty, it attempts to load
|
||||
// the configuration from a well-known gcloud location.
|
||||
// This function is exposed to allow other packages, such as the
|
||||
// externalaccount package, to retrieve the certificate path without needing
|
||||
// to load the entire certificate configuration.
|
||||
func GetCertificatePath(configFilePath string) (string, error) {
|
||||
if configFilePath == "" {
|
||||
configFilePath = getconfigFilePath()
|
||||
}
|
||||
certFile, _, err := getCertAndKeyFiles(configFilePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return certFile, nil
|
||||
}
|
||||
|
||||
// NewWorkloadX509CertProvider creates a certificate source
|
||||
// that reads a certificate and private key file from the local file system.
|
||||
// This is intended to be used for workload identity federation.
|
||||
//
|
||||
// The configFilePath points to a config file containing relevant parameters
|
||||
// such as the certificate and key file paths.
|
||||
// If configFilePath is empty, the client will attempt to load the config from
|
||||
// a well-known gcloud location.
|
||||
func NewWorkloadX509CertProvider(configFilePath string) (Provider, error) {
|
||||
if configFilePath == "" {
|
||||
configFilePath = getconfigFilePath()
|
||||
}
|
||||
certFile, keyFile, err := getCertAndKeyFiles(configFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
source := &workloadSource{
|
||||
CertPath: certFile,
|
||||
KeyPath: keyFile,
|
||||
}
|
||||
return source.getClientCertificate, nil
|
||||
}
|
||||
|
||||
// getClientCertificate attempts to load the certificate and key from the files specified in the
|
||||
// certificate config.
|
||||
func (s *workloadSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
cert, err := tls.LoadX509KeyPair(s.CertPath, s.KeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// getCertAndKeyFiles attempts to read the provided config file and return the certificate and private
|
||||
// key file paths.
|
||||
func getCertAndKeyFiles(configFilePath string) (string, string, error) {
|
||||
jsonFile, err := os.Open(configFilePath)
|
||||
if err != nil {
|
||||
return "", "", errSourceUnavailable
|
||||
}
|
||||
|
||||
byteValue, err := io.ReadAll(jsonFile)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var config certificateConfig
|
||||
if err := json.Unmarshal(byteValue, &config); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if config.CertConfigs.Workload == nil {
|
||||
return "", "", errSourceUnavailable
|
||||
}
|
||||
|
||||
certFile := config.CertConfigs.Workload.CertPath
|
||||
keyFile := config.CertConfigs.Workload.KeyPath
|
||||
|
||||
if certFile == "" {
|
||||
return "", "", errors.New("certificate configuration is missing the certificate file location")
|
||||
}
|
||||
|
||||
if keyFile == "" {
|
||||
return "", "", errors.New("certificate configuration is missing the key file location")
|
||||
}
|
||||
|
||||
return certFile, keyFile, nil
|
||||
}
|
||||
61
vendor/cloud.google.com/go/auth/internal/transport/headers/headers.go
generated
vendored
Normal file
61
vendor/cloud.google.com/go/auth/internal/transport/headers/headers.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// 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 headers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
)
|
||||
|
||||
// SetAuthHeader uses the provided token to set the Authorization and trust
|
||||
// boundary headers on a request. If the token.Type is empty, the type is
|
||||
// assumed to be Bearer.
|
||||
func SetAuthHeader(token *auth.Token, req *http.Request) {
|
||||
typ := token.Type
|
||||
if typ == "" {
|
||||
typ = internal.TokenTypeBearer
|
||||
}
|
||||
req.Header.Set("Authorization", typ+" "+token.Value)
|
||||
|
||||
if headerVal, setHeader := getTrustBoundaryHeader(token); setHeader {
|
||||
req.Header.Set("x-allowed-locations", headerVal)
|
||||
}
|
||||
}
|
||||
|
||||
// SetAuthMetadata uses the provided token to set the Authorization and trust
|
||||
// boundary metadata. If the token.Type is empty, the type is assumed to be
|
||||
// Bearer.
|
||||
func SetAuthMetadata(token *auth.Token, m map[string]string) {
|
||||
typ := token.Type
|
||||
if typ == "" {
|
||||
typ = internal.TokenTypeBearer
|
||||
}
|
||||
m["authorization"] = typ + " " + token.Value
|
||||
|
||||
if headerVal, setHeader := getTrustBoundaryHeader(token); setHeader {
|
||||
m["x-allowed-locations"] = headerVal
|
||||
}
|
||||
}
|
||||
|
||||
func getTrustBoundaryHeader(token *auth.Token) (val string, present bool) {
|
||||
if data, ok := token.Metadata[internal.TrustBoundaryDataKey]; ok {
|
||||
if tbd, ok := data.(internal.TrustBoundaryData); ok {
|
||||
return tbd.TrustBoundaryHeader()
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
138
vendor/cloud.google.com/go/auth/internal/transport/s2a.go
generated
vendored
Normal file
138
vendor/cloud.google.com/go/auth/internal/transport/s2a.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2023 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 transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"cloud.google.com/go/auth/internal/transport/cert"
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
configEndpointSuffix = "instance/platform-security/auto-mtls-configuration"
|
||||
)
|
||||
|
||||
var (
|
||||
mtlsConfiguration *mtlsConfig
|
||||
|
||||
mtlsOnce sync.Once
|
||||
)
|
||||
|
||||
// GetS2AAddress returns the S2A address to be reached via plaintext connection.
|
||||
// Returns empty string if not set or invalid.
|
||||
func GetS2AAddress(logger *slog.Logger) string {
|
||||
getMetadataMTLSAutoConfig(logger)
|
||||
if !mtlsConfiguration.valid() {
|
||||
return ""
|
||||
}
|
||||
return mtlsConfiguration.S2A.PlaintextAddress
|
||||
}
|
||||
|
||||
// GetMTLSS2AAddress returns the S2A address to be reached via MTLS connection.
|
||||
// Returns empty string if not set or invalid.
|
||||
func GetMTLSS2AAddress(logger *slog.Logger) string {
|
||||
getMetadataMTLSAutoConfig(logger)
|
||||
if !mtlsConfiguration.valid() {
|
||||
return ""
|
||||
}
|
||||
return mtlsConfiguration.S2A.MTLSAddress
|
||||
}
|
||||
|
||||
// mtlsConfig contains the configuration for establishing MTLS connections with Google APIs.
|
||||
type mtlsConfig struct {
|
||||
S2A *s2aAddresses `json:"s2a"`
|
||||
}
|
||||
|
||||
func (c *mtlsConfig) valid() bool {
|
||||
return c != nil && c.S2A != nil
|
||||
}
|
||||
|
||||
// s2aAddresses contains the plaintext and/or MTLS S2A addresses.
|
||||
type s2aAddresses struct {
|
||||
// PlaintextAddress is the plaintext address to reach S2A
|
||||
PlaintextAddress string `json:"plaintext_address"`
|
||||
// MTLSAddress is the MTLS address to reach S2A
|
||||
MTLSAddress string `json:"mtls_address"`
|
||||
}
|
||||
|
||||
func getMetadataMTLSAutoConfig(logger *slog.Logger) {
|
||||
var err error
|
||||
mtlsOnce.Do(func() {
|
||||
mtlsConfiguration, err = queryConfig(logger)
|
||||
if err != nil {
|
||||
log.Printf("Getting MTLS config failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var httpGetMetadataMTLSConfig = func(logger *slog.Logger) (string, error) {
|
||||
metadataClient := metadata.NewWithOptions(&metadata.Options{
|
||||
Logger: logger,
|
||||
})
|
||||
return metadataClient.GetWithContext(context.Background(), configEndpointSuffix)
|
||||
}
|
||||
|
||||
func queryConfig(logger *slog.Logger) (*mtlsConfig, error) {
|
||||
resp, err := httpGetMetadataMTLSConfig(logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying MTLS config from MDS endpoint failed: %w", err)
|
||||
}
|
||||
var config mtlsConfig
|
||||
err = json.Unmarshal([]byte(resp), &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshalling MTLS config from MDS endpoint failed: %w", err)
|
||||
}
|
||||
if config.S2A == nil {
|
||||
return nil, fmt.Errorf("returned MTLS config from MDS endpoint is invalid: %v", config)
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func shouldUseS2A(clientCertSource cert.Provider, opts *Options) bool {
|
||||
// If client cert is found, use that over S2A.
|
||||
if clientCertSource != nil {
|
||||
return false
|
||||
}
|
||||
// If EXPERIMENTAL_GOOGLE_API_USE_S2A is not set to true, skip S2A.
|
||||
if !isGoogleS2AEnabled() {
|
||||
return false
|
||||
}
|
||||
// If DefaultMTLSEndpoint is not set or has endpoint override, skip S2A.
|
||||
if opts.DefaultMTLSEndpoint == "" || opts.Endpoint != "" {
|
||||
return false
|
||||
}
|
||||
// If custom HTTP client is provided, skip S2A.
|
||||
if opts.Client != nil {
|
||||
return false
|
||||
}
|
||||
// If directPath is enabled, skip S2A.
|
||||
return !opts.EnableDirectPath && !opts.EnableDirectPathXds
|
||||
}
|
||||
|
||||
func isGoogleS2AEnabled() bool {
|
||||
b, err := strconv.ParseBool(os.Getenv(googleAPIUseS2AEnv))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return b
|
||||
}
|
||||
107
vendor/cloud.google.com/go/auth/internal/transport/transport.go
generated
vendored
Normal file
107
vendor/cloud.google.com/go/auth/internal/transport/transport.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright 2023 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 transport provided internal helpers for the two transport packages
|
||||
// (grpctransport and httptransport).
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/credentials"
|
||||
)
|
||||
|
||||
// CloneDetectOptions clones a user set detect option into some new memory that
|
||||
// we can internally manipulate before sending onto the detect package.
|
||||
func CloneDetectOptions(oldDo *credentials.DetectOptions) *credentials.DetectOptions {
|
||||
if oldDo == nil {
|
||||
// it is valid for users not to set this, but we will need to to default
|
||||
// some options for them in this case so return some initialized memory
|
||||
// to work with.
|
||||
return &credentials.DetectOptions{}
|
||||
}
|
||||
newDo := &credentials.DetectOptions{
|
||||
// Simple types
|
||||
TokenBindingType: oldDo.TokenBindingType,
|
||||
Audience: oldDo.Audience,
|
||||
Subject: oldDo.Subject,
|
||||
EarlyTokenRefresh: oldDo.EarlyTokenRefresh,
|
||||
TokenURL: oldDo.TokenURL,
|
||||
STSAudience: oldDo.STSAudience,
|
||||
CredentialsFile: oldDo.CredentialsFile,
|
||||
UseSelfSignedJWT: oldDo.UseSelfSignedJWT,
|
||||
UniverseDomain: oldDo.UniverseDomain,
|
||||
|
||||
// These fields are pointer types that we just want to use exactly as
|
||||
// the user set, copy the ref
|
||||
Client: oldDo.Client,
|
||||
Logger: oldDo.Logger,
|
||||
AuthHandlerOptions: oldDo.AuthHandlerOptions,
|
||||
}
|
||||
|
||||
// Smartly size this memory and copy below.
|
||||
if len(oldDo.CredentialsJSON) > 0 {
|
||||
newDo.CredentialsJSON = make([]byte, len(oldDo.CredentialsJSON))
|
||||
copy(newDo.CredentialsJSON, oldDo.CredentialsJSON)
|
||||
}
|
||||
if len(oldDo.Scopes) > 0 {
|
||||
newDo.Scopes = make([]string, len(oldDo.Scopes))
|
||||
copy(newDo.Scopes, oldDo.Scopes)
|
||||
}
|
||||
|
||||
return newDo
|
||||
}
|
||||
|
||||
// ValidateUniverseDomain verifies that the universe domain configured for the
|
||||
// client matches the universe domain configured for the credentials.
|
||||
func ValidateUniverseDomain(clientUniverseDomain, credentialsUniverseDomain string) error {
|
||||
if clientUniverseDomain != credentialsUniverseDomain {
|
||||
return fmt.Errorf(
|
||||
"the configured universe domain (%q) does not match the universe "+
|
||||
"domain found in the credentials (%q). If you haven't configured "+
|
||||
"the universe domain explicitly, \"googleapis.com\" is the default",
|
||||
clientUniverseDomain,
|
||||
credentialsUniverseDomain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultHTTPClientWithTLS constructs an HTTPClient using the provided tlsConfig, to support mTLS.
|
||||
func DefaultHTTPClientWithTLS(tlsConfig *tls.Config) *http.Client {
|
||||
trans := BaseTransport()
|
||||
trans.TLSClientConfig = tlsConfig
|
||||
return &http.Client{Transport: trans}
|
||||
}
|
||||
|
||||
// BaseTransport returns a default [http.Transport] which can be used if
|
||||
// [http.DefaultTransport] has been overwritten.
|
||||
func BaseTransport() *http.Transport {
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
100
vendor/cloud.google.com/go/auth/internal/trustboundary/external_accounts_config_providers.go
generated
vendored
Normal file
100
vendor/cloud.google.com/go/auth/internal/trustboundary/external_accounts_config_providers.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
workloadAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/projects/%s/locations/global/workloadIdentityPools/%s/allowedLocations"
|
||||
workforceAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/locations/global/workforcePools/%s/allowedLocations"
|
||||
)
|
||||
|
||||
var (
|
||||
workforceAudiencePattern = regexp.MustCompile(`//iam\.([^/]+)/locations/global/workforcePools/([^/]+)`)
|
||||
workloadAudiencePattern = regexp.MustCompile(`//iam\.([^/]+)/projects/([^/]+)/locations/global/workloadIdentityPools/([^/]+)`)
|
||||
)
|
||||
|
||||
// NewExternalAccountConfigProvider creates a new ConfigProvider for external accounts.
|
||||
func NewExternalAccountConfigProvider(audience, inputUniverseDomain string) (ConfigProvider, error) {
|
||||
var audienceDomain, projectNumber, poolID string
|
||||
var isWorkload bool
|
||||
|
||||
matches := workloadAudiencePattern.FindStringSubmatch(audience)
|
||||
if len(matches) == 4 { // Expecting full match, domain, projectNumber, poolID
|
||||
audienceDomain = matches[1]
|
||||
projectNumber = matches[2]
|
||||
poolID = matches[3]
|
||||
isWorkload = true
|
||||
} else {
|
||||
matches = workforceAudiencePattern.FindStringSubmatch(audience)
|
||||
if len(matches) == 3 { // Expecting full match, domain, poolID
|
||||
audienceDomain = matches[1]
|
||||
poolID = matches[2]
|
||||
isWorkload = false
|
||||
} else {
|
||||
return nil, fmt.Errorf("trustboundary: unknown audience format: %q", audience)
|
||||
}
|
||||
}
|
||||
|
||||
effectiveUniverseDomain := inputUniverseDomain
|
||||
if effectiveUniverseDomain == "" {
|
||||
effectiveUniverseDomain = audienceDomain
|
||||
} else if audienceDomain != "" && effectiveUniverseDomain != audienceDomain {
|
||||
return nil, fmt.Errorf("trustboundary: provided universe domain (%q) does not match domain in audience (%q)", inputUniverseDomain, audienceDomain)
|
||||
}
|
||||
|
||||
if isWorkload {
|
||||
return &workloadIdentityPoolConfigProvider{
|
||||
projectNumber: projectNumber,
|
||||
poolID: poolID,
|
||||
universeDomain: effectiveUniverseDomain,
|
||||
}, nil
|
||||
}
|
||||
return &workforcePoolConfigProvider{
|
||||
poolID: poolID,
|
||||
universeDomain: effectiveUniverseDomain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type workforcePoolConfigProvider struct {
|
||||
poolID string
|
||||
universeDomain string
|
||||
}
|
||||
|
||||
func (p *workforcePoolConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf(workforceAllowedLocationsEndpoint, p.universeDomain, p.poolID), nil
|
||||
}
|
||||
|
||||
func (p *workforcePoolConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
return p.universeDomain, nil
|
||||
}
|
||||
|
||||
type workloadIdentityPoolConfigProvider struct {
|
||||
projectNumber string
|
||||
poolID string
|
||||
universeDomain string
|
||||
}
|
||||
|
||||
func (p *workloadIdentityPoolConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf(workloadAllowedLocationsEndpoint, p.universeDomain, p.projectNumber, p.poolID), nil
|
||||
}
|
||||
|
||||
func (p *workloadIdentityPoolConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
return p.universeDomain, nil
|
||||
}
|
||||
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
|
||||
}
|
||||
20
vendor/cloud.google.com/go/auth/internal/version.go
generated
vendored
Normal file
20
vendor/cloud.google.com/go/auth/internal/version.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2026 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.
|
||||
|
||||
// Code generated by gapicgen. DO NOT EDIT.
|
||||
|
||||
package internal
|
||||
|
||||
// Version is the current tagged release of the library.
|
||||
const Version = "0.18.1"
|
||||
Reference in New Issue
Block a user