Skip to content

Commit

Permalink
Merge pull request #47 from hslatman/herman/improve-module-documentation
Browse files Browse the repository at this point in the history
Add logging when potentially missing modules are detected
  • Loading branch information
hslatman authored Sep 27, 2024
2 parents 6022901 + c35364b commit 3aec1dc
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 15 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
test:
strategy:
matrix:
go-version: [1.20.x]
go-version: [1.21.x]
full-tests: [false]
include:
- go-version: 1.20.x
- go-version: 1.21.x
full-tests: true

runs-on: ubuntu-latest
Expand All @@ -22,7 +22,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.20.7' # TODO: fix matrix
go-version: '1.21.13' # TODO: fix matrix
cache: true

- name: Checkout code
Expand All @@ -32,7 +32,7 @@ jobs:
if: matrix.full-tests
run: |
curl -sL https://github.com/golangci/golangci-lint/master/install.sh |
sh -s -- -b $HOME/go/bin v1.53.3
sh -s -- -b $HOME/go/bin v1.60.3
$HOME/go/bin/golangci-lint run --timeout=30m \
--max-issues-per-linter 0 \
--max-same-issues 0 \
Expand Down
23 changes: 13 additions & 10 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
{
debug
crowdsec {
api_url http://localhost:8080
api_key <api_key>
}
log {
level DEBUG
}
crowdsec {
api_url http://localhost:7080
api_key {env.CROWDSEC_API_KEY}
ticker_interval 3s
}
}

localhost {
route {
crowdsec
respond "Allowed by CrowdSec!"
}
}
route {
crowdsec
respond "Allowed by CrowdSec!"
}
}
104 changes: 104 additions & 0 deletions crowdsec/crowdsec.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import (
"errors"
"fmt"
"net"
"reflect"
"runtime/debug"
"slices"
"strings"

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
Expand Down Expand Up @@ -113,10 +117,110 @@ func (c *CrowdSec) Validate() error {
if c.bouncer == nil {
return errors.New("bouncer instance not available due to (potential) misconfiguration")
}
if err := c.checkModules(); err != nil {
return fmt.Errorf("failed checking CrowdSec modules: %w", err)
}

return nil
}

const (
handlerName = "http.handlers.crowdsec"
matcherName = "layer4.matchers.crowdsec"
)

var crowdSecModules = []string{handlerName, matcherName}

func (c *CrowdSec) checkModules() error {
modules, err := matchModules(crowdSecModules...)
if err != nil {
return fmt.Errorf("failed retrieving CrowdSec modules: %w", err)
}

layer4, err := matchModules("layer4")
if err != nil {
return fmt.Errorf("failed retrieving layer4 module: %w", err)
}

hasLayer4 := len(layer4) > 0
switch {
case hasLayer4 && len(modules) == 0:
c.logger.Warn(fmt.Sprintf("%s and %s modules are not available", handlerName, matcherName))
case hasLayer4 && hasModule(modules, matcherName) && !hasModule(modules, handlerName):
c.logger.Warn(fmt.Sprintf("%s module is not available", handlerName))
case hasLayer4 && hasModule(modules, handlerName) && !hasModule(modules, matcherName):
c.logger.Warn(fmt.Sprintf("%s module is not available", matcherName))
case len(modules) == 0:
c.logger.Warn(fmt.Sprintf("%s module is not available", handlerName))
}

return nil
}

type moduleInfo struct {
caddyModuleID string
standard bool
goModule *debug.Module
err error
}

func hasModule(modules []moduleInfo, moduleIdentifier string) bool {
for _, m := range modules {
if m.caddyModuleID == moduleIdentifier {
return true
}
}
return false
}

func matchModules(moduleIdentifiers ...string) (modules []moduleInfo, err error) {
bi, ok := debug.ReadBuildInfo()
if !ok {
err = fmt.Errorf("no build info")
return
}

for _, modID := range caddy.Modules() {
if !slices.Contains(moduleIdentifiers, modID) {
continue
}

modInfo, err := caddy.GetModule(modID)
if err != nil {
modules = append(modules, moduleInfo{caddyModuleID: modID, err: err})
continue
}

// to get the Caddy plugin's version info, we need to know
// the package that the Caddy module's value comes from; we
// can use reflection but we need a non-pointer value (I'm
// not sure why), and since New() should return a pointer
// value, we need to dereference it first
iface := any(modInfo.New())
if rv := reflect.ValueOf(iface); rv.Kind() == reflect.Ptr {
iface = reflect.New(reflect.TypeOf(iface).Elem()).Elem().Interface()
}
modPkgPath := reflect.TypeOf(iface).PkgPath()

// now we find the Go module that the Caddy module's package
// belongs to; we assume the Caddy module package path will
// be prefixed by its Go module path, and we will choose the
// longest matching prefix in case there are nested modules
var matched *debug.Module
for _, dep := range bi.Deps {
if strings.HasPrefix(modPkgPath, dep.Path) {
if matched == nil || len(dep.Path) > len(matched.Path) {
matched = dep
}
}
}

standard := strings.HasPrefix(modPkgPath, caddy.ImportPath)
modules = append(modules, moduleInfo{caddyModuleID: modID, standard: standard, goModule: matched})
}
return
}

func (c *CrowdSec) Cleanup() error {
if err := c.bouncer.Shutdown(); err != nil {
return fmt.Errorf("failed cleaning up: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/hslatman/caddy-crowdsec-bouncer

go 1.20
go 1.21

require (
github.com/caddyserver/caddy/v2 v2.7.5
Expand Down
Loading

0 comments on commit 3aec1dc

Please sign in to comment.