// Copyright 2018 Google Inc. All Rights Reserved. // // 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 transport provides a mechanism to send requests with https cert, // key, and CA. package transport import ( "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "net/http" "sync" "github.com/google/pprof/internal/plugin" ) type transport struct { cert *string key *string ca *string caCertPool *x509.CertPool certs []tls.Certificate initOnce sync.Once initErr error } const extraUsage = ` -tls_cert TLS client certificate file for fetching profile and symbols -tls_key TLS private key file for fetching profile and symbols -tls_ca TLS CA certs file for fetching profile and symbols` // New returns a round tripper for making requests with the // specified cert, key, and ca. The flags tls_cert, tls_key, and tls_ca are // added to the flagset to allow a user to specify the cert, key, and ca. If // the flagset is nil, no flags will be added, and users will not be able to // use these flags. func New(flagset plugin.FlagSet) http.RoundTripper { if flagset == nil { return &transport{} } flagset.AddExtraUsage(extraUsage) return &transport{ cert: flagset.String("tls_cert", "", "TLS client certificate file for fetching profile and symbols"), key: flagset.String("tls_key", "", "TLS private key file for fetching profile and symbols"), ca: flagset.String("tls_ca", "", "TLS CA certs file for fetching profile and symbols"), } } // initialize uses the cert, key, and ca to initialize the certs // to use these when making requests. func (tr *transport) initialize() error { var cert, key, ca string if tr.cert != nil { cert = *tr.cert } if tr.key != nil { key = *tr.key } if tr.ca != nil { ca = *tr.ca } if cert != "" && key != "" { tlsCert, err := tls.LoadX509KeyPair(cert, key) if err != nil { return fmt.Errorf("could not load certificate/key pair specified by -tls_cert and -tls_key: %v", err) } tr.certs = []tls.Certificate{tlsCert} } else if cert == "" && key != "" { return fmt.Errorf("-tls_key is specified, so -tls_cert must also be specified") } else if cert != "" && key == "" { return fmt.Errorf("-tls_cert is specified, so -tls_key must also be specified") } if ca != "" { caCertPool := x509.NewCertPool() caCert, err := ioutil.ReadFile(ca) if err != nil { return fmt.Errorf("could not load CA specified by -tls_ca: %v", err) } caCertPool.AppendCertsFromPEM(caCert) tr.caCertPool = caCertPool } return nil } // RoundTrip executes a single HTTP transaction, returning // a Response for the provided Request. func (tr *transport) RoundTrip(req *http.Request) (*http.Response, error) { tr.initOnce.Do(func() { tr.initErr = tr.initialize() }) if tr.initErr != nil { return nil, tr.initErr } tlsConfig := &tls.Config{ RootCAs: tr.caCertPool, Certificates: tr.certs, } if req.URL.Scheme == "https+insecure" { // Make shallow copy of request, and req.URL, so the request's URL can be // modified. r := *req *r.URL = *req.URL req = &r tlsConfig.InsecureSkipVerify = true req.URL.Scheme = "https" } transport := http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: tlsConfig, } return transport.RoundTrip(req) }