feat: allow bypassing of basic auth from a list of authorized cidrs (#50)
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good

This commit is contained in:
2025-08-05 16:24:41 +02:00
parent 9d10a69b0d
commit a50f926463
7 changed files with 208 additions and 56 deletions

View File

@ -1,16 +1,13 @@
package network
import (
"context"
"net"
"net/http"
"strings"
"forge.cadoles.com/cadoles/bouncer/internal/cidr"
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/authn"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/wpetit/goweb/logger"
)
type Authenticator struct {
@ -18,14 +15,12 @@ type Authenticator struct {
// Authenticate implements authn.Authenticator.
func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, layer *store.Layer) (*authn.User, error) {
ctx := r.Context()
options, err := fromStoreOptions(layer.Options)
if err != nil {
return nil, errors.WithStack(err)
}
matches, err := a.matchAnyAuthorizedCIDRs(ctx, r.RemoteAddr, options.AuthorizedCIDRs)
matches, err := cidr.MatchAny(r.RemoteAddr, options.AuthorizedCIDRs...)
if err != nil {
return nil, errors.WithStack(err)
}
@ -49,42 +44,6 @@ func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, lay
return user, nil
}
func (a *Authenticator) matchAnyAuthorizedCIDRs(ctx context.Context, remoteHostPort string, CIDRs []string) (bool, error) {
var remoteHost string
if strings.Contains(remoteHostPort, ":") {
var err error
remoteHost, _, err = net.SplitHostPort(remoteHostPort)
if err != nil {
return false, errors.WithStack(err)
}
} else {
remoteHost = remoteHostPort
}
remoteAddr := net.ParseIP(remoteHost)
if remoteAddr == nil {
return false, errors.Errorf("remote host '%s' is not a valid ip address", remoteHost)
}
for _, rawCIDR := range CIDRs {
_, net, err := net.ParseCIDR(rawCIDR)
if err != nil {
return false, errors.WithStack(err)
}
match := net.Contains(remoteAddr)
if !match {
continue
}
return true, nil
}
logger.Debug(ctx, "comparing remote host with authorized cidrs", logger.F("remoteAddr", remoteAddr))
return false, nil
}
var (
_ authn.Authenticator = &Authenticator{}
)

View File

@ -1,77 +0,0 @@
package network
import (
"context"
"fmt"
"testing"
"github.com/pkg/errors"
)
func TestMatchAuthorizedCIDRs(t *testing.T) {
type testCase struct {
RemoteHostPort string
AuthorizedCIDRs []string
ExpectedResult bool
ExpectedError error
}
testCases := []testCase{
{
RemoteHostPort: "192.168.1.15",
AuthorizedCIDRs: []string{
"192.168.1.0/24",
},
ExpectedResult: true,
},
{
RemoteHostPort: "192.168.1.15:43349",
AuthorizedCIDRs: []string{
"192.168.1.0/24",
},
ExpectedResult: true,
},
{
RemoteHostPort: "192.168.1.15:43349",
AuthorizedCIDRs: []string{
"192.168.1.5/32",
},
ExpectedResult: false,
},
{
RemoteHostPort: "192.168.1.15:43349",
AuthorizedCIDRs: []string{
"192.168.1.5/32",
"192.168.1.0/24",
},
ExpectedResult: true,
},
{
RemoteHostPort: "192.168.1.15:43349",
AuthorizedCIDRs: []string{
"192.168.1.5/32",
"192.168.1.6/32",
"192.168.1.7/32",
},
ExpectedResult: false,
},
}
auth := Authenticator{}
ctx := context.Background()
for idx, tc := range testCases {
t.Run(fmt.Sprintf("Case #%d", idx), func(t *testing.T) {
result, err := auth.matchAnyAuthorizedCIDRs(ctx, tc.RemoteHostPort, tc.AuthorizedCIDRs)
if g, e := result, tc.ExpectedResult; e != g {
t.Errorf("result: expected '%v', got '%v'", e, g)
}
if e, g := tc.ExpectedError, err; !errors.Is(err, tc.ExpectedError) {
t.Errorf("err: expected '%v', got '%v'", e, g)
}
})
}
}