Move challenge structs to external state
This commit is contained in:
34
lib/challenge/state.go
Normal file
34
lib/challenge/state.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package challenge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Result int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ResultStop Stop testing other challenges and return
|
||||||
|
ResultStop = Result(iota)
|
||||||
|
// ResultContinue Test next
|
||||||
|
ResultContinue
|
||||||
|
// ResultPass passed, return and proxy
|
||||||
|
ResultPass
|
||||||
|
)
|
||||||
|
|
||||||
|
type Challenge struct {
|
||||||
|
Path string
|
||||||
|
|
||||||
|
Verify func(key []byte, result string) (bool, error)
|
||||||
|
VerifyProbability float64
|
||||||
|
|
||||||
|
ServeStatic http.Handler
|
||||||
|
|
||||||
|
ServeChallenge func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) Result
|
||||||
|
|
||||||
|
ServeScript http.Handler
|
||||||
|
ServeScriptPath string
|
||||||
|
|
||||||
|
ServeMakeChallenge http.Handler
|
||||||
|
ServeVerifyChallenge http.Handler
|
||||||
|
}
|
||||||
27
lib/http.go
27
lib/http.go
@@ -9,6 +9,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.gammaspectra.live/git/go-away/embed"
|
"git.gammaspectra.live/git/go-away/embed"
|
||||||
|
"git.gammaspectra.live/git/go-away/lib/challenge"
|
||||||
"git.gammaspectra.live/git/go-away/lib/policy"
|
"git.gammaspectra.live/git/go-away/lib/policy"
|
||||||
"github.com/google/cel-go/common/types"
|
"github.com/google/cel-go/common/types"
|
||||||
"html/template"
|
"html/template"
|
||||||
@@ -251,15 +252,15 @@ 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 _, challengeName := range rule.Challenges {
|
for _, challengeName := range rule.Challenges {
|
||||||
c := state.Challenges[challengeName]
|
c := state.Challenges[challengeName]
|
||||||
if c.Challenge != nil {
|
if c.ServeChallenge != nil {
|
||||||
result := c.Challenge(w, r, state.GetChallengeKeyForRequest(challengeName, expiry, r), expiry)
|
result := c.ServeChallenge(w, r, state.GetChallengeKeyForRequest(challengeName, expiry, r), expiry)
|
||||||
switch result {
|
switch result {
|
||||||
case ChallengeResultStop:
|
case challenge.ResultStop:
|
||||||
lg.Info("request challenged", "rule", rule.Name, "rule_hash", rule.Hash, "challenge", challengeName)
|
lg.Info("request challenged", "rule", rule.Name, "rule_hash", rule.Hash, "challenge", challengeName)
|
||||||
return
|
return
|
||||||
case ChallengeResultContinue:
|
case challenge.ResultContinue:
|
||||||
continue
|
continue
|
||||||
case ChallengeResultPass:
|
case challenge.ResultPass:
|
||||||
if rule.Action == policy.RuleActionCHECK {
|
if rule.Action == policy.RuleActionCHECK {
|
||||||
goto nextRule
|
goto nextRule
|
||||||
}
|
}
|
||||||
@@ -347,20 +348,20 @@ func (state *State) setupRoutes() error {
|
|||||||
state.Mux.Handle("GET "+state.UrlPath+"/assets/", http.StripPrefix(state.UrlPath, gzipped.FileServer(gzipped.FS(embed.AssetsFs))))
|
state.Mux.Handle("GET "+state.UrlPath+"/assets/", http.StripPrefix(state.UrlPath, gzipped.FileServer(gzipped.FS(embed.AssetsFs))))
|
||||||
|
|
||||||
for challengeName, c := range state.Challenges {
|
for challengeName, c := range state.Challenges {
|
||||||
if c.Static != nil {
|
if c.ServeStatic != nil {
|
||||||
state.Mux.Handle("GET "+c.Path+"/static/", c.Static)
|
state.Mux.Handle("GET "+c.Path+"/static/", c.ServeStatic)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ChallengeScript != nil {
|
if c.ServeScript != nil {
|
||||||
state.Mux.Handle("GET "+c.ChallengeScriptPath, c.ChallengeScript)
|
state.Mux.Handle("GET "+c.ServeScriptPath, c.ServeScript)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.MakeChallenge != nil {
|
if c.ServeMakeChallenge != nil {
|
||||||
state.Mux.Handle(fmt.Sprintf("POST %s/make-challenge", c.Path), c.MakeChallenge)
|
state.Mux.Handle(fmt.Sprintf("POST %s/make-challenge", c.Path), c.ServeMakeChallenge)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.VerifyChallenge != nil {
|
if c.ServeVerifyChallenge != nil {
|
||||||
state.Mux.Handle(fmt.Sprintf("GET %s/verify-challenge", c.Path), c.VerifyChallenge)
|
state.Mux.Handle(fmt.Sprintf("GET %s/verify-challenge", c.Path), c.ServeVerifyChallenge)
|
||||||
} else if c.Verify != nil {
|
} else if c.Verify != nil {
|
||||||
state.Mux.HandleFunc(fmt.Sprintf("GET %s/verify-challenge", c.Path), func(w http.ResponseWriter, r *http.Request) {
|
state.Mux.HandleFunc(fmt.Sprintf("GET %s/verify-challenge", c.Path), func(w http.ResponseWriter, r *http.Request) {
|
||||||
err := func() (err error) {
|
err := func() (err error) {
|
||||||
|
|||||||
74
lib/state.go
74
lib/state.go
@@ -12,6 +12,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.gammaspectra.live/git/go-away/embed"
|
"git.gammaspectra.live/git/go-away/embed"
|
||||||
|
"git.gammaspectra.live/git/go-away/lib/challenge"
|
||||||
"git.gammaspectra.live/git/go-away/lib/challenge/wasm"
|
"git.gammaspectra.live/git/go-away/lib/challenge/wasm"
|
||||||
"git.gammaspectra.live/git/go-away/lib/challenge/wasm/interface"
|
"git.gammaspectra.live/git/go-away/lib/challenge/wasm/interface"
|
||||||
"git.gammaspectra.live/git/go-away/lib/condition"
|
"git.gammaspectra.live/git/go-away/lib/condition"
|
||||||
@@ -47,7 +48,7 @@ type State struct {
|
|||||||
|
|
||||||
Wasm *wasm.Runner
|
Wasm *wasm.Runner
|
||||||
|
|
||||||
Challenges map[string]ChallengeState
|
Challenges map[string]challenge.Challenge
|
||||||
|
|
||||||
RulesEnv *cel.Env
|
RulesEnv *cel.Env
|
||||||
|
|
||||||
@@ -70,31 +71,6 @@ type RuleState struct {
|
|||||||
Challenges []string
|
Challenges []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChallengeResult int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ChallengeResultStop Stop testing challenges and return
|
|
||||||
ChallengeResultStop = ChallengeResult(iota)
|
|
||||||
// ChallengeResultContinue Test next challenge
|
|
||||||
ChallengeResultContinue
|
|
||||||
// ChallengeResultPass Challenge passed, return and proxy
|
|
||||||
ChallengeResultPass
|
|
||||||
)
|
|
||||||
|
|
||||||
type ChallengeState struct {
|
|
||||||
Path string
|
|
||||||
|
|
||||||
Static http.Handler
|
|
||||||
Challenge func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) ChallengeResult
|
|
||||||
ChallengeScriptPath string
|
|
||||||
ChallengeScript http.Handler
|
|
||||||
MakeChallenge http.Handler
|
|
||||||
VerifyChallenge http.Handler
|
|
||||||
Verify func(key []byte, result string) (bool, error)
|
|
||||||
|
|
||||||
VerifyProbability float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type StateSettings struct {
|
type StateSettings struct {
|
||||||
Backends map[string]http.Handler
|
Backends map[string]http.Handler
|
||||||
PrivateKeySeed []byte
|
PrivateKeySeed []byte
|
||||||
@@ -190,10 +166,10 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
|
|
||||||
state.Wasm = wasm.NewRunner(true)
|
state.Wasm = wasm.NewRunner(true)
|
||||||
|
|
||||||
state.Challenges = make(map[string]ChallengeState)
|
state.Challenges = make(map[string]challenge.Challenge)
|
||||||
|
|
||||||
for challengeName, p := range p.Challenges {
|
for challengeName, p := range p.Challenges {
|
||||||
c := ChallengeState{
|
c := challenge.Challenge{
|
||||||
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,
|
||||||
}
|
}
|
||||||
@@ -208,7 +184,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
assetPath := c.Path + "/static/"
|
assetPath := c.Path + "/static/"
|
||||||
subFs, err := fs.Sub(embed.ChallengeFs, fmt.Sprintf("challenge/%s/static", challengeName))
|
subFs, err := fs.Sub(embed.ChallengeFs, fmt.Sprintf("challenge/%s/static", challengeName))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.Static = http.StripPrefix(
|
c.ServeStatic = http.StripPrefix(
|
||||||
assetPath,
|
assetPath,
|
||||||
gzipped.FileServer(gzipped.FS(subFs)),
|
gzipped.FileServer(gzipped.FS(subFs)),
|
||||||
)
|
)
|
||||||
@@ -233,23 +209,23 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
|
|
||||||
expectedCookie := p.Parameters["http-cookie"]
|
expectedCookie := p.Parameters["http-cookie"]
|
||||||
|
|
||||||
c.Challenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) ChallengeResult {
|
c.ServeChallenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) challenge.Result {
|
||||||
if expectedCookie != "" {
|
if expectedCookie != "" {
|
||||||
if cookie, err := r.Cookie(expectedCookie); err != nil || cookie == nil {
|
if cookie, err := r.Cookie(expectedCookie); err != nil || cookie == nil {
|
||||||
// skip check if we don't have cookie or it's expired
|
// skip check if we don't have cookie or it's expired
|
||||||
return ChallengeResultContinue
|
return challenge.ResultContinue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request, err := http.NewRequest(method, *p.Url, nil)
|
request, err := http.NewRequest(method, *p.Url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ChallengeResultContinue
|
return challenge.ResultContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Header = r.Header
|
request.Header = r.Header
|
||||||
response, err := state.Client.Do(request)
|
response, err := state.Client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ChallengeResultContinue
|
return challenge.ResultContinue
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
defer io.Copy(io.Discard, response.Body)
|
defer io.Copy(io.Discard, response.Body)
|
||||||
@@ -257,7 +233,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
if response.StatusCode != httpCode {
|
if response.StatusCode != httpCode {
|
||||||
ClearCookie(CookiePrefix+challengeName, w)
|
ClearCookie(CookiePrefix+challengeName, w)
|
||||||
// continue other challenges!
|
// continue other challenges!
|
||||||
return ChallengeResultContinue
|
return challenge.ResultContinue
|
||||||
} else {
|
} else {
|
||||||
token, err := state.IssueChallengeToken(challengeName, key, nil, expiry)
|
token, err := state.IssueChallengeToken(challengeName, key, nil, expiry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -267,12 +243,12 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// we passed it!
|
// we passed it!
|
||||||
return ChallengeResultPass
|
return challenge.ResultPass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "cookie":
|
case "cookie":
|
||||||
c.Challenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) ChallengeResult {
|
c.ServeChallenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) challenge.Result {
|
||||||
token, err := state.IssueChallengeToken(challengeName, key, nil, expiry)
|
token, err := state.IssueChallengeToken(challengeName, key, nil, expiry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ClearCookie(CookiePrefix+challengeName, w)
|
ClearCookie(CookiePrefix+challengeName, w)
|
||||||
@@ -282,10 +258,10 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
// self redirect!
|
// self redirect!
|
||||||
//TODO: add redirect loop detect parameter
|
//TODO: add redirect loop detect parameter
|
||||||
http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
|
||||||
return ChallengeResultStop
|
return challenge.ResultStop
|
||||||
}
|
}
|
||||||
case "meta-refresh":
|
case "meta-refresh":
|
||||||
c.Challenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) ChallengeResult {
|
c.ServeChallenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) challenge.Result {
|
||||||
redirectUri := new(url.URL)
|
redirectUri := new(url.URL)
|
||||||
redirectUri.Path = c.Path + "/verify-challenge"
|
redirectUri.Path = c.Path + "/verify-challenge"
|
||||||
|
|
||||||
@@ -302,10 +278,10 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return ChallengeResultStop
|
return challenge.ResultStop
|
||||||
}
|
}
|
||||||
case "header-refresh":
|
case "header-refresh":
|
||||||
c.Challenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) ChallengeResult {
|
c.ServeChallenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) challenge.Result {
|
||||||
redirectUri := new(url.URL)
|
redirectUri := new(url.URL)
|
||||||
redirectUri.Path = c.Path + "/verify-challenge"
|
redirectUri.Path = c.Path + "/verify-challenge"
|
||||||
|
|
||||||
@@ -321,10 +297,10 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
|
|
||||||
_ = state.challengePage(w, r.Header.Get("X-Away-Id"), http.StatusTeapot, "", nil)
|
_ = state.challengePage(w, r.Header.Get("X-Away-Id"), http.StatusTeapot, "", nil)
|
||||||
|
|
||||||
return ChallengeResultStop
|
return challenge.ResultStop
|
||||||
}
|
}
|
||||||
case "resource-load":
|
case "resource-load":
|
||||||
c.Challenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) ChallengeResult {
|
c.ServeChallenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) challenge.Result {
|
||||||
redirectUri := new(url.URL)
|
redirectUri := new(url.URL)
|
||||||
redirectUri.Path = c.Path + "/verify-challenge"
|
redirectUri.Path = c.Path + "/verify-challenge"
|
||||||
|
|
||||||
@@ -343,16 +319,16 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return ChallengeResultStop
|
return challenge.ResultStop
|
||||||
}
|
}
|
||||||
case "js":
|
case "js":
|
||||||
c.Challenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) ChallengeResult {
|
c.ServeChallenge = func(w http.ResponseWriter, r *http.Request, key []byte, expiry time.Time) challenge.Result {
|
||||||
_ = state.challengePage(w, r.Header.Get("X-Away-Id"), http.StatusTeapot, challengeName, nil)
|
_ = state.challengePage(w, r.Header.Get("X-Away-Id"), http.StatusTeapot, challengeName, nil)
|
||||||
|
|
||||||
return ChallengeResultStop
|
return challenge.ResultStop
|
||||||
}
|
}
|
||||||
c.ChallengeScriptPath = c.Path + "/challenge.mjs"
|
c.ServeScriptPath = c.Path + "/challenge.mjs"
|
||||||
c.ChallengeScript = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
c.ServeScript = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
params, _ := json.Marshal(p.Parameters)
|
params, _ := json.Marshal(p.Parameters)
|
||||||
|
|
||||||
//TODO: move this to http.go as a template
|
//TODO: move this to http.go as a template
|
||||||
@@ -415,7 +391,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.VerifyChallenge = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
c.ServeVerifyChallenge = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
err := func() (err error) {
|
err := func() (err error) {
|
||||||
expiry := time.Now().UTC().Add(DefaultValidity).Round(DefaultValidity)
|
expiry := time.Now().UTC().Add(DefaultValidity).Round(DefaultValidity)
|
||||||
@@ -475,7 +451,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
return nil, fmt.Errorf("c %s: compiling runtime: %w", challengeName, err)
|
return nil, fmt.Errorf("c %s: compiling runtime: %w", challengeName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.MakeChallenge = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
c.ServeMakeChallenge = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
err := state.Wasm.Instantiate(challengeName, func(ctx context.Context, mod api.Module) (err error) {
|
err := state.Wasm.Instantiate(challengeName, func(ctx context.Context, mod api.Module) (err error) {
|
||||||
|
|
||||||
in := _interface.MakeChallengeInput{
|
in := _interface.MakeChallengeInput{
|
||||||
|
|||||||
Reference in New Issue
Block a user