Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 71 additions & 18 deletions pkg/acquisition/modules/appsec/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ var (
type Configuration struct {
ListenAddr string `yaml:"listen_addr"`
ListenSocket string `yaml:"listen_socket"`
TLSAuth bool `yaml:"tls_auth"`
CertFilePath string `yaml:"cert_file"`
KeyFilePath string `yaml:"key_file"`
CaCertPath string `yaml:"ca_cert_file"`
Path string `yaml:"path"`
Routines int `yaml:"routines"`
AppsecConfig string `yaml:"appsec_config"`
Expand Down Expand Up @@ -82,6 +84,20 @@ func (w *Source) UnmarshalConfig(yamlConfig []byte) error {
w.config.Routines = 1
}

if w.config.TLSAuth {
if w.config.CaCertPath == "" {
return errors.New("TLSAuth is enabled, but ca_cert is not provided")
}

if w.config.CertFilePath == "" {
return errors.New("cert_file is required")
}

if w.config.KeyFilePath == "" {
return errors.New("key_file is required")
}
}

if w.config.AppsecConfig == "" && w.config.AppsecConfigPath == "" && len(w.config.AppsecConfigs) == 0 {
return errors.New("appsec_config or appsec_config_path must be set")
}
Expand Down Expand Up @@ -126,7 +142,7 @@ func loadCertPool(caCertPath string, logger log.FieldLogger) (*x509.CertPool, er
}

// expandAppsecConfigEntry resolves a single appsec_config(s) entry into the list
// of appsec-config item names to load. A literal entry is returned untouched. An entry containing a glob meta-character
// of appsec-config item names to load. A literal entry is returned untouched. An entry containing a glob meta-character
// is matched against the installed appsec-configs with the same matcher used
// to expand appsec-rule patterns; it errors when no installed config matches.
func expandAppsecConfigEntry(entry string, hub *cwhub.Hub) ([]string, error) {
Expand Down Expand Up @@ -180,6 +196,49 @@ func resolveAppsecConfigEntries(entries []string, hub *cwhub.Hub) ([]string, err
return toLoad, nil
}

func (w *Source) configureHTTPServer() error {
w.mux = http.NewServeMux()

w.server = &http.Server{
Addr: w.config.ListenAddr,
Handler: w.mux,
Protocols: &http.Protocols{},
}

w.server.Protocols.SetHTTP1(true)
w.server.Protocols.SetUnencryptedHTTP2(true)
w.server.Protocols.SetHTTP2(true)

if w.config.TLSAuth {
tlsConfig := tls.Config{}

if w.config.CaCertPath == "" {
return fmt.Errorf("missing ca certificate")
}

if w.config.CertFilePath == "" || w.config.KeyFilePath == "" {
return fmt.Errorf("missing server cert/key")
}

cert, err := tls.LoadX509KeyPair(w.config.CertFilePath, w.config.KeyFilePath)
if err != nil {
return fmt.Errorf("failed to load server cert/key: %w", err)
}

caCertPool, err := loadCertPool(w.config.CaCertPath, w.logger)
if err != nil {
return fmt.Errorf("unable to load Appsec CA cert pool: %w", err)
}

tlsConfig.Certificates = []tls.Certificate{cert}
tlsConfig.ClientCAs = caCertPool
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert

w.server.TLSConfig = &tlsConfig
}
return nil
}

func (w *Source) Configure(ctx context.Context, yamlConfig []byte, logger *log.Entry, _ metrics.AcquisitionMetricsLevel) error {
if w.hub == nil {
return errors.New("appsec datasource requires a hub. this is a bug, please report")
Expand Down Expand Up @@ -213,18 +272,10 @@ func (w *Source) Configure(ctx context.Context, yamlConfig []byte, logger *log.E
w.logger.Infof("Body read timeout not set, using default: %v", *w.config.BodyReadTimeout)
}

w.mux = http.NewServeMux()

w.server = &http.Server{
Addr: w.config.ListenAddr,
Handler: w.mux,
Protocols: &http.Protocols{},
if err := w.configureHTTPServer(); err != nil {
return err
}

w.server.Protocols.SetHTTP1(true)
w.server.Protocols.SetUnencryptedHTTP2(true)
w.server.Protocols.SetHTTP2(true)

w.InChan = make(chan appsec.ParsedRequest)
appsecCfg := appsec.AppsecConfig{Logger: w.logger.WithField("component", "appsec_config")}

Expand Down Expand Up @@ -414,14 +465,16 @@ func (w *Source) appsecHandler(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
w.logger.Debugf("Received request from '%s' on %s", r.RemoteAddr, r.URL.Path)

apiKey := r.Header.Get(appsec.APIKeyHeaderName)
clientIP := r.Header.Get(appsec.IPHeaderName)
remoteIP := r.RemoteAddr
if !w.config.TLSAuth {
apiKey := r.Header.Get(appsec.APIKeyHeaderName)
clientIP := r.Header.Get(appsec.IPHeaderName)
remoteIP := r.RemoteAddr

if err := w.checkAuth(ctx, apiKey); err != nil {
w.logger.Errorf("Unauthorized request from '%s' (real IP = %s): %s", remoteIP, clientIP, err)
rw.WriteHeader(http.StatusUnauthorized)
return
if err := w.checkAuth(ctx, apiKey); err != nil {
w.logger.Errorf("Unauthorized request from '%s' (real IP = %s): %s", remoteIP, clientIP, err)
rw.WriteHeader(http.StatusUnauthorized)
return
}
}

// Force client to send the body quickly enough.
Expand Down
8 changes: 4 additions & 4 deletions pkg/acquisition/modules/appsec/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ func (w *Source) listenAndServe(ctx context.Context, t *tomb.Tomb) error {

serverError := make(chan error, 2)

startServer := func(listener net.Listener, canTLS bool) {
startServer := func(listener net.Listener) {
var err error

if canTLS && (w.config.CertFilePath != "" || w.config.KeyFilePath != "") {
if w.config.TLSAuth || w.config.CertFilePath != "" || w.config.KeyFilePath != "" {
if w.config.KeyFilePath == "" {
serverError <- errors.New("missing TLS key file")
return
Expand Down Expand Up @@ -74,7 +74,7 @@ func (w *Source) listenAndServe(ctx context.Context, t *tomb.Tomb) error {
}

w.logger.Infof("Appsec listening on Unix socket %s", socket)
startServer(listener, false)
startServer(listener)
}(w.config.ListenSocket)

// Starting TCP listener
Expand All @@ -90,7 +90,7 @@ func (w *Source) listenAndServe(ctx context.Context, t *tomb.Tomb) error {
}

w.logger.Infof("Appsec listening on %s", url)
startServer(listener, true)
startServer(listener)
}(w.config.ListenAddr)

select {
Expand Down
155 changes: 155 additions & 0 deletions pkg/acquisition/modules/appsec/run_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package appsecacquisition

import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"path/filepath"
"testing"
"time"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/tomb.v2"
)

func TestSource_listenAndServe(t *testing.T) {
tests := []struct {
name string
clientCrtPath string
clientKeyPath string
useSocket bool
wantErr bool
}{
{
name: "Test TCP server with trust client cert",
clientCrtPath: "testdata/client.crt",
clientKeyPath: "testdata/client.key",
useSocket: false,
wantErr: false,
},
{
name: "Test TCP server with untrust client cert",
clientCrtPath: "testdata/selfsined-client.crt",
clientKeyPath: "testdata/selfsined-client.key",
useSocket: false,
wantErr: true,
},
{
name: "Test Socket server with trust client cert",
clientCrtPath: "testdata/client.crt",
clientKeyPath: "testdata/client.key",
useSocket: true,
wantErr: false,
},
{
name: "Test Socket server with untrust client cert",
clientCrtPath: "testdata/selfsined-client.crt",
clientKeyPath: "testdata/selfsined-client.key",
useSocket: true,
wantErr: true,
},
}

tempDir := t.TempDir()
socketFile := filepath.Join(tempDir, "test.sock")
url := "https://127.0.0.1:7422"

config := &Configuration{
ListenAddr: "127.0.0.1:7422",
ListenSocket: socketFile,
TLSAuth: true,
CertFilePath: "testdata/server.crt",
KeyFilePath: "testdata/server.key",
CaCertPath: "testdata/ca.crt",
}
runner := make([]AppsecRunner, 0)
tt := &tomb.Tomb{}

logger := logrus.New()
// Disable output
logger.SetOutput(io.Discard)
// Create Entry from disabled loggger
testLogger := logrus.NewEntry(logger)

w := &Source{AppsecRunners: runner, config: *config, logger: testLogger}

err := w.configureHTTPServer()
require.NoError(t, err)

// Supress server TLS error messages
w.server.ErrorLog = log.New(io.Discard, "", 0)

w.mux.HandleFunc("/test", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusOK)
})

tt.Go(func() error {
err := w.listenAndServe(context.Background(), tt)
require.NoError(t, err)
return nil
})

time.Sleep(1 * time.Second)

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// init client cert
cert, err := tls.LoadX509KeyPair(tt.clientCrtPath, tt.clientKeyPath)
require.NoError(t, err)

caCert, err := os.ReadFile("testdata/ca.crt")
require.NoError(t, err)

caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
}

dialer := &net.Dialer{}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
}

if tt.useSocket {
client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, "unix", socketFile)
},
},
}
}

req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, fmt.Sprintf("%s/test", url), http.NoBody)
require.NoError(t, err)

resp, err := client.Do(req)

if tt.wantErr {
require.ErrorContains(t, err, "tls: certificate required")
} else {
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
})
}

w.server.Close()
tt.Kill(nil)
err = tt.Wait()
require.NoError(t, err)
}
19 changes: 19 additions & 0 deletions pkg/acquisition/modules/appsec/testdata/ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDBzCCAe+gAwIBAgIURyZ0TsCbGUa/8fljwS3uF5m2PBQwDQYJKoZIhvcNAQEL
BQAwEjEQMA4GA1UEAwwHdGVzdC1jYTAgFw0yNjA3MjExODIxNDJaGA8yNTI2MDMy
MjE4MjE0MlowEjEQMA4GA1UEAwwHdGVzdC1jYTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANN1EqTtt4AbMvfzroTtb9i5p+irKqyJUHLGkSHA/pEvGSJn
v2G95aWqMLExx1yD8najcBk/rL7ZSKOo9unqHp0+mXLPXwaBNOfiJIg4vzzBxrhp
rMbP9D+UcBRNau7SS4zJaUo2fxIjhnLXizP/QR+8xPhVPh7euKg/zdzWkXPFe6JI
Ab3Rly4F7XQ9D9PjMkPP4NhiLkTl2yQ7+OMHwDXq+LTusEsW8IPTwdqvzscTRnuD
vfrMWEk1wvU3c0F1uxVOnbrSq6IE7NsdZH9G4dpvHBmz2JGs8SCy90EQa1xxaEsa
HBibLULVF0MQNpGk/n5bBdByHKohYrQyoySMQHkCAwEAAaNTMFEwHQYDVR0OBBYE
FBHTM7QJnIHbvnALhozmlCCl/JU7MB8GA1UdIwQYMBaAFBHTM7QJnIHbvnALhozm
lCCl/JU7MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAL7DI0D5
ug+Q3sOnvmPwJv+gVcKrNW69NHJklGr8pUEjxY/SexIz4eh7nhS9nw2wSkcv2m50
3HQCeOKCEUyt3u9fK6hTEBqj4ifDAKU1cbuM8X0CmLKdmBgfZYZzPzVOVMmbHWef
jPeGJP2v6S+JRIS57Qakz8+FmD1VXLlUywNde4xz71wCX4xx0nanVmiPJHLK3o5l
z6GvxjgISajwT9nRpKp2a+fNvrl/MdqYdGZD5fDS/6IO2BOB6/KLtYsdUAZN8Csd
NYp2pwfRH71LTizRx6FD8jjzhMXScj2dTiH5lmdmaXSQffj3aYediIlaDqfN11EG
YxkIvwGrv+zPSlA=
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions pkg/acquisition/modules/appsec/testdata/ca.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTdRKk7beAGzL3
866E7W/YuafoqyqsiVByxpEhwP6RLxkiZ79hveWlqjCxMcdcg/J2o3AZP6y+2Uij
qPbp6h6dPplyz18GgTTn4iSIOL88wca4aazGz/Q/lHAUTWru0kuMyWlKNn8SI4Zy
14sz/0EfvMT4VT4e3rioP83c1pFzxXuiSAG90ZcuBe10PQ/T4zJDz+DYYi5E5dsk
O/jjB8A16vi07rBLFvCD08Har87HE0Z7g736zFhJNcL1N3NBdbsVTp260quiBOzb
HWR/RuHabxwZs9iRrPEgsvdBEGtccWhLGhwYmy1C1RdDEDaRpP5+WwXQchyqIWK0
MqMkjEB5AgMBAAECggEADHq7jbPA0WTpimQdf9dSc0ArtmnJpDKbZpas8IjHDVLy
AQuN6isOn0sOwhpduWpJGDLXlgnhvEavCIebgN0SDvYnkLTH1yavdGLuhIKS2LdX
7sC/02Do+YqdXble7vbGwimN1vUl5TBnCiaJ3tj44zJdDonbV2+OBtHzKI4aOfKJ
XmF9IXJ9iVrBt7fPM6AXKj8ksiE4/l05fO4HqBBxj6oUkNqt9yL6m1QKJnB5RCrO
cAufc1zjZDbM+qqn1QHjlaH5YY5VVr/lLsqb/dtGlOlXroWGzWOtglNl3yQbP5pd
tW483LhRU6JuwIIuRBHcAcJo9pXInrJWhMZeASNW7QKBgQD/sspmhqdyYfPT6Tl2
bj3To9Mpc6O38k/d16oZ9dGUho8ADxuE6yVLMfTj7w67kPkZUa8VqI4vSaqjclpZ
kfQCN8T3OlDaIw7dxc/T/+iJZWCatUE5biYH7To2/R/dlGbDNPxoCb3faEOqtJX1
06WCcVtuuahpokWDY2QTKl+o7QKBgQDTtOxjaAll65LQlq2PRklVAVzojaubXaQ2
kyzP4Qr5dRqpRpSEfjZVS1dheJ5luOsxOizajyWSJe+/67ruQVaPraFHWq7M37JN
lfQsxT7pAA6l7/WBFApNMeOqTgjrIFAOSikAjtxf1/g0nV/Xldzt3s3vJyc1vMmb
TLX0d6UAPQKBgQDsyJN3yAAuAxk5RsNosYPdhdrYiva0M9bT14/0xFeVE9o+sr7I
Ir0BWjyuCsV42jJiyOq6wsEQd0ZmX1HdmjP9Dcn/K2iN6GMu6aygZF3hRhjkiy7g
UOk08/Cx1Ut59KAI6vtGGKdUyAfJf7tSzvB40CJccd7MG3IFzrdwrA+zrQKBgBwi
4X/cpdMb2elK/UobZ0ea5MQrf/EoItj1+nCuUg0RWTfCH570PzhayQQ1SafvSaLD
Po7Vk4K29AQnyjI8EMsLonGfRbgIyuOw2EDS/idtUIURPSmohi34QW+y61CEfN9J
T+W5FuTOCTjE6aX3TuN2RxxPsdBchNbU/SG+fPlpAoGBAOFe1M79mJFB3aHSAoos
FjF34rgKBOvUCMzeGg2xw8ZBJHVaFOiBGGhoDNlAhPXmTrzXXZEHvYLvWX8x4Tzm
qtlWWQnAuJYB5tI5NlrlqNDPdPPuJMeYYnMgNReDazfJWAd4gtzNwIcOEDobVpml
zr3pzKzMMF8PsiIOm9/KbWb5
-----END PRIVATE KEY-----
1 change: 1 addition & 0 deletions pkg/acquisition/modules/appsec/testdata/ca.srl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1DA74B42EA2A55D319E112DDE173ED48922DE76A
18 changes: 18 additions & 0 deletions pkg/acquisition/modules/appsec/testdata/client.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC+jCCAeKgAwIBAgIUHadLQuoqVdMZ4RLd4XPtSJIt52owDQYJKoZIhvcNAQEL
BQAwEjEQMA4GA1UEAwwHdGVzdC1jYTAgFw0yNjA3MjExODIxNDJaGA8yNTI2MDMy
MjE4MjE0MlowFjEUMBIGA1UEAwwLdGVzdC1jbGllbnQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCTvTqc02Zh/UZVJnvH3Foc3HsRkzY03LN9B0fsuMGB
YrQR5p7gDAEerKaSZKbsCr8L/XofXrC9bMauTvlGnj0mxvQW8gG3iAQIHjo29L9e
S70xC7KQg5Ws+DAEzXOFPd4n1uIAQ0ACpkOXgbfx2KE0PTMgZScy8SbV9O1m4ej8
7qwctgS5MrauDuZT8nS5TJr2bmPWAHknjTzkbWSdf6LfdnI3lGY5kLEIGw00SZI4
AgkKDdJKplS2y5xTTH2qpZ9ycUUX1AakaavD4jSkkIq6iHLN4RaLiq5JByr9fvMe
2cf+jvKeytB2M+GbUaaInVHZ3kHWW73xDvphUQpqgbq3AgMBAAGjQjBAMB0GA1Ud
DgQWBBSZRZENS7KAjO9uTA37HlfsaKwc0TAfBgNVHSMEGDAWgBQR0zO0CZyB275w
C4aM5pQgpfyVOzANBgkqhkiG9w0BAQsFAAOCAQEAbSM8P3AQOPQyM5segsLH+LZJ
L1loV7tqmwIIPzX4DLkdtvs3titzmae6lc+nePO5iNkdXNG3lXi1YLq4vY+EgInJ
MabNaPlLCuGm5N36VbQKn+p/Iwb7t85KCOrBXxMJjGqW9yzhSrRRxCJWaQ8KBtWf
m/52xRsmTjX/9WOIG76StgaZyk/alOg5zHg2QlEwpt/8uGtwHgTyDUWtL+rUQJWV
h/eBBKgtD1dxP5BTY2G4hkt74WC9/xXBZ2wngKm+8uvaZfW5pr4tc+FodhSDYlmk
h8qiaw/RXh71EHri1G/rVb0/PlfeOW1CFru/X2Kh4UyGbfXBxBg8Fqq30ntsmQ==
-----END CERTIFICATE-----
Loading
Loading