Skip to content

Commit

Permalink
feat: support method filtering (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
danroc authored Nov 12, 2024
1 parent 870c9c5 commit 7e200e7
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 6 deletions.
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
- [Response](#response-1)
- [Environment variables](#environment-variables)
- [Manual testing](#manual-testing)
- [Missing `X-Forwarded-For` and `X-Forwarded-Host` headers](#missing-x-forwarded-for-and-x-forwarded-host-headers)
- [Missing `X-Forwarded-For` and `X-Forwarded-Host` and `X-Forwarded-Method` headers](#missing-x-forwarded-for-and-x-forwarded-host-and-x-forwarded-method-headers)
- [Missing `X-Forwarded-Host` header](#missing-x-forwarded-host-header)
- [Missing `X-Forwarded-For` header](#missing-x-forwarded-for-header)
- [Missing `X-Forwarded-Method` header](#missing-x-forwarded-method-header)
- [Blocked country](#blocked-country)
- [Request authorized](#request-authorized)
- [Roadmap](#roadmap)
Expand All @@ -31,6 +32,7 @@ based on:
- Client's IP address
- Client's ASN (Autonomous System Number)
- Requested domain
- Requested method

## Configuration

Expand All @@ -43,6 +45,7 @@ more of the following criteria:

- `countries`: List of country codes (ISO 3166-1 alpha-2)
- `domains`: List of domain names
- `methods`: List of HTTP methods
- `networks`: List of IP ranges in CIDR notation
- `autonomous_systems`: List of ASNs

Expand Down Expand Up @@ -74,13 +77,17 @@ access_control:
policy: deny

# Allow access to example.com and example.org from clients in
# France (FR) and the United States (US).
# France (FR) and the United States (US) using the GET or POST HTTP
# methods.
- domains:
- example.com
- example.org
countries:
- FR
- US
methods:
- GET
- POST
policy: allow
```
Expand Down Expand Up @@ -167,7 +174,7 @@ Start geoblock with the provided example configuration:
GEOBLOCK_CONFIG=examples/config.yaml GEOBLOCK_PORT=8080 make run
```

### Missing `X-Forwarded-For` and `X-Forwarded-Host` headers
### Missing `X-Forwarded-For` and `X-Forwarded-Host` and `X-Forwarded-Method` headers

```http
GET http://localhost:8080/v1/forward-auth
Expand All @@ -178,13 +185,23 @@ GET http://localhost:8080/v1/forward-auth
```http
GET http://localhost:8080/v1/forward-auth
X-Forwarded-For: 127.0.0.1
X-Forwarded-Method: GET
```

### Missing `X-Forwarded-For` header

```http
GET http://localhost:8080/v1/forward-auth
X-Forwarded-Host: example.com
X-Forwarded-Method: GET
```

### Missing `X-Forwarded-Method` header

```http
GET http://localhost:8080/v1/forward-auth
X-Forwarded-For: 8.8.8.8
X-Forwarded-Host: example.com
```

### Blocked country
Expand All @@ -193,6 +210,7 @@ X-Forwarded-Host: example.com
GET http://localhost:8080/v1/forward-auth
X-Forwarded-For: 8.8.8.8
X-Forwarded-Host: example.com
X-Forwarded-Method: GET
```

### Request authorized
Expand All @@ -201,6 +219,7 @@ X-Forwarded-Host: example.com
GET http://localhost:8080/v1/forward-auth
X-Forwarded-For: 127.0.0.1
X-Forwarded-Host: example.com
X-Forwarded-Method: GET
```

## Roadmap
Expand Down
9 changes: 9 additions & 0 deletions pkg/rules/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func NewEngine(config *schema.AccessControl) *Engine {
// Query represents a query to be checked by the access control engine.
type Query struct {
RequestedDomain string
RequestedMethod string
SourceIP net.IP
SourceCountry string
SourceASN uint32
Expand All @@ -48,6 +49,14 @@ func ruleApplies(rule *schema.AccessControlRule, query *Query) bool {
}
}

if len(rule.Methods) > 0 {
if utils.None(rule.Methods, func(method string) bool {
return strings.EqualFold(method, query.RequestedMethod)
}) {
return false
}
}

if len(rule.Networks) > 0 {
if utils.None(rule.Networks, func(network schema.CIDR) bool {
return network.Contains(query.SourceIP)
Expand Down
48 changes: 48 additions & 0 deletions pkg/rules/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,54 @@ func TestEngine_Authorize(t *testing.T) {
},
want: false,
},
{
name: "allow by method",
config: &schema.AccessControl{
Rules: []schema.AccessControlRule{
{
Methods: []string{"GET", "POST"},
Policy: schema.PolicyAllow,
},
},
DefaultPolicy: schema.PolicyDeny,
},
query: &Query{
RequestedMethod: "POST",
},
want: true,
},
{
name: "deny by method",
config: &schema.AccessControl{
Rules: []schema.AccessControlRule{
{
Methods: []string{"GET", "POST"},
Policy: schema.PolicyDeny,
},
},
DefaultPolicy: schema.PolicyAllow,
},
query: &Query{
RequestedMethod: "POST",
},
want: false,
},
{
name: "deny unknown method",
config: &schema.AccessControl{
Rules: []schema.AccessControlRule{
{
Methods: []string{"GET"},
Policy: schema.PolicyAllow,
},
},
DefaultPolicy: schema.PolicyDeny,
},
query: &Query{
RequestedMethod: "POST",
},
want: false,
},
{
name: "allow by network",
config: &schema.AccessControl{
Expand Down
1 change: 1 addition & 0 deletions pkg/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type AccessControlRule struct {
Policy string `yaml:"policy" validate:"required,oneof=allow deny"`
Networks []CIDR `yaml:"networks,omitempty" validate:"dive,cidr"`
Domains []string `yaml:"domains,omitempty" validate:"dive,fqdn"`
Methods []string `yaml:"methods,omitempty" validate:"dive,oneof=GET HEAD POST PUT DELETE PATCH"`
Countries []string `yaml:"countries,omitempty" validate:"dive,iso3166_1_alpha2"`
AutonomousSystems []uint32 `yaml:"autonomous_systems,omitempty" validate:"dive,numeric"`
}
Expand Down
14 changes: 11 additions & 3 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
// Fields used in the log messages.
const (
FieldRequestedDomain = "requested_domain"
FieldRequestedMethod = "requested_method"
FieldSourceIP = "source_ip"
FieldSourceCountry = "source_country"
FieldSourceASN = "source_asn"
Expand All @@ -39,14 +40,18 @@ func getForwardAuth(
resolver *database.Resolver,
engine *rules.Engine,
) {
origin := request.Header.Get(HeaderXForwardedFor)
domain := request.Header.Get(HeaderXForwardedHost)
var (
origin = request.Header.Get(HeaderXForwardedFor)
domain = request.Header.Get(HeaderXForwardedHost)
method = request.Header.Get(HeaderXForwardedMethod)
)

// Block the request if one or more of the required headers are missing. It
// probably means that the request didn't come from the reverse proxy.
if origin == "" || domain == "" {
if origin == "" || domain == "" || method == "" {
log.WithFields(log.Fields{
FieldRequestedDomain: domain,
FieldRequestedMethod: method,
FieldSourceIP: origin,
}).Error("Missing required headers")
writer.WriteHeader(http.StatusForbidden)
Expand All @@ -59,6 +64,7 @@ func getForwardAuth(
if sourceIP == nil {
log.WithFields(log.Fields{
FieldRequestedDomain: domain,
FieldRequestedMethod: method,
FieldSourceIP: origin,
}).Error("Invalid source IP")
writer.WriteHeader(http.StatusForbidden)
Expand All @@ -69,13 +75,15 @@ func getForwardAuth(

query := &rules.Query{
RequestedDomain: domain,
RequestedMethod: method,
SourceIP: sourceIP,
SourceCountry: resolved.CountryCode,
SourceASN: resolved.ASN,
}

logFields := log.Fields{
FieldRequestedDomain: domain,
FieldRequestedMethod: method,
FieldSourceIP: sourceIP,
FieldSourceCountry: resolved.CountryCode,
FieldSourceASN: resolved.ASN,
Expand Down

0 comments on commit 7e200e7

Please sign in to comment.