github.com/govau/cf-common@v0.0.7/credhub/client.go (about)

     1  package credhub
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"net/url"
    13  	"time"
    14  )
    15  
    16  type Client struct {
    17  	ClientID       string   `yaml:"client_id"`
    18  	ClientSecret   string   `yaml:"client_secret"`
    19  	UAAURL         string   `yaml:"uaa_url"`                 // URL to access
    20  	UAACACerts     []string `yaml:"uaa_ca_certificates"`     // CA certs for credhub host
    21  	CredHubURL     string   `yaml:"credhub_url"`             // URL to access
    22  	CredHubCACerts []string `yaml:"credhub_ca_certificates"` // CA certs for credhub host
    23  
    24  	uaaClient     *http.Client
    25  	credHubClient *http.Client
    26  	token         *oauthToken
    27  }
    28  
    29  var (
    30  	errCredNotFound = credHubErr{errors.New("not found in credhub")}
    31  )
    32  
    33  type credHubErr struct {
    34  	error
    35  }
    36  
    37  func IsCommsRelatedError(err error) bool {
    38  	_, ok := err.(credHubErr)
    39  	return ok
    40  }
    41  
    42  func IsNotFoundError(err error) bool {
    43  	return err == errCredNotFound
    44  }
    45  
    46  func (c *Client) Init() error {
    47  	uaaCaCertPool := x509.NewCertPool()
    48  	credHubCaCertPool := x509.NewCertPool()
    49  
    50  	for _, ca := range c.UAACACerts {
    51  		ok := uaaCaCertPool.AppendCertsFromPEM([]byte(ca))
    52  		if !ok {
    53  			return credHubErr{errors.New("AppendCertsFromPEM was not ok")}
    54  		}
    55  	}
    56  	for _, ca := range c.CredHubCACerts {
    57  		ok := credHubCaCertPool.AppendCertsFromPEM([]byte(ca))
    58  		if !ok {
    59  			return credHubErr{errors.New("AppendCertsFromPEM was not ok")}
    60  		}
    61  	}
    62  
    63  	uaaTLS := &tls.Config{RootCAs: uaaCaCertPool}
    64  	credhubTLS := &tls.Config{RootCAs: credHubCaCertPool}
    65  
    66  	uaaTLS.BuildNameToCertificate()
    67  	credhubTLS.BuildNameToCertificate()
    68  
    69  	c.uaaClient = &http.Client{Transport: &http.Transport{TLSClientConfig: uaaTLS}}
    70  	c.credHubClient = &http.Client{Transport: &http.Transport{TLSClientConfig: credhubTLS}}
    71  
    72  	return nil
    73  }
    74  
    75  type oauthToken struct {
    76  	AccessToken string `json:"access_token"`
    77  	Expiry      int64  `json:"expires_in"`
    78  }
    79  
    80  func (ch *Client) updateToken() error {
    81  	if ch.token == nil || time.Unix(ch.token.Expiry, 0).Before(time.Now().Add(5*time.Minute)) {
    82  		r, err := http.NewRequest(http.MethodPost, ch.UAAURL+"/oauth/token", bytes.NewReader([]byte((&url.Values{
    83  			"client_id":     {ch.ClientID},
    84  			"client_secret": {ch.ClientSecret},
    85  			"grant_type":    {"client_credentials"},
    86  			"response_type": {"token"},
    87  		}).Encode())))
    88  		if err != nil {
    89  			return credHubErr{err}
    90  		}
    91  		r.Header.Set("Accept", "application/json")
    92  		r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    93  
    94  		resp, err := ch.uaaClient.Do(r)
    95  		if err != nil {
    96  			return credHubErr{err}
    97  		}
    98  		data, err := ioutil.ReadAll(resp.Body)
    99  		resp.Body.Close()
   100  		if err != nil {
   101  			return credHubErr{err}
   102  		}
   103  		if resp.StatusCode != http.StatusOK {
   104  			return credHubErr{fmt.Errorf("not OK response from UAA: %s", data)}
   105  		}
   106  
   107  		var at oauthToken
   108  		err = json.Unmarshal(data, &at)
   109  		if err != nil {
   110  			return credHubErr{err}
   111  		}
   112  
   113  		ch.token = &at
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  func (ch *Client) MakeRequest(path string, params url.Values, rv interface{}) error {
   120  	req, err := http.NewRequest(http.MethodGet, ch.CredHubURL+path+"?"+params.Encode(), nil)
   121  	if err != nil {
   122  		return credHubErr{err}
   123  	}
   124  
   125  	return ch.rawMakeRequest(req, rv)
   126  }
   127  
   128  func (ch *Client) PutRequest(path string, val, rv interface{}) error {
   129  	data, err := json.Marshal(val)
   130  	if err != nil {
   131  		return credHubErr{err}
   132  	}
   133  
   134  	req, err := http.NewRequest(http.MethodPut, ch.CredHubURL+path, bytes.NewReader(data))
   135  	if err != nil {
   136  		return credHubErr{err}
   137  	}
   138  
   139  	req.Header.Set("Content-Type", "application/json")
   140  
   141  	return ch.rawMakeRequest(req, rv)
   142  }
   143  
   144  func (ch *Client) DeleteRequest(path string, params url.Values) error {
   145  	req, err := http.NewRequest(http.MethodDelete, ch.CredHubURL+path+"?"+params.Encode(), nil)
   146  	if err != nil {
   147  		return credHubErr{err}
   148  	}
   149  
   150  	req.Header.Set("Content-Type", "application/json")
   151  
   152  	return ch.rawMakeRequest(req, nil)
   153  }
   154  
   155  func (ch *Client) rawMakeRequest(req *http.Request, rv interface{}) error {
   156  	err := ch.updateToken()
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	req.Header.Set("Authorization", "Bearer "+ch.token.AccessToken)
   162  
   163  	resp, err := ch.credHubClient.Do(req)
   164  	if err != nil {
   165  		return credHubErr{err}
   166  	}
   167  	contents, err := ioutil.ReadAll(resp.Body)
   168  	resp.Body.Close()
   169  	if err != nil {
   170  		return credHubErr{err}
   171  	}
   172  
   173  	switch resp.StatusCode {
   174  	case http.StatusOK:
   175  		err = json.Unmarshal(contents, rv)
   176  		if err != nil {
   177  			return credHubErr{err}
   178  		}
   179  		return nil
   180  	case http.StatusNoContent:
   181  		return nil // expected for deleted
   182  	case http.StatusNotFound:
   183  		return errCredNotFound
   184  	default:
   185  		return credHubErr{fmt.Errorf("not OK response from CredHub: %s", contents)}
   186  	}
   187  }