refactor v1, start support for more protocols and implement nak

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer
2025-05-20 22:39:14 +02:00
parent 8cf8f1e199
commit b6686cff14
12 changed files with 252 additions and 106 deletions

View File

@ -4,11 +4,12 @@ import (
"crypto/hmac"
"crypto/md5"
"encoding/base64"
"fmt"
"github.com/gorilla/securecookie"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/radius/eap/legacy_nak"
"goauthentik.io/internal/outpost/radius/eap/protocol"
"goauthentik.io/internal/outpost/radius/eap/tls"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
"layeh.com/radius/rfc2869"
@ -22,66 +23,42 @@ func sendErrorResponse(w radius.ResponseWriter, r *radius.Request) {
}
}
func (p *Packet) Handle(stm StateManager, w radius.ResponseWriter, r *radius.Request) {
func (p *Packet) HandleRadiusPacket(w radius.ResponseWriter, r *radius.Request) {
rst := rfc2865.State_GetString(r.Packet)
if rst == "" {
rst = base64.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(12))
}
st := stm.GetEAPState(rst)
if st == nil {
log.Debug("EAP: blank state")
st = BlankState(stm.GetEAPSettings())
}
if len(st.ChallengesToOffer) < 1 {
log.Error("No more challenges to offer")
sendErrorResponse(w, r)
return
}
nextChallengeToOffer := st.ChallengesToOffer[0]
p.state = rst
ctx := &context{
req: r,
state: st.TypeState[nextChallengeToOffer],
log: log.WithField("type", nextChallengeToOffer),
settings: stm.GetEAPSettings().ProtocolSettings[nextChallengeToOffer],
}
res := p.GetChallengeForType(ctx, nextChallengeToOffer)
st.TypeState[nextChallengeToOffer] = ctx.GetProtocolState(nil)
stm.SetEAPState(rst, st)
rres := r.Response(radius.CodeAccessChallenge)
switch ctx.endStatus {
case protocol.StatusSuccess:
res.code = CodeSuccess
res.id -= 1
rres = ctx.endModifier(rres)
st.ChallengesToOffer = st.ChallengesToOffer[1:]
if len(st.ChallengesToOffer) < 1 {
rp, err := p.handleInner(r)
rres := r.Response(radius.CodeAccessReject)
if err == nil {
rres = p.endModifier(rres)
switch rp.code {
case CodeFailure:
rres.Code = radius.CodeAccessReject
case CodeSuccess:
rres.Code = radius.CodeAccessAccept
}
case protocol.StatusError:
res.code = CodeFailure
res.id -= 1
st.ChallengesToOffer = st.ChallengesToOffer[1:]
rres = ctx.endModifier(rres)
if len(st.ChallengesToOffer) < 1 {
rres.Code = radius.CodeAccessReject
}
case protocol.StatusUnknown:
} else {
rres.Code = radius.CodeAccessReject
log.WithError(err).Debug("Rejecting request")
}
rfc2865.State_SetString(rres, rst)
eapEncoded, err := res.Encode()
rfc2865.State_SetString(rres, p.state)
eapEncoded, err := rp.Encode()
if err != nil {
log.WithError(err).Warning("failed to encode response")
sendErrorResponse(w, r)
return
}
log.WithField("length", len(eapEncoded)).Debug("EAP: encapsulated challenge")
log.WithField("length", len(eapEncoded)).WithField("type", fmt.Sprintf("%T", rp.Payload)).Debug("EAP: encapsulated challenge")
rfc2869.EAPMessage_Set(rres, eapEncoded)
err = p.setMessageAuthenticator(rres)
if err != nil {
log.WithError(err).Warning("failed to send message authenticator")
sendErrorResponse(w, r)
return
}
err = w.Write(rres)
if err != nil {
@ -89,21 +66,75 @@ func (p *Packet) Handle(stm StateManager, w radius.ResponseWriter, r *radius.Req
}
}
func (p *Packet) GetChallengeForType(ctx *context, t protocol.Type) *Packet {
func (p *Packet) handleInner(r *radius.Request) (*Packet, error) {
st := p.stm.GetEAPState(p.state)
if st == nil {
log.Debug("EAP: blank state")
st = BlankState(p.stm.GetEAPSettings())
}
nextChallengeToOffer, err := st.GetNextProtocol()
if err != nil {
return &Packet{
code: CodeFailure,
id: p.id,
}, err
}
if _, ok := p.Payload.(*legacy_nak.Payload); ok {
log.Debug("EAP: received NAK, trying next protocol")
st.ProtocolIndex += 1
p.stm.SetEAPState(p.state, st)
return p.handleInner(r)
}
np, _ := emptyPayload(p.stm, nextChallengeToOffer)
ctx := &context{
req: r,
state: st.TypeState[np.Type()],
log: log.WithField("type", fmt.Sprintf("%T", np)),
settings: p.stm.GetEAPSettings().ProtocolSettings[np.Type()],
}
ctx.log.Debug("EAP: Passing to protocol")
res := p.GetChallengeForType(ctx, np)
st.TypeState[np.Type()] = ctx.GetProtocolState()
p.stm.SetEAPState(p.state, st)
if ctx.endModifier != nil {
p.endModifier = ctx.endModifier
}
switch ctx.endStatus {
case protocol.StatusSuccess:
res.code = CodeSuccess
res.id -= 1
case protocol.StatusError:
res.code = CodeFailure
res.id -= 1
case protocol.StatusNextProtocol:
ctx.log.Debug("EAP: Protocol ended, starting next protocol")
st.ProtocolIndex += 1
p.stm.SetEAPState(p.state, st)
return p.handleInner(r)
case protocol.StatusUnknown:
}
return res, nil
}
func (p *Packet) GetChallengeForType(ctx *context, np protocol.Payload) *Packet {
res := &Packet{
code: CodeRequest,
id: p.id + 1,
msgType: t,
msgType: np.Type(),
}
var payload any
switch t {
case tls.TypeTLS:
if _, ok := p.Payload.(*tls.Payload); !ok {
p.Payload = &tls.Payload{}
p.Payload.Decode(p.rawPayload)
}
payload = p.Payload.(*tls.Payload).Handle(ctx)
if ctx.IsProtocolStart() {
p.Payload = np
p.Payload.Decode(p.rawPayload)
}
payload = p.Payload.Handle(ctx)
if payload != nil {
res.Payload = payload.(protocol.Payload)
}