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 }