...

Source file src/google.golang.org/grpc/credentials/oauth/oauth.go

Documentation: google.golang.org/grpc/credentials/oauth

     1  /*
     2   *
     3   * Copyright 2015 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  // Package oauth implements gRPC credentials using OAuth.
    20  package oauth
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"net/url"
    26  	"os"
    27  	"sync"
    28  
    29  	"golang.org/x/oauth2"
    30  	"golang.org/x/oauth2/google"
    31  	"golang.org/x/oauth2/jwt"
    32  	"google.golang.org/grpc/credentials"
    33  )
    34  
    35  // TokenSource supplies PerRPCCredentials from an oauth2.TokenSource.
    36  type TokenSource struct {
    37  	oauth2.TokenSource
    38  }
    39  
    40  // GetRequestMetadata gets the request metadata as a map from a TokenSource.
    41  func (ts TokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
    42  	token, err := ts.Token()
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	ri, _ := credentials.RequestInfoFromContext(ctx)
    47  	if err = credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {
    48  		return nil, fmt.Errorf("unable to transfer TokenSource PerRPCCredentials: %v", err)
    49  	}
    50  	return map[string]string{
    51  		"authorization": token.Type() + " " + token.AccessToken,
    52  	}, nil
    53  }
    54  
    55  // RequireTransportSecurity indicates whether the credentials requires transport security.
    56  func (ts TokenSource) RequireTransportSecurity() bool {
    57  	return true
    58  }
    59  
    60  // removeServiceNameFromJWTURI removes RPC service name from URI.
    61  func removeServiceNameFromJWTURI(uri string) (string, error) {
    62  	parsed, err := url.Parse(uri)
    63  	if err != nil {
    64  		return "", err
    65  	}
    66  	parsed.Path = "/"
    67  	return parsed.String(), nil
    68  }
    69  
    70  type jwtAccess struct {
    71  	jsonKey []byte
    72  }
    73  
    74  // NewJWTAccessFromFile creates PerRPCCredentials from the given keyFile.
    75  func NewJWTAccessFromFile(keyFile string) (credentials.PerRPCCredentials, error) {
    76  	jsonKey, err := os.ReadFile(keyFile)
    77  	if err != nil {
    78  		return nil, fmt.Errorf("credentials: failed to read the service account key file: %v", err)
    79  	}
    80  	return NewJWTAccessFromKey(jsonKey)
    81  }
    82  
    83  // NewJWTAccessFromKey creates PerRPCCredentials from the given jsonKey.
    84  func NewJWTAccessFromKey(jsonKey []byte) (credentials.PerRPCCredentials, error) {
    85  	return jwtAccess{jsonKey}, nil
    86  }
    87  
    88  func (j jwtAccess) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
    89  	// Remove RPC service name from URI that will be used as audience
    90  	// in a self-signed JWT token. It follows https://google.aip.dev/auth/4111.
    91  	aud, err := removeServiceNameFromJWTURI(uri[0])
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	// TODO: the returned TokenSource is reusable. Store it in a sync.Map, with
    96  	// uri as the key, to avoid recreating for every RPC.
    97  	ts, err := google.JWTAccessTokenSourceFromJSON(j.jsonKey, aud)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	token, err := ts.Token()
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	ri, _ := credentials.RequestInfoFromContext(ctx)
   106  	if err = credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {
   107  		return nil, fmt.Errorf("unable to transfer jwtAccess PerRPCCredentials: %v", err)
   108  	}
   109  	return map[string]string{
   110  		"authorization": token.Type() + " " + token.AccessToken,
   111  	}, nil
   112  }
   113  
   114  func (j jwtAccess) RequireTransportSecurity() bool {
   115  	return true
   116  }
   117  
   118  // oauthAccess supplies PerRPCCredentials from a given token.
   119  type oauthAccess struct {
   120  	token oauth2.Token
   121  }
   122  
   123  // NewOauthAccess constructs the PerRPCCredentials using a given token.
   124  //
   125  // Deprecated: use oauth.TokenSource instead.
   126  func NewOauthAccess(token *oauth2.Token) credentials.PerRPCCredentials {
   127  	return oauthAccess{token: *token}
   128  }
   129  
   130  func (oa oauthAccess) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
   131  	ri, _ := credentials.RequestInfoFromContext(ctx)
   132  	if err := credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {
   133  		return nil, fmt.Errorf("unable to transfer oauthAccess PerRPCCredentials: %v", err)
   134  	}
   135  	return map[string]string{
   136  		"authorization": oa.token.Type() + " " + oa.token.AccessToken,
   137  	}, nil
   138  }
   139  
   140  func (oa oauthAccess) RequireTransportSecurity() bool {
   141  	return true
   142  }
   143  
   144  // NewComputeEngine constructs the PerRPCCredentials that fetches access tokens from
   145  // Google Compute Engine (GCE)'s metadata server. It is only valid to use this
   146  // if your program is running on a GCE instance.
   147  // TODO(dsymonds): Deprecate and remove this.
   148  func NewComputeEngine() credentials.PerRPCCredentials {
   149  	return TokenSource{google.ComputeTokenSource("")}
   150  }
   151  
   152  // serviceAccount represents PerRPCCredentials via JWT signing key.
   153  type serviceAccount struct {
   154  	mu     sync.Mutex
   155  	config *jwt.Config
   156  	t      *oauth2.Token
   157  }
   158  
   159  func (s *serviceAccount) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
   160  	s.mu.Lock()
   161  	defer s.mu.Unlock()
   162  	if !s.t.Valid() {
   163  		var err error
   164  		s.t, err = s.config.TokenSource(ctx).Token()
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  	}
   169  	ri, _ := credentials.RequestInfoFromContext(ctx)
   170  	if err := credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {
   171  		return nil, fmt.Errorf("unable to transfer serviceAccount PerRPCCredentials: %v", err)
   172  	}
   173  	return map[string]string{
   174  		"authorization": s.t.Type() + " " + s.t.AccessToken,
   175  	}, nil
   176  }
   177  
   178  func (s *serviceAccount) RequireTransportSecurity() bool {
   179  	return true
   180  }
   181  
   182  // NewServiceAccountFromKey constructs the PerRPCCredentials using the JSON key slice
   183  // from a Google Developers service account.
   184  func NewServiceAccountFromKey(jsonKey []byte, scope ...string) (credentials.PerRPCCredentials, error) {
   185  	config, err := google.JWTConfigFromJSON(jsonKey, scope...)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	return &serviceAccount{config: config}, nil
   190  }
   191  
   192  // NewServiceAccountFromFile constructs the PerRPCCredentials using the JSON key file
   193  // of a Google Developers service account.
   194  func NewServiceAccountFromFile(keyFile string, scope ...string) (credentials.PerRPCCredentials, error) {
   195  	jsonKey, err := os.ReadFile(keyFile)
   196  	if err != nil {
   197  		return nil, fmt.Errorf("credentials: failed to read the service account key file: %v", err)
   198  	}
   199  	return NewServiceAccountFromKey(jsonKey, scope...)
   200  }
   201  
   202  // NewApplicationDefault returns "Application Default Credentials". For more
   203  // detail, see https://developers.google.com/accounts/docs/application-default-credentials.
   204  func NewApplicationDefault(ctx context.Context, scope ...string) (credentials.PerRPCCredentials, error) {
   205  	creds, err := google.FindDefaultCredentials(ctx, scope...)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	// If JSON is nil, the authentication is provided by the environment and not
   211  	// with a credentials file, e.g. when code is running on Google Cloud
   212  	// Platform. Use the returned token source.
   213  	if creds.JSON == nil {
   214  		return TokenSource{creds.TokenSource}, nil
   215  	}
   216  
   217  	// If auth is provided by env variable or creds file, the behavior will be
   218  	// different based on whether scope is set. Because the returned
   219  	// creds.TokenSource does oauth with jwt by default, and it requires scope.
   220  	// We can only use it if scope is not empty, otherwise it will fail with
   221  	// missing scope error.
   222  	//
   223  	// If scope is set, use it, it should just work.
   224  	//
   225  	// If scope is not set, we try to use jwt directly without oauth (this only
   226  	// works if it's a service account).
   227  
   228  	if len(scope) != 0 {
   229  		return TokenSource{creds.TokenSource}, nil
   230  	}
   231  
   232  	// Try to convert JSON to a jwt config without setting the optional scope
   233  	// parameter to check if it's a service account (the function errors if it's
   234  	// not). This is necessary because the returned config doesn't show the type
   235  	// of the account.
   236  	if _, err := google.JWTConfigFromJSON(creds.JSON); err != nil {
   237  		// If this fails, it's not a service account, return the original
   238  		// TokenSource from above.
   239  		return TokenSource{creds.TokenSource}, nil
   240  	}
   241  
   242  	// If it's a service account, create a JWT only access with the key.
   243  	return NewJWTAccessFromKey(creds.JSON)
   244  }
   245  

View as plain text