github.com/go-chef/chef@v0.30.1/http.go (about)

     1  package chef
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rsa"
     6  	"crypto/tls"
     7  	"crypto/x509"
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"encoding/pem"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"log"
    15  	"net"
    16  	"net/http"
    17  	"net/url"
    18  	"path"
    19  	"strings"
    20  	"time"
    21  )
    22  
    23  // AuthVersion of server authentication
    24  type AuthVersion = string
    25  
    26  const (
    27  	AuthVersion10 AuthVersion = "1.0"
    28  	AuthVersion13 AuthVersion = "1.3"
    29  )
    30  
    31  // DefaultChefVersion that we pretend to emulate
    32  const DefaultChefVersion = "14.0.0"
    33  
    34  // Body wraps io.Reader and adds methods for calculating hashes and detecting content
    35  type Body struct {
    36  	io.Reader
    37  }
    38  
    39  // AuthConfig representing a client and a private key used for encryption
    40  //
    41  //	This is embedded in the Client type
    42  type AuthConfig struct {
    43  	PrivateKey            *rsa.PrivateKey
    44  	ClientName            string
    45  	AuthenticationVersion AuthVersion
    46  	ServerVersion         string
    47  }
    48  
    49  // Client is vessel for public methods used against the chef-server
    50  type Client struct {
    51  	Auth       *AuthConfig
    52  	BaseURL    *url.URL
    53  	Client     *http.Client
    54  	IsWebuiKey bool
    55  
    56  	ACLs              *ACLService
    57  	Associations      *AssociationService
    58  	AuthenticateUser  *AuthenticateUserService
    59  	Clients           *ApiClientService
    60  	Containers        *ContainerService
    61  	CookbookArtifacts *CBAService
    62  	Cookbooks         *CookbookService
    63  	DataBags          *DataBagService
    64  	Environments      *EnvironmentService
    65  	Groups            *GroupService
    66  	License           *LicenseService
    67  	Nodes             *NodeService
    68  	Organizations     *OrganizationService
    69  	Policies          *PolicyService
    70  	PolicyGroups      *PolicyGroupService
    71  	Principals        *PrincipalService
    72  	RequiredRecipe    *RequiredRecipeService
    73  	Roles             *RoleService
    74  	Sandboxes         *SandboxService
    75  	Search            *SearchService
    76  	Stats             *StatsService
    77  	Status            *StatusService
    78  	Universe          *UniverseService
    79  	UpdatedSince      *UpdatedSinceService
    80  	Users             *UserService
    81  }
    82  
    83  // Config contains the configuration options for a chef client. This structure is used primarily in the NewClient() constructor in order to setup a proper client object
    84  type Config struct {
    85  	// This should be the user ID on the chef server
    86  	Name string
    87  
    88  	// This is the plain text private Key for the user
    89  	Key string
    90  
    91  	// BaseURL is the chef server URL used to connect to. If using orgs you should include your org in the url and terminate the url with a "/"
    92  	BaseURL string
    93  
    94  	// When set to false (default) this will enable SSL Cert Verification. If you need to disable Cert Verification set to true
    95  	SkipSSL bool
    96  
    97  	// RootCAs is a reference to x509.CertPool for TLS
    98  	RootCAs *x509.CertPool
    99  
   100  	// Time to wait in seconds before giving up on a request to the server
   101  	Timeout int
   102  
   103  	// Authentication Protocol Version
   104  	AuthenticationVersion AuthVersion
   105  
   106  	// Chef Server Version
   107  	ServerVersion string
   108  
   109  	// When set to true corresponding API is using webui key in the request
   110  	IsWebuiKey bool
   111  
   112  	// Proxy function to be used when making requests
   113  	Proxy func(*http.Request) (*url.URL, error)
   114  
   115  	// Pointer to an HTTP Client to use instead of the default
   116  	Client *http.Client
   117  
   118  	// A function which wraps an existing RoundTripper.
   119  	// Cannot be used if Client is set.
   120  	RoundTripper func(http.RoundTripper) http.RoundTripper
   121  }
   122  
   123  /*
   124  An ErrorResponse reports one or more errors caused by an API request.
   125  Thanks to https://github.com/google/go-github
   126  
   127  The Response structure includes:
   128  
   129  	        Status string
   130  		StatusCode int
   131  */
   132  type ErrorResponse struct {
   133  	Response *http.Response // HTTP response that caused this error
   134  	// extracted error message converted to string if possible
   135  	ErrorMsg string
   136  	// json body raw byte stream from an error
   137  	ErrorText []byte
   138  }
   139  
   140  type ErrorMsg struct {
   141  	Error interface{} `json:"error"`
   142  }
   143  
   144  // Buffer creates a  byte.Buffer copy from a io.Reader resets read on reader to 0,0
   145  func (body *Body) Buffer() *bytes.Buffer {
   146  	var b bytes.Buffer
   147  	if body.Reader == nil {
   148  		return &b
   149  	}
   150  
   151  	_, _ = b.ReadFrom(body.Reader)
   152  	_, err := body.Reader.(io.Seeker).Seek(0, 0)
   153  	if err != nil {
   154  		log.Fatal(err)
   155  	}
   156  	return &b
   157  }
   158  
   159  // Hash calculates the body content hash
   160  func (body *Body) Hash() (h string) {
   161  	b := body.Buffer()
   162  	// empty buffs should return a empty string
   163  	if b.Len() == 0 {
   164  		h = HashStr("")
   165  	}
   166  	h = HashStr(b.String())
   167  	return
   168  }
   169  
   170  // Hash256 calculates the body content hash
   171  func (body *Body) Hash256() (h string) {
   172  	b := body.Buffer()
   173  	// empty buffs should return a empty string
   174  	if b.Len() == 0 {
   175  		h = HashStr256("")
   176  	}
   177  	h = HashStr256(b.String())
   178  	return
   179  }
   180  
   181  // ContentType returns the content-type string of Body as detected by http.DetectContentType()
   182  func (body *Body) ContentType() string {
   183  	if json.Unmarshal(body.Buffer().Bytes(), &struct{}{}) == nil {
   184  		return "application/json"
   185  	}
   186  	return http.DetectContentType(body.Buffer().Bytes())
   187  }
   188  
   189  // Error implements the error interface method for ErrorResponse
   190  func (r *ErrorResponse) Error() string {
   191  	return fmt.Sprintf("%v %v: %d",
   192  		r.Response.Request.Method, r.Response.Request.URL,
   193  		r.Response.StatusCode)
   194  }
   195  
   196  // StatusCode returns the status code from the http response embedded in the ErrorResponse
   197  func (r *ErrorResponse) StatusCode() int {
   198  	return r.Response.StatusCode
   199  }
   200  
   201  // StatusMsg returns the error msg string from the http response. The message is a best
   202  // effort value and depends on the Chef Server json return format
   203  func (r *ErrorResponse) StatusMsg() string {
   204  	return r.ErrorMsg
   205  }
   206  
   207  // StatusText returns the raw json response included in the http response
   208  func (r *ErrorResponse) StatusText() []byte {
   209  	return r.ErrorText
   210  }
   211  
   212  // StatusMethod returns the method used from the http response embedded in the ErrorResponse
   213  func (r *ErrorResponse) StatusMethod() string {
   214  	return r.Response.Request.Method
   215  }
   216  
   217  // StatusURL returns the URL used from the http response embedded in the ErrorResponse
   218  func (r *ErrorResponse) StatusURL() *url.URL {
   219  	return r.Response.Request.URL
   220  }
   221  
   222  // NewClient is the client generator used to instantiate a client for talking to a chef-server
   223  // It is a simple constructor for the Client struct intended as a easy interface for issuing
   224  // signed requests
   225  func NewClient(cfg *Config) (*Client, error) {
   226  	pk, err := PrivateKeyFromString([]byte(cfg.Key))
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	baseUrl, err := url.Parse(cfg.BaseURL)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	tlsConfig := &tls.Config{InsecureSkipVerify: cfg.SkipSSL}
   237  	if cfg.RootCAs != nil {
   238  		tlsConfig.RootCAs = cfg.RootCAs
   239  	}
   240  	tr := &http.Transport{
   241  		Proxy: http.ProxyFromEnvironment,
   242  		DialContext: (&net.Dialer{
   243  			Timeout:   30 * time.Second,
   244  			KeepAlive: 30 * time.Second,
   245  		}).DialContext,
   246  		TLSClientConfig:     tlsConfig,
   247  		TLSHandshakeTimeout: 10 * time.Second,
   248  	}
   249  
   250  	if cfg.Proxy != nil {
   251  		tr.Proxy = cfg.Proxy
   252  	}
   253  
   254  	if cfg.AuthenticationVersion == "" {
   255  		cfg.AuthenticationVersion = AuthVersion10
   256  	}
   257  
   258  	if cfg.ServerVersion == "" {
   259  		cfg.ServerVersion = DefaultChefVersion
   260  	}
   261  
   262  	c := &Client{
   263  		Auth: &AuthConfig{
   264  			PrivateKey:            pk,
   265  			ClientName:            cfg.Name,
   266  			AuthenticationVersion: cfg.AuthenticationVersion,
   267  			ServerVersion:         cfg.ServerVersion,
   268  		},
   269  		BaseURL: baseUrl,
   270  	}
   271  
   272  	if cfg.Client != nil {
   273  		if cfg.RoundTripper != nil {
   274  			return nil, errors.New("NewClient: cannot set both Client and RoundTripper")
   275  		}
   276  		c.Client = cfg.Client
   277  	} else {
   278  		tlsConfig := &tls.Config{InsecureSkipVerify: cfg.SkipSSL}
   279  		if cfg.RootCAs != nil {
   280  			tlsConfig.RootCAs = cfg.RootCAs
   281  		}
   282  		tr := &http.Transport{
   283  			Proxy: http.ProxyFromEnvironment,
   284  			Dial: (&net.Dialer{
   285  				Timeout:   30 * time.Second,
   286  				KeepAlive: 30 * time.Second,
   287  			}).Dial,
   288  			TLSClientConfig:     tlsConfig,
   289  			TLSHandshakeTimeout: 10 * time.Second,
   290  		}
   291  
   292  		if cfg.Proxy != nil {
   293  			tr.Proxy = cfg.Proxy
   294  		}
   295  
   296  		var transport http.RoundTripper = tr
   297  		if cfg.RoundTripper != nil {
   298  			transport = cfg.RoundTripper(tr)
   299  		}
   300  
   301  		c.Client = &http.Client{
   302  			Transport: transport,
   303  			Timeout:   time.Duration(cfg.Timeout) * time.Second,
   304  		}
   305  	}
   306  	c.IsWebuiKey = cfg.IsWebuiKey
   307  	c.ACLs = &ACLService{client: c}
   308  	c.AuthenticateUser = &AuthenticateUserService{client: c}
   309  	c.Associations = &AssociationService{client: c}
   310  	c.Clients = &ApiClientService{client: c}
   311  	c.Containers = &ContainerService{client: c}
   312  	c.Cookbooks = &CookbookService{client: c}
   313  	c.CookbookArtifacts = &CBAService{client: c}
   314  	c.DataBags = &DataBagService{client: c}
   315  	c.Environments = &EnvironmentService{client: c}
   316  	c.Groups = &GroupService{client: c}
   317  	c.License = &LicenseService{client: c}
   318  	c.Nodes = &NodeService{client: c}
   319  	c.Organizations = &OrganizationService{client: c}
   320  	c.Policies = &PolicyService{client: c}
   321  	c.PolicyGroups = &PolicyGroupService{client: c}
   322  	c.RequiredRecipe = &RequiredRecipeService{client: c}
   323  	c.Principals = &PrincipalService{client: c}
   324  	c.Roles = &RoleService{client: c}
   325  	c.Sandboxes = &SandboxService{client: c}
   326  	c.Search = &SearchService{client: c}
   327  	c.Stats = &StatsService{client: c}
   328  	c.Status = &StatusService{client: c}
   329  	c.UpdatedSince = &UpdatedSinceService{client: c}
   330  	c.Universe = &UniverseService{client: c}
   331  	c.Users = &UserService{client: c}
   332  	return c, nil
   333  }
   334  
   335  func NewClientWithOutConfig(baseurl string) (*Client, error) {
   336  	baseUrl, _ := url.Parse(baseurl)
   337  	tr := &http.Transport{
   338  		Proxy: http.ProxyFromEnvironment,
   339  		DialContext: (&net.Dialer{
   340  			Timeout:   30 * time.Second,
   341  			KeepAlive: 30 * time.Second,
   342  		}).DialContext,
   343  		TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
   344  		TLSHandshakeTimeout: 10 * time.Second,
   345  	}
   346  
   347  	c := &Client{
   348  		Client: &http.Client{
   349  			Transport: tr,
   350  			Timeout:   60 * time.Second,
   351  		},
   352  		BaseURL: baseUrl,
   353  	}
   354  
   355  	return c, nil
   356  }
   357  
   358  // basicRequestDecoder performs a request on an endpoint, and decodes the response into the passed in Type
   359  // basicRequestDecoder is the same code as magic RequestDecoder with the addition of a generated Authentication: Basic header
   360  // to the http request
   361  func (c *Client) basicRequestDecoder(method, path string, body io.Reader, v interface{}, user string, password string) error {
   362  	req, err := c.NewRequest(method, path, body)
   363  	if err != nil {
   364  		return err
   365  	}
   366  
   367  	basicAuthHeader(req, user, password)
   368  
   369  	debug("\n\nRequest: %+v \n", req)
   370  	res, err := c.Do(req, v)
   371  	if res != nil {
   372  		defer func() {
   373  			_ = res.Body.Close()
   374  		}()
   375  	}
   376  	debug("Response: %+v\n", res)
   377  	if err != nil {
   378  		return err
   379  	}
   380  	return err
   381  }
   382  
   383  // magicRequestDecoder performs a request on an endpoint, and decodes the response into the passed in Type
   384  func (c *Client) magicRequestDecoder(method, path string, body io.Reader, v interface{}) error {
   385  	req, err := c.NewRequest(method, path, body)
   386  	if err != nil {
   387  		return err
   388  	}
   389  
   390  	debug("\n\nRequest: %+v \n", req)
   391  	res, err := c.Do(req, v)
   392  	if res != nil {
   393  		defer func() {
   394  			_ = res.Body.Close()
   395  		}()
   396  	}
   397  	debug("Response: %+v\n", res)
   398  	if err != nil {
   399  		return err
   400  	}
   401  	return err
   402  }
   403  
   404  // NewRequest returns a signed request  suitable for the chef server
   405  func (c *Client) NewRequest(method string, requestUrl string, body io.Reader) (*http.Request, error) {
   406  	relativeUrl, err := url.Parse(requestUrl)
   407  	if err != nil {
   408  		return nil, err
   409  	}
   410  	u := c.BaseURL.ResolveReference(relativeUrl)
   411  
   412  	// NewRequest uses a new value object of body
   413  	req, err := http.NewRequest(method, u.String(), body)
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  
   418  	// parse and encode Querystring Values
   419  	values := req.URL.Query()
   420  	req.URL.RawQuery = values.Encode()
   421  	debug("Encoded url %+v\n", u)
   422  
   423  	myBody := &Body{body}
   424  
   425  	if body != nil {
   426  		// Detect Content-type
   427  		req.Header.Set("Content-Type", myBody.ContentType())
   428  	}
   429  
   430  	// Calculate the body hash
   431  	if c.Auth.AuthenticationVersion == AuthVersion13 {
   432  		req.Header.Set("X-Ops-Content-Hash", myBody.Hash256())
   433  	} else {
   434  		req.Header.Set("X-Ops-Content-Hash", myBody.Hash())
   435  	}
   436  
   437  	if c.IsWebuiKey {
   438  		req.Header.Set("X-Ops-Request-Source", "web")
   439  	}
   440  	err = c.Auth.SignRequest(req)
   441  	if err != nil {
   442  		return nil, err
   443  	}
   444  
   445  	return req, nil
   446  }
   447  
   448  // NoAuthNewRequest returns a request  suitable for public apis
   449  func (c *Client) NoAuthNewRequest(method string, requestUrl string, body io.Reader) (*http.Request, error) {
   450  	relativeUrl, err := url.Parse(requestUrl)
   451  	if err != nil {
   452  		return nil, err
   453  	}
   454  	u := c.BaseURL.ResolveReference(relativeUrl)
   455  
   456  	// NewRequest uses a new value object of body
   457  	req, err := http.NewRequest(method, u.String(), body)
   458  	if err != nil {
   459  		return nil, err
   460  	}
   461  
   462  	// parse and encode Querystring Values
   463  	values := req.URL.Query()
   464  	req.URL.RawQuery = values.Encode()
   465  	debug("Encoded url %+v\n", u)
   466  
   467  	myBody := &Body{body}
   468  
   469  	if body != nil {
   470  		// Detect Content-type
   471  		req.Header.Set("Content-Type", myBody.ContentType())
   472  	}
   473  	return req, nil
   474  }
   475  
   476  // basicAuth does base64 encoding of a user and password
   477  func basicAuth(user string, password string) string {
   478  	creds := user + ":" + password
   479  	return base64.StdEncoding.EncodeToString([]byte(creds))
   480  }
   481  
   482  // basicAuthHeader adds an Authentication Basic header to the request
   483  // The user and password values should be clear text. They will be
   484  // base64 encoded for the header.
   485  func basicAuthHeader(r *http.Request, user string, password string) {
   486  	r.Header.Add("authorization", "Basic "+basicAuth(user, password))
   487  }
   488  
   489  // CheckResponse receives a pointer to a http.Response and generates an Error via unmarshalling
   490  func CheckResponse(r *http.Response) error {
   491  	if c := r.StatusCode; 200 <= c && c <= 299 {
   492  		return nil
   493  	}
   494  	errorResponse := &ErrorResponse{Response: r}
   495  	data, err := io.ReadAll(r.Body)
   496  	debug("Response Error Body: %+v\n", string(data))
   497  	if err == nil && data != nil {
   498  		json.Unmarshal(data, errorResponse)
   499  		errorResponse.ErrorText = data
   500  		errorResponse.ErrorMsg = extractErrorMsg(data)
   501  	}
   502  	return errorResponse
   503  }
   504  
   505  // extractErrorMsg makes a best faith effort to extract the error message text
   506  // from the response body returned from the Chef Server. Error messages are
   507  // typically formatted in a json body as {"error": ["msg"]}
   508  func extractErrorMsg(data []byte) string {
   509  	errorMsg := &ErrorMsg{}
   510  	json.Unmarshal(data, errorMsg)
   511  	switch t := errorMsg.Error.(type) {
   512  	case []interface{}:
   513  		// Return the string as a byte stream
   514  		var rmsg string
   515  		for _, val := range t {
   516  			switch inval := val.(type) {
   517  			case string:
   518  				rmsg = rmsg + inval + "\n"
   519  			default:
   520  				debug("Unknown type  %+v data %+v\n", inval, val)
   521  			}
   522  			return strings.TrimSpace(rmsg)
   523  		}
   524  	default:
   525  		debug("Unknown type  %+v data %+v msg %+v\n", t, string(data), errorMsg.Error)
   526  	}
   527  	return ""
   528  }
   529  
   530  // ChefError tries to unwind a chef client err return embedded in an error
   531  // Unwinding allows easy access the StatusCode, StatusMethod and StatusURL functions
   532  func ChefError(err error) (cerr *ErrorResponse, nerr error) {
   533  	if err == nil {
   534  		return cerr, err
   535  	}
   536  	if cerr, ok := err.(*ErrorResponse); ok {
   537  		return cerr, err
   538  	}
   539  	return cerr, err
   540  }
   541  
   542  // Do is used either internally via our magic request shite or a user may use it
   543  func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
   544  	res, err := c.Client.Do(req)
   545  	if err != nil {
   546  		return nil, err
   547  	}
   548  
   549  	// BUG(fujin) tightly coupled
   550  	err = CheckResponse(res)
   551  	if err != nil {
   552  		return res, err
   553  	}
   554  
   555  	var resBuf bytes.Buffer
   556  	resTee := io.TeeReader(res.Body, &resBuf)
   557  
   558  	// add the body back to the response so
   559  	// subsequent calls to res.Body contain data
   560  	res.Body = io.NopCloser(&resBuf)
   561  
   562  	// no response interface specified
   563  	if v == nil {
   564  		if debug_on() {
   565  			// show the response body as a string
   566  			resbody, _ := io.ReadAll(resTee)
   567  			debug("Response body: %+v\n", string(resbody))
   568  		} else {
   569  			_, _ = io.ReadAll(resTee)
   570  		}
   571  		debug("No response body requested\n")
   572  		return res, nil
   573  	}
   574  
   575  	// response interface, v, is an io writer
   576  	if w, ok := v.(io.Writer); ok {
   577  		debug("Response output desired is an io Writer\n")
   578  		_, err = io.Copy(w, resTee)
   579  		return res, err
   580  	}
   581  
   582  	// response content-type specifies JSON encoded - decode it
   583  	if hasJsonContentType(res) {
   584  		err = json.NewDecoder(resTee).Decode(v)
   585  		if debug_on() {
   586  			// show the response body as a string
   587  			resbody, _ := io.ReadAll(&resBuf)
   588  			debug("Response body: %+v\n", string(resbody))
   589  			var repBuffer bytes.Buffer
   590  			repBuffer.Write(resbody)
   591  			res.Body = io.NopCloser(&repBuffer)
   592  		}
   593  		debug("Response body specifies content as JSON: %+v Err: %+v\n", v, err)
   594  		return res, err
   595  	}
   596  
   597  	// response interface, v, is type string and the content is plain text
   598  	if _, ok := v.(*string); ok && hasTextContentType(res) {
   599  		resbody, _ := io.ReadAll(resTee)
   600  		if err != nil {
   601  			return res, err
   602  		}
   603  		out := string(resbody)
   604  		debug("Response body parsed as string: %+v\n", out)
   605  		*v.(*string) = out
   606  		return res, nil
   607  	}
   608  
   609  	// Default response: Content-Type is not JSON. Assume v is a struct and decode the response as json
   610  	err = json.NewDecoder(resTee).Decode(v)
   611  	if debug_on() {
   612  		// show the response body as a string
   613  		resbody, _ := io.ReadAll(&resBuf)
   614  		debug("Response body: %+v\n", string(resbody))
   615  		var repBuffer bytes.Buffer
   616  		repBuffer.Write(resbody)
   617  		res.Body = io.NopCloser(&repBuffer)
   618  	}
   619  	debug("Response body defaulted to JSON parsing: %+v Err: %+v\n", v, err)
   620  	return res, err
   621  }
   622  
   623  func hasJsonContentType(res *http.Response) bool {
   624  	contentType := res.Header.Get("Content-Type")
   625  	return contentType == "application/json"
   626  }
   627  
   628  func hasTextContentType(res *http.Response) bool {
   629  	contentType := res.Header.Get("Content-Type")
   630  	return contentType == "text/plain"
   631  }
   632  
   633  // SignRequest modifies headers of an http.Request
   634  func (ac AuthConfig) SignRequest(request *http.Request) error {
   635  	var (
   636  		requestHeaders []string
   637  		endpoint       string
   638  	)
   639  
   640  	if request.URL.Path != "" {
   641  		endpoint = path.Clean(request.URL.Path)
   642  		request.URL.Path = endpoint
   643  	} else {
   644  		endpoint = request.URL.Path
   645  	}
   646  
   647  	vals := map[string]string{
   648  		"Method":                   request.Method,
   649  		"Accept":                   "application/json",
   650  		"X-Chef-Version":           ac.ServerVersion,
   651  		"X-Ops-Server-API-Version": "1",
   652  		"X-Ops-Timestamp":          time.Now().UTC().Format(time.RFC3339),
   653  		"X-Ops-Content-Hash":       request.Header.Get("X-Ops-Content-Hash"),
   654  		"X-Ops-UserId":             ac.ClientName,
   655  		"X-Ops-Request-Source":     request.Header.Get("X-Ops-Request-Source"),
   656  	}
   657  
   658  	if ac.AuthenticationVersion == AuthVersion13 {
   659  		vals["Path"] = endpoint
   660  		vals["X-Ops-Sign"] = "version=" + AuthVersion13
   661  		requestHeaders = []string{"Method", "Path", "Accept", "X-Chef-Version", "X-Ops-Server-API-Version", "X-Ops-Timestamp", "X-Ops-UserId", "X-Ops-Sign", "X-Ops-Request-Source"}
   662  	} else {
   663  		vals["Hashed Path"] = HashStr(endpoint)
   664  		vals["X-Ops-Sign"] = "algorithm=sha1;version=" + AuthVersion10
   665  		requestHeaders = []string{"Method", "Accept", "X-Chef-Version", "X-Ops-Server-API-Version", "X-Ops-Timestamp", "X-Ops-UserId", "X-Ops-Sign", "X-Ops-Request-Source"}
   666  	}
   667  
   668  	// Add the vals to the request
   669  	for _, key := range requestHeaders {
   670  		request.Header.Set(key, vals[key])
   671  	}
   672  
   673  	content := ac.SignatureContent(vals)
   674  
   675  	// generate signed string of headers
   676  	var signature []byte
   677  	var err error
   678  	if ac.AuthenticationVersion == AuthVersion13 {
   679  		signature, err = GenerateDigestSignature(ac.PrivateKey, content)
   680  		if err != nil {
   681  			fmt.Printf("Error from signature %+v\n", err)
   682  			return err
   683  		}
   684  	} else {
   685  		signature, err = GenerateSignature(ac.PrivateKey, content)
   686  		if err != nil {
   687  			return err
   688  		}
   689  	}
   690  
   691  	// THIS IS CHEF PROTOCOL SPECIFIC
   692  	// Signature is made up of n 60 length chunks
   693  	base64sig := Base64BlockEncode(signature, 60)
   694  
   695  	// roll over the auth slice and add the appropriate header
   696  	for index, value := range base64sig {
   697  		request.Header.Set(fmt.Sprintf("X-Ops-Authorization-%d", index+1), value)
   698  	}
   699  
   700  	return nil
   701  }
   702  
   703  func (ac AuthConfig) SignatureContent(vals map[string]string) (content string) {
   704  	// sanitize the path for the chef-server
   705  	// chef-server doesn't support '//' in the Hash Path.
   706  
   707  	// The signature is very particular, the exact headers and the order they are included in the signature matter
   708  	var signedHeaders []string
   709  
   710  	if ac.AuthenticationVersion == AuthVersion13 {
   711  		signedHeaders = []string{"Method", "Path", "X-Ops-Content-Hash", "X-Ops-Sign", "X-Ops-Timestamp",
   712  			"X-Ops-UserId", "X-Ops-Server-API-Version"}
   713  	} else {
   714  		signedHeaders = []string{"Method", "Hashed Path", "X-Ops-Content-Hash", "X-Ops-Timestamp", "X-Ops-UserId"}
   715  	}
   716  
   717  	for _, key := range signedHeaders {
   718  		content += fmt.Sprintf("%s:%s\n", key, vals[key])
   719  	}
   720  
   721  	content = strings.TrimSuffix(content, "\n")
   722  	return
   723  }
   724  
   725  // PrivateKeyFromString parses an private key from a string
   726  func PrivateKeyFromString(key []byte) (*rsa.PrivateKey, error) {
   727  	block, _ := pem.Decode(key)
   728  	if block == nil {
   729  		return nil, fmt.Errorf("private key block size invalid")
   730  	}
   731  
   732  	if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
   733  		return key, nil
   734  	}
   735  	if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
   736  		switch key := key.(type) {
   737  		case *rsa.PrivateKey:
   738  			return key, nil
   739  		default:
   740  			return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping")
   741  		}
   742  	}
   743  
   744  	return nil, errors.New("tls: failed to parse private key")
   745  }
   746  
   747  func (c *Client) MagicRequestResponseDecoderWithOutAuth(url, method string, body io.Reader, v interface{}) error {
   748  	req, err := c.NoAuthNewRequest(method, url, body)
   749  	if err != nil {
   750  		return err
   751  	}
   752  
   753  	res, err := c.Do(req, v)
   754  	if res != nil {
   755  		defer func() {
   756  			_ = res.Body.Close()
   757  		}()
   758  	}
   759  	if err != nil {
   760  		return err
   761  	}
   762  	return err
   763  }