Skip to content

Commit

Permalink
apiserver/apiclient: unix socket support
Browse files Browse the repository at this point in the history
  • Loading branch information
mmetc committed Jan 31, 2024
1 parent 39f187d commit 4c772af
Show file tree
Hide file tree
Showing 18 changed files with 476 additions and 83 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ msi
__pycache__
*.py[cod]
*.egg-info

.idea
1 change: 1 addition & 0 deletions cmd/crowdsec-cli/config_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ API Client:
{{- if .API.Server }}
Local API Server{{if and .API.Server.Enable (not (ValueBool .API.Server.Enable))}} (disabled){{end}}:
- Listen URL : {{.API.Server.ListenURI}}
- Listen Socket : {{.API.Server.ListenSocket}}
- Profile File : {{.API.Server.ProfilesPath}}
{{- if .API.Server.TLS }}
Expand Down
34 changes: 19 additions & 15 deletions cmd/crowdsec-cli/lapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,7 @@ func runLapiRegister(cmd *cobra.Command, args []string) error {
}
}
password := strfmt.Password(generatePassword(passwordLength))
if apiURL == "" {
if csConfig.API.Client == nil || csConfig.API.Client.Credentials == nil || csConfig.API.Client.Credentials.URL == "" {
return fmt.Errorf("no Local API URL. Please provide it in your configuration or with the -u parameter")
}
apiURL = csConfig.API.Client.Credentials.URL
}
/*URL needs to end with /, but user doesn't care*/
if !strings.HasSuffix(apiURL, "/") {
apiURL += "/"
}
/*URL needs to start with http://, but user doesn't care*/
if !strings.HasPrefix(apiURL, "http://") && !strings.HasPrefix(apiURL, "https://") {
apiURL = "http://" + apiURL
}
apiurl, err := url.Parse(apiURL)
apiurl, err := prepareApiURL(csConfig.API.Client, apiURL)
if err != nil {
return fmt.Errorf("parsing api url: %w", err)
}
Expand Down Expand Up @@ -160,6 +146,24 @@ func runLapiRegister(cmd *cobra.Command, args []string) error {
return nil
}

func prepareApiURL(clientConfig *csconfig.LocalApiClientCfg, apiURL string) (*url.URL, error) {
if apiURL == "" {
if clientConfig == nil || clientConfig.Credentials == nil || clientConfig.Credentials.URL == "" {
return nil, fmt.Errorf("no Local API URL. Please provide it in your configuration or with the -u parameter")
}
apiURL = clientConfig.Credentials.URL
}

if !strings.HasSuffix(apiURL, "/") {
apiURL += "/"
}

if !strings.HasPrefix(apiURL, "http://") && !strings.HasPrefix(apiURL, "https://") && !strings.HasPrefix(apiURL, "/") {
apiURL = "http://" + apiURL
}
return url.Parse(apiURL)
}

