Files
authentik/internal/outpost/radius/handler_eap.go
2026-01-26 19:51:38 +01:00

133 lines
3.8 KiB
Go

package radius
import (
"context"
ttls "crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"net/url"
eap "beryju.io/radius-eap"
"beryju.io/radius-eap/protocol"
"beryju.io/radius-eap/protocol/identity"
"beryju.io/radius-eap/protocol/legacy_nak"
"beryju.io/radius-eap/protocol/peap"
"beryju.io/radius-eap/protocol/tls"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/utils"
"layeh.com/radius"
"layeh.com/radius/rfc2869"
)
func (rs *RadiusServer) Handle_AccessRequest_EAP(w radius.ResponseWriter, r *RadiusRequest) {
er := rfc2869.EAPMessage_Get(r.Packet)
ep, err := eap.Decode(r.pi, er)
if err != nil {
rs.log.WithError(err).Warning("failed to parse EAP packet")
return
}
ep.HandleRadiusPacket(w, r.Request)
}
func (pi *ProviderInstance) GetEAPState(key string) *protocol.State {
return pi.eapState[key]
}
func (pi *ProviderInstance) SetEAPState(key string, state *protocol.State) {
pi.eapState[key] = state
}
func (pi *ProviderInstance) GetEAPSettings() protocol.Settings {
settings := protocol.Settings{
Logger: &logrusAdapter{pi.log},
Protocols: []protocol.ProtocolConstructor{
identity.Protocol,
legacy_nak.Protocol,
},
}
certId := pi.certId
if certId == "" {
return settings
}
cert := pi.s.cryptoStore.Get(certId)
if cert == nil {
return settings
}
settings.Protocols = append(settings.Protocols, tls.Protocol, peap.Protocol)
settings.ProtocolPriority = []protocol.Type{
identity.TypeIdentity,
tls.TypeTLS,
}
settings.ProtocolSettings = map[protocol.Type]any{
tls.TypeTLS: tls.Settings{
Config: &ttls.Config{
Certificates: []ttls.Certificate{*cert},
ClientAuth: ttls.RequireAnyClientCert,
},
HandshakeSuccessful: func(ctx protocol.Context, certs []*x509.Certificate) protocol.Status {
ident := ctx.GetProtocolState(identity.TypeIdentity).(*identity.State).Identity
ctx.Log().Debug("Starting authn flow")
pem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certs[0].Raw,
})
fe := flow.NewFlowExecutor(context.Background(), pi.flowSlug, pi.s.ac.Client.GetConfig(), log.Fields{
"client": utils.GetIP(ctx.Packet().RemoteAddr),
"identity": ident,
})
fe.Answers[flow.StageIdentification] = ident
fe.DelegateClientIP(utils.GetIP(ctx.Packet().RemoteAddr))
fe.Params.Add("goauthentik.io/outpost/radius", "true")
fe.AddHeader("X-Authentik-Outpost-Certificate", url.QueryEscape(string(pem)))
passed, err := fe.Execute()
if err != nil {
ctx.Log().Warn("failed to execute flow", "error", err)
return protocol.StatusError
}
ctx.Log().Debug("Finished flow")
if !passed {
return protocol.StatusError
}
access, _, err := fe.ApiClient().OutpostsAPI.OutpostsRadiusAccessCheck(context.Background(), pi.providerId).AppSlug(pi.appSlug).Execute()
if err != nil {
ctx.Log().Warn("failed to check access: %v", err)
return protocol.StatusError
}
if !access.Access.Passing {
ctx.Log().Info("Access denied for user")
return protocol.StatusError
}
if access.HasAttributes() {
ctx.AddResponseModifier(func(r, q *radius.Packet) error {
rawData, err := base64.StdEncoding.DecodeString(access.GetAttributes())
if err != nil {
ctx.Log().Warn("failed to decode attributes from core: %v", err)
return errors.New("attribute_decode_failed")
}
p, err := radius.Parse(rawData, pi.SharedSecret)
if err != nil {
ctx.Log().Warn("failed to parse attributes from core: %v", err)
return errors.New("attribute_parse_failed")
}
for _, attr := range p.Attributes {
r.Add(attr.Type, attr.Attribute)
}
return nil
})
}
return protocol.StatusSuccess
},
},
}
return settings
}