Skip to content

Commit

Permalink
add "dry-run" backend mode, added connection tests with api key and tls
Browse files Browse the repository at this point in the history
  • Loading branch information
mmetc committed Jun 9, 2023
1 parent c43e65a commit 9195ad4
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 11 deletions.
3 changes: 2 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func Execute() error {
log.SetLevel(log.DebugLevel)
}

log.Infof("crowdsec-firewall-bouncer %s", version.String())
log.Infof("Starting crowdsec-firewall-bouncer %s", version.String())

backend, err := backend.NewBackend(config)
if err != nil {
Expand All @@ -181,6 +181,7 @@ func Execute() error {
if err != nil {
return fmt.Errorf("unable to configure bouncer: %w", err)
}

bouncer.UserAgent = fmt.Sprintf("%s/%s", name, version.String())
if err := bouncer.Init(); err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ go 1.20

require (
github.com/crowdsecurity/crowdsec v1.5.2
github.com/crowdsecurity/go-cs-bouncer v0.0.5
github.com/crowdsecurity/go-cs-bouncer v0.0.6
github.com/crowdsecurity/go-cs-lib v0.0.2
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
github.com/prometheus/client_golang v1.15.1
github.com/sirupsen/logrus v1.9.2
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc
golang.org/x/sync v0.2.0
golang.org/x/sys v0.8.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/crowdsecurity/crowdsec v1.5.2 h1:2wl5ULsZlD8Du9PGe415x1fYRcOfVx95KI2Si0Qeb98=
github.com/crowdsecurity/crowdsec v1.5.2/go.mod h1:R1wnz8wqV4r1teYt9Yc5PVTaBb37ug2yqCffIvXEuRw=
github.com/crowdsecurity/go-cs-bouncer v0.0.5 h1:vZ989qKUDTavycjGLjqm2M6UzXJpmLaq35UoaiF9474=
github.com/crowdsecurity/go-cs-bouncer v0.0.5/go.mod h1:ShrcSSYmzBTKnpqON9/UFvorDMhhn5mbeQC2HXCv7kE=
github.com/crowdsecurity/go-cs-bouncer v0.0.6 h1:vzHMPBVcAKH77P/93Q69pTQgYbwBJEMnrg6BxtOaM6c=
github.com/crowdsecurity/go-cs-bouncer v0.0.6/go.mod h1:ShrcSSYmzBTKnpqON9/UFvorDMhhn5mbeQC2HXCv7kE=
github.com/crowdsecurity/go-cs-lib v0.0.2 h1:+Tjmf/IclOXNzU9sxKVQvUl9CkMfbM60xQ0zA05NWps=
github.com/crowdsecurity/go-cs-lib v0.0.2/go.mod h1:iznTJ19qLTYdZBcRb5RVDlcUdSlayBCivBkWsXlOY3g=
github.com/crowdsecurity/grokky v0.2.1 h1:t4VYnDlAd0RjDM2SlILalbwfCrQxtJSMGdQOR0zwkE4=
Expand Down Expand Up @@ -222,8 +222,8 @@ golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaE
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
Expand Down
6 changes: 6 additions & 0 deletions pkg/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/crowdsecurity/crowdsec/pkg/models"

"github.com/crowdsecurity/cs-firewall-bouncer/pkg/cfg"
"github.com/crowdsecurity/cs-firewall-bouncer/pkg/dryrun"

Check failure on line 12 in pkg/backend/backend.go

View workflow job for this annotation

GitHub Actions / Test .deb packages

no required module provides package github.com/crowdsecurity/cs-firewall-bouncer/pkg/dryrun; to add it:

Check failure on line 12 in pkg/backend/backend.go

View workflow job for this annotation

GitHub Actions / Test .deb packages

no required module provides package github.com/crowdsecurity/cs-firewall-bouncer/pkg/dryrun; to add it:

Check failure on line 12 in pkg/backend/backend.go

View workflow job for this annotation

GitHub Actions / Build + tests

no required module provides package github.com/crowdsecurity/cs-firewall-bouncer/pkg/dryrun; to add it:
"github.com/crowdsecurity/cs-firewall-bouncer/pkg/iptables"
"github.com/crowdsecurity/cs-firewall-bouncer/pkg/nftables"
"github.com/crowdsecurity/cs-firewall-bouncer/pkg/pf"
Expand Down Expand Up @@ -89,6 +90,11 @@ func NewBackend(config *cfg.BouncerConfig) (*BackendCTX, error) {
if err != nil {
return nil, err
}
case "dry-run":
b.firewall, err = dryrun.NewDryRun(config)
if err != nil {
return nil, err
}
default:
return b, fmt.Errorf("firewall '%s' is not supported", config.Mode)
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/cfg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
IptablesMode = "iptables"
NftablesMode = "nftables"
PfMode = "pf"
DryRunMode = "dry-run"
)

type BouncerConfig struct {
Expand Down Expand Up @@ -139,6 +140,8 @@ func NewConfig(reader io.Reader) (*BouncerConfig, error) {
if err != nil {
return nil, err
}
case DryRunMode:
// nothing specific to do
default:
log.Warningf("unexpected %s mode", config.Mode)
}
Expand Down
90 changes: 86 additions & 4 deletions test/bouncer/test_firewall_bouncer.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@

def test_partial_config(crowdsec, bouncer, fw_cfg_factory):
import json

def test_backend_mode(bouncer, fw_cfg_factory):
cfg = fw_cfg_factory()

del cfg['mode']

with bouncer(cfg) as fw:
fw.wait_for_lines_fnmatch([
# XXX: improve this message
"*unable to load configuration: config does not contain 'mode'*",
])
fw.proc.wait(timeout=0.2)
Expand All @@ -19,5 +22,84 @@ def test_partial_config(crowdsec, bouncer, fw_cfg_factory):
fw.proc.wait(timeout=0.2)
assert not fw.proc.is_running()

# cfg['mode'] = 'pf'
cfg['api_key'] = ''
cfg['mode'] = 'dry-run'

with bouncer(cfg) as fw:
fw.wait_for_lines_fnmatch([
"*Starting crowdsec-firewall-bouncer*",
"*backend type : dry-run*",
"*backend.Init() called*",
"*unable to configure bouncer: config does not contain LAPI url*",
])
fw.proc.wait(timeout=0.2)
assert not fw.proc.is_running()


def test_api_url(crowdsec, bouncer, fw_cfg_factory):
cfg = fw_cfg_factory()

with bouncer(cfg) as fw:
fw.wait_for_lines_fnmatch([
"*unable to configure bouncer: config does not contain LAPI url*",
])
fw.proc.wait()
assert not fw.proc.is_running()

cfg['api_url'] = ''

with bouncer(cfg) as fw:
fw.wait_for_lines_fnmatch([
"*unable to configure bouncer: config does not contain LAPI url*",
])
fw.proc.wait()
assert not fw.proc.is_running()


def test_api_key(crowdsec, bouncer, fw_cfg_factory, api_key_factory, bouncer_under_test):
api_key = api_key_factory()
env = {
'BOUNCER_KEY_firewall': api_key
}

with crowdsec(environment=env) as lapi:
lapi.wait_for_http(8080, '/health')
port = lapi.probe.get_bound_port('8080')

cfg = fw_cfg_factory()
cfg['api_url'] = f'http://localhost:{port}'

with bouncer(cfg) as fw:
fw.wait_for_lines_fnmatch([
"*unable to configure bouncer: config does not contain LAPI key or certificate*",
])
fw.proc.wait()
assert not fw.proc.is_running()

cfg['api_key'] = 'badkey'

with bouncer(cfg) as fw:
fw.wait_for_lines_fnmatch([
"*Using API key auth*",
"*API error: access forbidden*",
"*process terminated with error: bouncer stream halted*",
])
fw.proc.wait()
assert not fw.proc.is_running()

cfg['api_key'] = api_key

with bouncer(cfg) as fw:
fw.wait_for_lines_fnmatch([
"*Using API key auth*",
"*Processing new and deleted decisions*",
])
assert fw.proc.is_running()

# check that the bouncer is registered
res = lapi.cont.exec_run('cscli bouncers list -o json')
assert res.exit_code == 0
bouncers = json.loads(res.output)
assert len(bouncers) == 1
assert bouncers[0]['name'] == 'firewall'
assert bouncers[0]['auth_type'] == 'api-key'
assert bouncers[0]['type'] == bouncer_under_test
111 changes: 111 additions & 0 deletions test/bouncer/test_tls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import json
import pytest

def test_tls_server(crowdsec, certs_dir, api_key_factory, bouncer, fw_cfg_factory):
"""TLS with server-only certificate"""

api_key = api_key_factory()

lapi_env = {
'CACERT_FILE': '/etc/ssl/crowdsec/ca.crt',
'LAPI_CERT_FILE': '/etc/ssl/crowdsec/lapi.crt',
'LAPI_KEY_FILE': '/etc/ssl/crowdsec/lapi.key',
'USE_TLS': 'true',
'LOCAL_API_URL': 'https://localhost:8080',
'BOUNCER_KEY_custom': api_key,
}

certs = certs_dir(lapi_hostname='lapi')

volumes = {
certs: {'bind': '/etc/ssl/crowdsec', 'mode': 'ro'},
}

with crowdsec(environment=lapi_env, volumes=volumes) as cs:
cs.wait_for_log("*CrowdSec Local API listening*")
# TODO: wait_for_https
cs.wait_for_http(8080, '/health', want_status=None)

port = cs.probe.get_bound_port('8080')
cfg = fw_cfg_factory()
cfg['api_url'] = f'https://localhost:{port}'
cfg['api_key'] = api_key

with bouncer(cfg) as cb:
cb.wait_for_lines_fnmatch([
"*backend type : dry-run*",
"*Using API key auth*",
"*auth-api: auth with api key failed*",
"*tls: failed to verify certificate: x509: certificate signed by unknown authority*",
])

cfg['ca_cert_path'] = (certs / 'ca.crt').as_posix()

with bouncer(cfg) as cb:
cb.wait_for_lines_fnmatch([
"*backend type : dry-run*",
"*Using CA cert *ca.crt*",
"*Using API key auth*",
"*Processing new and deleted decisions*",
])


def test_tls_mutual(crowdsec, certs_dir, api_key_factory, bouncer, fw_cfg_factory, bouncer_under_test):
"""TLS with two-way bouncer/lapi authentication"""

lapi_env = {
'CACERT_FILE': '/etc/ssl/crowdsec/ca.crt',
'LAPI_CERT_FILE': '/etc/ssl/crowdsec/lapi.crt',
'LAPI_KEY_FILE': '/etc/ssl/crowdsec/lapi.key',
'USE_TLS': 'true',
'LOCAL_API_URL': 'https://localhost:8080',
}

certs = certs_dir(lapi_hostname='lapi')

volumes = {
certs: {'bind': '/etc/ssl/crowdsec', 'mode': 'ro'},
}

with crowdsec(environment=lapi_env, volumes=volumes) as cs:
cs.wait_for_log("*CrowdSec Local API listening*")
# TODO: wait_for_https
cs.wait_for_http(8080, '/health', want_status=None)

port = cs.probe.get_bound_port('8080')
cfg = fw_cfg_factory()
cfg['api_url'] = f'https://localhost:{port}'
cfg['ca_cert_path'] = (certs / 'ca.crt').as_posix()

cfg['cert_path'] = (certs / 'agent.crt').as_posix()
cfg['key_path'] = (certs / 'agent.key').as_posix()

with bouncer(cfg) as cb:
cb.wait_for_lines_fnmatch([
"*Starting crowdsec-firewall-bouncer*",
"*Using CA cert*",
"*Using cert auth with cert * and key *",
"*API error: access forbidden*",
])

cs.wait_for_log("*client certificate OU (?agent-ou?) doesn't match expected OU (?bouncer-ou?)*")

cfg['cert_path'] = (certs / 'bouncer.crt').as_posix()
cfg['key_path'] = (certs / 'bouncer.key').as_posix()

with bouncer(cfg) as cb:
cb.wait_for_lines_fnmatch([
"*backend type : dry-run*",
"*Using CA cert*",
"*Using cert auth with cert * and key *",
"*Processing new and deleted decisions . . .*",
])

# check that the bouncer is registered
res = cs.cont.exec_run('cscli bouncers list -o json')
assert res.exit_code == 0
bouncers = json.loads(res.output)
assert len(bouncers) == 1
assert bouncers[0]['name'].startswith('@')
assert bouncers[0]['auth_type'] == 'tls'
assert bouncers[0]['type'] == bouncer_under_test
2 changes: 2 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ def closure(config_lapi=None, config_bouncer=None, api_key=None):


_default_config = {
'mode': 'dry-run',
'log_level': 'info',
'update_frequency': "10s",
}


Expand Down

0 comments on commit 9195ad4

Please sign in to comment.