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 httptransport 16 17 import ( 18 "crypto/tls" 19 "errors" 20 "fmt" 21 "net/http" 22 23 "cloud.google.com/go/auth" 24 detect "cloud.google.com/go/auth/credentials" 25 "cloud.google.com/go/auth/internal" 26 "cloud.google.com/go/auth/internal/transport" 27 ) 28 29 // ClientCertProvider is a function that returns a TLS client certificate to be 30 // used when opening TLS connections. It follows the same semantics as 31 // [crypto/tls.Config.GetClientCertificate]. 32 type ClientCertProvider = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) 33 34 // Options used to configure a [net/http.Client] from [NewClient]. 35 type Options struct { 36 // DisableTelemetry disables default telemetry (OpenCensus). An example 37 // reason to do so would be to bind custom telemetry that overrides the 38 // defaults. 39 DisableTelemetry bool 40 // DisableAuthentication specifies that no authentication should be used. It 41 // is suitable only for testing and for accessing public resources, like 42 // public Google Cloud Storage buckets. 43 DisableAuthentication bool 44 // Headers are extra HTTP headers that will be appended to every outgoing 45 // request. 46 Headers http.Header 47 // BaseRoundTripper overrides the base transport used for serving requests. 48 // If specified ClientCertProvider is ignored. 49 BaseRoundTripper http.RoundTripper 50 // Endpoint overrides the default endpoint to be used for a service. 51 Endpoint string 52 // APIKey specifies an API key to be used as the basis for authentication. 53 // If set DetectOpts are ignored. 54 APIKey string 55 // Credentials used to add Authorization header to all requests. If set 56 // DetectOpts are ignored. 57 Credentials *auth.Credentials 58 // ClientCertProvider is a function that returns a TLS client certificate to 59 // be used when opening TLS connections. It follows the same semantics as 60 // crypto/tls.Config.GetClientCertificate. 61 ClientCertProvider ClientCertProvider 62 // DetectOpts configures settings for detect Application Default 63 // Credentials. 64 DetectOpts *detect.DetectOptions 65 // UniverseDomain is the default service domain for a given Cloud universe. 66 // The default value is "googleapis.com". This is the universe domain 67 // configured for the client, which will be compared to the universe domain 68 // that is separately configured for the credentials. 69 UniverseDomain string 70 71 // InternalOptions are NOT meant to be set directly by consumers of this 72 // package, they should only be set by generated client code. 73 InternalOptions *InternalOptions 74 } 75 76 func (o *Options) validate() error { 77 if o == nil { 78 return errors.New("httptransport: opts required to be non-nil") 79 } 80 if o.InternalOptions != nil && o.InternalOptions.SkipValidation { 81 return nil 82 } 83 hasCreds := o.APIKey != "" || 84 o.Credentials != nil || 85 (o.DetectOpts != nil && len(o.DetectOpts.CredentialsJSON) > 0) || 86 (o.DetectOpts != nil && o.DetectOpts.CredentialsFile != "") 87 if o.DisableAuthentication && hasCreds { 88 return errors.New("httptransport: DisableAuthentication is incompatible with options that set or detect credentials") 89 } 90 return nil 91 } 92 93 // client returns the client a user set for the detect options or nil if one was 94 // not set. 95 func (o *Options) client() *http.Client { 96 if o.DetectOpts != nil && o.DetectOpts.Client != nil { 97 return o.DetectOpts.Client 98 } 99 return nil 100 } 101 102 func (o *Options) resolveDetectOptions() *detect.DetectOptions { 103 io := o.InternalOptions 104 // soft-clone these so we are not updating a ref the user holds and may reuse 105 do := transport.CloneDetectOptions(o.DetectOpts) 106 107 // If scoped JWTs are enabled user provided an aud, allow self-signed JWT. 108 if (io != nil && io.EnableJWTWithScope) || do.Audience != "" { 109 do.UseSelfSignedJWT = true 110 } 111 // Only default scopes if user did not also set an audience. 112 if len(do.Scopes) == 0 && do.Audience == "" && io != nil && len(io.DefaultScopes) > 0 { 113 do.Scopes = make([]string, len(io.DefaultScopes)) 114 copy(do.Scopes, io.DefaultScopes) 115 } 116 if len(do.Scopes) == 0 && do.Audience == "" && io != nil { 117 do.Audience = o.InternalOptions.DefaultAudience 118 } 119 return do 120 } 121 122 // InternalOptions are only meant to be set by generated client code. These are 123 // not meant to be set directly by consumers of this package. Configuration in 124 // this type is considered EXPERIMENTAL and may be removed at any time in the 125 // future without warning. 126 type InternalOptions struct { 127 // EnableJWTWithScope specifies if scope can be used with self-signed JWT. 128 EnableJWTWithScope bool 129 // DefaultAudience specifies a default audience to be used as the audience 130 // field ("aud") for the JWT token authentication. 131 DefaultAudience string 132 // DefaultEndpointTemplate combined with UniverseDomain specifies the 133 // default endpoint. 134 DefaultEndpointTemplate string 135 // DefaultMTLSEndpoint specifies the default mTLS endpoint. 136 DefaultMTLSEndpoint string 137 // DefaultScopes specifies the default OAuth2 scopes to be used for a 138 // service. 139 DefaultScopes []string 140 // SkipValidation bypasses validation on Options. It should only be used 141 // internally for clients that needs more control over their transport. 142 SkipValidation bool 143 } 144 145 // AddAuthorizationMiddleware adds a middleware to the provided client's 146 // transport that sets the Authorization header with the value produced by the 147 // provided [cloud.google.com/go/auth.Credentials]. An error is returned only 148 // if client or creds is nil. 149 func AddAuthorizationMiddleware(client *http.Client, creds *auth.Credentials) error { 150 if client == nil || creds == nil { 151 return fmt.Errorf("httptransport: client and tp must not be nil") 152 } 153 base := client.Transport 154 if base == nil { 155 base = http.DefaultTransport.(*http.Transport).Clone() 156 } 157 client.Transport = &authTransport{ 158 creds: creds, 159 base: base, 160 // TODO(quartzmo): Somehow set clientUniverseDomain from impersonate calls. 161 } 162 return nil 163 } 164 165 // NewClient returns a [net/http.Client] that can be used to communicate with a 166 // Google cloud service, configured with the provided [Options]. It 167 // automatically appends Authorization headers to all outgoing requests. 168 func NewClient(opts *Options) (*http.Client, error) { 169 if err := opts.validate(); err != nil { 170 return nil, err 171 } 172 173 tOpts := &transport.Options{ 174 Endpoint: opts.Endpoint, 175 ClientCertProvider: opts.ClientCertProvider, 176 Client: opts.client(), 177 UniverseDomain: opts.UniverseDomain, 178 } 179 if io := opts.InternalOptions; io != nil { 180 tOpts.DefaultEndpointTemplate = io.DefaultEndpointTemplate 181 tOpts.DefaultMTLSEndpoint = io.DefaultMTLSEndpoint 182 } 183 clientCertProvider, dialTLSContext, err := transport.GetHTTPTransportConfig(tOpts) 184 if err != nil { 185 return nil, err 186 } 187 baseRoundTripper := opts.BaseRoundTripper 188 if baseRoundTripper == nil { 189 baseRoundTripper = defaultBaseTransport(clientCertProvider, dialTLSContext) 190 } 191 trans, err := newTransport(baseRoundTripper, opts) 192 if err != nil { 193 return nil, err 194 } 195 return &http.Client{ 196 Transport: trans, 197 }, nil 198 } 199 200 // SetAuthHeader uses the provided token to set the Authorization header on a 201 // request. If the token.Type is empty, the type is assumed to be Bearer. 202 func SetAuthHeader(token *auth.Token, req *http.Request) { 203 typ := token.Type 204 if typ == "" { 205 typ = internal.TokenTypeBearer 206 } 207 req.Header.Set("Authorization", typ+" "+token.Value) 208 } 209