This commit is contained in:
Zeev Diukman 2025-03-03 14:27:20 +00:00
parent 5a6eed8c57
commit 5724939500
17 changed files with 576 additions and 356 deletions

View file

@ -5,16 +5,16 @@ tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/server/."
cmd = "go build -buildvcs=false -o ./tmp/main ./cmd/server/"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata","docker"]
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html","yml","yaml"]
include_ext = ["go", "tpl", "tmpl", "html","yml"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"

52
.air.toml.bkup Normal file
View file

@ -0,0 +1,52 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/server/."
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata","docker"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html","yml","yaml"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
silent = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true

15
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Process",
"type": "go",
"request": "attach",
"mode": "local",
"processId": 0
}
]
}

View file

@ -10,87 +10,85 @@ import (
)
func CallbackHandler(w http.ResponseWriter, r *http.Request) {
config.Wrapper(func(c *config.Config) {
// ctx := context.Background()
query := r.URL.Query()
configData := config.Get()
code := query.Get("code")
state := query.Get("state")
// ctx := context.Background()
query := r.URL.Query()
verifier := app.SessionManager.GetString(r.Context(), "code_verifier")
if verifier == "" {
http.Error(w, "Code verifier not found in session", http.StatusBadRequest)
return
}
expectedState := app.SessionManager.GetString(r.Context(), "state")
if state != expectedState {
http.Error(w, "Invalid state parameter", http.StatusBadRequest)
return
}
// originalURL, err := decodeState(state)
// if err != nil {
// dump.P(err.Error())
// http.Error(w, "Invalid state", http.StatusBadRequest)
// return
// }
originalPath := app.SessionManager.GetString(r.Context(), "original_path")
code := query.Get("code")
state := query.Get("state")
authName := c.GetAuthNameByDomain(r.Host)
token, fullResponse, e := exchangeCode(code, verifier, c, authName)
if e != nil {
dump.Println("exchangeCode: " + e.Error())
}
verifier := appData.SessionManager.GetString(r.Context(), "code_verifier")
if verifier == "" {
http.Error(w, "Code verifier not found in session", http.StatusBadRequest)
return
}
expectedState := appData.SessionManager.GetString(r.Context(), "state")
if state != expectedState {
http.Error(w, "Invalid state parameter", http.StatusBadRequest)
return
}
originalURL, err := decodeState(state)
if err != nil {
dump.P(err.Error())
http.Error(w, "Invalid state", http.StatusBadRequest)
return
}
dump.P("Original_Path: " + originalURL)
// originalPath := appData.SessionManager.GetString(r.Context(), "original_path")
app.SessionManager.Put(r.Context(), "access_token", token.AccessToken)
app.SessionManager.Put(r.Context(), "full_token", fullResponse)
authName := configData.GetAuthNameByDomain(r.Host)
token, fullResponse, e := exchangeCode(code, verifier, authName)
if e != nil {
dump.Println("exchangeCode: " + e.Error())
}
// SetAuthHeader(w, token.AccessToken)
http.Redirect(w, r, originalPath, http.StatusFound)
// http.Redirect(w, r, originalURL, http.StatusFound)
})
appData.SessionManager.Put(r.Context(), "access_token", token.AccessToken)
appData.SessionManager.Put(r.Context(), "full_token", fullResponse)
// SetAuthHeader(w, token.AccessToken)
// http.Redirect(w, r, originalPath, http.StatusFound)
http.Redirect(w, r, originalURL, http.StatusFound)
}
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
config.Wrapper(func(c *config.Config) {
configData := config.Get()
//TODO: only after returninig, delete the session!
app.SessionManager.Remove(r.Context(), "access_token")
app.SessionManager.Remove(r.Context(), "full_token")
//TODO: only after returninig, delete the session!
appData.SessionManager.Remove(r.Context(), "access_token")
appData.SessionManager.Remove(r.Context(), "full_token")
authName := c.DataMaps.DomainToAuth[r.Host]
a := c.Auth[authName]
u := a.OpenID.EndPoints.Logout
http.Redirect(w, r, u, http.StatusFound)
})
authName := configData.DataMaps.DomainToAuth[r.Host]
a := configData.AuthMap[authName]
u := a.OpenID.EndPoints.Logout
http.Redirect(w, r, u, http.StatusFound)
}
func LoginHandler(w http.ResponseWriter, r *http.Request) {
config.Wrapper(func(c *config.Config) {
configData := config.Get()
authName := c.DataMaps.DomainToAuth[r.Host]
authName := configData.DataMaps.DomainToAuth[r.Host]
// state := helper.RandStringByBits(64)
nonce := helper.RandStringByBits(64)
authURL, _ := url.Parse(c.Auth[authName].OpenID.EndPoints.Auth)
query := authURL.Query()
// state := helper.RandStringByBits(64)
nonce := helper.RandStringByBits(64)
authURL, _ := url.Parse(configData.AuthMap[authName].OpenID.EndPoints.Auth)
query := authURL.Query()
codeVerifier, _ := generateCodeVerifier()
codeChallenge := generateCodeChallenge(codeVerifier)
originalPath := app.SessionManager.GetString(r.Context(), "original_path")
state := generateState(url.QueryEscape(originalPath))
query.Set("client_id", c.Auth[authName].OpenID.ClientID)
query.Set("response_type", "code")
query.Set("scope", "openid")
query.Set("redirect_uri", c.Auth[authName].OpenID.RedirectURI)
query.Set("code_challenge", codeChallenge)
query.Set("code_challenge_method", "S256")
query.Set("state", state)
query.Set("nonce", nonce)
authURL.RawQuery = query.Encode()
app.SessionManager.Put(r.Context(), "state", state)
app.SessionManager.Put(r.Context(), "code_verifier", codeVerifier)
http.Redirect(w, r, authURL.String(), http.StatusFound)
})
codeVerifier, _ := generateCodeVerifier()
codeChallenge := generateCodeChallenge(codeVerifier)
originalPath := appData.SessionManager.GetString(r.Context(), "original_path")
state := generateState(url.QueryEscape(originalPath))
query.Set("client_id", configData.AuthMap[authName].OpenID.ClientID)
query.Set("response_type", "code")
query.Set("scope", "openid")
query.Set("redirect_uri", configData.AuthMap[authName].OpenID.RedirectURI)
query.Set("code_challenge", codeChallenge)
query.Set("code_challenge_method", "S256")
query.Set("state", state)
query.Set("nonce", nonce)
authURL.RawQuery = query.Encode()
appData.SessionManager.Put(r.Context(), "state", state)
appData.SessionManager.Put(r.Context(), "code_verifier", codeVerifier)
http.Redirect(w, r, authURL.String(), http.StatusFound)
}

