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