Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apiclient apiserver socket #119

Closed
wants to merge 14 commits into from
Closed
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
48 changes: 28 additions & 20 deletions cmd/crowdsec-cli/lapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ func (cli *cliLapi) status() error {
password := strfmt.Password(cfg.API.Client.Credentials.Password)
login := cfg.API.Client.Credentials.Login

apiurl, err := url.Parse(cfg.API.Client.Credentials.URL)
origURL := cfg.API.Client.Credentials.URL

apiURL, err := url.Parse(origURL)
if err != nil {
return fmt.Errorf("parsing api url: %w", err)
}
Expand All @@ -59,7 +61,7 @@ func (cli *cliLapi) status() error {
return fmt.Errorf("failed to get scenarios: %w", err)
}

Client, err = apiclient.NewDefaultClient(apiurl,
Client, err = apiclient.NewDefaultClient(apiURL,
LAPIURLPrefix,
fmt.Sprintf("crowdsec/%s", version.String()),
nil)
Expand All @@ -74,7 +76,8 @@ func (cli *cliLapi) status() error {
}

log.Infof("Loaded credentials from %s", cfg.API.Client.CredentialsFilePath)
log.Infof("Trying to authenticate with username %s on %s", login, apiurl)
// use the original string because apiURL would print 'http://unix/'
log.Infof("Trying to authenticate with username %s on %s", login, origURL)

_, _, err = Client.Auth.AuthenticateWatcher(context.Background(), t)
if err != nil {
Expand All @@ -101,23 +104,7 @@ func (cli *cliLapi) register(apiURL string, outputFile string, machine string) e

password := strfmt.Password(generatePassword(passwordLength))

if apiURL == "" {
if cfg.API.Client == nil || cfg.API.Client.Credentials == nil || cfg.API.Client.Credentials.URL == "" {
return fmt.Errorf("no Local API URL. Please provide it in your configuration or with the -u parameter")
}

apiURL = cfg.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(cfg.API.Client, apiURL)
if err != nil {
return fmt.Errorf("parsing api url: %w", err)
}
Expand Down Expand Up @@ -173,6 +160,27 @@ func (cli *cliLapi) register(apiURL string, outputFile string, machine string) e
return nil
}

func prepareAPIURL(clientCfg *csconfig.LocalApiClientCfg, apiURL string) (*url.URL, error) {
if apiURL == "" {
if clientCfg == nil || clientCfg.Credentials == nil || clientCfg.Credentials.URL == "" {
return nil, fmt.Errorf("no Local API URL. Please provide it in your configuration or with the -u parameter")
}
apiURL = clientCfg.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://") && !strings.HasPrefix(apiURL, "/") {
apiURL = "http://" + apiURL
}

return url.Parse(apiURL)
}

func (cli *cliLapi) newStatusCmd() *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())
}
4 changes: 2 additions & 2 deletions cmd/crowdsec-cli/machines.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,8 @@ func (cli *cliMachines) add(args []string, machinePassword string, dumpFile stri
if apiURL == "" {
if clientCfg != nil && clientCfg.Credentials != nil && clientCfg.Credentials.URL != "" {
apiURL = clientCfg.Credentials.URL
} else if serverCfg != nil && serverCfg.ListenURI != "" {
apiURL = "http://" + serverCfg.ListenURI
} else if serverCfg.ClientURL() != "" {
apiURL = serverCfg.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 @@
"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 @@
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 @@
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 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 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

Check warning on line 144 in pkg/apiclient/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiclient/client.go#L144

Added line #L144 was not covered by tests
} else {
tlsconfig := tls.Config{InsecureSkipVerify: InsecureSkipVerify}
if Cert != nil {
tlsconfig.RootCAs = CaCertPool
tlsconfig.Certificates = []tls.Certificate{*Cert}
}

Check warning on line 150 in pkg/apiclient/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiclient/client.go#L148-L150

Added lines #L148 - L150 were not covered by tests
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 @@
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
Expand Down
Loading
Loading