Forráskód Böngészése

apiclient: split auth_key, auth_retry, auth_jwt (#2743)

mmetc 1 éve
szülő
commit
d760b401e6

+ 3 - 2
.golangci.yml

@@ -54,7 +54,7 @@ linters-settings:
       main:
         deny:
           - pkg: "github.com/pkg/errors"
-            desc: "errors.New() is deprecated in favor of fmt.Errorf()"
+            desc: "errors.Wrap() is deprecated in favor of fmt.Errorf()"
 
 linters:
   enable-all: true
@@ -287,7 +287,8 @@ issues:
         - bodyclose
       text: "response body must be closed"
 
-    # named/naked returns are evil
+    # named/naked returns are evil, with a single exception
+    # https://go.dev/wiki/CodeReviewComments#named-result-parameters
     - linters:
         - nonamedreturns
       text: "named return .* with type .* found"

+ 0 - 161
pkg/apiclient/auth.go → pkg/apiclient/auth_jwt.go

@@ -3,10 +3,8 @@ package apiclient
 import (
 	"bytes"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"io"
-	"math/rand"
 	"net/http"
 	"net/http/httputil"
 	"net/url"
@@ -16,143 +14,9 @@ import (
 	"github.com/go-openapi/strfmt"
 	log "github.com/sirupsen/logrus"
 
-	"github.com/crowdsecurity/crowdsec/pkg/fflag"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
 )
 
-type APIKeyTransport struct {
-	APIKey string
-	// Transport is the underlying HTTP transport to use when making requests.
-	// It will default to http.DefaultTransport if nil.
-	Transport     http.RoundTripper
-	URL           *url.URL
-	VersionPrefix string
-	UserAgent     string
-}
-
-// RoundTrip implements the RoundTripper interface.
-func (t *APIKeyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
-	if t.APIKey == "" {
-		return nil, errors.New("APIKey is empty")
-	}
-
-	// We must make a copy of the Request so
-	// that we don't modify the Request we were given. This is required by the
-	// specification of http.RoundTripper.
-	req = cloneRequest(req)
-	req.Header.Add("X-Api-Key", t.APIKey)
-
-	if t.UserAgent != "" {
-		req.Header.Add("User-Agent", t.UserAgent)
-	}
-
-	log.Debugf("req-api: %s %s", req.Method, req.URL.String())
-
-	if log.GetLevel() >= log.TraceLevel {
-		dump, _ := httputil.DumpRequest(req, true)
-		log.Tracef("auth-api request: %s", string(dump))
-	}
-
-	// Make the HTTP request.
-	resp, err := t.transport().RoundTrip(req)
-	if err != nil {
-		log.Errorf("auth-api: auth with api key failed return nil response, error: %s", err)
-
-		return resp, err
-	}
-
-	if log.GetLevel() >= log.TraceLevel {
-		dump, _ := httputil.DumpResponse(resp, true)
-		log.Tracef("auth-api response: %s", string(dump))
-	}
-
-	log.Debugf("resp-api: http %d", resp.StatusCode)
-
-	return resp, err
-}
-
-func (t *APIKeyTransport) Client() *http.Client {
-	return &http.Client{Transport: t}
-}
-
-func (t *APIKeyTransport) transport() http.RoundTripper {
-	if t.Transport != nil {
-		return t.Transport
-	}
-
-	return http.DefaultTransport
-}
-
-type retryRoundTripper struct {
-	next             http.RoundTripper
-	maxAttempts      int
-	retryStatusCodes []int
-	withBackOff      bool
-	onBeforeRequest  func(attempt int)
-}
-
-func (r retryRoundTripper) ShouldRetry(statusCode int) bool {
-	for _, code := range r.retryStatusCodes {
-		if code == statusCode {
-			return true
-		}
-	}
-
-	return false
-}
-
-func (r retryRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
-	var (
-		resp *http.Response
-		err  error
-	)
-
-	backoff := 0
-	maxAttempts := r.maxAttempts
-
-	if fflag.DisableHttpRetryBackoff.IsEnabled() {
-		maxAttempts = 1
-	}
-
-	for i := 0; i < maxAttempts; i++ {
-		if i > 0 {
-			if r.withBackOff {
-				//nolint:gosec
-				backoff += 10 + rand.Intn(20)
-			}
-
-			log.Infof("retrying in %d seconds (attempt %d of %d)", backoff, i+1, r.maxAttempts)
-
-			select {
-			case <-req.Context().Done():
-				return nil, req.Context().Err()
-			case <-time.After(time.Duration(backoff) * time.Second):
-			}
-		}
-
-		if r.onBeforeRequest != nil {
-			r.onBeforeRequest(i)
-		}
-
-		clonedReq := cloneRequest(req)
-
-		resp, err = r.next.RoundTrip(clonedReq)
-		if err != nil {
-			if left := maxAttempts - i - 1; left > 0 {
-				log.Errorf("error while performing request: %s; %d retries left", err, left)
-			}
-
-			continue
-		}
-
-		if !r.ShouldRetry(resp.StatusCode) {
-			return resp, nil
-		}
-	}
-
-	return resp, err
-}
-
 type JWTTransport struct {
 	MachineID     *string
 	Password      *strfmt.Password
@@ -351,28 +215,3 @@ func (t *JWTTransport) transport() http.RoundTripper {
 		},
 	}
 }
-
-// cloneRequest returns a clone of the provided *http.Request. The clone is a
-// shallow copy of the struct and its Header map.
-func cloneRequest(r *http.Request) *http.Request {
-	// shallow copy of the struct
-	r2 := new(http.Request)
-	*r2 = *r
-	// deep copy of the Header
-	r2.Header = make(http.Header, len(r.Header))
-
-	for k, s := range r.Header {
-		r2.Header[k] = append([]string(nil), s...)
-	}
-
-	if r.Body != nil {
-		var b bytes.Buffer
-
-		b.ReadFrom(r.Body)
-
-		r.Body = io.NopCloser(&b)
-		r2.Body = io.NopCloser(bytes.NewReader(b.Bytes()))
-	}
-
-	return r2
-}

+ 73 - 0
pkg/apiclient/auth_key.go

@@ -0,0 +1,73 @@
+package apiclient
+
+import (
+	"errors"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+
+	log "github.com/sirupsen/logrus"
+)
+
+type APIKeyTransport struct {
+	APIKey string
+	// Transport is the underlying HTTP transport to use when making requests.
+	// It will default to http.DefaultTransport if nil.
+	Transport     http.RoundTripper
+	URL           *url.URL
+	VersionPrefix string
+	UserAgent     string
+}
+
+// RoundTrip implements the RoundTripper interface.
+func (t *APIKeyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	if t.APIKey == "" {
+		return nil, errors.New("APIKey is empty")
+	}
+
+	// We must make a copy of the Request so
+	// that we don't modify the Request we were given. This is required by the
+	// specification of http.RoundTripper.
+	req = cloneRequest(req)
+	req.Header.Add("X-Api-Key", t.APIKey)
+
+	if t.UserAgent != "" {
+		req.Header.Add("User-Agent", t.UserAgent)
+	}
+
+	log.Debugf("req-api: %s %s", req.Method, req.URL.String())
+
+	if log.GetLevel() >= log.TraceLevel {
+		dump, _ := httputil.DumpRequest(req, true)
+		log.Tracef("auth-api request: %s", string(dump))
+	}
+
+	// Make the HTTP request.
+	resp, err := t.transport().RoundTrip(req)
+	if err != nil {
+		log.Errorf("auth-api: auth with api key failed return nil response, error: %s", err)
+
+		return resp, err
+	}
+
+	if log.GetLevel() >= log.TraceLevel {
+		dump, _ := httputil.DumpResponse(resp, true)
+		log.Tracef("auth-api response: %s", string(dump))
+	}
+
+	log.Debugf("resp-api: http %d", resp.StatusCode)
+
+	return resp, err
+}
+
+func (t *APIKeyTransport) Client() *http.Client {
+	return &http.Client{Transport: t}
+}
+
+func (t *APIKeyTransport) transport() http.RoundTripper {
+	if t.Transport != nil {
+		return t.Transport
+	}
+
+	return http.DefaultTransport
+}

+ 0 - 0
pkg/apiclient/auth_test.go → pkg/apiclient/auth_key_test.go


+ 81 - 0
pkg/apiclient/auth_retry.go

@@ -0,0 +1,81 @@
+package apiclient
+
+import (
+	"math/rand"
+	"net/http"
+	"time"
+
+	log "github.com/sirupsen/logrus"
+
+	"github.com/crowdsecurity/crowdsec/pkg/fflag"
+)
+
+type retryRoundTripper struct {
+	next             http.RoundTripper
+	maxAttempts      int
+	retryStatusCodes []int
+	withBackOff      bool
+	onBeforeRequest  func(attempt int)
+}
+
+func (r retryRoundTripper) ShouldRetry(statusCode int) bool {
+	for _, code := range r.retryStatusCodes {
+		if code == statusCode {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (r retryRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+	var (
+		resp *http.Response
+		err  error
+	)
+
+	backoff := 0
+	maxAttempts := r.maxAttempts
+
+	if fflag.DisableHttpRetryBackoff.IsEnabled() {
+		maxAttempts = 1
+	}
+
+	for i := 0; i < maxAttempts; i++ {
+		if i > 0 {
+			if r.withBackOff {
+				//nolint:gosec
+				backoff += 10 + rand.Intn(20)
+			}
+
+			log.Infof("retrying in %d seconds (attempt %d of %d)", backoff, i+1, r.maxAttempts)
+
+			select {
+			case <-req.Context().Done():
+				return nil, req.Context().Err()
+			case <-time.After(time.Duration(backoff) * time.Second):
+			}
+		}
+
+		if r.onBeforeRequest != nil {
+			r.onBeforeRequest(i)
+		}
+
+		clonedReq := cloneRequest(req)
+
+		resp, err = r.next.RoundTrip(clonedReq)
+		if err != nil {
+			if left := maxAttempts - i - 1; left > 0 {
+				log.Errorf("error while performing request: %s; %d retries left", err, left)
+			}
+
+			continue
+		}
+
+		if !r.ShouldRetry(resp.StatusCode) {
+			return resp, nil
+		}
+	}
+
+	return resp, err
+}

+ 32 - 0
pkg/apiclient/clone.go

@@ -0,0 +1,32 @@
+package apiclient
+
+import (
+	"bytes"
+	"io"
+	"net/http"
+)
+
+// cloneRequest returns a clone of the provided *http.Request. The clone is a
+// shallow copy of the struct and its Header map.
+func cloneRequest(r *http.Request) *http.Request {
+	// shallow copy of the struct
+	r2 := new(http.Request)
+	*r2 = *r
+	// deep copy of the Header
+	r2.Header = make(http.Header, len(r.Header))
+
+	for k, s := range r.Header {
+		r2.Header[k] = append([]string(nil), s...)
+	}
+
+	if r.Body != nil {
+		var b bytes.Buffer
+
+		b.ReadFrom(r.Body)
+
+		r.Body = io.NopCloser(&b)
+		r2.Body = io.NopCloser(bytes.NewReader(b.Bytes()))
+	}
+
+	return r2
+}