...

Source file src/cloud.google.com/go/auth/internal/internal.go

Documentation: cloud.google.com/go/auth/internal

     1  // Copyright 2023 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package internal
    16  
    17  import (
    18  	"context"
    19  	"crypto/rsa"
    20  	"crypto/x509"
    21  	"encoding/json"
    22  	"encoding/pem"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"net/http"
    27  	"os"
    28  	"sync"
    29  	"time"
    30  
    31  	"cloud.google.com/go/compute/metadata"
    32  )
    33  
    34  const (
    35  	// TokenTypeBearer is the auth header prefix for bearer tokens.
    36  	TokenTypeBearer = "Bearer"
    37  
    38  	// QuotaProjectEnvVar is the environment variable for setting the quota
    39  	// project.
    40  	QuotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
    41  	projectEnvVar      = "GOOGLE_CLOUD_PROJECT"
    42  	maxBodySize        = 1 << 20
    43  
    44  	// DefaultUniverseDomain is the default value for universe domain.
    45  	// Universe domain is the default service domain for a given Cloud universe.
    46  	DefaultUniverseDomain = "googleapis.com"
    47  )
    48  
    49  // CloneDefaultClient returns a [http.Client] with some good defaults.
    50  func CloneDefaultClient() *http.Client {
    51  	return &http.Client{
    52  		Transport: http.DefaultTransport.(*http.Transport).Clone(),
    53  		Timeout:   30 * time.Second,
    54  	}
    55  }
    56  
    57  // ParseKey converts the binary contents of a private key file
    58  // to an *rsa.PrivateKey. It detects whether the private key is in a
    59  // PEM container or not. If so, it extracts the the private key
    60  // from PEM container before conversion. It only supports PEM
    61  // containers with no passphrase.
    62  func ParseKey(key []byte) (*rsa.PrivateKey, error) {
    63  	block, _ := pem.Decode(key)
    64  	if block != nil {
    65  		key = block.Bytes
    66  	}
    67  	parsedKey, err := x509.ParsePKCS8PrivateKey(key)
    68  	if err != nil {
    69  		parsedKey, err = x509.ParsePKCS1PrivateKey(key)
    70  		if err != nil {
    71  			return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8: %w", err)
    72  		}
    73  	}
    74  	parsed, ok := parsedKey.(*rsa.PrivateKey)
    75  	if !ok {
    76  		return nil, errors.New("private key is invalid")
    77  	}
    78  	return parsed, nil
    79  }
    80  
    81  // GetQuotaProject retrieves quota project with precedence being: override,
    82  // environment variable, creds json file.
    83  func GetQuotaProject(b []byte, override string) string {
    84  	if override != "" {
    85  		return override
    86  	}
    87  	if env := os.Getenv(QuotaProjectEnvVar); env != "" {
    88  		return env
    89  	}
    90  	if b == nil {
    91  		return ""
    92  	}
    93  	var v struct {
    94  		QuotaProject string `json:"quota_project_id"`
    95  	}
    96  	if err := json.Unmarshal(b, &v); err != nil {
    97  		return ""
    98  	}
    99  	return v.QuotaProject
   100  }
   101  
   102  // GetProjectID retrieves project with precedence being: override,
   103  // environment variable, creds json file.
   104  func GetProjectID(b []byte, override string) string {
   105  	if override != "" {
   106  		return override
   107  	}
   108  	if env := os.Getenv(projectEnvVar); env != "" {
   109  		return env
   110  	}
   111  	if b == nil {
   112  		return ""
   113  	}
   114  	var v struct {
   115  		ProjectID string `json:"project_id"` // standard service account key
   116  		Project   string `json:"project"`    // gdch key
   117  	}
   118  	if err := json.Unmarshal(b, &v); err != nil {
   119  		return ""
   120  	}
   121  	if v.ProjectID != "" {
   122  		return v.ProjectID
   123  	}
   124  	return v.Project
   125  }
   126  
   127  // ReadAll consumes the whole reader and safely reads the content of its body
   128  // with some overflow protection.
   129  func ReadAll(r io.Reader) ([]byte, error) {
   130  	return io.ReadAll(io.LimitReader(r, maxBodySize))
   131  }
   132  
   133  // StaticCredentialsProperty is a helper for creating static credentials
   134  // properties.
   135  func StaticCredentialsProperty(s string) StaticProperty {
   136  	return StaticProperty(s)
   137  }
   138  
   139  // StaticProperty always returns that value of the underlying string.
   140  type StaticProperty string
   141  
   142  // GetProperty loads the properly value provided the given context.
   143  func (p StaticProperty) GetProperty(context.Context) (string, error) {
   144  	return string(p), nil
   145  }
   146  
   147  // ComputeUniverseDomainProvider fetches the credentials universe domain from
   148  // the google cloud metadata service.
   149  type ComputeUniverseDomainProvider struct {
   150  	universeDomainOnce sync.Once
   151  	universeDomain     string
   152  	universeDomainErr  error
   153  }
   154  
   155  // GetProperty fetches the credentials universe domain from the google cloud
   156  // metadata service.
   157  func (c *ComputeUniverseDomainProvider) GetProperty(ctx context.Context) (string, error) {
   158  	c.universeDomainOnce.Do(func() {
   159  		c.universeDomain, c.universeDomainErr = getMetadataUniverseDomain(ctx)
   160  	})
   161  	if c.universeDomainErr != nil {
   162  		return "", c.universeDomainErr
   163  	}
   164  	return c.universeDomain, nil
   165  }
   166  
   167  // httpGetMetadataUniverseDomain is a package var for unit test substitution.
   168  var httpGetMetadataUniverseDomain = func(ctx context.Context) (string, error) {
   169  	client := metadata.NewClient(&http.Client{Timeout: time.Second})
   170  	// TODO(quartzmo): set ctx on request
   171  	return client.Get("universe/universe_domain")
   172  }
   173  
   174  func getMetadataUniverseDomain(ctx context.Context) (string, error) {
   175  	universeDomain, err := httpGetMetadataUniverseDomain(ctx)
   176  	if err == nil {
   177  		return universeDomain, nil
   178  	}
   179  	if _, ok := err.(metadata.NotDefinedError); ok {
   180  		// http.StatusNotFound (404)
   181  		return DefaultUniverseDomain, nil
   182  	}
   183  	return "", err
   184  }
   185  

View as plain text