func NewLapiStatusCmd() *cobra.Command {
cmdLapiStatus := &cobra.Command{
Use: "status",
Expand Down
47 changes: 47 additions & 0 deletions cmd/crowdsec-cli/lapi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package main

import (
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func TestPrepareApiURL_NoProtocol(t *testing.T) {
url, err := prepareApiURL(nil, "localhost:81")
require.NoError(t, err)
assert.Equal(t, "http://localhost:81/", url.String())
}

func TestPrepareApiURL_Http(t *testing.T) {
url, err := prepareApiURL(nil, "http://localhost:81")
require.NoError(t, err)
assert.Equal(t, "http://localhost:81/", url.String())
}

func TestPrepareApiURL_Https(t *testing.T) {
url, err := prepareApiURL(nil, "https://localhost:81")
require.NoError(t, err)
assert.Equal(t, "https://localhost:81/", url.String())
}

func TestPrepareApiURL_UnixSocket(t *testing.T) {
url, err := prepareApiURL(nil, "/path/socket")
require.NoError(t, err)
assert.Equal(t, "/path/socket/", url.String())
}

func TestPrepareApiURL_Empty(t *testing.T) {
_, err := prepareApiURL(nil, "")
require.Error(t, err)
}

func TestPrepareApiURL_Empty_ConfigOverride(t *testing.T) {
url, err := prepareApiURL(&csconfig.LocalApiClientCfg{
Credentials: &csconfig.ApiCredentialsCfg{
URL: "localhost:80",
},
}, "")
require.NoError(t, err)
assert.Equal(t, "http://localhost:80/", url.String())
}
2 changes: 1 addition & 1 deletion cmd/crowdsec-cli/machines.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ func (cli cliMachines) add(cmd *cobra.Command, args []string) error {
if csConfig.API.Client != nil && csConfig.API.Client.Credentials != nil && csConfig.API.Client.Credentials.URL != "" {
apiURL = csConfig.API.Client.Credentials.URL
} else if csConfig.API.Server != nil && csConfig.API.Server.ListenURI != "" {
apiURL = "http://" + csConfig.API.Server.ListenURI
apiURL = csConfig.API.Server.ClientUrl()
} else {
return fmt.Errorf("unable to dump an api URL. Please provide it in your configuration or with the -u parameter")
}
Expand Down
5 changes: 2 additions & 3 deletions docker/test/tests/test_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ def test_missing_key_file(crowdsec, flavor):
}

with crowdsec(flavor=flavor, environment=env, wait_status=Status.EXITED) as cs:
# XXX: this message appears twice, is that normal?
cs.wait_for_log("*while starting API server: missing TLS key file*")
cs.wait_for_log("*local API server stopped with error: missing TLS key file*")


def test_missing_cert_file(crowdsec, flavor):
Expand All @@ -35,7 +34,7 @@ def test_missing_cert_file(crowdsec, flavor):
}

with crowdsec(flavor=flavor, environment=env, wait_status=Status.EXITED) as cs:
cs.wait_for_log("*while starting API server: missing TLS cert file*")
cs.wait_for_log("*local API server stopped with error: missing TLS cert file*")


def test_tls_missing_ca(crowdsec, flavor, certs_dir):
Expand Down
7 changes: 6 additions & 1 deletion pkg/apiclient/auth_jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,14 @@ func (t *JWTTransport) refreshJwtToken() error {

req.Header.Add("Content-Type", "application/json")

transport := t.Transport
if transport == nil {
transport = http.DefaultTransport
}

client := &http.Client{
Transport: &retryRoundTripper{
next: http.DefaultTransport,
next: transport,
maxAttempts: 5,
withBackOff: true,
retryStatusCodes: []int{http.StatusTooManyRequests, http.StatusServiceUnavailable, http.StatusGatewayTimeout, http.StatusInternalServerError},
Expand Down
75 changes: 55 additions & 20 deletions pkg/apiclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"net/http"
"net/url"
"strings"

"github.com/golang-jwt/jwt/v4"

Expand Down Expand Up @@ -67,11 +69,15 @@ func NewClient(config *Config) (*ApiClient, error) {
MachineID: &config.MachineID,
Password: &config.Password,
Scenarios: config.Scenarios,
URL: config.URL,
UserAgent: config.UserAgent,
VersionPrefix: config.VersionPrefix,
UpdateScenario: config.UpdateScenario,
}
transport, baseUrl := CreateTransport(config.URL)
if transport != nil {
t.Transport = transport
}
t.URL = baseUrl

tlsconfig := tls.Config{InsecureSkipVerify: InsecureSkipVerify}
tlsconfig.RootCAs = CaCertPool
Expand All @@ -84,7 +90,7 @@ func NewClient(config *Config) (*ApiClient, error) {
ht.TLSClientConfig = &tlsconfig
}

c := &ApiClient{client: t.Client(), BaseURL: config.URL, UserAgent: config.UserAgent, URLPrefix: config.VersionPrefix, PapiURL: config.PapiURL}
c := &ApiClient{client: t.Client(), BaseURL: baseUrl, UserAgent: config.UserAgent, URLPrefix: config.VersionPrefix, PapiURL: config.PapiURL}
c.common.client = c
c.Decisions = (*DecisionsService)(&c.common)
c.Alerts = (*AlertsService)(&c.common)
Expand All @@ -98,23 +104,26 @@ func NewClient(config *Config) (*ApiClient, error) {
}

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 ht, ok := http.DefaultTransport.(*http.Transport); ok {
tlsconfig := tls.Config{InsecureSkipVerify: InsecureSkipVerify}
tlsconfig.RootCAs = CaCertPool

if Cert != nil {
tlsconfig.Certificates = []tls.Certificate{*Cert}
if transport != nil {
client.Transport = transport
} else {
if ht, ok := http.DefaultTransport.(*http.Transport); ok {
tlsconfig := tls.Config{InsecureSkipVerify: InsecureSkipVerify}
tlsconfig.RootCAs = CaCertPool
if Cert != nil {
tlsconfig.Certificates = []tls.Certificate{*Cert}
}
ht.TLSClientConfig = &tlsconfig
client.Transport = ht
}

ht.TLSClientConfig = &tlsconfig
client.Transport = ht
}
}

c := &ApiClient{client: client, BaseURL: URL, UserAgent: userAgent, URLPrefix: prefix}
c := &ApiClient{client: client, BaseURL: baseUrl, UserAgent: userAgent, URLPrefix: prefix}
c.common.client = c
c.Decisions = (*DecisionsService)(&c.common)
c.Alerts = (*AlertsService)(&c.common)
Expand All @@ -128,18 +137,24 @@ func NewDefaultClient(URL *url.URL, prefix string, userAgent string, client *htt
}

func RegisterClient(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}
}
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tlsconfig
}
} else if client.Transport == nil && transport != nil {
client.Transport = transport
}

tlsconfig := tls.Config{InsecureSkipVerify: InsecureSkipVerify}
if Cert != nil {
tlsconfig.RootCAs = CaCertPool
tlsconfig.Certificates = []tls.Certificate{*Cert}
}

http.DefaultTransport.(*http.Transport).TLSClientConfig = &tlsconfig
c := &ApiClient{client: client, BaseURL: config.URL, UserAgent: config.UserAgent, URLPrefix: config.VersionPrefix}
c := &ApiClient{client: client, BaseURL: baseUrl, UserAgent: config.UserAgent, URLPrefix: config.VersionPrefix}
c.common.client = c
c.Decisions = (*DecisionsService)(&c.common)
c.Alerts = (*AlertsService)(&c.common)
Expand All @@ -158,6 +173,26 @@ func RegisterClient(config *Config, client *http.Client) (*ApiClient, error) {
return c, nil
}

func CreateTransport(url *url.URL) (*http.Transport, *url.URL) {
urlString := url.String()
if !strings.HasPrefix(urlString, "/") {
return nil, url
}

ToUnixSocketUrl(url)
return &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", strings.TrimSuffix(urlString, "/"))
},
}, url
}

func ToUnixSocketUrl(url *url.URL) {
url.Path = "/"
url.Host = "unix"
url.Scheme = "http"
}

type Response struct {
Response *http.Response
//add our pagination stuff
Expand Down
Loading

0 comments on commit 4c772af

Please sign in to comment.