mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-24 14:11:25 +00:00
225 lines
8.2 KiB
Go
225 lines
8.2 KiB
Go
|
// Copyright 2023 Google LLC
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package httptransport
|
||
|
|
||
|
import (
|
||
|
"crypto/tls"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
|
||
|
"cloud.google.com/go/auth"
|
||
|
detect "cloud.google.com/go/auth/credentials"
|
||
|
"cloud.google.com/go/auth/internal"
|
||
|
"cloud.google.com/go/auth/internal/transport"
|
||
|
)
|
||
|
|
||
|
// ClientCertProvider is a function that returns a TLS client certificate to be
|
||
|
// used when opening TLS connections. It follows the same semantics as
|
||
|
// [crypto/tls.Config.GetClientCertificate].
|
||
|
type ClientCertProvider = func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
|
||
|
|
||
|
// Options used to configure a [net/http.Client] from [NewClient].
|
||
|
type Options struct {
|
||
|
// DisableTelemetry disables default telemetry (OpenTelemetry). An example
|
||
|
// reason to do so would be to bind custom telemetry that overrides the
|
||
|
// defaults.
|
||
|
DisableTelemetry bool
|
||
|
// DisableAuthentication specifies that no authentication should be used. It
|
||
|
// is suitable only for testing and for accessing public resources, like
|
||
|
// public Google Cloud Storage buckets.
|
||
|
DisableAuthentication bool
|
||
|
// Headers are extra HTTP headers that will be appended to every outgoing
|
||
|
// request.
|
||
|
Headers http.Header
|
||
|
// BaseRoundTripper overrides the base transport used for serving requests.
|
||
|
// If specified ClientCertProvider is ignored.
|
||
|
BaseRoundTripper http.RoundTripper
|
||
|
// Endpoint overrides the default endpoint to be used for a service.
|
||
|
Endpoint string
|
||
|
// APIKey specifies an API key to be used as the basis for authentication.
|
||
|
// If set DetectOpts are ignored.
|
||
|
APIKey string
|
||
|
// Credentials used to add Authorization header to all requests. If set
|
||
|
// DetectOpts are ignored.
|
||
|
Credentials *auth.Credentials
|
||
|
// ClientCertProvider is a function that returns a TLS client certificate to
|
||
|
// be used when opening TLS connections. It follows the same semantics as
|
||
|
// crypto/tls.Config.GetClientCertificate.
|
||
|
ClientCertProvider ClientCertProvider
|
||
|
// DetectOpts configures settings for detect Application Default
|
||
|
// Credentials.
|
||
|
DetectOpts *detect.DetectOptions
|
||
|
// UniverseDomain is the default service domain for a given Cloud universe.
|
||
|
// The default value is "googleapis.com". This is the universe domain
|
||
|
// configured for the client, which will be compared to the universe domain
|
||
|
// that is separately configured for the credentials.
|
||
|
UniverseDomain string
|
||
|
|
||
|
// InternalOptions are NOT meant to be set directly by consumers of this
|
||
|
// package, they should only be set by generated client code.
|
||
|
InternalOptions *InternalOptions
|
||
|
}
|
||
|
|
||
|
func (o *Options) validate() error {
|
||
|
if o == nil {
|
||
|
return errors.New("httptransport: opts required to be non-nil")
|
||
|
}
|
||
|
if o.InternalOptions != nil && o.InternalOptions.SkipValidation {
|
||
|
return nil
|
||
|
}
|
||
|
hasCreds := o.APIKey != "" ||
|
||
|
o.Credentials != nil ||
|
||
|
(o.DetectOpts != nil && len(o.DetectOpts.CredentialsJSON) > 0) ||
|
||
|
(o.DetectOpts != nil && o.DetectOpts.CredentialsFile != "")
|
||
|
if o.DisableAuthentication && hasCreds {
|
||
|
return errors.New("httptransport: DisableAuthentication is incompatible with options that set or detect credentials")
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// client returns the client a user set for the detect options or nil if one was
|
||
|
// not set.
|
||
|
func (o *Options) client() *http.Client {
|
||
|
if o.DetectOpts != nil && o.DetectOpts.Client != nil {
|
||
|
return o.DetectOpts.Client
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (o *Options) resolveDetectOptions() *detect.DetectOptions {
|
||
|
io := o.InternalOptions
|
||
|
// soft-clone these so we are not updating a ref the user holds and may reuse
|
||
|
do := transport.CloneDetectOptions(o.DetectOpts)
|
||
|
|
||
|
// If scoped JWTs are enabled user provided an aud, allow self-signed JWT.
|
||
|
if (io != nil && io.EnableJWTWithScope) || do.Audience != "" {
|
||
|
do.UseSelfSignedJWT = true
|
||
|
}
|
||
|
// Only default scopes if user did not also set an audience.
|
||
|
if len(do.Scopes) == 0 && do.Audience == "" && io != nil && len(io.DefaultScopes) > 0 {
|
||
|
do.Scopes = make([]string, len(io.DefaultScopes))
|
||
|
copy(do.Scopes, io.DefaultScopes)
|
||
|
}
|
||
|
if len(do.Scopes) == 0 && do.Audience == "" && io != nil {
|
||
|
do.Audience = o.InternalOptions.DefaultAudience
|
||
|
}
|
||
|
if o.ClientCertProvider != nil {
|
||
|
tlsConfig := &tls.Config{
|
||
|
GetClientCertificate: o.ClientCertProvider,
|
||
|
}
|
||
|
do.Client = transport.DefaultHTTPClientWithTLS(tlsConfig)
|
||
|
do.TokenURL = detect.GoogleMTLSTokenURL
|
||
|
}
|
||
|
return do
|
||
|
}
|
||
|
|
||
|
// InternalOptions are only meant to be set by generated client code. These are
|
||
|
// not meant to be set directly by consumers of this package. Configuration in
|
||
|
// this type is considered EXPERIMENTAL and may be removed at any time in the
|
||
|
// future without warning.
|
||
|
type InternalOptions struct {
|
||
|
// EnableJWTWithScope specifies if scope can be used with self-signed JWT.
|
||
|
EnableJWTWithScope bool
|
||
|
// DefaultAudience specifies a default audience to be used as the audience
|
||
|
// field ("aud") for the JWT token authentication.
|
||
|
DefaultAudience string
|
||
|
// DefaultEndpointTemplate combined with UniverseDomain specifies the
|
||
|
// default endpoint.
|
||
|
DefaultEndpointTemplate string
|
||
|
// DefaultMTLSEndpoint specifies the default mTLS endpoint.
|
||
|
DefaultMTLSEndpoint string
|
||
|
// DefaultScopes specifies the default OAuth2 scopes to be used for a
|
||
|
// service.
|
||
|
DefaultScopes []string
|
||
|
// SkipValidation bypasses validation on Options. It should only be used
|
||
|
// internally for clients that needs more control over their transport.
|
||
|
SkipValidation bool
|
||
|
}
|
||
|
|
||
|
// AddAuthorizationMiddleware adds a middleware to the provided client's
|
||
|
// transport that sets the Authorization header with the value produced by the
|
||
|
// provided [cloud.google.com/go/auth.Credentials]. An error is returned only
|
||
|
// if client or creds is nil.
|
||
|
func AddAuthorizationMiddleware(client *http.Client, creds *auth.Credentials) error {
|
||
|
if client == nil || creds == nil {
|
||
|
return fmt.Errorf("httptransport: client and tp must not be nil")
|
||
|
}
|
||
|
base := client.Transport
|
||
|
if base == nil {
|
||
|
if dt, ok := http.DefaultTransport.(*http.Transport); ok {
|
||
|
base = dt.Clone()
|
||
|
} else {
|
||
|
// Directly reuse the DefaultTransport if the application has
|
||
|
// replaced it with an implementation of RoundTripper other than
|
||
|
// http.Transport.
|
||
|
base = http.DefaultTransport
|
||
|
}
|
||
|
}
|
||
|
client.Transport = &authTransport{
|
||
|
creds: creds,
|
||
|
base: base,
|
||
|
// TODO(quartzmo): Somehow set clientUniverseDomain from impersonate calls.
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// NewClient returns a [net/http.Client] that can be used to communicate with a
|
||
|
// Google cloud service, configured with the provided [Options]. It
|
||
|
// automatically appends Authorization headers to all outgoing requests.
|
||
|
func NewClient(opts *Options) (*http.Client, error) {
|
||
|
if err := opts.validate(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
tOpts := &transport.Options{
|
||
|
Endpoint: opts.Endpoint,
|
||
|
ClientCertProvider: opts.ClientCertProvider,
|
||
|
Client: opts.client(),
|
||
|
UniverseDomain: opts.UniverseDomain,
|
||
|
}
|
||
|
if io := opts.InternalOptions; io != nil {
|
||
|
tOpts.DefaultEndpointTemplate = io.DefaultEndpointTemplate
|
||
|
tOpts.DefaultMTLSEndpoint = io.DefaultMTLSEndpoint
|
||
|
}
|
||
|
clientCertProvider, dialTLSContext, err := transport.GetHTTPTransportConfig(tOpts)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
baseRoundTripper := opts.BaseRoundTripper
|
||
|
if baseRoundTripper == nil {
|
||
|
baseRoundTripper = defaultBaseTransport(clientCertProvider, dialTLSContext)
|
||
|
}
|
||
|
// Ensure the token exchange transport uses the same ClientCertProvider as the API transport.
|
||
|
opts.ClientCertProvider = clientCertProvider
|
||
|
trans, err := newTransport(baseRoundTripper, opts)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return &http.Client{
|
||
|
Transport: trans,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// SetAuthHeader uses the provided token to set the Authorization header on a
|
||
|
// request. If the token.Type is empty, the type is assumed to be Bearer.
|
||
|
func SetAuthHeader(token *auth.Token, req *http.Request) {
|
||
|
typ := token.Type
|
||
|
if typ == "" {
|
||
|
typ = internal.TokenTypeBearer
|
||
|
}
|
||
|
req.Header.Set("Authorization", typ+" "+token.Value)
|
||
|
}
|