mirror of
https://github.com/crowdsecurity/crowdsec.git
synced 2025-05-10 20:05:55 +02:00
* lint: enable reflectvaluecompare * lint: remove exception * lint: stylecheck ST016 - methods on the same type should have the same receiver name * lint: enable revive[time-equal]
322 lines
8.1 KiB
Go
322 lines
8.1 KiB
Go
package apiclient
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-openapi/strfmt"
|
|
"github.com/golang-jwt/jwt/v4"
|
|
|
|
"github.com/crowdsecurity/crowdsec/pkg/apiclient/useragent"
|
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
|
)
|
|
|
|
var (
|
|
InsecureSkipVerify = false
|
|
Cert *tls.Certificate
|
|
CaCertPool *x509.CertPool
|
|
lapiClient *ApiClient
|
|
)
|
|
|
|
type ApiClient struct {
|
|
/*The http client used to make requests*/
|
|
client *http.Client
|
|
/*Reuse a single struct instead of allocating one for each service on the heap.*/
|
|
common service
|
|
/*config stuff*/
|
|
BaseURL *url.URL
|
|
PapiURL *url.URL
|
|
URLPrefix string
|
|
UserAgent string
|
|
/*exposed Services*/
|
|
Decisions *DecisionsService
|
|
DecisionDelete *DecisionDeleteService
|
|
Alerts *AlertsService
|
|
Allowlists *AllowlistsService
|
|
Auth *AuthService
|
|
Metrics *MetricsService
|
|
Signal *SignalService
|
|
HeartBeat *HeartBeatService
|
|
UsageMetrics *UsageMetricsService
|
|
}
|
|
|
|
func (c *ApiClient) GetClient() *http.Client {
|
|
return c.client
|
|
}
|
|
|
|
func (c *ApiClient) IsEnrolled() bool {
|
|
jwtTransport := c.client.Transport.(*JWTTransport)
|
|
tokenStr := jwtTransport.Token
|
|
|
|
token, _ := jwt.Parse(tokenStr, nil)
|
|
if token == nil {
|
|
return false
|
|
}
|
|
|
|
claims := token.Claims.(jwt.MapClaims)
|
|
_, ok := claims["organization_id"]
|
|
|
|
return ok
|
|
}
|
|
|
|
type service struct {
|
|
client *ApiClient
|
|
}
|
|
|
|
func InitLAPIClient(ctx context.Context, apiUrl string, papiUrl string, login string, password string, scenarios []string) error {
|
|
apiURL, err := url.Parse(apiUrl)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing api url ('%s'): %w", apiURL, err)
|
|
}
|
|
|
|
papiURL, err := url.Parse(papiUrl)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing polling api url ('%s'): %w", papiURL, err)
|
|
}
|
|
|
|
pwd := strfmt.Password(password)
|
|
|
|
client, err := NewClient(&Config{
|
|
MachineID: login,
|
|
Password: pwd,
|
|
Scenarios: scenarios,
|
|
URL: apiURL,
|
|
PapiURL: papiURL,
|
|
VersionPrefix: "v1",
|
|
UpdateScenario: func(_ context.Context) ([]string, error) {
|
|
return scenarios, nil
|
|
},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("new client api: %w", err)
|
|
}
|
|
|
|
authResp, _, err := client.Auth.AuthenticateWatcher(ctx, models.WatcherAuthRequest{
|
|
MachineID: &login,
|
|
Password: &pwd,
|
|
Scenarios: scenarios,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("authenticate watcher (%s): %w", login, err)
|
|
}
|
|
|
|
var expiration time.Time
|
|
if err := expiration.UnmarshalText([]byte(authResp.Expire)); err != nil {
|
|
return fmt.Errorf("unable to parse jwt expiration: %w", err)
|
|
}
|
|
|
|
client.GetClient().Transport.(*JWTTransport).Token = authResp.Token
|
|
client.GetClient().Transport.(*JWTTransport).Expiration = expiration
|
|
|
|
lapiClient = client
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetLAPIClient() (*ApiClient, error) {
|
|
if lapiClient == nil {
|
|
return nil, errors.New("client not initialized")
|
|
}
|
|
|
|
return lapiClient, nil
|
|
}
|
|
|
|
func NewClient(config *Config) (*ApiClient, error) {
|
|
userAgent := config.UserAgent
|
|
if userAgent == "" {
|
|
userAgent = useragent.Default()
|
|
}
|
|
|
|
t := &JWTTransport{
|
|
MachineID: &config.MachineID,
|
|
Password: &config.Password,
|
|
Scenarios: config.Scenarios,
|
|
UserAgent: userAgent,
|
|
VersionPrefix: config.VersionPrefix,
|
|
UpdateScenario: config.UpdateScenario,
|
|
RetryConfig: NewRetryConfig(
|
|
WithStatusCodeConfig(http.StatusUnauthorized, 2, false, true),
|
|
WithStatusCodeConfig(http.StatusForbidden, 2, false, true),
|
|
WithStatusCodeConfig(http.StatusTooManyRequests, 5, true, false),
|
|
WithStatusCodeConfig(http.StatusServiceUnavailable, 5, true, false),
|
|
WithStatusCodeConfig(http.StatusGatewayTimeout, 5, true, false),
|
|
),
|
|
}
|
|
|
|
transport, baseURL := createTransport(config.URL)
|
|
if transport != nil {
|
|
t.Transport = transport
|
|
} else {
|
|
// can be httpmock.MockTransport
|
|
if ht, ok := http.DefaultTransport.(*http.Transport); ok {
|
|
t.Transport = ht.Clone()
|
|
}
|
|
}
|
|
|
|
t.URL = baseURL
|
|
|
|
tlsconfig := tls.Config{InsecureSkipVerify: InsecureSkipVerify}
|
|
tlsconfig.RootCAs = CaCertPool
|
|
|
|
if Cert != nil {
|
|
tlsconfig.Certificates = []tls.Certificate{*Cert}
|
|
}
|
|
|
|
if t.Transport != nil {
|
|
t.Transport.(*http.Transport).TLSClientConfig = &tlsconfig
|
|
}
|
|
|
|
c := &ApiClient{client: t.Client(), BaseURL: baseURL, UserAgent: userAgent, URLPrefix: config.VersionPrefix, PapiURL: config.PapiURL}
|
|
c.common.client = c
|
|
c.Decisions = (*DecisionsService)(&c.common)
|
|
c.Alerts = (*AlertsService)(&c.common)
|
|
c.Allowlists = (*AllowlistsService)(&c.common)
|
|
c.Auth = (*AuthService)(&c.common)
|
|
c.Metrics = (*MetricsService)(&c.common)
|
|
c.Signal = (*SignalService)(&c.common)
|
|
c.DecisionDelete = (*DecisionDeleteService)(&c.common)
|
|
c.HeartBeat = (*HeartBeatService)(&c.common)
|
|
c.UsageMetrics = (*UsageMetricsService)(&c.common)
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func NewDefaultClient(url *url.URL, prefix string, userAgent string, client *http.Client) (*ApiClient, error) {
|
|
transport, baseURL := createTransport(url)
|
|
|
|
if client == nil {
|
|
client = &http.Client{}
|
|
|
|
if transport != nil {
|
|
client.Transport = transport
|
|
} else {
|
|
if ht, ok := http.DefaultTransport.(*http.Transport); ok {
|
|
ht = ht.Clone()
|
|
tlsconfig := tls.Config{InsecureSkipVerify: InsecureSkipVerify}
|
|
tlsconfig.RootCAs = CaCertPool
|
|
|
|
if Cert != nil {
|
|
tlsconfig.Certificates = []tls.Certificate{*Cert}
|
|
}
|
|
|
|
ht.TLSClientConfig = &tlsconfig
|
|
client.Transport = ht
|
|
}
|
|
}
|
|
}
|
|
|
|
if userAgent == "" {
|
|
userAgent = useragent.Default()
|
|
}
|
|
|
|
c := &ApiClient{client: client, BaseURL: baseURL, UserAgent: userAgent, URLPrefix: prefix}
|
|
c.common.client = c
|
|
c.Decisions = (*DecisionsService)(&c.common)
|
|
c.Alerts = (*AlertsService)(&c.common)
|
|
c.Allowlists = (*AllowlistsService)(&c.common)
|
|
c.Auth = (*AuthService)(&c.common)
|
|
c.Metrics = (*MetricsService)(&c.common)
|
|
c.Signal = (*SignalService)(&c.common)
|
|
c.DecisionDelete = (*DecisionDeleteService)(&c.common)
|
|
c.HeartBeat = (*HeartBeatService)(&c.common)
|
|
c.UsageMetrics = (*UsageMetricsService)(&c.common)
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func RegisterClient(ctx context.Context, config *Config, client *http.Client) (*ApiClient, error) {
|
|
transport, baseURL := createTransport(config.URL)
|
|
|
|
if client == nil {
|
|
client = &http.Client{}
|
|
if transport != nil {
|
|
client.Transport = transport
|
|
} else {
|
|
tlsconfig := tls.Config{InsecureSkipVerify: InsecureSkipVerify}
|
|
if Cert != nil {
|
|
tlsconfig.RootCAs = CaCertPool
|
|
tlsconfig.Certificates = []tls.Certificate{*Cert}
|
|
}
|
|
|
|
client.Transport = http.DefaultTransport.(*http.Transport).Clone()
|
|
client.Transport.(*http.Transport).TLSClientConfig = &tlsconfig
|
|
}
|
|
} else if client.Transport == nil && transport != nil {
|
|
client.Transport = transport
|
|
}
|
|
|
|
userAgent := config.UserAgent
|
|
if userAgent == "" {
|
|
userAgent = useragent.Default()
|
|
}
|
|
|
|
c := &ApiClient{client: client, BaseURL: baseURL, UserAgent: userAgent, URLPrefix: config.VersionPrefix}
|
|
c.common.client = c
|
|
c.Decisions = (*DecisionsService)(&c.common)
|
|
c.Alerts = (*AlertsService)(&c.common)
|
|
c.Auth = (*AuthService)(&c.common)
|
|
|
|
resp, err := c.Auth.RegisterWatcher(ctx, models.WatcherRegistrationRequest{MachineID: &config.MachineID, Password: &config.Password, RegistrationToken: config.RegistrationToken})
|
|
if err != nil {
|
|
/*if we have http status, return it*/
|
|
if resp != nil && resp.Response != nil {
|
|
return nil, fmt.Errorf("api register (%s) http %s: %w", c.BaseURL, resp.Response.Status, err)
|
|
}
|
|
|
|
return nil, fmt.Errorf("api register (%s): %w", c.BaseURL, err)
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func createTransport(url *url.URL) (*http.Transport, *url.URL) {
|
|
urlString := url.String()
|
|
|
|
// TCP transport
|
|
if !strings.HasPrefix(urlString, "/") {
|
|
return nil, url
|
|
}
|
|
|
|
// Unix transport
|
|
url.Path = "/"
|
|
url.Host = "unix"
|
|
url.Scheme = "http"
|
|
|
|
return &http.Transport{
|
|
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
|
return net.Dial("unix", strings.TrimSuffix(urlString, "/"))
|
|
},
|
|
}, url
|
|
}
|
|
|
|
type Response struct {
|
|
Response *http.Response
|
|
// add our pagination stuff
|
|
// NextPage int
|
|
// ...
|
|
}
|
|
|
|
func newResponse(r *http.Response) *Response {
|
|
return &Response{Response: r}
|
|
}
|
|
|
|
type ListOpts struct {
|
|
// Page int
|
|
// PerPage int
|
|
}
|
|
|
|
type DeleteOpts struct {
|
|
// ??
|
|
}
|
|
|
|
type AddOpts struct {
|
|
// ??
|
|
}
|