View file

@ -36,87 +36,94 @@ type EntryPoint struct {
type ReverseProxies map[string]ReverseProxy
type ReverseProxy *httputil.ReverseProxy
var app = logic.NewApp()
var (
appData *logic.App = logic.NewApp()
// configData *config.Config
)
func main() {
helper.AppRunner(func() {
config.Wrapper(func(c *config.Config) {
groups := logic.NewGroups()
mainRouter := router.New()
groups.ForEach(func(k string, g *logic.Group) {
groupSubRouter := mainRouter.Mux.NewRoute().Subrouter()
groupSubRouter.Use(Domain_Middleware)
for k := range g.ReverseProxies {
rpConfig := c.ReverseProxies[k]
domain := rpConfig.Domain
proxy := reverse_proxy.New(rpConfig.Host)
proxy.Name = domain
newRoute := groupSubRouter.NewRoute()
subRouter := newRoute.Host(domain).Subrouter()
if rpConfig.Auth != "" {
if _, ok := c.Auth[rpConfig.Auth]; !ok {
err := errors.New("Error: Auth " + rpConfig.Auth + " not exist!")
panic(err.Error())
}
pths := c.Auth[rpConfig.Auth].Paths
authRoute := subRouter.NewRoute()
subRouter.Use(Middleware_SetHeaders)
authSubRouter := authRoute.PathPrefix(pths.Prefix).Subrouter()
authSubRouter.Path(pths.Login).Handler(http.HandlerFunc(LoginHandler))
authSubRouter.Path(pths.Logout).Handler(http.HandlerFunc(LogoutHandler))
authSubRouter.Path(pths.Callback).Handler(http.HandlerFunc(CallbackHandler))
subRouter.Use(authMiddleware)
configData := config.Get()
//////////TESTING AREA//////////////////
////////////////////////////////////////
groups := logic.NewGroups()
mainRouter := router.New()
groups.ForEach(func(groupName string, g *logic.Group) {
mainRouter.AddGroupRouter(groupName)
mainRouter.GetGroupRouter(groupName).Mux.Use(Domain_Middleware)
for k := range g.ReverseProxies {
rpConfig := configData.ReverseProxies[k]
domain := rpConfig.Domain
proxy := reverse_proxy.New(rpConfig.Host)
proxy.Name = domain
mainRouter.AddHostSubRouter(groupName, domain)
if rpConfig.Auth != "" {
if _, ok := configData.AuthMap[rpConfig.Auth]; !ok {
err := errors.New("Error: Auth " + rpConfig.Auth + " not exist!")
panic(err.Error())
}
subRouter.PathPrefix("/").Handler(proxy.Httputil)
// Filter out static file requests first
pths := configData.AuthMap[rpConfig.Auth].Paths
hostRouterMux := mainRouter.GetHostSubRouter(groupName, domain).Mux
hostRouterMux.Use(Middleware_SetHeaders)
hostRouterMux.Use(authMiddleware)
authRoute := hostRouterMux.NewRoute()
authSubRouter := authRoute.PathPrefix(pths.Prefix).Subrouter()
authSubRouter.Path(pths.Login).Handler(http.HandlerFunc(LoginHandler))
authSubRouter.Path(pths.Logout).Handler(http.HandlerFunc(LogoutHandler))
authSubRouter.Path(pths.Callback).Handler(http.HandlerFunc(CallbackHandler))
}
mainRouter.GetHostSubRouter(groupName, domain).Mux.PathPrefix("/").Handler(proxy.Httputil)
// Filter out static file requests first
if len(g.ReverseProxies) > 0 {
tlsConfig := &tls.Config{
}
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
// crt, key := "", ""
if len(g.ReverseProxies) > 0 {
tlsConfig := &tls.Config{
crt, key := c.GetCertsPairByDomain(info.ServerName)
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
// crt, key := "", ""
if crt == "" && key == "" {
// crt = c.TLS.Certs["default"].Cert
// key = c.TLS.Certs["default"].Key
// panic("Error: TLS cert and key not found!")
crt, key := configData.GetCertsPairByDomain(info.ServerName)
if crt == "" && key == "" {
// crt = configData.TLS.Certs["default"].Cert
// key = configData.TLS.Certs["default"].Key
// panic("Error: TLS cert and key not found!")
}
cert, err := loadCertificate(crt, key)
if err != nil {
return nil, err
}
return &cert, nil
},
}
server := &http.Server{
Addr: ":" + g.Port,
Handler: app.SessionManager.LoadAndSave(groupSubRouter),
TLSConfig: tlsConfig,
}
var err error
go func() {
ipAddr := helper.GetIP()
log.Println("Test server is running at http://" + ipAddr + ":" + g.Port)
if g.TLS {
err = server.ListenAndServeTLS("", "")
} else {
err = server.ListenAndServe()
}
cert, err := loadCertificate(crt, key)
if err != nil {
log.Println(err.Error())
return nil, err
}
}()
return &cert, nil
},
}
server := &http.Server{
Addr: ":" + g.Port,
Handler: appData.SessionManager.LoadAndSave(mainRouter.GetGroupRouter(groupName).Mux),
TLSConfig: tlsConfig,
}
var err error
go func() {
ipAddr := helper.GetIP()
log.Println("Test server is running at http://" + ipAddr + ":" + g.Port)
if g.TLS {
err = server.ListenAndServeTLS("", "")
} else {
err = server.ListenAndServe()
}
if err != nil {
log.Println(err.Error())
}
}()
}
})
helper.StartTestHTTPServer(3000)
})
helper.StartTestHTTPServer(3000, "app")
helper.StartTestHTTPServer(3001, "oded")
})
}
@ -130,67 +137,69 @@ func main() {
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
configData := config.Get()
currentPath := r.URL.Path
authName := configData.GetAuthNameByDomain(r.Host)
loginPath := configData.AuthMap[authName].Paths.Prefix + configData.AuthMap[authName].Paths.Login
logoutPath := configData.AuthMap[authName].Paths.Prefix + configData.AuthMap[authName].Paths.Logout
callbackPath := configData.AuthMap[authName].Paths.Prefix + configData.AuthMap[authName].Paths.Callback
// TODO: mark auth reverse proxy in yaml
proxyName := configData.GetProxyNameByDomain(r.Host)
if configData.ReverseProxies[proxyName].IsAuthServer {
next.ServeHTTP(w, r)
return
}
config.Wrapper(func(c *config.Config) {
currentPath := r.URL.Path
authName := c.GetAuthNameByDomain(r.Host)
loginPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Login
logoutPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Logout
callbackPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Callback
// TODO: mark auth reverse proxy in yaml
switch currentPath {
case loginPath:
{
// fmt.Fprintln(w, "LOGIN")
next.ServeHTTP(w, r)
if r.Host == "keycloak.z.com" {
}
case logoutPath:
{
next.ServeHTTP(w, r)
// return
}
case callbackPath:
{
next.ServeHTTP(w, r)
// return
}
default:
{
accessToken := appData.SessionManager.GetString(r.Context(), "access_token")
if accessToken == "" {
authName := configData.DataMaps.DomainToAuth[r.Host]
http.Redirect(w, r, configData.AuthMap[authName].Paths.Prefix+configData.AuthMap[authName].Paths.Login, http.StatusFound)
return
}
// auth.SetAuthHeader(w, accessToken)
a := configData.AuthMap[authName]
pths := a.Paths
prefix := pths.Prefix
login := pths.Login
logout := pths.Logout
loginPath := prefix + login
logoutPath := prefix + logout
if loginPath == r.URL.Path || logoutPath == r.URL.Path {
next.ServeHTTP(w, r)
// return
}
tokenOk := IsAuthorizedJWT(accessToken, authName)
if !tokenOk {
http.Redirect(w, r, loginPath, http.StatusFound)
return
}
next.ServeHTTP(w, r)
}
switch currentPath {
case loginPath:
{
// fmt.Fprintln(w, "LOGIN")
next.ServeHTTP(w, r)
}
case logoutPath:
{
next.ServeHTTP(w, r)
// return
}
case callbackPath:
{
next.ServeHTTP(w, r)
// return
}
default:
{
accessToken := app.SessionManager.GetString(r.Context(), "access_token")
if accessToken == "" {
authName := c.DataMaps.DomainToAuth[r.Host]
http.Redirect(w, r, c.Auth[authName].Paths.Prefix+c.Auth[authName].Paths.Login, http.StatusFound)
return
}
// auth.SetAuthHeader(w, accessToken)
a := c.Auth[authName]
pths := a.Paths
prefix := pths.Prefix
login := pths.Login
logout := pths.Logout
loginPath := prefix + login
logoutPath := prefix + logout
if loginPath == r.URL.Path || logoutPath == r.URL.Path {
next.ServeHTTP(w, r)
// return
}
tokenOk := IsAuthorizedJWT(accessToken, c, "default")
if !tokenOk {
http.Redirect(w, r, loginPath, http.StatusFound)
return
}
next.ServeHTTP(w, r)
}
}
})
}
})
}
@ -200,8 +209,8 @@ func Domain_Middleware(next http.Handler) http.Handler {
// c := config.Get()
// requestedPath := r.URL.Path
// authName := c.GetAuthNameByDomain(r.Host)
// auth := c.Auth[authName]
// authName := configData.GetAuthNameByDomain(r.Host)
// auth := configData.AuthMap[authName]
// excludedPaths := []string{
// auth.Paths.Prefix + auth.Paths.Login,
// auth.Paths.Prefix + auth.Paths.Callback,
@ -210,7 +219,7 @@ func Domain_Middleware(next http.Handler) http.Handler {
// contains := helper.IsSliceContains(excludedPaths, requestedPath)
// contains := slices.Contains(excludedPaths, requestedPath)
// if !contains {
// app.SessionManager.Put(r.Context(), "original_path", requestedPath)
// appData.SessionManager.Put(r.Context(), "original_path", requestedPath)
// }
// dump.P(requestedPath)
@ -303,12 +312,22 @@ func decodeState(encodedState string) (string, error) {
}
return stateData["redirect_uri"], nil
}
func exchangeCode(code string, verifier string, c *config.Config, authName string) (*TokenResponse, string, error) {
func exchangeCode(code string, verifier string, authName string) (*TokenResponse, string, error) {
configData := config.Get()
clientID := configData.AuthMap[authName].OpenID.ClientID
clientSecert := configData.AuthMap[authName].OpenID.ClientSecert
redirectURI := configData.AuthMap[authName].OpenID.RedirectURI
// if strings.Contains(redirectURI,"<{{dynamic}}>") {
// configData.GenerateDynamicRedirectUri()
// }
// GenerateDynamicRedirectUri
data := url.Values{}
data.Set("grant_type", "authorization_code")
data.Set("client_id", c.Auth[authName].OpenID.ClientID)
data.Set("client_secret", c.Auth[authName].OpenID.ClientSecert)
data.Set("redirect_uri", c.Auth[authName].OpenID.RedirectURI)
data.Set("client_id", clientID)
data.Set("client_secret", clientSecert)
data.Set("redirect_uri", redirectURI)
data.Set("code", code)
data.Set("scope", "openid zapp")
if verifier != "" {
@ -318,7 +337,7 @@ func exchangeCode(code string, verifier string, c *config.Config, authName strin
TLSClientConfig: &tls.Config{InsecureSkipVerify: DEVELOPMENT},
}
client := &http.Client{Transport: tr}
u := c.Auth[authName].OpenID.EndPoints.Token
u := configData.AuthMap[authName].OpenID.EndPoints.Token
r, _ := http.NewRequest(http.MethodPost, u, strings.NewReader(data.Encode()))
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(r)
@ -358,7 +377,9 @@ func generateCodeChallenge(verifier string) string {
return base64.RawURLEncoding.EncodeToString(hash[:])
}
func IsAuthorizedJWT(rawAccessToken string, c *config.Config, authName string) bool {
func IsAuthorizedJWT(rawAccessToken string, authName string) bool {
configData := config.Get()
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
@ -367,14 +388,14 @@ func IsAuthorizedJWT(rawAccessToken string, c *config.Config, authName string) b
Transport: tr,
}
ctx := oidc.ClientContext(context.Background(), client)
provider, err := oidc.NewProvider(ctx, c.Auth[authName].OpenID.EndPoints.Issuer)
provider, err := oidc.NewProvider(ctx, configData.AuthMap[authName].OpenID.EndPoints.Issuer)
if err != nil {
dump.Println("authorisation failed while getting the provider: " + err.Error())
return false
}
oidcConfig := &oidc.Config{
ClientID: c.Auth[authName].OpenID.ClientID,
ClientID: configData.AuthMap[authName].OpenID.ClientID,
}
verifier := provider.Verifier(oidcConfig)
idToken, err := verifier.Verify(ctx, rawAccessToken)
@ -394,22 +415,22 @@ func IsAuthorizedJWT(rawAccessToken string, c *config.Config, authName string) b
///////////////////////////////////
///////////////////////////////////
func isStaticFileRequest(path string) bool {
// Check for common static file prefixes
if strings.HasPrefix(path, "/static/") || strings.HasPrefix(path, "/assets/") {
return true
}
// func isStaticFileRequest(path string) bool {
// // Check for common static file prefixes
// if strings.HasPrefix(path, "/static/") || strings.HasPrefix(path, "/assets/") {
// return true
// }
// Check for common static file extensions
staticExtensions := []string{
".css", ".js", ".jpg", ".jpeg", ".png", ".gif", ".svg", ".ico",
}
// // Check for common static file extensions
// staticExtensions := []string{
// ".css", ".js", ".jpg", ".jpeg", ".png", ".gif", ".svg", ".ico",
// }
for _, ext := range staticExtensions {
if strings.HasSuffix(path, ext) {
return true
}
}
// for _, ext := range staticExtensions {
// if strings.HasSuffix(path, ext) {
// return true
// }
// }
return false
}
// return false
// }

View file

@ -8,7 +8,6 @@ reverse_proxies:
enabled: true
certs: default
auth_server: true
app:
domain: app.z.com
host: http://127.0.0.1:3000
@ -16,7 +15,16 @@ reverse_proxies:
tls:
enabled: true
certs: default
auth: default
auth: app_auth
oded:
domain: oded.z.com
host: http://127.0.0.1:3001
entry_point: https
tls:
enabled: true
certs: default
auth: oded_auth
tls:
certs:
@ -32,7 +40,7 @@ entry_points:
port: 80
auth:
default:
app_auth:
paths:
prefix: /auth
login: /login
@ -43,19 +51,25 @@ auth:
realm: dev
client_id: dev_client
client_secret: dWhSJgARBAuBAXN7sUTpqpIq2sKQdugs
redirect_uri: https://app.z.com/auth/callback
redirect_uri: <{{dynamic}}>/auth/callback
post_logout_redirect_uri: https://app.z.com/auth/logout
config_path: /realms/{{realm}}/.well-known/openid-configuration
# config_fields:
# - issuer
# - authorization_endpoint
# - token_endpoint
# - introspection_endpoint
# - userinfo_endpoint
# - end_session_endpoint
# - jwks_uri
# issuer: http://127.0.0.1:8080/realms/dev
config_path: /realms/<{{realm}}>/.well-known/openid-configuration
oded_auth:
paths:
prefix: /auth
login: /login
logout: /logout
callback: /callback
open_id:
host: http://127.0.0.1:8080
realm: dev
client_id: dev_client
client_secret: dWhSJgARBAuBAXN7sUTpqpIq2sKQdugs
# redirect_uri: https://oded.z.com/auth/callback
redirect_uri: <{{dynamic}}>/auth/callback
post_logout_redirect_uri: https://oded.z.com/auth/logout
# post_logout_redirect_uri: <{{dynamic}}>/auth/logout
config_path: /realms/<{{realm}}>/.well-known/openid-configuration
# scope: openid profile email

View file

@ -15,20 +15,28 @@ const Viper_File_Name string = "config"
const Viper_File_Type string = "yaml"
const Viper_File_Path string = "."
var c *Config
var (
configData *Config
)
func init() {
helper.New().Screen.Clear()
c = createConfig()
h := helper.New()
h.Screen.Clear()
configData = createConfig()
}
func Wrapper(fn func(c *Config)) {
fn(c)
fn(configData)
}
func Get() *Config {
return c
return configData
}
func Get2() *Config {
return configData
}
func createConfig() *Config {
c := &Config{}
c.initViper()
c.setViperOptions(Viper_File_Name, Viper_File_Type, Viper_File_Path)
@ -76,6 +84,19 @@ func (rp *ReverseProxies) ForEach(fn func(rpName string, rpConfig *ReverseProxy)
}
}
func (rp *ReverseProxies) ForEachReturn(fn func(rpName string, rpConfig *ReverseProxy) (res ForEachReturn)) *ForEachReturn {
res := ForEachReturn{}
for rpName, rpData := range *rp {
res = append(fn(rpName, &rpData), res)
}
return &res
}
// func (rp AuthMap) ForEach(fn func(authName string, authConfig Auth)) {
// for authName, authConfig := range rp {
// fn(authName, authConfig)
// }
// }
func (c *Config) fetchEndPointsByAuthName(authName string) map[string]any {
configURL := c.KeycloakWellknownURL(authName)
resp := helper.IsFetchOK[EndPoints](configURL, "", http.Get)
@ -89,10 +110,10 @@ func (c *Config) fetchEndPointsByAuthName(authName string) map[string]any {
}
func (c *Config) initOpenIDEndPoints() {
for authName := range c.Auth {
for authName := range c.AuthMap {
data := c.fetchEndPointsByAuthName(authName)
c.Auth[authName].OpenID.EndPoints = &EndPoints{
c.AuthMap[authName].OpenID.EndPoints = &EndPoints{
Issuer: data["issuer"].(string),
Auth: data["authorization_endpoint"].(string),
Introspection: data["introspection_endpoint"].(string),
@ -107,17 +128,35 @@ func (c *Config) initOpenIDEndPoints() {
}
func (c *Config) KeycloakWellknownURL(authName string) string {
if _, ok := c.Auth[authName]; !ok {
if _, ok := c.AuthMap[authName]; !ok {
return ""
}
hostUrl := c.Auth[authName].OpenID.Host
realm := c.Auth[authName].OpenID.Realm
configPath := c.Auth[authName].OpenID.ConfigPath
hostUrl := c.AuthMap[authName].OpenID.Host
realm := c.AuthMap[authName].OpenID.Realm
configPath := c.AuthMap[authName].OpenID.ConfigPath
u := hostUrl
u += strings.ReplaceAll(configPath, "{{realm}}", realm)
u += strings.ReplaceAll(configPath, "<{{realm}}>", realm)
return u
}
func (c *Config) GenerateDynamicRedirectUri(proxyName string, authName string) string {
if _, ok := c.AuthMap[authName]; !ok {
return ""
}
proto := "http"
domain := c.ReverseProxies[proxyName].Domain
tls := c.ReverseProxies[proxyName].TLS.Enabled
if tls {
proto = "https"
}
redirectURI := c.AuthMap[authName].OpenID.RedirectURI
u := redirectURI
u = strings.ReplaceAll(redirectURI, "<{{dynamic}}>", proto+"://"+domain)
return u
}
func (c *Config) initDomainToProxyNameMap() {
mp := make(map[string]string)
c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) {
@ -126,17 +165,22 @@ func (c *Config) initDomainToProxyNameMap() {
c.DataMaps.DomainToProxy = mp
}
func (c *Config) initDomainToProxyAuthName() {
authMap := map[string]int{}
for authName := range c.Auth {
for authName := range c.AuthMap {
authMap[authName] = 1
}
mp := make(map[string]string)
c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) {
if v, ok := authMap[rpConfig.Auth]; ok && v == 1 {
mp[rpConfig.Domain] = rpConfig.Auth
}
})
c.DataMaps.DomainToAuth = mp
}
func (c *Config) initDomainToCertName() {
crtMap := map[string]int{}
@ -155,6 +199,7 @@ func (c *Config) initDomainToCertName() {
func (c *Config) GetAuthNameByDomain(domain string) string {
return c.DataMaps.DomainToAuth[domain]
}
func (c *Config) GetProxyNameByDomain(domain string) string {
return c.DataMaps.DomainToProxy[domain]
}
@ -174,5 +219,25 @@ func (c *Config) GetCertsPairByDomain(domain string) (string, string) {
}
return crt, key
}
func (c *Config) GetProxiesSliceByAuthName(authName string) []string {
slc := &[]string{}
c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) {
isOK := true
if authName != rpConfig.Auth {
isOK = false
}
if _, ok := c.AuthMap[authName]; !ok {
isOK = false
}
if isOK {
helper.AppendToPointer(slc, rpName)
}
})
return *slc
}
func (c *Config) GetAuthNameByProxyName(proxyName string) string {
return c.ReverseProxies[proxyName].Auth
}

View file

@ -1,93 +0,0 @@
package config
import "github.com/spf13/viper"
type Config struct {
Viper *viper.Viper
DataMaps DataMaps
ReverseProxies ReverseProxies `mapstructure:"reverse_proxies"`
TLS TLS `mapstructure:"tls"`
EntryPoints EntryPoints `mapstructure:"entry_points"`
Auth map[string]*AuthInstance `mapstructure:"auth"`
}
type DataMaps struct {
DomainToProxy map[string]string
DomainToAuth map[string]string
DomainToCert map[string]string
}
// AUTH
// type Auth map[string]AuthInstance
type AuthInstance struct {
Paths Paths `mapstructure:"paths"`
OpenID OpenID `mapstructure:"open_id"`
}
type Paths struct {
Prefix string `mapstructure:"prefix"`
Login string `mapstructure:"login"`
Logout string `mapstructure:"logout"`
Callback string `mapstructure:"callback"`
}
type OpenID struct {
Host string `mapstructure:"host"`
Realm string `mapstructure:"realm"`
ClientID string `mapstructure:"client_id"`
ClientSecert string `mapstructure:"client_secret"`
RedirectURI string `mapstructure:"redirect_uri"`
PostLogoutRedirectURI string `mapstructure:"post_logout_redirect_uri"`
ConfigPath string `mapstructure:"config_path"`
EndPoints *EndPoints
}
type EndPoints struct {
Issuer string
Auth string
Introspection string
Token string
UserInfo string
Logout string
JwksUri string
}
// type OpenIdEndPoints struct {
// Issuer string
// Authorization string
// Token string
// Introspection string
// UserInfo string
// EndSession string
// }
// ReverseProxies
type ReverseProxies map[string]ReverseProxy
type ReverseProxy struct {
Domain string `mapstructure:"domain"`
Host string `mapstructure:"host"`
EntryPoint string `mapstructure:"entry_point"`
TLS TLS_RP `mapstructure:"tls"`
Auth string `mapstructure:"auth"`
}
type TLS_RP struct {
Enabled bool `mapstructure:"enabled"`
Certs string `mapstructure:"certs"`
}
// TLS
type TLS struct {
Certs map[string]Certs `mapstructure:"certs"`
}
type Certs struct {
Cert string `mapstructure:"cert"`
Key string `mapstructure:"key"`
}
// EntryPoints
type EntryPoints map[string]EntryPoint
type EntryPoint struct {
Port string `mapstructure:"port"`
TLS bool `mapstructure:"tls"`
}

View file

@ -0,0 +1,36 @@
package config
type AuthMap map[string]*Auth
type Auth struct {
Paths Paths `mapstructure:"paths"`
OpenID OpenID `mapstructure:"open_id"`
}
type Paths struct {
Prefix string `mapstructure:"prefix"`
Login string `mapstructure:"login"`
Logout string `mapstructure:"logout"`
Callback string `mapstructure:"callback"`
}
type OpenID struct {
Host string `mapstructure:"host"`
Realm string `mapstructure:"realm"`
ClientID string `mapstructure:"client_id"`
ClientSecert string `mapstructure:"client_secret"`
RedirectURI string `mapstructure:"redirect_uri"`
PostLogoutRedirectURI string `mapstructure:"post_logout_redirect_uri"`
ConfigPath string `mapstructure:"config_path"`
EndPoints *EndPoints
}
type EndPoints struct {
Issuer string
Auth string
Introspection string
Token string
UserInfo string
Logout string
JwksUri string
}

View file

@ -0,0 +1,20 @@
package config
import "github.com/spf13/viper"
type Config struct {
Viper *viper.Viper
DataMaps DataMaps
ReverseProxies ReverseProxies `mapstructure:"reverse_proxies"`
TLS TLS `mapstructure:"tls"`
EntryPoints EntryPoints `mapstructure:"entry_points"`
AuthMap AuthMap `mapstructure:"auth"`
}
type DataMaps struct {
DomainToProxy map[string]string
DomainToAuth map[string]string
DomainToCert map[string]string
}
type ForEachReturn []any

View file

@ -0,0 +1,8 @@
package config
type EntryPoints map[string]EntryPoint
type EntryPoint struct {
Port string `mapstructure:"port"`
TLS bool `mapstructure:"tls"`
}

View file

@ -0,0 +1,12 @@
package config
type ReverseProxies map[string]ReverseProxy
type ReverseProxy struct {
Domain string `mapstructure:"domain"`
Host string `mapstructure:"host"`
EntryPoint string `mapstructure:"entry_point"`
TLS TLS_RP `mapstructure:"tls"`
Auth string `mapstructure:"auth"`
IsAuthServer bool `mapstructure:"auth_server"`
}

View file

@ -0,0 +1,15 @@
package config
type TLS struct {
Certs map[string]Certs `mapstructure:"certs"`
}
type Certs struct {
Cert string `mapstructure:"cert"`
Key string `mapstructure:"key"`
}
type TLS_RP struct {
Enabled bool `mapstructure:"enabled"`
Certs string `mapstructure:"certs"`
}

View file

@ -1,24 +1,77 @@
package router
import "github.com/gorilla/mux"
import (
"github.com/gorilla/mux"
"zeevdiukman.com/zprox/pkg/helper"
)
func New() *MainRouter {
r := &MainRouter{}
r.SetMux()
r.setMux()
return r
}
func (r *MainRouter) SetMux() {
func (r *MainRouter) setMux() {
r.Mux = mux.NewRouter()
}
func (mainRouter *MainRouter) AddGroupRouter(groupName string) *GroupRouter {
s := mainRouter.Mux.NewRoute().Subrouter()
groupRouter := &GroupRouter{
Mux: s,
}
newMap := make(GroupRouters)
helper.MapIter(mainRouter.GroupRouters, func(grName string, gr *GroupRouter) {
newMap[grName] = gr
})
newMap[groupName] = groupRouter
mainRouter.GroupRouters = newMap
return groupRouter
}
func (mainRouter *MainRouter) GetGroupRouter(groupName string) *GroupRouter {
return mainRouter.GroupRouters[groupName]
}
func (mainRouter *MainRouter) GetHostSubRouter(groupName string, domain string) *SubRouter {
return mainRouter.GroupRouters[groupName].SubRouters[domain]
}
func (mainRouter *MainRouter) AddHostSubRouter(groupName string, domain string) *SubRouter {
groupSubRouter := mainRouter.GroupRouters[groupName]
newRoute := groupSubRouter.Mux.NewRoute()
hostSubRouter := &SubRouter{
Mux: newRoute.Host(domain).Subrouter(),
}
newMap := make(SubRouters)
helper.MapIter(mainRouter.GroupRouters[groupName].SubRouters, func(grName string, gr *SubRouter) {
newMap[grName] = gr
})
newMap[domain] = hostSubRouter
mainRouter.GroupRouters[groupName].SubRouters = newMap
return hostSubRouter
}
func (mainRouter *MainRouter) AddGroupSubRouter(groupName string, subRouterName string) *SubRouter {
gsr := mainRouter.GetGroupRouter(groupName).Mux.NewRoute().Subrouter()
subRouter := &SubRouter{
Mux: gsr,
}
mainRouter.GroupRouters[groupName].SubRouters[subRouterName] = subRouter
return subRouter
}
type MainRouter struct {
Subrouters Subrouters
GroupRouters GroupRouters
Mux *mux.Router
}
type GroupRouters map[string]*GroupRouter
type GroupRouter struct {
SubRouters SubRouters
Mux *mux.Router
}
type Subrouters map[string]SubRouter
type SubRouters map[string]*SubRouter
type SubRouter struct {
Name string
Group string
*mux.Router
Mux *mux.Router
}

View file

@ -275,7 +275,7 @@ func CapitalizeFirstLetter(input string) string {
return string(runes) // Convert rune slice back to string
}
func MapIter[K comparable, V comparable](m map[K]V, fn func(K, V)) {
func MapIter[K string, V any](m map[K]V, fn func(K, V)) {
for p, d := range m {
fn(p, d)
}
@ -456,25 +456,25 @@ func AppRunner(runApp func()) {
<-ctx.Done()
//do stuff after ending
wg.Wait()
fmt.Println("BYE BYE!")
log.Println("AppRunner stopped")
os.Exit(0)
}
func StartTestHTTPServer(port int) {
func StartTestHTTPServer(port int, name string) {
p := strconv.Itoa(port)
go func() {
log.Println("Test server is running at http://" + GetIP() + ":" + p)
r := mux.NewRouter()
r.Path("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "/ OK")
fmt.Fprintln(w, r.Host+"/ OK")
})
r.Path("/test1").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "/test1 OK")
fmt.Fprintln(w, r.Host+"/test1 OK")
})
r.Path("/test2").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "/test2 OK")
fmt.Fprintln(w, r.Host+"/test2 OK")
})
err := http.ListenAndServe(":3000", r)
err := http.ListenAndServe(":"+p, r)
if err != nil {
log.Println(err.Error())
}
@ -510,3 +510,7 @@ func GetIP(prefix ...string) string {
}
return prfx
}
func AppendToPointer[T any](slc *[]T, val T) {
*slc = append(*slc, val)
}

File diff suppressed because one or more lines are too long

BIN
tmp/main

Binary file not shown.