Skip to content

Commit

Permalink
Merge pull request #11 from quix-labs/feature/security_addon
Browse files Browse the repository at this point in the history
add security constraints values + range
  • Loading branch information
alancolant authored Nov 23, 2024
2 parents 26a045a + 2a4e384 commit 1ebf01f
Show file tree
Hide file tree
Showing 6 changed files with 416 additions and 21 deletions.
51 changes: 44 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,52 @@ This gives you fine-grained control over image processing in your Caddy server.

### Example with `on_fail` and Security Configuration
```plaintext
localhost {
image_processor {
on_fail bypass # Default value
localhost:80 {
import common
root test-dataset
file_server
image_processor {
# Serve original image if image in unprocessable
on_fail bypass
# Return 500 Internal Server Error if processing fails
# on_fail abort
security {
on_security_fail ignore # Default value
# Use ignore to remove param from processing, all valid param are processed
on_security_fail ignore
# Use abort to return 400 Bad Request when fails
# on_security_fail abort
# Use bypass to serve original image without processing
# on_security_fail bypass
# Explicitely disable rotate capabilities
disallowed_params r
disallowed_params w r ... # These parameters are disallowed in the image processing request. You can also use allowed_params to restrict parameters further.
# Note: 'allowed_params' and 'disallowed_params' cannot be used together. You must choose one or the other.
# As an alternative use this to only accept width and height processing
# allowed_params w h
constraints {
h range 60 480
w {
values 60 130 240 480 637
# Shortcut range 60 637
range {
from 60
to 637
}
}
}
}
}
}
}
```

Expand All @@ -189,6 +225,7 @@ localhost {
* `allowed_params`: Specify which query parameters are allowed. As an alternative to `disallowed_params`.

* **Important**: You cannot use both allowed_params and disallowed_params in the same configuration.
* `constraints`: You san specify constraints for each parameter (see example)


## Planned Features
Expand Down
107 changes: 107 additions & 0 deletions constraint_range.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package CADDY_FILE_SERVER

import (
"fmt"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"slices"
"strconv"
)

func init() {
RegisterConstraintType(func() Constraint {
return new(RangeConstraint)
})
}

type RangeConstraint struct {
From int `json:"from,omitempty"`
To int `json:"to,omitempty"`
}

func (r *RangeConstraint) ID() string {
return "range"
}

func (r *RangeConstraint) Validate(param string) error {
if !slices.Contains([]string{"w", "h", "q", "ah", "aw", "t", "l", "r", "b"}, param) {
return fmt.Errorf("range constraint cannot be applied on param: '%s'", param)
}
if r.From < 0 {
return fmt.Errorf("range constraint must have minimum value less than 0")
}
if r.From >= r.To {
return fmt.Errorf("range constraint must have minimum value less than max")
}
return nil
}

func (r *RangeConstraint) ValidateParam(param string, value string) error {
intValue, err := strconv.Atoi(value)
if err != nil {
return fmt.Errorf("invalid integer value for %s: %s", param, value)
}

if intValue < r.From || intValue > r.To {
return fmt.Errorf("%s must be in range %d to %d", param, r.From, r.To)
}

return nil
}

func (r *RangeConstraint) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
var nested bool

// Try to load nested block if present
for nesting := d.Nesting(); d.NextBlock(nesting); {
nested = true
param := d.Val()

switch param {
case "from":
if !d.NextArg() {
return d.Err("missing value for from")
}
var err error
r.From, err = strconv.Atoi(d.Val())
if err != nil {
return d.Errf("invalid from value for range: %v", err)
}
case "to":
if !d.NextArg() {
return d.Err("missing value for to")
}
var err error
r.To, err = strconv.Atoi(d.Val())
if err != nil {
return d.Errf("invalid to value for range: %v", err)
}
default:
return d.Errf("unexpected parameter '%s' in range constraint", param)
}
}

// If not a nested block, process inline arguments
if !nested {
if !d.NextArg() {
return d.Err("missing from value for range constraint")
}
var err error
r.From, err = strconv.Atoi(d.Val())
if err != nil {
return d.Errf("invalid from value for range: %v", err)
}

if !d.NextArg() {
return d.Err("missing to value for range constraint")
}
r.To, err = strconv.Atoi(d.Val())
if err != nil {
return d.Errf("invalid to value for range: %v", err)
}

if d.NextArg() {
return d.ArgErr()
}
}
return nil
}
59 changes: 59 additions & 0 deletions constraint_values.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package CADDY_FILE_SERVER

import (
"errors"
"fmt"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"slices"
"strconv"
)

func init() {
RegisterConstraintType(func() Constraint {
return new(ValuesConstraint)
})
}

type ValuesConstraint struct {
Values []int `json:"values"`
}

func (r *ValuesConstraint) ID() string {
return "values"
}

func (r *ValuesConstraint) Validate(param string) error {
if !slices.Contains([]string{"w", "h", "q", "ah", "aw", "t", "l", "r", "b"}, param) {
return fmt.Errorf("values constraint cannot be applied on param: '%s'", param)
}
if len(r.Values) == 0 {
return errors.New("you need to provide at least one value for values constraint")
}
return nil
}

func (r *ValuesConstraint) ValidateParam(param string, value string) error {
intValue, err := strconv.Atoi(value)
if err != nil {
return fmt.Errorf("invalid integer value for %s: %s", param, value)
}

if !slices.Contains(r.Values, intValue) {
return fmt.Errorf("parameter %s has an invalid value: %d", param, intValue)
}

return nil
}

func (r *ValuesConstraint) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
values := d.RemainingArgs()
r.Values = make([]int, len(values))
for idx, v := range values {
var err error
if r.Values[idx], err = strconv.Atoi(v); err != nil {
return err
}
}

return nil
}
Loading

0 comments on commit 1ebf01f

Please sign in to comment.