...

Source file src/cloud.google.com/go/auth/internal/transport/s2a.go

Documentation: cloud.google.com/go/auth/internal/transport

     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 transport
    16  
    17  import (
    18  	"encoding/json"
    19  	"log"
    20  	"os"
    21  	"strconv"
    22  	"sync"
    23  	"time"
    24  
    25  	"cloud.google.com/go/auth/internal/transport/cert"
    26  	"cloud.google.com/go/compute/metadata"
    27  )
    28  
    29  const (
    30  	configEndpointSuffix = "instance/platform-security/auto-mtls-configuration"
    31  )
    32  
    33  var (
    34  	// The period an MTLS config can be reused before needing refresh.
    35  	configExpiry = time.Hour
    36  
    37  	// mdsMTLSAutoConfigSource is an instance of reuseMTLSConfigSource, with metadataMTLSAutoConfig as its config source.
    38  	mtlsOnce sync.Once
    39  )
    40  
    41  // GetS2AAddress returns the S2A address to be reached via plaintext connection.
    42  // Returns empty string if not set or invalid.
    43  func GetS2AAddress() string {
    44  	c, err := getMetadataMTLSAutoConfig().Config()
    45  	if err != nil {
    46  		return ""
    47  	}
    48  	if !c.Valid() {
    49  		return ""
    50  	}
    51  	return c.S2A.PlaintextAddress
    52  }
    53  
    54  type mtlsConfigSource interface {
    55  	Config() (*mtlsConfig, error)
    56  }
    57  
    58  // mtlsConfig contains the configuration for establishing MTLS connections with Google APIs.
    59  type mtlsConfig struct {
    60  	S2A    *s2aAddresses `json:"s2a"`
    61  	Expiry time.Time
    62  }
    63  
    64  func (c *mtlsConfig) Valid() bool {
    65  	return c != nil && c.S2A != nil && !c.expired()
    66  }
    67  func (c *mtlsConfig) expired() bool {
    68  	return c.Expiry.Before(time.Now())
    69  }
    70  
    71  // s2aAddresses contains the plaintext and/or MTLS S2A addresses.
    72  type s2aAddresses struct {
    73  	// PlaintextAddress is the plaintext address to reach S2A
    74  	PlaintextAddress string `json:"plaintext_address"`
    75  	// MTLSAddress is the MTLS address to reach S2A
    76  	MTLSAddress string `json:"mtls_address"`
    77  }
    78  
    79  // getMetadataMTLSAutoConfig returns mdsMTLSAutoConfigSource, which is backed by config from MDS with auto-refresh.
    80  func getMetadataMTLSAutoConfig() mtlsConfigSource {
    81  	mtlsOnce.Do(func() {
    82  		mdsMTLSAutoConfigSource = &reuseMTLSConfigSource{
    83  			src: &metadataMTLSAutoConfig{},
    84  		}
    85  	})
    86  	return mdsMTLSAutoConfigSource
    87  }
    88  
    89  // reuseMTLSConfigSource caches a valid version of mtlsConfig, and uses `src` to refresh upon config expiry.
    90  // It implements the mtlsConfigSource interface, so calling Config() on it returns an mtlsConfig.
    91  type reuseMTLSConfigSource struct {
    92  	src    mtlsConfigSource // src.Config() is called when config is expired
    93  	mu     sync.Mutex       // mutex guards config
    94  	config *mtlsConfig      // cached config
    95  }
    96  
    97  func (cs *reuseMTLSConfigSource) Config() (*mtlsConfig, error) {
    98  	cs.mu.Lock()
    99  	defer cs.mu.Unlock()
   100  
   101  	if cs.config.Valid() {
   102  		return cs.config, nil
   103  	}
   104  	c, err := cs.src.Config()
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	cs.config = c
   109  	return c, nil
   110  }
   111  
   112  // metadataMTLSAutoConfig is an implementation of the interface mtlsConfigSource
   113  // It has the logic to query MDS and return an mtlsConfig
   114  type metadataMTLSAutoConfig struct{}
   115  
   116  var httpGetMetadataMTLSConfig = func() (string, error) {
   117  	return metadata.Get(configEndpointSuffix)
   118  }
   119  
   120  func (cs *metadataMTLSAutoConfig) Config() (*mtlsConfig, error) {
   121  	resp, err := httpGetMetadataMTLSConfig()
   122  	if err != nil {
   123  		log.Printf("querying MTLS config from MDS endpoint failed: %v", err)
   124  		return defaultMTLSConfig(), nil
   125  	}
   126  	var config mtlsConfig
   127  	err = json.Unmarshal([]byte(resp), &config)
   128  	if err != nil {
   129  		log.Printf("unmarshalling MTLS config from MDS endpoint failed: %v", err)
   130  		return defaultMTLSConfig(), nil
   131  	}
   132  
   133  	if config.S2A == nil {
   134  		log.Printf("returned MTLS config from MDS endpoint is invalid: %v", config)
   135  		return defaultMTLSConfig(), nil
   136  	}
   137  
   138  	// set new expiry
   139  	config.Expiry = time.Now().Add(configExpiry)
   140  	return &config, nil
   141  }
   142  
   143  func defaultMTLSConfig() *mtlsConfig {
   144  	return &mtlsConfig{
   145  		S2A: &s2aAddresses{
   146  			PlaintextAddress: "",
   147  			MTLSAddress:      "",
   148  		},
   149  		Expiry: time.Now().Add(configExpiry),
   150  	}
   151  }
   152  
   153  func shouldUseS2A(clientCertSource cert.Provider, opts *Options) bool {
   154  	// If client cert is found, use that over S2A.
   155  	if clientCertSource != nil {
   156  		return false
   157  	}
   158  	// If EXPERIMENTAL_GOOGLE_API_USE_S2A is not set to true, skip S2A.
   159  	if !isGoogleS2AEnabled() {
   160  		return false
   161  	}
   162  	// If DefaultMTLSEndpoint is not set or has endpoint override, skip S2A.
   163  	if opts.DefaultMTLSEndpoint == "" || opts.Endpoint != "" {
   164  		return false
   165  	}
   166  	// If custom HTTP client is provided, skip S2A.
   167  	if opts.Client != nil {
   168  		return false
   169  	}
   170  	// If directPath is enabled, skip S2A.
   171  	return !opts.EnableDirectPath && !opts.EnableDirectPathXds
   172  }
   173  
   174  func isGoogleS2AEnabled() bool {
   175  	b, err := strconv.ParseBool(os.Getenv(googleAPIUseS2AEnv))
   176  	if err != nil {
   177  		return false
   178  	}
   179  	return b
   180  }
   181  

View as plain text