mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-01 13:17:21 +02:00
Merge pull request #646 from james-d-elliott/feat-forward-auth
feat: forward auth
This commit is contained in:
commit
b590e15ef2
@ -80,10 +80,9 @@ func RegisterTLSAPIs(authRouter *auth.RouterDef) {
|
|||||||
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
|
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the APIs for Authentication handlers like Authelia and OAUTH2
|
// Register the APIs for Authentication handlers like Forward Auth and OAUTH2
|
||||||
func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) {
|
func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) {
|
||||||
authRouter.HandleFunc("/api/sso/Authelia", autheliaRouter.HandleSetAutheliaURLAndHTTPS)
|
authRouter.HandleFunc("/api/sso/forward-auth", forwardAuthRouter.HandleAPIOptions)
|
||||||
authRouter.HandleFunc("/api/sso/Authentik", authentikRouter.HandleSetAuthentikURLAndHTTPS)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the APIs for redirection rules management functions
|
// Register the APIs for redirection rules management functions
|
||||||
|
12
src/def.go
12
src/def.go
@ -13,12 +13,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/auth/sso/authentik"
|
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/access"
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/acme"
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/auth/sso/authelia"
|
"imuslab.com/zoraxy/mod/auth/sso/forward"
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
"imuslab.com/zoraxy/mod/dockerux"
|
"imuslab.com/zoraxy/mod/dockerux"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
@ -43,8 +41,9 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
/* Build Constants */
|
/* Build Constants */
|
||||||
SYSTEM_NAME = "Zoraxy"
|
SYSTEM_NAME = "Zoraxy"
|
||||||
SYSTEM_VERSION = "3.2.1"
|
SYSTEM_VERSION = "3.2.2"
|
||||||
|
DEVELOPMENT_BUILD = false
|
||||||
|
|
||||||
/* System Constants */
|
/* System Constants */
|
||||||
TMP_FOLDER = "./tmp"
|
TMP_FOLDER = "./tmp"
|
||||||
@ -144,8 +143,7 @@ var (
|
|||||||
pluginManager *plugins.Manager //Plugin manager for managing plugins
|
pluginManager *plugins.Manager //Plugin manager for managing plugins
|
||||||
|
|
||||||
//Authentication Provider
|
//Authentication Provider
|
||||||
autheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication
|
forwardAuthRouter *forward.AuthRouter // Forward Auth router for Authelia/Authentik/etc authentication
|
||||||
authentikRouter *authentik.AuthentikRouter //Authentik router for Authentik authentication
|
|
||||||
|
|
||||||
//Helper modules
|
//Helper modules
|
||||||
EmailSender *email.Sender //Email sender that handle email sending
|
EmailSender *email.Sender //Email sender that handle email sending
|
||||||
|
@ -1,164 +0,0 @@
|
|||||||
package authelia
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/database"
|
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AutheliaRouterOptions struct {
|
|
||||||
UseHTTPS bool //If the Authelia server is using HTTPS
|
|
||||||
AutheliaURL string //The URL of the Authelia server
|
|
||||||
Logger *logger.Logger
|
|
||||||
Database *database.Database
|
|
||||||
}
|
|
||||||
|
|
||||||
type AutheliaRouter struct {
|
|
||||||
options *AutheliaRouterOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAutheliaRouter creates a new AutheliaRouter object
|
|
||||||
func NewAutheliaRouter(options *AutheliaRouterOptions) *AutheliaRouter {
|
|
||||||
options.Database.NewTable("authelia")
|
|
||||||
|
|
||||||
//Read settings from database, if exists
|
|
||||||
options.Database.Read("authelia", "autheliaURL", &options.AutheliaURL)
|
|
||||||
options.Database.Read("authelia", "useHTTPS", &options.UseHTTPS)
|
|
||||||
|
|
||||||
return &AutheliaRouter{
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleSetAutheliaURLAndHTTPS is the internal handler for setting the Authelia URL and HTTPS
|
|
||||||
func (ar *AutheliaRouter) HandleSetAutheliaURLAndHTTPS(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == http.MethodGet {
|
|
||||||
//Return the current settings
|
|
||||||
js, _ := json.Marshal(map[string]interface{}{
|
|
||||||
"useHTTPS": ar.options.UseHTTPS,
|
|
||||||
"autheliaURL": ar.options.AutheliaURL,
|
|
||||||
})
|
|
||||||
|
|
||||||
utils.SendJSONResponse(w, string(js))
|
|
||||||
return
|
|
||||||
} else if r.Method == http.MethodPost {
|
|
||||||
//Update the settings
|
|
||||||
autheliaURL, err := utils.PostPara(r, "autheliaURL")
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "autheliaURL not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
useHTTPS, err := utils.PostBool(r, "useHTTPS")
|
|
||||||
if err != nil {
|
|
||||||
useHTTPS = false
|
|
||||||
}
|
|
||||||
|
|
||||||
//Write changes to runtime
|
|
||||||
ar.options.AutheliaURL = autheliaURL
|
|
||||||
ar.options.UseHTTPS = useHTTPS
|
|
||||||
|
|
||||||
//Write changes to database
|
|
||||||
ar.options.Database.Write("authelia", "autheliaURL", autheliaURL)
|
|
||||||
ar.options.Database.Write("authelia", "useHTTPS", useHTTPS)
|
|
||||||
|
|
||||||
utils.SendOK(w)
|
|
||||||
} else {
|
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleAutheliaAuth is the internal handler for Authelia authentication
|
|
||||||
// Set useHTTPS to true if your authelia server is using HTTPS
|
|
||||||
// Set autheliaURL to the URL of the Authelia server, e.g. authelia.example.com
|
|
||||||
func (ar *AutheliaRouter) HandleAutheliaAuth(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
client := &http.Client{}
|
|
||||||
|
|
||||||
if ar.options.AutheliaURL == "" {
|
|
||||||
ar.options.Logger.PrintAndLog("Authelia", "Authelia URL not set", nil)
|
|
||||||
w.WriteHeader(500)
|
|
||||||
w.Write([]byte("500 - Internal Server Error"))
|
|
||||||
return errors.New("authelia URL not set")
|
|
||||||
}
|
|
||||||
protocol := "http"
|
|
||||||
if ar.options.UseHTTPS {
|
|
||||||
protocol = "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
autheliaURL := &url.URL{
|
|
||||||
Scheme: protocol,
|
|
||||||
Host: ar.options.AutheliaURL,
|
|
||||||
}
|
|
||||||
|
|
||||||
//Make a request to Authelia to verify the request
|
|
||||||
req, err := http.NewRequest("POST", autheliaURL.JoinPath("api", "verify").String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
ar.options.Logger.PrintAndLog("Authelia", "Unable to create request", err)
|
|
||||||
w.WriteHeader(401)
|
|
||||||
return errors.New("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
originalURL := rOriginalHeaders(r, req)
|
|
||||||
|
|
||||||
// Copy cookies from the incoming request
|
|
||||||
for _, cookie := range r.Cookies() {
|
|
||||||
req.AddCookie(cookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Making the verification request
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
ar.options.Logger.PrintAndLog("Authelia", "Unable to verify", err)
|
|
||||||
w.WriteHeader(401)
|
|
||||||
return errors.New("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
redirectURL := autheliaURL.JoinPath()
|
|
||||||
|
|
||||||
query := redirectURL.Query()
|
|
||||||
|
|
||||||
query.Set("rd", originalURL.String())
|
|
||||||
query.Set("rm", r.Method)
|
|
||||||
|
|
||||||
http.Redirect(w, r, redirectURL.String(), http.StatusSeeOther)
|
|
||||||
return errors.New("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rOriginalHeaders(r, req *http.Request) *url.URL {
|
|
||||||
if r.RemoteAddr != "" {
|
|
||||||
before, _, _ := strings.Cut(r.RemoteAddr, ":")
|
|
||||||
|
|
||||||
if ip := net.ParseIP(before); ip != nil {
|
|
||||||
req.Header.Set("X-Forwarded-For", ip.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
originalURL := &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: r.Host,
|
|
||||||
Path: r.URL.Path,
|
|
||||||
RawPath: r.URL.RawPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.TLS != nil {
|
|
||||||
originalURL.Scheme = "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("X-Forwarded-Method", r.Method)
|
|
||||||
req.Header.Add("X-Original-URL", originalURL.String())
|
|
||||||
|
|
||||||
return originalURL
|
|
||||||
}
|
|
@ -1,169 +0,0 @@
|
|||||||
package authentik
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/database"
|
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AuthentikRouterOptions struct {
|
|
||||||
UseHTTPS bool //If the Authentik server is using HTTPS
|
|
||||||
AuthentikURL string //The URL of the Authentik server
|
|
||||||
Logger *logger.Logger
|
|
||||||
Database *database.Database
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthentikRouter struct {
|
|
||||||
options *AuthentikRouterOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAuthentikRouter creates a new AuthentikRouter object
|
|
||||||
func NewAuthentikRouter(options *AuthentikRouterOptions) *AuthentikRouter {
|
|
||||||
options.Database.NewTable("authentik")
|
|
||||||
|
|
||||||
//Read settings from database, if exists
|
|
||||||
options.Database.Read("authentik", "authentikURL", &options.AuthentikURL)
|
|
||||||
options.Database.Read("authentik", "useHTTPS", &options.UseHTTPS)
|
|
||||||
|
|
||||||
return &AuthentikRouter{
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleSetAuthentikURLAndHTTPS is the internal handler for setting the Authentik URL and HTTPS
|
|
||||||
func (ar *AuthentikRouter) HandleSetAuthentikURLAndHTTPS(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == http.MethodGet {
|
|
||||||
//Return the current settings
|
|
||||||
js, _ := json.Marshal(map[string]interface{}{
|
|
||||||
"useHTTPS": ar.options.UseHTTPS,
|
|
||||||
"authentikURL": ar.options.AuthentikURL,
|
|
||||||
})
|
|
||||||
|
|
||||||
utils.SendJSONResponse(w, string(js))
|
|
||||||
return
|
|
||||||
} else if r.Method == http.MethodPost {
|
|
||||||
//Update the settings
|
|
||||||
AuthentikURL, err := utils.PostPara(r, "authentikURL")
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "authentikURL not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
useHTTPS, err := utils.PostBool(r, "useHTTPS")
|
|
||||||
if err != nil {
|
|
||||||
useHTTPS = false
|
|
||||||
}
|
|
||||||
|
|
||||||
//Write changes to runtime
|
|
||||||
ar.options.AuthentikURL = AuthentikURL
|
|
||||||
ar.options.UseHTTPS = useHTTPS
|
|
||||||
|
|
||||||
//Write changes to database
|
|
||||||
ar.options.Database.Write("authentik", "authentikURL", AuthentikURL)
|
|
||||||
ar.options.Database.Write("authentik", "useHTTPS", useHTTPS)
|
|
||||||
|
|
||||||
utils.SendOK(w)
|
|
||||||
} else {
|
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleAuthentikAuth is the internal handler for Authentik authentication
|
|
||||||
// Set useHTTPS to true if your Authentik server is using HTTPS
|
|
||||||
// Set AuthentikURL to the URL of the Authentik server, e.g. Authentik.example.com
|
|
||||||
func (ar *AuthentikRouter) HandleAuthentikAuth(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
const outpostPrefix = "outpost.goauthentik.io"
|
|
||||||
client := &http.Client{}
|
|
||||||
|
|
||||||
if ar.options.AuthentikURL == "" {
|
|
||||||
ar.options.Logger.PrintAndLog("Authentik", "Authentik URL not set", nil)
|
|
||||||
w.WriteHeader(500)
|
|
||||||
w.Write([]byte("500 - Internal Server Error"))
|
|
||||||
return errors.New("authentik URL not set")
|
|
||||||
}
|
|
||||||
protocol := "http"
|
|
||||||
if ar.options.UseHTTPS {
|
|
||||||
protocol = "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
authentikBaseURL := protocol + "://" + ar.options.AuthentikURL
|
|
||||||
//Remove tailing slash if any
|
|
||||||
authentikBaseURL = strings.TrimSuffix(authentikBaseURL, "/")
|
|
||||||
|
|
||||||
scheme := "http"
|
|
||||||
if r.TLS != nil {
|
|
||||||
scheme = "https"
|
|
||||||
}
|
|
||||||
reqUrl := scheme + "://" + r.Host + r.RequestURI
|
|
||||||
// Pass request to outpost if path matches outpost prefix
|
|
||||||
if reqPath := strings.TrimPrefix(r.URL.Path, "/"); strings.HasPrefix(reqPath, outpostPrefix) {
|
|
||||||
req, err := http.NewRequest(r.Method, authentikBaseURL+r.RequestURI, r.Body)
|
|
||||||
if err != nil {
|
|
||||||
ar.options.Logger.PrintAndLog("Authentik", "Unable to create request", err)
|
|
||||||
w.WriteHeader(401)
|
|
||||||
return errors.New("unauthorized")
|
|
||||||
}
|
|
||||||
req.Header.Set("X-Original-URL", reqUrl)
|
|
||||||
req.Header.Set("Host", r.Host)
|
|
||||||
for _, cookie := range r.Cookies() {
|
|
||||||
req.AddCookie(cookie)
|
|
||||||
}
|
|
||||||
if resp, err := client.Do(req); err != nil {
|
|
||||||
ar.options.Logger.PrintAndLog("Authentik", "Unable to pass request to Authentik outpost", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return errors.New("internal server error")
|
|
||||||
} else {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
for k := range resp.Header {
|
|
||||||
w.Header().Set(k, resp.Header.Get(k))
|
|
||||||
}
|
|
||||||
w.WriteHeader(resp.StatusCode)
|
|
||||||
if _, err = io.Copy(w, resp.Body); err != nil {
|
|
||||||
ar.options.Logger.PrintAndLog("Authentik", "Unable to pass Authentik outpost response to client", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return errors.New("internal server error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//Make a request to Authentik to verify the request
|
|
||||||
req, err := http.NewRequest(http.MethodGet, authentikBaseURL+"/"+outpostPrefix+"/auth/nginx", nil)
|
|
||||||
if err != nil {
|
|
||||||
ar.options.Logger.PrintAndLog("Authentik", "Unable to create request", err)
|
|
||||||
w.WriteHeader(401)
|
|
||||||
return errors.New("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("X-Original-URL", reqUrl)
|
|
||||||
|
|
||||||
// Copy cookies from the incoming request
|
|
||||||
for _, cookie := range r.Cookies() {
|
|
||||||
req.AddCookie(cookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Making the verification request
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
ar.options.Logger.PrintAndLog("Authentik", "Unable to verify", err)
|
|
||||||
w.WriteHeader(401)
|
|
||||||
return errors.New("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
redirectURL := authentikBaseURL + "/" + outpostPrefix + "/start?rd=" + url.QueryEscape(scheme+"://"+r.Host+r.URL.String())
|
|
||||||
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
|
|
||||||
return errors.New("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
46
src/mod/auth/sso/forward/const.go
Normal file
46
src/mod/auth/sso/forward/const.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package forward
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogTitle = "Forward Auth"
|
||||||
|
|
||||||
|
DatabaseTable = "auth_sso_forward"
|
||||||
|
|
||||||
|
DatabaseKeyAddress = "address"
|
||||||
|
DatabaseKeyResponseHeaders = "responseHeaders"
|
||||||
|
DatabaseKeyResponseClientHeaders = "responseClientHeaders"
|
||||||
|
DatabaseKeyRequestHeaders = "requestHeaders"
|
||||||
|
DatabaseKeyRequestExcludedCookies = "requestExcludedCookies"
|
||||||
|
|
||||||
|
HeaderXForwardedProto = "X-Forwarded-Proto"
|
||||||
|
HeaderXForwardedHost = "X-Forwarded-Host"
|
||||||
|
HeaderXForwardedFor = "X-Forwarded-For"
|
||||||
|
HeaderXForwardedURI = "X-Forwarded-URI"
|
||||||
|
HeaderXForwardedMethod = "X-Forwarded-Method"
|
||||||
|
|
||||||
|
HeaderCookie = "Cookie"
|
||||||
|
|
||||||
|
HeaderUpgrade = "Upgrade"
|
||||||
|
HeaderConnection = "Connection"
|
||||||
|
HeaderTransferEncoding = "Transfer-Encoding"
|
||||||
|
HeaderTE = "TE"
|
||||||
|
HeaderTrailers = "Trailers"
|
||||||
|
HeaderKeepAlive = "Keep-Alive"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInternalServerError = errors.New("internal server error")
|
||||||
|
ErrUnauthorized = errors.New("unauthorized")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
doNotCopyHeaders = []string{
|
||||||
|
HeaderUpgrade,
|
||||||
|
HeaderConnection,
|
||||||
|
HeaderTransferEncoding,
|
||||||
|
HeaderTE,
|
||||||
|
HeaderTrailers,
|
||||||
|
HeaderKeepAlive,
|
||||||
|
}
|
||||||
|
)
|
334
src/mod/auth/sso/forward/forward.go
Normal file
334
src/mod/auth/sso/forward/forward.go
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
package forward
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthRouterOptions struct {
|
||||||
|
// Address of the forward auth endpoint.
|
||||||
|
Address string
|
||||||
|
|
||||||
|
// ResponseHeaders is a list of headers to be copied from the response if provided by the forward auth endpoint to
|
||||||
|
// the request.
|
||||||
|
ResponseHeaders []string
|
||||||
|
|
||||||
|
// ResponseClientHeaders is a list of headers to be copied from the response if provided by the forward auth
|
||||||
|
// endpoint to the response to the client.
|
||||||
|
ResponseClientHeaders []string
|
||||||
|
|
||||||
|
// RequestHeaders is a list of headers to be copied from the request to the authorization server. If empty all
|
||||||
|
// headers are copied.
|
||||||
|
RequestHeaders []string
|
||||||
|
|
||||||
|
// RequestExcludedCookies is a list of cookie keys that should be removed from every request sent to the upstream.
|
||||||
|
RequestExcludedCookies []string
|
||||||
|
|
||||||
|
Logger *logger.Logger
|
||||||
|
Database *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthRouter struct {
|
||||||
|
client *http.Client
|
||||||
|
options *AuthRouterOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthRouter creates a new AuthRouter object
|
||||||
|
func NewAuthRouter(options *AuthRouterOptions) *AuthRouter {
|
||||||
|
options.Database.NewTable(DatabaseTable)
|
||||||
|
|
||||||
|
//Read settings from database if available.
|
||||||
|
options.Database.Read(DatabaseTable, DatabaseKeyAddress, &options.Address)
|
||||||
|
|
||||||
|
responseHeaders, responseClientHeaders, requestHeaders, requestExcludedCookies := "", "", "", ""
|
||||||
|
|
||||||
|
options.Database.Read(DatabaseTable, DatabaseKeyResponseHeaders, &responseHeaders)
|
||||||
|
options.Database.Read(DatabaseTable, DatabaseKeyResponseClientHeaders, &responseClientHeaders)
|
||||||
|
options.Database.Read(DatabaseTable, DatabaseKeyRequestHeaders, &requestHeaders)
|
||||||
|
options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, &requestExcludedCookies)
|
||||||
|
|
||||||
|
options.ResponseHeaders = strings.Split(responseHeaders, ",")
|
||||||
|
options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
|
||||||
|
options.RequestHeaders = strings.Split(requestHeaders, ",")
|
||||||
|
options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
||||||
|
|
||||||
|
return &AuthRouter{
|
||||||
|
client: &http.Client{
|
||||||
|
CheckRedirect: func(r *http.Request, via []*http.Request) (err error) {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleAPIOptions is the internal handler for setting the options.
|
||||||
|
func (ar *AuthRouter) HandleAPIOptions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
ar.handleOptionsGET(w, r)
|
||||||
|
case http.MethodPost:
|
||||||
|
ar.handleOptionsPOST(w, r)
|
||||||
|
default:
|
||||||
|
ar.handleOptionsMethodNotAllowed(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *AuthRouter) handleOptionsGET(w http.ResponseWriter, r *http.Request) {
|
||||||
|
js, _ := json.Marshal(map[string]interface{}{
|
||||||
|
DatabaseKeyAddress: ar.options.Address,
|
||||||
|
DatabaseKeyResponseHeaders: ar.options.ResponseHeaders,
|
||||||
|
DatabaseKeyResponseClientHeaders: ar.options.ResponseClientHeaders,
|
||||||
|
DatabaseKeyRequestHeaders: ar.options.RequestHeaders,
|
||||||
|
DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies,
|
||||||
|
})
|
||||||
|
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Update the settings
|
||||||
|
address, err := utils.PostPara(r, DatabaseKeyAddress)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "address not found")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are optional fields and can be empty strings.
|
||||||
|
responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders)
|
||||||
|
responseClientHeaders, _ := utils.PostPara(r, DatabaseKeyResponseClientHeaders)
|
||||||
|
requestHeaders, _ := utils.PostPara(r, DatabaseKeyRequestHeaders)
|
||||||
|
requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies)
|
||||||
|
|
||||||
|
// Write changes to runtime
|
||||||
|
ar.options.Address = address
|
||||||
|
ar.options.ResponseHeaders = strings.Split(responseHeaders, ",")
|
||||||
|
ar.options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
|
||||||
|
ar.options.RequestHeaders = strings.Split(requestHeaders, ",")
|
||||||
|
ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
||||||
|
|
||||||
|
// Write changes to database
|
||||||
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyAddress, address)
|
||||||
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseHeaders, responseHeaders)
|
||||||
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseClientHeaders, responseClientHeaders)
|
||||||
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestHeaders, requestHeaders)
|
||||||
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *AuthRouter) handleOptionsMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleAuthProviderRouting is the internal handler for Forward Auth authentication.
|
||||||
|
func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
if ar.options.Address == "" {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
ar.options.Logger.PrintAndLog(LogTitle, "Address not set", nil)
|
||||||
|
|
||||||
|
return ErrInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a request to Authz Server to verify the request
|
||||||
|
req, err := http.NewRequest(http.MethodGet, ar.options.Address, nil)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
ar.options.Logger.PrintAndLog(LogTitle, "Unable to create request", err)
|
||||||
|
|
||||||
|
return ErrInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add opt-in support for copying the request body to the forward auth request.
|
||||||
|
headerCopyIncluded(r.Header, req.Header, ar.options.RequestHeaders, true)
|
||||||
|
|
||||||
|
// TODO: Add support for upstream headers.
|
||||||
|
rSetForwardedHeaders(r, req)
|
||||||
|
|
||||||
|
// Make the Authz Request.
|
||||||
|
respForwarded, err := ar.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
ar.options.Logger.PrintAndLog(LogTitle, "Unable to perform forwarded auth due to a request error", err)
|
||||||
|
|
||||||
|
return ErrInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
defer respForwarded.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(respForwarded.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
ar.options.Logger.PrintAndLog(LogTitle, "Unable to read response to forward auth request", err)
|
||||||
|
|
||||||
|
return ErrInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responses within the 200-299 range are considered successful and allow the proxy to handle the request.
|
||||||
|
if respForwarded.StatusCode >= http.StatusOK && respForwarded.StatusCode < http.StatusMultipleChoices {
|
||||||
|
if len(ar.options.ResponseClientHeaders) != 0 {
|
||||||
|
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseClientHeaders, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ar.options.RequestExcludedCookies) != 0 {
|
||||||
|
// If the user has specified a list of cookies to be removed from the request, deterministically remove them.
|
||||||
|
headerCookieRedact(r, ar.options.RequestExcludedCookies)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ar.options.ResponseHeaders) != 0 {
|
||||||
|
// Copy specific user-specified headers from the response of the forward auth request to the request sent to the
|
||||||
|
// upstream server/next hop.
|
||||||
|
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseHeaders, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the response.
|
||||||
|
headerCopyExcluded(respForwarded.Header, w.Header(), nil)
|
||||||
|
|
||||||
|
w.WriteHeader(respForwarded.StatusCode)
|
||||||
|
if _, err = w.Write(body); err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
ar.options.Logger.PrintAndLog(LogTitle, "Unable to write response", err)
|
||||||
|
|
||||||
|
return ErrInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
func scheme(r *http.Request) string {
|
||||||
|
if r.TLS != nil {
|
||||||
|
return "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
func headerCookieRedact(r *http.Request, excluded []string) {
|
||||||
|
original := r.Cookies()
|
||||||
|
|
||||||
|
if len(original) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookies []string
|
||||||
|
|
||||||
|
for _, cookie := range original {
|
||||||
|
if stringInSlice(cookie.Name, excluded) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cookies = append(cookies, cookie.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Header.Set(HeaderCookie, strings.Join(cookies, "; "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func headerCopyExcluded(original, destination http.Header, excludedHeaders []string) {
|
||||||
|
for key, values := range original {
|
||||||
|
// We should never copy the headers in the below list.
|
||||||
|
if stringInSliceFold(key, doNotCopyHeaders) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if stringInSliceFold(key, excludedHeaders) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
destination[key] = append(destination[key], values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func headerCopyIncluded(original, destination http.Header, includedHeaders []string, allIfEmpty bool) {
|
||||||
|
if allIfEmpty && len(includedHeaders) == 0 {
|
||||||
|
headerCopyAll(original, destination)
|
||||||
|
} else {
|
||||||
|
headerCopyIncludedExact(original, destination, includedHeaders)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func headerCopyAll(original, destination http.Header) {
|
||||||
|
for key, values := range original {
|
||||||
|
// We should never copy the headers in the below list, even if they're in the list provided by a user.
|
||||||
|
if stringInSliceFold(key, doNotCopyHeaders) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
destination[key] = append(destination[key], values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func headerCopyIncludedExact(original, destination http.Header, keys []string) {
|
||||||
|
for _, key := range keys {
|
||||||
|
// We should never copy the headers in the below list, even if they're in the list provided by a user.
|
||||||
|
if stringInSliceFold(key, doNotCopyHeaders) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if values, ok := original[key]; ok {
|
||||||
|
destination[key] = append(destination[key], values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringInSlice(needle string, haystack []string) bool {
|
||||||
|
if len(haystack) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range haystack {
|
||||||
|
if needle == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringInSliceFold(needle string, haystack []string) bool {
|
||||||
|
if len(haystack) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range haystack {
|
||||||
|
if strings.EqualFold(needle, v) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func rSetForwardedHeaders(r, req *http.Request) {
|
||||||
|
if r.RemoteAddr != "" {
|
||||||
|
before, _, _ := strings.Cut(r.RemoteAddr, ":")
|
||||||
|
|
||||||
|
if ip := net.ParseIP(before); ip != nil {
|
||||||
|
req.Header.Set(HeaderXForwardedFor, ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set(HeaderXForwardedMethod, r.Method)
|
||||||
|
req.Header.Set(HeaderXForwardedProto, scheme(r))
|
||||||
|
req.Header.Set(HeaderXForwardedHost, r.Host)
|
||||||
|
req.Header.Set(HeaderXForwardedURI, r.URL.Path)
|
||||||
|
}
|
@ -32,20 +32,16 @@ and return a boolean indicate if the request is written to http.ResponseWriter
|
|||||||
*/
|
*/
|
||||||
func handleAuthProviderRouting(sep *ProxyEndpoint, w http.ResponseWriter, r *http.Request, h *ProxyHandler) bool {
|
func handleAuthProviderRouting(sep *ProxyEndpoint, w http.ResponseWriter, r *http.Request, h *ProxyHandler) bool {
|
||||||
requestHostname := r.Host
|
requestHostname := r.Host
|
||||||
if sep.AuthenticationProvider.AuthMethod == AuthMethodBasic {
|
|
||||||
|
switch sep.AuthenticationProvider.AuthMethod {
|
||||||
|
case AuthMethodBasic:
|
||||||
err := h.handleBasicAuthRouting(w, r, sep)
|
err := h.handleBasicAuthRouting(w, r, sep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else if sep.AuthenticationProvider.AuthMethod == AuthMethodAuthelia {
|
case AuthMethodForward:
|
||||||
err := h.handleAutheliaAuth(w, r)
|
err := h.handleForwardAuth(w, r)
|
||||||
if err != nil {
|
|
||||||
h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else if sep.AuthenticationProvider.AuthMethod == AuthMethodAuthentik {
|
|
||||||
err := h.handleAuthentikAuth(w, r)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
|
||||||
return true
|
return true
|
||||||
@ -106,13 +102,9 @@ func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Authelia */
|
/* Forward Auth */
|
||||||
|
|
||||||
// Handle authelia auth routing
|
// Handle forward auth routing
|
||||||
func (h *ProxyHandler) handleAutheliaAuth(w http.ResponseWriter, r *http.Request) error {
|
func (h *ProxyHandler) handleForwardAuth(w http.ResponseWriter, r *http.Request) error {
|
||||||
return h.Parent.Option.AutheliaRouter.HandleAutheliaAuth(w, r)
|
return h.Parent.Option.ForwardAuthRouter.HandleAuthProviderRouting(w, r)
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ProxyHandler) handleAuthentikAuth(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
return h.Parent.Option.AuthentikRouter.HandleAuthentikAuth(w, r)
|
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,15 @@ import (
|
|||||||
// GetDefaultAuthenticationProvider return a default authentication provider
|
// GetDefaultAuthenticationProvider return a default authentication provider
|
||||||
func GetDefaultAuthenticationProvider() *AuthenticationProvider {
|
func GetDefaultAuthenticationProvider() *AuthenticationProvider {
|
||||||
return &AuthenticationProvider{
|
return &AuthenticationProvider{
|
||||||
AuthMethod: AuthMethodNone,
|
AuthMethod: AuthMethodNone,
|
||||||
BasicAuthCredentials: []*BasicAuthCredentials{},
|
BasicAuthCredentials: []*BasicAuthCredentials{},
|
||||||
BasicAuthExceptionRules: []*BasicAuthExceptionRule{},
|
BasicAuthExceptionRules: []*BasicAuthExceptionRule{},
|
||||||
BasicAuthGroupIDs: []string{},
|
BasicAuthGroupIDs: []string{},
|
||||||
AutheliaURL: "",
|
ForwardAuthURL: "",
|
||||||
UseHTTPS: false,
|
ForwardAuthResponseHeaders: []string{},
|
||||||
|
ForwardAuthResponseClientHeaders: []string{},
|
||||||
|
ForwardAuthRequestHeaders: []string{},
|
||||||
|
ForwardAuthRequestExcludedCookies: []string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,13 +9,12 @@ package dynamicproxy
|
|||||||
*/
|
*/
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"imuslab.com/zoraxy/mod/auth/sso/authentik"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/access"
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/auth/sso/authelia"
|
"imuslab.com/zoraxy/mod/auth/sso/forward"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
@ -64,8 +63,7 @@ type RouterOption struct {
|
|||||||
PluginManager *plugins.Manager //Plugin manager for handling plugin routing
|
PluginManager *plugins.Manager //Plugin manager for handling plugin routing
|
||||||
|
|
||||||
/* Authentication Providers */
|
/* Authentication Providers */
|
||||||
AutheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication
|
ForwardAuthRouter *forward.AuthRouter
|
||||||
AuthentikRouter *authentik.AuthentikRouter //Authentik router for Authentik authentication
|
|
||||||
|
|
||||||
/* Utilities */
|
/* Utilities */
|
||||||
Logger *logger.Logger //Logger for reverse proxy requets
|
Logger *logger.Logger //Logger for reverse proxy requets
|
||||||
@ -141,11 +139,10 @@ type HeaderRewriteRules struct {
|
|||||||
type AuthMethod int
|
type AuthMethod int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AuthMethodNone AuthMethod = iota //No authentication required
|
AuthMethodNone AuthMethod = iota //No authentication required
|
||||||
AuthMethodBasic //Basic Auth
|
AuthMethodBasic //Basic Auth
|
||||||
AuthMethodAuthelia //Authelia
|
AuthMethodForward //Forward
|
||||||
AuthMethodOauth2 //Oauth2
|
AuthMethodOauth2 //Oauth2
|
||||||
AuthMethodAuthentik
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthenticationProvider struct {
|
type AuthenticationProvider struct {
|
||||||
@ -155,9 +152,12 @@ type AuthenticationProvider struct {
|
|||||||
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||||
BasicAuthGroupIDs []string //Group IDs that are allowed to access this endpoint
|
BasicAuthGroupIDs []string //Group IDs that are allowed to access this endpoint
|
||||||
|
|
||||||
/* Authelia Settings */
|
/* Forward Auth Settings */
|
||||||
AutheliaURL string //URL of the Authelia server, leave empty to use global settings e.g. authelia.example.com
|
ForwardAuthURL string // Full URL of the Forward Auth endpoint. Example: https://auth.example.com/api/authz/forward-auth
|
||||||
UseHTTPS bool //Whether to use HTTPS for the Authelia server
|
ForwardAuthResponseHeaders []string // List of headers to copy from the forward auth server response to the request.
|
||||||
|
ForwardAuthResponseClientHeaders []string // List of headers to copy from the forward auth server response to the client response.
|
||||||
|
ForwardAuthRequestHeaders []string // List of headers to copy from the original request to the auth server. If empty all are copied.
|
||||||
|
ForwardAuthRequestExcludedCookies []string // List of cookies to exclude from the request after sending it to the forward auth server.
|
||||||
}
|
}
|
||||||
|
|
||||||
// A proxy endpoint record, a general interface for handling inbound routing
|
// A proxy endpoint record, a general interface for handling inbound routing
|
||||||
|
@ -115,8 +115,7 @@ func ReverseProxtInit() {
|
|||||||
StatisticCollector: statisticCollector,
|
StatisticCollector: statisticCollector,
|
||||||
WebDirectory: *path_webserver,
|
WebDirectory: *path_webserver,
|
||||||
AccessController: accessController,
|
AccessController: accessController,
|
||||||
AutheliaRouter: autheliaRouter,
|
ForwardAuthRouter: forwardAuthRouter,
|
||||||
AuthentikRouter: authentikRouter,
|
|
||||||
LoadBalancer: loadBalancer,
|
LoadBalancer: loadBalancer,
|
||||||
PluginManager: pluginManager,
|
PluginManager: pluginManager,
|
||||||
/* Utilities */
|
/* Utilities */
|
||||||
@ -585,11 +584,9 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
if authProviderType == 1 {
|
if authProviderType == 1 {
|
||||||
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodBasic
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodBasic
|
||||||
} else if authProviderType == 2 {
|
} else if authProviderType == 2 {
|
||||||
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthelia
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodForward
|
||||||
} else if authProviderType == 3 {
|
} else if authProviderType == 3 {
|
||||||
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOauth2
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOauth2
|
||||||
} else if authProviderType == 4 {
|
|
||||||
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthentik
|
|
||||||
} else {
|
} else {
|
||||||
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodNone
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodNone
|
||||||
}
|
}
|
||||||
|
18
src/start.go
18
src/start.go
@ -15,7 +15,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/access"
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/acme"
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/auth/sso/authelia"
|
"imuslab.com/zoraxy/mod/auth/sso/forward"
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
"imuslab.com/zoraxy/mod/database/dbinc"
|
"imuslab.com/zoraxy/mod/database/dbinc"
|
||||||
"imuslab.com/zoraxy/mod/dockerux"
|
"imuslab.com/zoraxy/mod/dockerux"
|
||||||
@ -143,18 +143,10 @@ func startupSequence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Create authentication providers
|
//Create authentication providers
|
||||||
autheliaRouter = authelia.NewAutheliaRouter(&authelia.AutheliaRouterOptions{
|
forwardAuthRouter = forward.NewAuthRouter(&forward.AuthRouterOptions{
|
||||||
UseHTTPS: false, // Automatic populate in router initiation
|
Address: "",
|
||||||
AutheliaURL: "", // Automatic populate in router initiation
|
Logger: SystemWideLogger,
|
||||||
Logger: SystemWideLogger,
|
Database: sysdb,
|
||||||
Database: sysdb,
|
|
||||||
})
|
|
||||||
|
|
||||||
authentikRouter = authentik.NewAuthentikRouter(&authentik.AuthentikRouterOptions{
|
|
||||||
UseHTTPS: false, // Automatic populate in router initiation
|
|
||||||
AuthentikURL: "", // Automatic populate in router initiation
|
|
||||||
Logger: SystemWideLogger,
|
|
||||||
Database: sysdb,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
//Create a statistic collector
|
//Create a statistic collector
|
||||||
|
@ -185,9 +185,8 @@
|
|||||||
</td>
|
</td>
|
||||||
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||||
${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``}
|
${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``}
|
||||||
${subd.AuthenticationProvider.AuthMethod == 0x2?`<i class="ui blue key icon"></i> Authelia`:``}
|
${subd.AuthenticationProvider.AuthMethod == 0x2?`<i class="ui blue key icon"></i> Forward Auth`:``}
|
||||||
${subd.AuthenticationProvider.AuthMethod == 0x3?`<i class="ui yellow key icon"></i> Oauth2`:``}
|
${subd.AuthenticationProvider.AuthMethod == 0x3?`<i class="ui yellow key icon"></i> OAuth2`:``}
|
||||||
${subd.AuthenticationProvider.AuthMethod == 0x4?`<i class="ui blue key icon"></i> Authentik`:``}
|
|
||||||
${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"<br>":""}
|
${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"<br>":""}
|
||||||
${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
|
${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
|
||||||
${subd.AuthenticationProvider.AuthMethod == 0x0 && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
|
${subd.AuthenticationProvider.AuthMethod == 0x0 && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
|
||||||
@ -393,13 +392,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
<input type="radio" value="2" name="authProviderType" ${authProvider==0x2?"checked":""}>
|
<input type="radio" value="2" name="authProviderType" ${authProvider==0x2?"checked":""}>
|
||||||
<label>Authelia</label>
|
<label>Forward Auth</label>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<div class="ui radio checkbox">
|
|
||||||
<input type="radio" value="4" name="authProviderType" ${authProvider==0x4?"checked":""}>
|
|
||||||
<label>Authentik</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,44 +14,53 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<h3>Authelia</h3>
|
<h3>Forward Auth</h3>
|
||||||
<p>Configuration settings for Authelia authentication provider.</p>
|
<p>Configuration settings for the Forward Auth provider.</p>
|
||||||
|
<p>The Forward Auth provider makes a subrequest to an authorization server that supports Forward Auth, then either:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Allows the request to flow through to the backend when the authorization server responds with a 200-299 status code.</li>
|
||||||
|
<li>Responds with the response from the authorization server.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Example authorization servers that support this:</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://www.authelia.com" rel=”noopener noreferrer” target="_blank">Authelia</a></li>
|
||||||
|
<li><a href="https://goauthentik.io/" rel=”noopener noreferrer” target="_blank">Authentik</a></li>
|
||||||
|
</ul>
|
||||||
<form class="ui form">
|
<form class="ui form">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="autheliaServerUrl">Authelia Server URL</label>
|
<label for="forwardAuthAddress">Address</label>
|
||||||
<input type="text" id="autheliaServerUrl" name="autheliaServerUrl" placeholder="Enter Authelia Server URL">
|
<input type="text" id="forwardAuthAddress" name="forwardAuthAddress" placeholder="Enter Forward Auth Address">
|
||||||
<small>Example: auth.example.com</small>
|
<small>The full remote address or URL of the authorization servers forward auth endpoint. <strong>Example:</strong> https://auth.example.com/authz/forward-auth</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="ui advancedSSOForwardAuthOptions accordion" style="margin-top:0.6em;">
|
||||||
<div class="ui checkbox">
|
<div class="title">
|
||||||
<input type="checkbox" id="useHttps" name="useHttps">
|
<i class="dropdown icon"></i>
|
||||||
<label for="useHttps">Use HTTPS</label>
|
Advanced Options
|
||||||
<small>Check this if your authelia server uses HTTPS</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="content">
|
||||||
<button class="ui basic button" onclick="event.preventDefault(); updateAutheliaSettings();"><i class="green check icon"></i> Apply Change</button>
|
<div class="field">
|
||||||
</form>
|
<label for="forwardAuthResponseHeaders">Response Headers</label>
|
||||||
</div>
|
<input type="text" id="forwardAuthResponseHeaders" name="forwardAuthResponseHeaders" placeholder="Enter Forward Auth Response Headers">
|
||||||
<div class="ui divider"></div>
|
<small>Comma separated list of case-insensitive headers to copy from the authorization servers response to the request sent to the backend. If not set no headers are copied. <strong>Example:</strong> <code>Remote-User,Remote-Groups,Remote-Email,Remote-Name</code></small>
|
||||||
<div class="ui basic segment">
|
</div>
|
||||||
<h3>Authentik</h3>
|
<div class="field">
|
||||||
<p>Configuration settings for Authentik authentication provider.</p>
|
<label for="forwardAuthResponseClientHeaders">Response Client Headers</label>
|
||||||
|
<input type="text" id="forwardAuthResponseClientHeaders" name="forwardAuthResponseClientHeaders" placeholder="Enter Forward Auth Response Client Headers">
|
||||||
<form class="ui form">
|
<small>Comma separated list of case-insensitive headers to copy from the authorization servers response to the response sent to the client. If not set no headers are copied. <strong>Example:</strong> <code>Set-Cookie,WWW-Authenticate</code></small>
|
||||||
<div class="field">
|
</div>
|
||||||
<label for="authentikServerUrl">Authentik Server URL</label>
|
<div class="field">
|
||||||
<input type="text" id="authentikServerUrl" name="authentikServerUrl" placeholder="Enter Authentik Server URL">
|
<label for="forwardAuthRequestHeaders">Request Headers</label>
|
||||||
<small>Example: auth.example.com</small>
|
<input type="text" id="forwardAuthRequestHeaders" name="forwardAuthRequestHeaders" placeholder="Enter Forward Auth Request Headers">
|
||||||
</div>
|
<small>Comma separated list of case-insensitive headers to copy from the original request to the request made to the authorization server. If not set all headers are copied. <strong>Example:</strong> <code>Cookie,Authorization</code></small>
|
||||||
<div class="field">
|
</div>
|
||||||
<div class="ui checkbox">
|
<div class="field">
|
||||||
<input type="checkbox" id="authentikUseHttps" name="useHttps">
|
<label for="forwardAuthRequestExcludedCookies">Request Excluded Cookies</label>
|
||||||
<label for="authentikUseHttps">Use HTTPS</label>
|
<input type="text" id="forwardAuthRequestExcludedCookies" name="forwardAuthRequestExcludedCookies" placeholder="Enter Forward Auth Request Excluded Cookies">
|
||||||
<small>Check this if your Authentik server uses HTTPS</small>
|
<small>Comma separated list of case-sensitive cookie names to exclude from the request to the backend. If not set no cookies are excluded. <strong>Example:</strong> <code>authelia_session,another_session</code></small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div><br />
|
||||||
<button class="ui basic button" onclick="event.preventDefault(); updateAuthentikSettings();"><i class="green check icon"></i> Apply Change</button>
|
<button class="ui basic button" onclick="event.preventDefault(); updateForwardAuthSettings();"><i class="green check icon"></i> Apply Change</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
@ -60,24 +69,15 @@
|
|||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$.cjax({
|
$.cjax({
|
||||||
url: '/api/sso/Authelia',
|
url: '/api/sso/forward-auth',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
$('#autheliaServerUrl').val(data.autheliaURL);
|
$('#forwardAuthAddress').val(data.address);
|
||||||
$('#useHttps').prop('checked', data.useHTTPS);
|
$('#forwardAuthResponseHeaders').val(data.responseHeaders.join(","));
|
||||||
},
|
$('#forwardAuthResponseClientHeaders').val(data.responseClientHeaders.join(","));
|
||||||
error: function(jqXHR, textStatus, errorThrown) {
|
$('#forwardAuthRequestHeaders').val(data.requestHeaders.join(","));
|
||||||
console.error('Error fetching SSO settings:', textStatus, errorThrown);
|
$('#forwardAuthRequestExcludedCookies').val(data.requestExcludedCookies.join(","));
|
||||||
}
|
|
||||||
});
|
|
||||||
$.cjax({
|
|
||||||
url: '/api/sso/Authentik',
|
|
||||||
method: 'GET',
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(data) {
|
|
||||||
$('#authentikServerUrl').val(data.authentikURL);
|
|
||||||
$('#authentikUseHttps').prop('checked', data.useHTTPS);
|
|
||||||
},
|
},
|
||||||
error: function(jqXHR, textStatus, errorThrown) {
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
console.error('Error fetching SSO settings:', textStatus, errorThrown);
|
console.error('Error fetching SSO settings:', textStatus, errorThrown);
|
||||||
@ -85,51 +85,35 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateAutheliaSettings(){
|
function updateForwardAuthSettings() {
|
||||||
var autheliaServerUrl = $('#autheliaServerUrl').val();
|
const address = $('#forwardAuthAddress').val();
|
||||||
var useHttps = $('#useHttps').prop('checked');
|
const responseHeaders = $('#forwardAuthResponseHeaders').val();
|
||||||
|
const responseClientHeaders = $('#forwardAuthResponseClientHeaders').val();
|
||||||
|
const requestHeaders = $('#forwardAuthRequestHeaders').val();
|
||||||
|
const requestExcludedCookies = $('#forwardAuthRequestExcludedCookies').val();
|
||||||
|
|
||||||
|
console.log(`Updating Forward Auth settings. Address: ${address}. Response Headers: ${responseHeaders}. Response Client Headers: ${responseClientHeaders}. Request Headers: ${requestHeaders}. Request Excluded Cookies: ${requestExcludedCookies}.`);
|
||||||
|
|
||||||
$.cjax({
|
$.cjax({
|
||||||
url: '/api/sso/Authelia',
|
url: '/api/sso/forward-auth',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
autheliaURL: autheliaServerUrl,
|
address: address,
|
||||||
useHTTPS: useHttps
|
responseHeaders: responseHeaders,
|
||||||
|
responseClientHeaders: responseClientHeaders,
|
||||||
|
requestHeaders: requestHeaders,
|
||||||
|
requestExcludedCookies: requestExcludedCookies
|
||||||
},
|
},
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
if (data.error != undefined) {
|
if (data.error !== undefined) {
|
||||||
$.msgbox(data.error, false);
|
msgbox(data.error, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
msgbox('Authelia settings updated', true);
|
msgbox('Forward Auth settings updated', true);
|
||||||
console.log('Authelia settings updated:', data);
|
console.log('Forward Auth settings updated:', data);
|
||||||
},
|
},
|
||||||
error: function(jqXHR, textStatus, errorThrown) {
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
console.error('Error updating Authelia settings:', textStatus, errorThrown);
|
console.error('Error updating Forward Auth settings:', textStatus, errorThrown);
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function updateAuthentikSettings(){
|
|
||||||
var authentikServerUrl = $('#authentikServerUrl').val();
|
|
||||||
var useHttps = $('#authentikUseHttps').prop('checked');
|
|
||||||
|
|
||||||
$.cjax({
|
|
||||||
url: '/api/sso/Authentik',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
authentikURL: authentikServerUrl,
|
|
||||||
useHTTPS: useHttps
|
|
||||||
},
|
|
||||||
success: function(data) {
|
|
||||||
if (data.error != undefined) {
|
|
||||||
$.msgbox(data.error, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
msgbox('Authentik settings updated', true);
|
|
||||||
console.log('Authentik settings updated:', data);
|
|
||||||
},
|
|
||||||
error: function(jqXHR, textStatus, errorThrown) {
|
|
||||||
console.error('Error updating Authentik settings:', textStatus, errorThrown);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user