Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 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
7 changes: 7 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package anubis

import "errors"

var (
ErrMisconfiguration = errors.New("[unexpected] policy: administrator misconfiguration")
)
5 changes: 4 additions & 1 deletion lib/anubis.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ import (
"github.com/TecharoHQ/anubis/internal/dnsbl"
"github.com/TecharoHQ/anubis/internal/ogtags"
"github.com/TecharoHQ/anubis/lib/challenge"
"github.com/TecharoHQ/anubis/lib/checker"
"github.com/TecharoHQ/anubis/lib/localization"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/policy/checker"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store"

// checker implementations
_ "github.com/TecharoHQ/anubis/lib/checker/all"

// challenge implementations
_ "github.com/TecharoHQ/anubis/lib/challenge/metarefresh"
_ "github.com/TecharoHQ/anubis/lib/challenge/proofofwork"
Expand Down
8 changes: 8 additions & 0 deletions lib/checker/all/all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Package all imports all of the standard checker types.
package all

import (
_ "github.com/TecharoHQ/anubis/lib/checker/headerexists"

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

headerexists is not a recognized word. (unrecognized-spelling)
_ "github.com/TecharoHQ/anubis/lib/checker/headermatches"

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

headermatches is not a recognized word. (unrecognized-spelling)
_ "github.com/TecharoHQ/anubis/lib/checker/remoteaddress"

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

remoteaddress is not a recognized word. (unrecognized-spelling)
)
12 changes: 9 additions & 3 deletions lib/policy/checker/checker.go → lib/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@
package checker

import (
"errors"
"fmt"
"net/http"
"strings"

"github.com/TecharoHQ/anubis/internal"
)

type Impl interface {
Check(*http.Request) (bool, error)
var (
ErrUnparseableConfig = errors.New("checker: config is unparseable")
ErrInvalidConfig = errors.New("checker: config is invalid")
)

type Interface interface {
Check(*http.Request) (matches bool, err error)
Hash() string
}

type List []Impl
type List []Interface

func (l List) Check(r *http.Request) (bool, error) {
for _, c := range l {
Expand Down
32 changes: 32 additions & 0 deletions lib/checker/headerexists/checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package headerexists

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

headerexists is not a recognized word. (unrecognized-spelling)

Check failure

Code scanning / check-spelling

Check File Path Error

headerexists is not a recognized word. (check-file-path)

import (
"net/http"
"strings"

"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/lib/checker"
)

func New(key string) checker.Interface {
return headerExistsChecker{
header: strings.TrimSpace(http.CanonicalHeaderKey(key)),
hash: internal.FastHash(key),
}
}

type headerExistsChecker struct {
header, hash string
}

func (hec headerExistsChecker) Check(r *http.Request) (bool, error) {
if r.Header.Get(hec.header) != "" {
return true, nil
}

return false, nil
}

func (hec headerExistsChecker) Hash() string {
return hec.hash
}
57 changes: 57 additions & 0 deletions lib/checker/headerexists/checker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package headerexists

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error test

headerexists is not a recognized word. (unrecognized-spelling)

Check failure

Code scanning / check-spelling

Check File Path Error test

headerexists is not a recognized word. (check-file-path)

import (
"encoding/json"
"fmt"
"net/http"
"testing"
)

func TestChecker(t *testing.T) {
fac := Factory{}

for _, tt := range []struct {
name string
header string
reqHeader string
ok bool
}{
{
name: "match",
header: "Authorization",
reqHeader: "Authorization",
ok: true,
},
{
name: "not_match",
header: "Authorization",
reqHeader: "Authentication",
},
} {
t.Run(tt.name, func(t *testing.T) {
hec, err := fac.Build(t.Context(), json.RawMessage(fmt.Sprintf("%q", tt.header)))
if err != nil {
t.Fatal(err)
}

t.Log(hec.Hash())

r, err := http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
t.Fatalf("can't make request: %v", err)
}

r.Header.Set(tt.reqHeader, "hunter2")

ok, err := hec.Check(r)

if tt.ok != ok {
t.Errorf("ok: %v, wanted: %v", ok, tt.ok)
}

if err != nil {
t.Errorf("err: %v", err)
}
})
}
}
40 changes: 40 additions & 0 deletions lib/checker/headerexists/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package headerexists

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

headerexists is not a recognized word. (unrecognized-spelling)

Check failure

Code scanning / check-spelling

Check File Path Error

headerexists is not a recognized word. (check-file-path)

import (
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/TecharoHQ/anubis/lib/checker"
)

type Factory struct{}

func (f Factory) Build(ctx context.Context, data json.RawMessage) (checker.Interface, error) {
var headerName string

if err := json.Unmarshal([]byte(data), &headerName); err != nil {
return nil, fmt.Errorf("%w: want string", checker.ErrUnparseableConfig)
}

if err := f.Valid(ctx, data); err != nil {
return nil, err
}

return New(http.CanonicalHeaderKey(headerName)), nil
}

func (Factory) Valid(ctx context.Context, data json.RawMessage) error {
var headerName string

if err := json.Unmarshal([]byte(data), &headerName); err != nil {
return fmt.Errorf("%w: want string", checker.ErrUnparseableConfig)
}

if headerName == "" {
return fmt.Errorf("%w: string must not be empty", checker.ErrInvalidConfig)
}

return nil
}
60 changes: 60 additions & 0 deletions lib/checker/headerexists/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package headerexists

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error test

headerexists is not a recognized word. (unrecognized-spelling)

Check failure

Code scanning / check-spelling

Check File Path Error test

headerexists is not a recognized word. (check-file-path)

import (
"encoding/json"
"os"
"path/filepath"
"testing"
)

func TestFactoryGood(t *testing.T) {
files, err := os.ReadDir("./testdata/good")
if err != nil {
t.Fatal(err)
}

fac := Factory{}

for _, fname := range files {
t.Run(fname.Name(), func(t *testing.T) {
data, err := os.ReadFile(filepath.Join("testdata", "good", fname.Name()))
if err != nil {
t.Fatal(err)
}

if err := fac.Valid(t.Context(), json.RawMessage(data)); err != nil {
t.Fatal(err)
}
})
}
}

func TestFactoryBad(t *testing.T) {
files, err := os.ReadDir("./testdata/bad")
if err != nil {
t.Fatal(err)
}

fac := Factory{}

for _, fname := range files {
t.Run(fname.Name(), func(t *testing.T) {
data, err := os.ReadFile(filepath.Join("testdata", "bad", fname.Name()))
if err != nil {
t.Fatal(err)
}

t.Run("Build", func(t *testing.T) {
if _, err := fac.Build(t.Context(), json.RawMessage(data)); err == nil {
t.Fatal(err)
}
})

t.Run("Valid", func(t *testing.T) {
if err := fac.Valid(t.Context(), json.RawMessage(data)); err == nil {
t.Fatal(err)
}
})
})
}
}
1 change: 1 addition & 0 deletions lib/checker/headerexists/testdata/bad/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
""

Check failure

Code scanning / check-spelling

Check File Path Error test

headerexists is not a recognized word. (check-file-path)
1 change: 1 addition & 0 deletions lib/checker/headerexists/testdata/bad/object.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}

Check failure

Code scanning / check-spelling

Check File Path Error test

headerexists is not a recognized word. (check-file-path)
1 change: 1 addition & 0 deletions lib/checker/headerexists/testdata/good/authorization.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"Authorization"

Check failure

Code scanning / check-spelling

Check File Path Error test

headerexists is not a recognized word. (check-file-path)
24 changes: 24 additions & 0 deletions lib/checker/headermatches/checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package headermatches

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

headermatches is not a recognized word. (unrecognized-spelling)

Check failure

Code scanning / check-spelling

Check File Path Error

headermatches is not a recognized word. (check-file-path)

import (
"net/http"
"regexp"
)

type Checker struct {
header string
regexp *regexp.Regexp
hash string
}

func (c *Checker) Check(r *http.Request) (bool, error) {
if c.regexp.MatchString(r.Header.Get(c.header)) {
return true, nil
}

return false, nil
}

func (c *Checker) Hash() string {
return c.hash
}
98 changes: 98 additions & 0 deletions lib/checker/headermatches/checker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package headermatches

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error test

headermatches is not a recognized word. (unrecognized-spelling)

Check failure

Code scanning / check-spelling

Check File Path Error test

headermatches is not a recognized word. (check-file-path)

import (
"encoding/json"
"errors"
"net/http"
"testing"
)

func TestChecker(t *testing.T) {

}

func TestHeaderMatchesChecker(t *testing.T) {
fac := Factory{}

for _, tt := range []struct {
err error
name string
header string
rexStr string
reqHeaderKey string
reqHeaderValue string
ok bool
}{
{
name: "match",
header: "Cf-Worker",
rexStr: ".*",
reqHeaderKey: "Cf-Worker",
reqHeaderValue: "true",
ok: true,
err: nil,
},
{
name: "not_match",
header: "Cf-Worker",
rexStr: "false",
reqHeaderKey: "Cf-Worker",
reqHeaderValue: "true",
ok: false,
err: nil,
},
{
name: "not_present",
header: "Cf-Worker",
rexStr: "foobar",
reqHeaderKey: "Something-Else",
reqHeaderValue: "true",
ok: false,
err: nil,
},
{
name: "invalid_regex",
rexStr: "a(b",
err: ErrInvalidRegex,
},
} {
t.Run(tt.name, func(t *testing.T) {
fc := fileConfig{
Header: tt.header,
ValueRegex: tt.rexStr,
}
data, err := json.Marshal(fc)
if err != nil {
t.Fatal(err)
}

hmc, err := fac.Build(t.Context(), json.RawMessage(data))
if err != nil && !errors.Is(err, tt.err) {
t.Fatalf("creating HeaderMatchesChecker failed")
}

if tt.err != nil && hmc == nil {
return
}

t.Log(hmc.Hash())

r, err := http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
t.Fatalf("can't make request: %v", err)
}

r.Header.Set(tt.reqHeaderKey, tt.reqHeaderValue)

ok, err := hmc.Check(r)

if tt.ok != ok {
t.Errorf("ok: %v, wanted: %v", ok, tt.ok)
}

if err != nil && tt.err != nil && !errors.Is(err, tt.err) {
t.Errorf("err: %v, wanted: %v", err, tt.err)
}
})
}
}
Loading
Loading