google.golang.org/grpc@v1.62.1/credentials/oauth/oauth.go (about) 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 }