github.com/ConsenSys/Quorum@v20.10.0+incompatible/plugin/central.go (about)

     1  package plugin
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net"
    10  	"net/http"
    11  	"net/url"
    12  	"os"
    13  	"strings"
    14  
    15  	"github.com/ethereum/go-ethereum/log"
    16  )
    17  
    18  // Central https centralClient communicating with Plugin Central
    19  type CentralClient struct {
    20  	config     *PluginCentralConfiguration
    21  	httpClient *http.Client
    22  }
    23  
    24  // Create New Central Client
    25  func NewPluginCentralClient(config *PluginCentralConfiguration) *CentralClient {
    26  	c := &CentralClient{
    27  		config: config,
    28  	}
    29  	c.httpClient = &http.Client{}
    30  	c.httpClient.Transport = &http.Transport{
    31  		DialTLS: c.getNewSecureDialer(),
    32  	}
    33  	return c
    34  }
    35  
    36  // Builds a Dialer that supports CA Verification & Certificate Pinning.
    37  func (cc *CentralClient) getNewSecureDialer() Dialer {
    38  	return func(network, addr string) (net.Conn, error) {
    39  		c, err := tls.Dial(network, addr, &tls.Config{InsecureSkipVerify: cc.config.InsecureSkipTLSVerify})
    40  		if err != nil {
    41  			return c, err
    42  		}
    43  		// support certificate pinning?
    44  		if cc.config.CertFingerprint != "" {
    45  			conState := c.ConnectionState()
    46  			for _, peercert := range conState.PeerCertificates {
    47  				if bytes.Equal(peercert.Signature[0:], []byte(cc.config.CertFingerprint)) {
    48  					return c, nil
    49  				}
    50  			}
    51  			return nil, fmt.Errorf("certificate pinning failed")
    52  		}
    53  		return c, nil
    54  	}
    55  }
    56  
    57  // Get the public key from central
    58  func (cc *CentralClient) PublicKey() ([]byte, error) {
    59  	target := fmt.Sprintf("%s/%s", cc.config.BaseURL, cc.config.PublicKeyURI)
    60  	log.Debug("downloading public key", "url", target)
    61  	readCloser, err := cc.get(target)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	defer func() {
    66  		_ = readCloser.Close()
    67  	}()
    68  	return ioutil.ReadAll(readCloser)
    69  }
    70  
    71  // retrieve plugin signature
    72  func (cc *CentralClient) PluginSignature(definition *PluginDefinition) ([]byte, error) {
    73  	target := fmt.Sprintf("%s/%s/%s", cc.config.BaseURL, definition.RemotePath(), definition.SignatureFileName())
    74  	log.Debug("downloading plugin signature file", "url", target)
    75  	readCloser, err := cc.get(target)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	defer func() {
    80  		_ = readCloser.Close()
    81  	}()
    82  	return ioutil.ReadAll(readCloser)
    83  }
    84  
    85  // retrieve plugin distribution file
    86  func (cc *CentralClient) PluginDistribution(definition *PluginDefinition, outFilePath string) error {
    87  	target := fmt.Sprintf("%s/%s/%s", cc.config.BaseURL, definition.RemotePath(), definition.DistFileName())
    88  	log.Debug("downloading plugin zip file", "url", target)
    89  	outFile, err := os.Create(outFilePath)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	defer func() {
    94  		_ = outFile.Close()
    95  	}()
    96  	readCloser, err := cc.get(target)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	defer func() {
   101  		_ = readCloser.Close()
   102  	}()
   103  	_, err = io.Copy(outFile, readCloser)
   104  	return err
   105  }
   106  
   107  // perform HTTP GET
   108  //
   109  // caller needs to close the reader
   110  func (cc *CentralClient) get(target string) (io.ReadCloser, error) {
   111  	if err := isValidTargetURL(cc.config.BaseURL, target); err != nil {
   112  		return nil, err
   113  	}
   114  	res, err := cc.httpClient.Get(target)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	if res.StatusCode != http.StatusOK {
   119  		defer func() {
   120  			_ = res.Body.Close()
   121  		}()
   122  		data, _ := ioutil.ReadAll(res.Body)
   123  		return nil, fmt.Errorf("HTTP GET error: code=%d, status=%s, body=%s", res.StatusCode, res.Status, string(data))
   124  	}
   125  	return res.Body, nil
   126  }
   127  
   128  // An adapter function for tls.Dial with CA verification & SSL Pinning support.
   129  type Dialer func(network, addr string) (net.Conn, error)
   130  
   131  // Validate the target url is well formed and match base.
   132  func isValidTargetURL(base string, target string) error {
   133  	u, err := url.Parse(target)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	t, err := url.Parse(base)
   138  	if err != nil {
   139  		return err
   140  	}
   141  	if strings.Compare(t.Host, u.Host) != 0 || strings.Compare(t.Scheme, u.Scheme) != 0 {
   142  		return fmt.Errorf("target host doesnt match base host")
   143  	}
   144  	return nil
   145  }