Allow conditions on challenges, and early hint deadline
This commit is contained in:
@@ -116,12 +116,16 @@ challenges:
|
|||||||
# Challenges with a redirect via Link header with rel=preload and early hints (non-JS, requires HTTP parsing, fetching and logic)
|
# Challenges with a redirect via Link header with rel=preload and early hints (non-JS, requires HTTP parsing, fetching and logic)
|
||||||
# Works on HTTP/2 and above!
|
# Works on HTTP/2 and above!
|
||||||
self-preload-link:
|
self-preload-link:
|
||||||
|
# doesn't seem to work reliably on other stuff that firefox
|
||||||
|
# userAgent.contains("Firefox/") &&
|
||||||
|
condition: '"Sec-Fetch-Mode" in headers && headers["Sec-Fetch-Mode"] == "navigate"'
|
||||||
mode: "preload-link"
|
mode: "preload-link"
|
||||||
runtime:
|
runtime:
|
||||||
# verifies that result = key
|
# verifies that result = key
|
||||||
mode: "key"
|
mode: "key"
|
||||||
probability: 0.1
|
probability: 0.1
|
||||||
parameters:
|
parameters:
|
||||||
|
preload-early-hint-deadline: 3s
|
||||||
key-code: 200
|
key-code: 200
|
||||||
key-mime: text/css
|
key-mime: text/css
|
||||||
key-content: ""
|
key-content: ""
|
||||||
|
|||||||
@@ -20,6 +20,18 @@ type ChallengeInformation struct {
|
|||||||
IssuedAt *jwt.NumericDate `json:"iat,omitempty"`
|
IssuedAt *jwt.NumericDate `json:"iat,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRequestScheme(r *http.Request) string {
|
||||||
|
if proto := r.Header.Get("X-Forwarded-Proto"); proto == "http" || proto == "https" {
|
||||||
|
return proto
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.TLS != nil {
|
||||||
|
return "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
|
||||||
func getRequestAddress(r *http.Request, clientHeader string) net.IP {
|
func getRequestAddress(r *http.Request, clientHeader string) net.IP {
|
||||||
var ipStr string
|
var ipStr string
|
||||||
if clientHeader != "" {
|
if clientHeader != "" {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"git.gammaspectra.live/git/go-away/utils"
|
"git.gammaspectra.live/git/go-away/utils"
|
||||||
"github.com/go-jose/go-jose/v4"
|
"github.com/go-jose/go-jose/v4"
|
||||||
"github.com/go-jose/go-jose/v4/jwt"
|
"github.com/go-jose/go-jose/v4/jwt"
|
||||||
|
"github.com/google/cel-go/cel"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -26,9 +27,10 @@ const (
|
|||||||
type Id int
|
type Id int
|
||||||
|
|
||||||
type Challenge struct {
|
type Challenge struct {
|
||||||
Id Id
|
Id Id
|
||||||
Name string
|
Program cel.Program
|
||||||
Path string
|
Name string
|
||||||
|
Path string
|
||||||
|
|
||||||
Verify func(key []byte, result string, r *http.Request) (bool, error)
|
Verify func(key []byte, result string, r *http.Request) (bool, error)
|
||||||
VerifyProbability float64
|
VerifyProbability float64
|
||||||
@@ -86,6 +88,7 @@ type VerifyResult int
|
|||||||
const (
|
const (
|
||||||
VerifyResultNONE = VerifyResult(iota)
|
VerifyResultNONE = VerifyResult(iota)
|
||||||
VerifyResultFAIL
|
VerifyResultFAIL
|
||||||
|
VerifyResultSKIP
|
||||||
|
|
||||||
// VerifyResultPASS Client just passed this challenge
|
// VerifyResultPASS Client just passed this challenge
|
||||||
VerifyResultPASS
|
VerifyResultPASS
|
||||||
@@ -95,7 +98,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (r VerifyResult) Ok() bool {
|
func (r VerifyResult) Ok() bool {
|
||||||
return r > VerifyResultFAIL
|
return r >= VerifyResultPASS
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r VerifyResult) String() string {
|
func (r VerifyResult) String() string {
|
||||||
@@ -104,6 +107,8 @@ func (r VerifyResult) String() string {
|
|||||||
return "NONE"
|
return "NONE"
|
||||||
case VerifyResultFAIL:
|
case VerifyResultFAIL:
|
||||||
return "FAIL"
|
return "FAIL"
|
||||||
|
case VerifyResultSKIP:
|
||||||
|
return "SKIP"
|
||||||
case VerifyResultPASS:
|
case VerifyResultPASS:
|
||||||
return "PASS"
|
return "PASS"
|
||||||
case VerifyResultOK:
|
case VerifyResultOK:
|
||||||
|
|||||||
69
lib/conditions.go
Normal file
69
lib/conditions.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/cel-go/cel"
|
||||||
|
"github.com/google/cel-go/common/types"
|
||||||
|
"github.com/google/cel-go/common/types/ref"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (state *State) initConditions() (err error) {
|
||||||
|
state.RulesEnv, err = cel.NewEnv(
|
||||||
|
cel.DefaultUTCTimeZone(true),
|
||||||
|
cel.Variable("remoteAddress", cel.BytesType),
|
||||||
|
cel.Variable("host", cel.StringType),
|
||||||
|
cel.Variable("method", cel.StringType),
|
||||||
|
cel.Variable("userAgent", cel.StringType),
|
||||||
|
cel.Variable("path", cel.StringType),
|
||||||
|
cel.Variable("query", cel.MapType(cel.StringType, cel.StringType)),
|
||||||
|
// http.Header
|
||||||
|
cel.Variable("headers", cel.MapType(cel.StringType, cel.StringType)),
|
||||||
|
//TODO: dynamic type?
|
||||||
|
cel.Function("inNetwork",
|
||||||
|
cel.Overload("inNetwork_string_ip",
|
||||||
|
[]*cel.Type{cel.StringType, cel.AnyType},
|
||||||
|
cel.BoolType,
|
||||||
|
cel.BinaryBinding(func(lhs ref.Val, rhs ref.Val) ref.Val {
|
||||||
|
var ip net.IP
|
||||||
|
switch v := rhs.Value().(type) {
|
||||||
|
case []byte:
|
||||||
|
ip = v
|
||||||
|
case net.IP:
|
||||||
|
ip = v
|
||||||
|
case string:
|
||||||
|
ip = net.ParseIP(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip == nil {
|
||||||
|
panic(fmt.Errorf("invalid ip %v", rhs.Value()))
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := lhs.Value().(string)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("invalid value %v", lhs.Value()))
|
||||||
|
}
|
||||||
|
|
||||||
|
network, ok := state.Networks[val]
|
||||||
|
if !ok {
|
||||||
|
_, ipNet, err := net.ParseCIDR(val)
|
||||||
|
if err != nil {
|
||||||
|
panic("network not found")
|
||||||
|
}
|
||||||
|
return types.Bool(ipNet.Contains(ip))
|
||||||
|
} else {
|
||||||
|
ok, err := network.Contains(ip)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return types.Bool(ok)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
73
lib/http.go
73
lib/http.go
@@ -159,29 +159,6 @@ func (state *State) handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
//TODO better matcher! combo ast?
|
|
||||||
env := map[string]any{
|
|
||||||
"host": host,
|
|
||||||
"method": r.Method,
|
|
||||||
"remoteAddress": getRequestAddress(r, state.Settings.ClientIpHeader),
|
|
||||||
"userAgent": r.UserAgent(),
|
|
||||||
"path": r.URL.Path,
|
|
||||||
"query": func() map[string]string {
|
|
||||||
result := make(map[string]string)
|
|
||||||
for k, v := range r.URL.Query() {
|
|
||||||
result[k] = strings.Join(v, ",")
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}(),
|
|
||||||
"headers": func() map[string]string {
|
|
||||||
result := make(map[string]string)
|
|
||||||
for k, v := range r.Header {
|
|
||||||
result[k] = strings.Join(v, ",")
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}(),
|
|
||||||
}
|
|
||||||
|
|
||||||
state.addTiming(w, "rule-env", "Setup the rule environment", time.Since(start))
|
state.addTiming(w, "rule-env", "Setup the rule environment", time.Since(start))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -211,7 +188,7 @@ func (state *State) handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
out, _, err := rule.Program.Eval(env)
|
out, _, err := rule.Program.Eval(data.ProgramEnv)
|
||||||
ruleEvalDuration += time.Since(start)
|
ruleEvalDuration += time.Since(start)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -230,7 +207,6 @@ func (state *State) handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||||||
serve()
|
serve()
|
||||||
return
|
return
|
||||||
case policy.RuleActionCHALLENGE, policy.RuleActionCHECK:
|
case policy.RuleActionCHALLENGE, policy.RuleActionCHECK:
|
||||||
|
|
||||||
for _, challengeId := range rule.Challenges {
|
for _, challengeId := range rule.Challenges {
|
||||||
if result := data.Challenges[challengeId]; !result.Ok() {
|
if result := data.Challenges[challengeId]; !result.Ok() {
|
||||||
continue
|
continue
|
||||||
@@ -249,6 +225,11 @@ func (state *State) handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// none matched, issue first challenge in priority
|
// none matched, issue first challenge in priority
|
||||||
for _, challengeId := range rule.Challenges {
|
for _, challengeId := range rule.Challenges {
|
||||||
|
result := data.Challenges[challengeId]
|
||||||
|
if result.Ok() || result == challenge.VerifyResultSKIP {
|
||||||
|
// skip already ok'd challenges for some reason, and also skip skipped challenges
|
||||||
|
continue
|
||||||
|
}
|
||||||
c := state.Challenges[challengeId]
|
c := state.Challenges[challengeId]
|
||||||
if c.ServeChallenge != nil {
|
if c.ServeChallenge != nil {
|
||||||
result := c.ServeChallenge(w, r, state.GetChallengeKeyForRequest(c.Name, data.Expires, r), data.Expires)
|
result := c.ServeChallenge(w, r, state.GetChallengeKeyForRequest(c.Name, data.Expires, r), data.Expires)
|
||||||
@@ -264,7 +245,10 @@ func (state *State) handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
state.logger(r).Warn("challenge passed", "rule", rule.Name, "rule_hash", rule.Hash, "challenge", c.Name)
|
state.logger(r).Warn("challenge passed", "rule", rule.Name, "rule_hash", rule.Hash, "challenge", c.Name)
|
||||||
|
|
||||||
data.Challenges[c.Id] = challenge.VerifyResultOK
|
// set pass if caller didn't set one
|
||||||
|
if !data.Challenges[c.Id].Ok() {
|
||||||
|
data.Challenges[c.Id] = challenge.VerifyResultPASS
|
||||||
|
}
|
||||||
|
|
||||||
// we pass the challenge early!
|
// we pass the challenge early!
|
||||||
lg.Debug("request passed", "rule", rule.Name, "rule_hash", rule.Hash, "challenge", c.Name)
|
lg.Debug("request passed", "rule", rule.Name, "rule_hash", rule.Hash, "challenge", c.Name)
|
||||||
@@ -425,6 +409,27 @@ func (state *State) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
_, _ = rand.Read(data.Id[:])
|
_, _ = rand.Read(data.Id[:])
|
||||||
data.Challenges = make(map[challenge.Id]challenge.VerifyResult, len(state.Challenges))
|
data.Challenges = make(map[challenge.Id]challenge.VerifyResult, len(state.Challenges))
|
||||||
data.Expires = time.Now().UTC().Add(DefaultValidity).Round(DefaultValidity)
|
data.Expires = time.Now().UTC().Add(DefaultValidity).Round(DefaultValidity)
|
||||||
|
data.ProgramEnv = map[string]any{
|
||||||
|
"host": r.Host,
|
||||||
|
"method": r.Method,
|
||||||
|
"remoteAddress": getRequestAddress(r, state.Settings.ClientIpHeader),
|
||||||
|
"userAgent": r.UserAgent(),
|
||||||
|
"path": r.URL.Path,
|
||||||
|
"query": func() map[string]string {
|
||||||
|
result := make(map[string]string)
|
||||||
|
for k, v := range r.URL.Query() {
|
||||||
|
result[k] = strings.Join(v, ",")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}(),
|
||||||
|
"headers": func() map[string]string {
|
||||||
|
result := make(map[string]string)
|
||||||
|
for k, v := range r.Header {
|
||||||
|
result[k] = strings.Join(v, ",")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range state.Challenges {
|
for _, c := range state.Challenges {
|
||||||
key := state.GetChallengeKeyForRequest(c.Name, data.Expires, r)
|
key := state.GetChallengeKeyForRequest(c.Name, data.Expires, r)
|
||||||
@@ -433,6 +438,21 @@ func (state *State) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// clear invalid cookie
|
// clear invalid cookie
|
||||||
utils.ClearCookie(utils.CookiePrefix+c.Name, w)
|
utils.ClearCookie(utils.CookiePrefix+c.Name, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prevent the challenge if not solved
|
||||||
|
if !result.Ok() && c.Program != nil {
|
||||||
|
out, _, err := c.Program.Eval(data.ProgramEnv)
|
||||||
|
// verify eligibility
|
||||||
|
if err != nil {
|
||||||
|
state.logger(r).Error(err.Error(), "challenge", c.Name)
|
||||||
|
} else if out != nil && out.Type() == types.BoolType {
|
||||||
|
if out.Equal(types.True) != types.True {
|
||||||
|
// skip challenge match!
|
||||||
|
result = challenge.VerifyResultSKIP
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
data.Challenges[c.Id] = result
|
data.Challenges[c.Id] = result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,6 +469,7 @@ func RequestDataFromContext(ctx context.Context) *RequestData {
|
|||||||
|
|
||||||
type RequestData struct {
|
type RequestData struct {
|
||||||
Id [16]byte
|
Id [16]byte
|
||||||
|
ProgramEnv map[string]any
|
||||||
Expires time.Time
|
Expires time.Time
|
||||||
Challenges map[challenge.Id]challenge.VerifyResult
|
Challenges map[challenge.Id]challenge.VerifyResult
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package policy
|
package policy
|
||||||
|
|
||||||
type Challenge struct {
|
type Challenge struct {
|
||||||
Mode string `yaml:"mode"`
|
Conditions []string `yaml:"conditions"`
|
||||||
Asset *string `yaml:"asset,omitempty"`
|
Mode string `yaml:"mode"`
|
||||||
Url *string `yaml:"url,omitempty"`
|
Asset *string `yaml:"asset,omitempty"`
|
||||||
|
Url *string `yaml:"url,omitempty"`
|
||||||
|
|
||||||
Parameters map[string]string `json:"parameters,omitempty"`
|
Parameters map[string]string `json:"parameters,omitempty"`
|
||||||
Runtime struct {
|
Runtime struct {
|
||||||
|
|||||||
134
lib/state.go
134
lib/state.go
@@ -20,15 +20,12 @@ import (
|
|||||||
"git.gammaspectra.live/git/go-away/utils"
|
"git.gammaspectra.live/git/go-away/utils"
|
||||||
"git.gammaspectra.live/git/go-away/utils/inline"
|
"git.gammaspectra.live/git/go-away/utils/inline"
|
||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
"github.com/google/cel-go/common/types"
|
|
||||||
"github.com/google/cel-go/common/types/ref"
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
"github.com/tetratelabs/wazero/api"
|
||||||
"github.com/yl2chen/cidranger"
|
"github.com/yl2chen/cidranger"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -197,13 +194,56 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
|
|
||||||
state.Wasm = wasm.NewRunner(true)
|
state.Wasm = wasm.NewRunner(true)
|
||||||
|
|
||||||
|
err = state.initConditions()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var replacements []string
|
||||||
|
for k, entries := range p.Conditions {
|
||||||
|
ast, err := condition.FromStrings(state.RulesEnv, condition.OperatorOr, entries...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("conditions %s: error compiling conditions: %v", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cond, err := cel.AstToString(ast)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("conditions %s: error printing condition: %v", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
replacements = append(replacements, fmt.Sprintf("($%s)", k))
|
||||||
|
replacements = append(replacements, "("+cond+")")
|
||||||
|
}
|
||||||
|
conditionReplacer := strings.NewReplacer(replacements...)
|
||||||
|
|
||||||
state.Challenges = make(map[challenge.Id]challenge.Challenge)
|
state.Challenges = make(map[challenge.Id]challenge.Challenge)
|
||||||
|
|
||||||
idCounter := challenge.Id(1)
|
idCounter := challenge.Id(1)
|
||||||
|
|
||||||
for challengeName, p := range p.Challenges {
|
for challengeName, p := range p.Challenges {
|
||||||
|
|
||||||
|
// allow nesting
|
||||||
|
var conditions []string
|
||||||
|
for _, cond := range p.Conditions {
|
||||||
|
cond = conditionReplacer.Replace(cond)
|
||||||
|
conditions = append(conditions, cond)
|
||||||
|
}
|
||||||
|
|
||||||
|
var program cel.Program
|
||||||
|
if len(conditions) > 0 {
|
||||||
|
ast, err := condition.FromStrings(state.RulesEnv, condition.OperatorOr, conditions...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("challenge %s: error compiling conditions: %v", challengeName, err)
|
||||||
|
}
|
||||||
|
program, err = state.RulesEnv.Program(ast)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("challenge %s: error compiling program: %v", challengeName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c := challenge.Challenge{
|
c := challenge.Challenge{
|
||||||
Id: idCounter,
|
Id: idCounter,
|
||||||
|
Program: program,
|
||||||
Name: challengeName,
|
Name: challengeName,
|
||||||
Path: fmt.Sprintf("%s/challenge/%s", state.UrlPath, challengeName),
|
Path: fmt.Sprintf("%s/challenge/%s", state.UrlPath, challengeName),
|
||||||
VerifyProbability: p.Runtime.Probability,
|
VerifyProbability: p.Runtime.Probability,
|
||||||
@@ -383,13 +423,16 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
return challenge.ResultStop
|
return challenge.ResultStop
|
||||||
}
|
}
|
||||||
case "preload-link":
|
case "preload-link":
|
||||||
deadline := time.Second * 5
|
deadline, _ := time.ParseDuration(p.Parameters["preload-early-hint-deadline"])
|
||||||
|
if deadline == 0 {
|
||||||
|
deadline = time.Second * 3
|
||||||
|
}
|
||||||
|
|
||||||
c.ServeChallenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) challenge.Result {
|
c.ServeChallenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) challenge.Result {
|
||||||
// this only works on HTTP/2 and HTTP/3
|
// this only works on HTTP/2 and HTTP/3
|
||||||
|
|
||||||
if r.ProtoMajor < 2 {
|
if r.ProtoMajor < 2 {
|
||||||
// this can happen if we are an upgraded request
|
// this can happen if we are an upgraded request from HTTP/1.1 to HTTP/2 in H2C
|
||||||
if _, ok := w.(http.Pusher); !ok {
|
if _, ok := w.(http.Pusher); !ok {
|
||||||
return challenge.ResultContinue
|
return challenge.ResultContinue
|
||||||
}
|
}
|
||||||
@@ -397,18 +440,19 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
|
|
||||||
data := RequestDataFromContext(r.Context())
|
data := RequestDataFromContext(r.Context())
|
||||||
redirectUri := new(url.URL)
|
redirectUri := new(url.URL)
|
||||||
|
redirectUri.Scheme = getRequestScheme(r)
|
||||||
|
redirectUri.Host = r.Host
|
||||||
redirectUri.Path = c.Path + "/verify-challenge"
|
redirectUri.Path = c.Path + "/verify-challenge"
|
||||||
|
|
||||||
values := make(url.Values)
|
values := make(url.Values)
|
||||||
values.Set("result", hex.EncodeToString(key))
|
values.Set("result", hex.EncodeToString(key))
|
||||||
values.Set("redirect", r.URL.String())
|
|
||||||
values.Set("requestId", r.Header.Get("X-Away-Id"))
|
values.Set("requestId", r.Header.Get("X-Away-Id"))
|
||||||
|
|
||||||
redirectUri.RawQuery = values.Encode()
|
redirectUri.RawQuery = values.Encode()
|
||||||
|
|
||||||
w.Header().Set("Link", fmt.Sprintf("<%s>; rel=preload; as=style; fetchpriority=high", redirectUri.String()))
|
w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"preload\"; as=\"style\"; fetchpriority=high", redirectUri.String()))
|
||||||
defer func() {
|
defer func() {
|
||||||
// remove old header header!
|
// remove old header so it won't show on response!
|
||||||
w.Header().Del("Link")
|
w.Header().Del("Link")
|
||||||
}()
|
}()
|
||||||
w.WriteHeader(http.StatusEarlyHints)
|
w.WriteHeader(http.StatusEarlyHints)
|
||||||
@@ -656,80 +700,6 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
state.Challenges[c.Id] = c
|
state.Challenges[c.Id] = c
|
||||||
}
|
}
|
||||||
|
|
||||||
state.RulesEnv, err = cel.NewEnv(
|
|
||||||
cel.DefaultUTCTimeZone(true),
|
|
||||||
cel.Variable("remoteAddress", cel.BytesType),
|
|
||||||
cel.Variable("host", cel.StringType),
|
|
||||||
cel.Variable("method", cel.StringType),
|
|
||||||
cel.Variable("userAgent", cel.StringType),
|
|
||||||
cel.Variable("path", cel.StringType),
|
|
||||||
cel.Variable("query", cel.MapType(cel.StringType, cel.StringType)),
|
|
||||||
// http.Header
|
|
||||||
cel.Variable("headers", cel.MapType(cel.StringType, cel.StringType)),
|
|
||||||
//TODO: dynamic type?
|
|
||||||
cel.Function("inNetwork",
|
|
||||||
cel.Overload("inNetwork_string_ip",
|
|
||||||
[]*cel.Type{cel.StringType, cel.AnyType},
|
|
||||||
cel.BoolType,
|
|
||||||
cel.BinaryBinding(func(lhs ref.Val, rhs ref.Val) ref.Val {
|
|
||||||
var ip net.IP
|
|
||||||
switch v := rhs.Value().(type) {
|
|
||||||
case []byte:
|
|
||||||
ip = v
|
|
||||||
case net.IP:
|
|
||||||
ip = v
|
|
||||||
case string:
|
|
||||||
ip = net.ParseIP(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip == nil {
|
|
||||||
panic(fmt.Errorf("invalid ip %v", rhs.Value()))
|
|
||||||
}
|
|
||||||
|
|
||||||
val, ok := lhs.Value().(string)
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Errorf("invalid value %v", lhs.Value()))
|
|
||||||
}
|
|
||||||
|
|
||||||
network, ok := state.Networks[val]
|
|
||||||
if !ok {
|
|
||||||
_, ipNet, err := net.ParseCIDR(val)
|
|
||||||
if err != nil {
|
|
||||||
panic("network not found")
|
|
||||||
}
|
|
||||||
return types.Bool(ipNet.Contains(ip))
|
|
||||||
} else {
|
|
||||||
ok, err := network.Contains(ip)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return types.Bool(ok)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var replacements []string
|
|
||||||
for k, entries := range p.Conditions {
|
|
||||||
ast, err := condition.FromStrings(state.RulesEnv, condition.OperatorOr, entries...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("conditions %s: error compiling conditions: %v", k, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cond, err := cel.AstToString(ast)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("conditions %s: error printing condition: %v", k, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
replacements = append(replacements, fmt.Sprintf("($%s)", k))
|
|
||||||
replacements = append(replacements, "("+cond+")")
|
|
||||||
}
|
|
||||||
conditionReplacer := strings.NewReplacer(replacements...)
|
|
||||||
|
|
||||||
for _, rule := range p.Rules {
|
for _, rule := range p.Rules {
|
||||||
hasher := sha256.New()
|
hasher := sha256.New()
|
||||||
hasher.Write([]byte(rule.Name))
|
hasher.Write([]byte(rule.Name))
|
||||||
|
|||||||
Reference in New Issue
Block a user