22032025-1

This commit is contained in:
Zeev Diukman 2025-03-22 08:57:23 +00:00
parent 2c7fbd6617
commit 8e690668a4
23 changed files with 2463 additions and 442 deletions

View file

@ -1,52 +0,0 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./_tmp/main"
cmd = "go build -o ./_tmp/main ."
delay = 1000
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","yaml","yml","toml"]
include_file = [".air.toml"]
kill_delay = "0.5s"
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

BIN
_tmp/main

Binary file not shown.

View file

@ -1,53 +0,0 @@
http:
routers:
keycloak:
entryPoint: https
service: keycloak
rules: Domain(`keycloak.z.com`).PathPrefix(`/)
tls:
certProvider: default
# app1:
# entryPoint: https
# service: app1
# rules: Domain(`app1.z.com`).Path(`/`)
# tls:
# certProvider: default
# app2:
# entryPoint: https
# service: app2
# rules: Domain(`app2.z.com`).PathPrefix(`/test/`)
# tls:
# certProvider: default
aaa:
entryPoint: https
service: app1
rules: Domain(`a.z.com`).PathPrefix(`/`)
bbb:
entryPoint: https
service: app2
rules: Domain(`a.z.com`).PathPrefix(`/app2/`)
# ccc:
# entryPoint: https
# service: keycloak
# rules: Domain(`a.z.com`).PathPrefix(`/`)
services:
keycloak: http://192.168.10.2:8080
app1: http://192.168.10.2:3001
app2: http://192.168.10.2:3002
entryPoints:
https:
address: :443
tls:
enabled: true
http:
address: :80
tls:
certProviders:
default:
key: ./assets/certs/z.com.key.pem
cert: ./assets/certs/z.com.cert.pem

517
config.go Normal file
View file

@ -0,0 +1,517 @@
package main
// import (
// "errors"
// "os"
// "path/filepath"
// "regexp"
// "slices"
// "strings"
// "github.com/google/uuid"
// "github.com/gookit/goutil/dump"
// "github.com/spf13/viper"
// )
// // var routeRulesNames = []string{"Host", "Path", "PathPrefix"}
// const DEFAULT_CONFIG_PATH = "./z/config"
// const DEFAULT_CONFIG_TYPE = "yaml"
// const DEFAULT_CONFIG_NAME = "config"
// var config = ConfigBuild()
// type Config struct {
// Routers Routers
// Services Services
// EntryPoints EntryPoints
// TLS TLS
// AuthMap AuthMap
// Health *Health
// Logs *Logs
// }
// type Logs struct {
// ConfigBuild []string
// }
// type Health struct {
// ConfigHealth *ConfigHealth
// }
// type ConfigHealth struct {
// Routers bool
// Services bool
// EntryPoints bool
// AuthBuild bool
// }
// type ConfigLog map[string][]string
// type AuthMap map[string]*Auth
// type Auth struct {
// SessionSecret string
// Paths *Paths
// OpenID *OpenID
// }
// type OpenID struct {
// Realm string
// Issuer string
// ClientID string
// ClientSecret string
// AuthURL string
// TokenURL string
// UserURL string
// LogoutURL string
// Config string
// RedirectURI string
// PostLogoutRedirectUri string
// }
// type Paths struct {
// Prefix string
// Login string
// Logout string
// Callback string
// }
// type EntryPoints map[string]*EntryPoint
// type EntryPoint struct {
// Address string
// TLS *EntryPointTLS
// }
// type EntryPointTLS struct {
// Enabled bool
// }
// type TLS struct {
// CertProviders CertProviders
// }
// type CertProviders map[string]*certProvider
// type certProvider struct {
// Cert string
// Key string
// }
// type Routers map[string]*Router
// type Router struct {
// Priority int
// Name string
// Service string
// EntryPoint string
// Routes Routes
// Auth RouterAuth
// }
// type RouterAuth struct {
// Enabled bool
// Provider string
// }
// type Routes map[string]*Route
// type Route struct {
// ID string
// Router string
// Rule map[string]string
// }
// type Services map[string]string
// func (e EntryPoints) ForEach(fn func(name string, data *EntryPoint)) {
// for name, data := range e {
// fn(name, data)
// }
// }
// func (r Routers) ForEach(fn func(name string, data *Router) string) {
// for name, data := range r {
// msg := fn(name, data)
// if msg == "break" {
// break
// }
// if msg == "continue" {
// continue
// }
// }
// }
// func (r Routers) ForEachByPriority(fn func(name string, data *Router)) {
// slc := r.getSlicesByPriority()
// for _, n := range slc {
// d := r[n]
// fn(n, d)
// }
// }
// func (r Routers) getSlicesByPriority() []string {
// routersSlice := []string{}
// r.ForEach(func(routerName string, routerData *Router) string {
// routersSlice = append(routersSlice, routerName)
// return ""
// })
// slices.SortFunc(routersSlice, func(a, b string) int {
// if r[a].Priority == r[b].Priority {
// return 0
// }
// if r[a].Priority > r[b].Priority {
// return -1
// }
// if r[a].Priority < r[b].Priority {
// return 1
// }
// return r[a].Priority - r[b].Priority
// })
// return routersSlice
// }
// func (r *Router) GetRouteByID(routeID string) *Route {
// return r.Routes[routeID]
// }
// func (r Routes) ForEach(fn func(routeID string, v *Route) string) {
// for routeID, v := range r {
// msg := fn(routeID, v)
// if msg == "break" {
// break
// }
// if msg == "continue" {
// continue
// }
// }
// }
// func (r Route) ForEachRule(fn func(routeID string, ruleName string, ruleValue string)) {
// for k, v := range r.Rule {
// fn(r.ID, k, v)
// }
// }
// func InitConfigStruct() *Config {
// config := &Config{
// Routers: Routers{},
// Services: Services{},
// EntryPoints: EntryPoints{},
// TLS: TLS{
// CertProviders: CertProviders{},
// },
// AuthMap: AuthMap{},
// Health: &Health{
// ConfigHealth: &ConfigHealth{
// Routers: false,
// Services: false,
// EntryPoints: false,
// AuthBuild: false,
// },
// },
// Logs: &Logs{
// ConfigBuild: []string{},
// },
// }
// return config
// }
// func ConfigBuild() *Config {
// viperConfig := NewViperConfig()
// config := InitConfigStruct()
// // //Routers
// // //Services
// BuildRoutersConfig(viperConfig, config, "routers")
// BuildServicesConfig(viperConfig, config, "services")
// BuildEntryPointsConfig(viperConfig, config, "entrypoints")
// BuildCertProvidersConfig(viperConfig, config, "tls.certproviders")
// BuildAuthConfig(viperConfig, config, "auth")
// // if !BuildServicesConfig(viperConfig, config, "services") {
// // err = errors.Join(errors.New("build services config error ="))
// // }
// // if !BuildRoutersConfig(viperConfig, config, "routers") {
// // err = errors.Join(errors.New("build routers config error"))
// // }
// // // //Entrypoints
// // if !BuildEntryPointsConfig(viperConfig, config, "entryPoints") {
// // err = errors.Join(errors.New("build entry points config error"))
// // }
// // // //TLS Certificate Providers
// // if !BuildCertProvidersConfig(viperConfig, config, "tls.certproviders") {
// // err = errors.Join(errors.New("build cert providers config error"))
// // }
// // if !BuildAuthConfig(viperConfig, config, "auth") {
// // // err = errors.Join(errors.New("build auth config error"))
// // }
// return config
// }
// func BuildCertProvidersConfig(viperConfig *viper.Viper, config *Config, key string) bool {
// if viperConfig.Sub(key) == nil {
// config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
// return false
// } else {
// certProviders := viperConfig.Sub(key).AllSettings()
// // config.TLS.CertProviders = make(CertProviders)
// for k, v := range certProviders {
// v := v.(map[string]any)
// cert := v["cert"].(string)
// key := v["key"].(string)
// config.TLS.CertProviders[k] = &certProvider{
// Cert: cert,
// Key: key,
// }
// }
// return true
// }
// }
// func BuildServicesConfig(viperConfig *viper.Viper, config *Config, key string) bool {
// if viperConfig.Sub(key) == nil {
// config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
// dump.P("Missing \"" + key + "\" in Config")
// return false
// } else {
// config.Services = make(Services)
// services := viperConfig.Sub(key).AllSettings()
// for k, v := range services {
// config.Services[k] = v.(string)
// }
// return true
// }
// }
// func BuildEntryPointsConfig(viperConfig *viper.Viper, config *Config, key string) bool {
// if viperConfig.Sub(key) == nil {
// config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
// dump.P("Missing \"" + key + "\" in Config")
// return false
// } else {
// entryPoints := viperConfig.Sub(key).AllSettings()
// for k, v := range entryPoints {
// entryPoint := &EntryPoint{
// Address: "",
// TLS: &EntryPointTLS{
// Enabled: false,
// },
// }
// v := v.(map[string]any)
// if _, ok := v["address"]; ok {
// entryPoint.Address = v["address"].(string)
// }
// if _, ok := v["tls"]; ok {
// tls := v["tls"].(map[string]any)
// enabled := tls["enabled"].(bool)
// entryPoint.TLS = &EntryPointTLS{
// Enabled: enabled,
// }
// }
// config.EntryPoints[k] = entryPoint
// }
// return true
// }
// }
// func BuildAuthConfig(viperConfig *viper.Viper, config *Config, key string) bool {
// if viperConfig.Sub(key) == nil {
// config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
// dump.P("Missing \"" + key + "\" in Config")
// return false
// } else {
// auth := viperConfig.Sub(key).AllSettings()
// for k, v := range auth {
// paths := v.(map[string]any)["paths"].(map[string]any)
// openID := v.(map[string]any)["openid"].(map[string]any)
// sessionSecret := v.(map[string]any)["sessionsecret"].(string)
// config.AuthMap[k] = &Auth{
// SessionSecret: sessionSecret,
// Paths: &Paths{
// Prefix: paths["prefix"].(string),
// Login: paths["login"].(string),
// Logout: paths["logout"].(string),
// Callback: paths["callback"].(string),
// },
// OpenID: &OpenID{
// Realm: openID["realm"].(string),
// Issuer: openID["issuer"].(string),
// ClientID: openID["client_id"].(string),
// ClientSecret: openID["client_secret"].(string),
// RedirectURI: openID["redirect_uri"].(string),
// PostLogoutRedirectUri: openID["post_logout_redirect_uri"].(string),
// Config: openID["config"].(string),
// AuthURL: openID["authurl"].(string),
// TokenURL: openID["tokenurl"].(string),
// UserURL: openID["userurl"].(string),
// LogoutURL: openID["logouturl"].(string),
// },
// }
// }
// return true
// }
// }
// func splitStatementByRegex(str string, regx string) []string {
// input := str
// re := regexp.MustCompile(regx)
// matches := re.FindAllStringSubmatch(input, -1)
// if len(matches) == 0 {
// return []string{str} // Return original if no backticks found
// }
// result := []string{}
// lastIndex := 0
// for _, match := range matches {
// startIndex := strings.Index(input, match[0])
// if startIndex > lastIndex {
// str := input[lastIndex:startIndex]
// str, _ = strings.CutPrefix(str, ".")
// result = append(result, str)
// }
// str := match[1]
// str, _ = strings.CutPrefix(str, ".")
// result = append(result, str) // Append the content within backticks
// lastIndex = startIndex + len(match[0])
// }
// if lastIndex < len(input) {
// str := input[lastIndex:]
// str, _ = strings.CutPrefix(str, ".")
// result = append(result, str)
// }
// return result
// }
// func BuildRoutersConfig(viperConfig *viper.Viper, config *Config, key string) bool {
// if viperConfig.Sub(key) == nil {
// config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
// dump.P("Missing \"" + key + "\" in Config")
// return false
// } else {
// routers := viperConfig.Sub(key).AllSettings()
// for routerName, d := range routers {
// data := d.(map[string]any)
// if _, ok := data["routes"]; !ok {
// continue
// }
// if _, ok := data["service"]; !ok {
// continue
// }
// if _, ok := data["entrypoint"]; !ok {
// continue
// }
// if _, ok := data["priority"]; !ok {
// data["priority"] = 1000
// }
// if _, ok := data["routes"]; !ok {
// continue
// }
// routes := data["routes"].([]any)
// service := data["service"].(string)
// entryPoint := data["entrypoint"].(string)
// priority := data["priority"].(int)
// enabled := false
// provider := ""
// if _, ok := data["auth"]; ok {
// enabled = data["auth"].(map[string]any)["enabled"].(bool)
// provider = data["auth"].(map[string]any)["provider"].(string)
// }
// router := &Router{
// Name: routerName,
// Priority: priority,
// Service: service,
// EntryPoint: entryPoint,
// Routes: Routes{},
// Auth: RouterAuth{
// Enabled: enabled,
// Provider: provider,
// },
// }
// for _, v := range routes {
// routeID := uuid.New().String()
// route := &Route{
// ID: routeID,
// Router: routerName,
// Rule: map[string]string{},
// }
// rawRoute := v.(string)
// route.Rule["raw"] = rawRoute
// routeStatmentsSlice := splitStatementByRegex(rawRoute, "\\(`(.*?)`\\)")
// for i, v := range routeStatmentsSlice {
// if i&1 == 0 {
// if i+1 < len(routeStatmentsSlice) {
// route.Rule[v] = routeStatmentsSlice[i+1]
// }
// }
// }
// // if _, ok := route.Rule["Auth"]; !ok {
// // route.Rule["Auth"] = ""
// // }
// router.Routes[routeID] = route
// // router.Routes = append(router.Routes, route)
// }
// config.Routers[routerName] = router
// }
// return true
// }
// }
// func NewViperConfig() *viper.Viper {
// vc := viper.New()
// vc.AddConfigPath(DEFAULT_CONFIG_PATH)
// vc.SetConfigType(DEFAULT_CONFIG_TYPE)
// vc.SetConfigName(DEFAULT_CONFIG_NAME)
// vc.ReadInConfig()
// fileNames, err := getFileNames(DEFAULT_CONFIG_PATH, DEFAULT_CONFIG_NAME)
// if err != nil {
// dump.Println("No extra config files" + err.Error())
// } else {
// for _, fileName := range fileNames {
// vc.SetConfigName(fileName)
// vc.MergeInConfig()
// dump.P("TEST")
// }
// }
// return vc
// }
// func getYamlFileNameExcExt(fileName string, exts ...string) (string, error) {
// var fileExt string
// for _, ext := range exts {
// if strings.HasSuffix(fileName, ext) {
// fileExt = ext
// break
// }
// }
// if name, ok := strings.CutSuffix(fileName, fileExt); ok {
// return name, nil
// } else {
// return "", errors.New("Error: no file extension found!")
// }
// }
// func getFileNames(folderPath string, skipFileName string) ([]string, error) {
// var fileNames []string
// err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
// if err != nil {
// return err
// }
// if !info.IsDir() { // Only add files, not directories
// filename := info.Name()
// filesize := info.Size()
// filenameNoExt, _ := getYamlFileNameExcExt(filename, ".yaml", ".yml")
// if skipFileName == filenameNoExt || filesize == 0 {
// return nil
// }
// fileNames = append(fileNames, filenameNoExt)
// }
// return nil
// })
// if err != nil {
// return nil, err
// }
// return fileNames, nil
// }

25
config/app.yml Normal file
View file

@ -0,0 +1,25 @@
routers:
app:
entryPoint: https
service: app
routes:
- Host(`app.z.com`).PathPrefix(`/`)
tls:
certProvider: default
auth:
enabled: true
provider: app_auth
# app-protected:
# entryPoint: https
# service: app
# routes:
# - Host(`app.z.com`).PathPrefix(`/test1`)
# tls:
# certProvider: default
# auth:
# enabled: true
# provider: app_auth
services:
app: http://127.0.0.1:3001

76
config/auth.yml Normal file
View file

@ -0,0 +1,76 @@
routers:
keycloak:
is_auth_router: true
# priority: 9999
entryPoint: https
service: keycloak
routes:
- Host(`auth.z.com`).PathPrefix(`/`)
# - Host(`auth.z.com`).PathPrefix(`/admin`)
# - Host(`auth.z.com`).PathPrefix(`/realms`)
# - Host(`auth.z.com`).PathPrefix(`/resources`)
tls:
certProvider: default
stripPrefix: false
services:
keycloak: http://127.0.0.1:8080
auth:
app_auth:
auth_root_url: https://auth.z.com
target_root_url: https://app.z.com
auth_local_root_url: http://127.0.0.1:8080
# sessionSecret: keycloak
paths:
prefix: /auth
login: /login
logout: /logout
callback: /callback
postlogout: /postlogout
openId:
realm: zprox
client_id: zprox_client
client_secret: dWhSJgARBAuBAXN7sUTpqpIq2sKQdugs
end_points:
# router target address
redirect_uri: <{{target_root_url}}>/auth/callback
post_logout_redirect_uri: <{{target_root_url}}>/auth/postlogout
# router exposed address
issuer: <{{auth_root_url}}>/realms/<{{realm}}>
authURL: <{{auth_root_url}}>/realms/<{{realm}}>/protocol/openid-connect/auth
logoutUrl: <{{auth_root_url}}>/realms/<{{realm}}>/protocol/openid-connect/logout
# local address
config: <{{auth_local_root_url}}>/realms/<{{realm}}>/.well-known/openid-configuration
tokenURL: <{{auth_local_root_url}}>/realms/<{{realm}}>/protocol/openid-connect/token
userURL: <{{auth_local_root_url}}>/realms/<{{realm}}>/protocol/openid-connect/userinfo
jwksURI: <{{auth_local_root_url}}>/realms/<{{realm}}>/protocol/openid-connect/certs
frontend_auth:
auth_root_url: https://auth.z.com
target_root_url: https://frontend.z.com
auth_local_root_url: http://127.0.0.1:8080
sessionSecret: keycloak
paths:
prefix: /auth
login: /login
logout: /logout
callback: /callback
postlogout: /postlogout
openId:
realm: zprox
client_id: zprox_client
client_secret: dWhSJgARBAuBAXN7sUTpqpIq2sKQdugs
end_points:
# router target address
redirect_uri: <{{target_root_url}}>/auth/callback
post_logout_redirect_uri: <{{target_root_url}}>/auth/postlogout
# router exposed address
issuer: <{{auth_root_url}}>/realms/<{{realm}}>
authURL: <{{auth_root_url}}>/realms/<{{realm}}>/protocol/openid-connect/auth
logoutUrl: <{{auth_root_url}}>/realms/<{{realm}}>/protocol/openid-connect/logout
# local address
config: <{{auth_local_root_url}}>/realms/<{{realm}}>/.well-known/openid-configuration
tokenURL: <{{auth_local_root_url}}>/realms/<{{realm}}>/protocol/openid-connect/token
userURL: <{{auth_local_root_url}}>/realms/<{{realm}}>/protocol/openid-connect/userinfo
jwksURI: <{{auth_local_root_url}}>/realms/<{{realm}}>/protocol/openid-connect/certs

13
config/config.yml Normal file
View file

@ -0,0 +1,13 @@
entrypoints:
https:
address: :443
tls:
enabled: true
http:
address: :80
tls:
certProviders:
default:
key: z/assets/certs/z.com.key.pem
cert: z/assets/certs/z.com.cert.pem

15
config/frontend.yml Normal file
View file

@ -0,0 +1,15 @@
routers:
frontend:
# priority: 1001
entryPoint: https
service: frontend
routes:
- Host(`frontend.z.com`).PathPrefix(`/`)
tls:
certProvider: default
auth:
enabled: true
provider: frontend_auth
services:
frontend: http://127.0.0.1:3000

32
entrypoint.go Normal file
View file

@ -0,0 +1,32 @@
package main
import (
"fmt"
"log"
"net/http"
"github.com/zeevdiukman/zprox/internal/config"
"github.com/zeevdiukman/zprox/internal/session"
)
func BuildEntryPoint(data *config.EntryPoint, handler http.Handler) {
h := session.Manager.LoadAndSave(handler)
if data.TLS.Enabled {
go func() {
fmt.Println("Listening at " + data.Address + " with TLS ")
err := http.ListenAndServeTLS(data.Address, "./z/crt.pem", "./z/key.pem", h)
if err != nil {
log.Println(err.Error())
}
}()
} else {
go func() {
fmt.Println("Listening at " + data.Address + " non TLS ")
err := http.ListenAndServe(data.Address, h)
if err != nil {
log.Println(err.Error())
}
}()
}
}

27
go.mod
View file

@ -2,13 +2,17 @@ module github.com/zeevdiukman/zprox
go 1.24.0 go 1.24.0
require github.com/gookit/goutil v0.6.18 // indirect require github.com/gookit/goutil v0.6.18
require github.com/zeevdiukman/go-zgate v0.0.0-20250307073122-f7ca358b2107 require (
github.com/gorilla/mux v1.8.1
golang.org/x/oauth2 v0.28.0
)
require ( require (
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
@ -19,23 +23,26 @@ require (
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/zeevdiukman/go-reverseproxy v0.0.0-20250305093102-9882ad3edb31 // indirect
github.com/zeevdiukman/go-router v0.0.0-20250305093130-650cd1d241f5 // indirect
github.com/zeevdiukman/go-server v0.0.0-20250305093228-f3ab0096fcba // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
require ( require (
github.com/alexedwards/scs/v2 v2.8.0
github.com/coreos/go-oidc/v3 v3.13.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.4.0
github.com/gookit/color v1.5.4 // indirect github.com/gookit/color v1.5.4 // indirect
github.com/spf13/viper v1.19.0 // indirect github.com/joho/godotenv v1.5.1
github.com/spf13/viper v1.19.0
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/zeevdiukman/go-config v0.0.0-20250305101848-6cef80370123
github.com/zeevdiukman/go-helper v0.0.0-20250305091316-396bd5057e2f github.com/zeevdiukman/go-helper v0.0.0-20250305091316-396bd5057e2f
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.23.0 // indirect
) )

41
go.sum
View file

@ -1,3 +1,7 @@
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8=
github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@ -6,8 +10,14 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gookit/goutil v0.6.18 h1:MUVj0G16flubWT8zYVicIuisUiHdgirPAkmnfD2kKgw= github.com/gookit/goutil v0.6.18 h1:MUVj0G16flubWT8zYVicIuisUiHdgirPAkmnfD2kKgw=
@ -16,6 +26,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -53,34 +65,29 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/zeevdiukman/go-config v0.0.0-20250305101848-6cef80370123 h1:AYl8UZu7+t6yZ3/a12KvJhLMpf0gg4MKDInoP5p+XG0=
github.com/zeevdiukman/go-config v0.0.0-20250305101848-6cef80370123/go.mod h1:oK8ESatUsIFPpj/p/0iMW3HPs8+97F52O40xjotcY64=
github.com/zeevdiukman/go-helper v0.0.0-20250305091316-396bd5057e2f h1:1UyqJ/MzVw+Oxl8kryguBsObG7qVw+IhlKTE/HhpLGE= github.com/zeevdiukman/go-helper v0.0.0-20250305091316-396bd5057e2f h1:1UyqJ/MzVw+Oxl8kryguBsObG7qVw+IhlKTE/HhpLGE=
github.com/zeevdiukman/go-helper v0.0.0-20250305091316-396bd5057e2f/go.mod h1:buB5zo+BkiM7kNOI2o33rmXBSlnjH1zpN0DtgNDbnCc= github.com/zeevdiukman/go-helper v0.0.0-20250305091316-396bd5057e2f/go.mod h1:buB5zo+BkiM7kNOI2o33rmXBSlnjH1zpN0DtgNDbnCc=
github.com/zeevdiukman/go-reverseproxy v0.0.0-20250305093102-9882ad3edb31 h1:OaOVvayXo4yf/gg8/IuZzGnFxTXEXGOVXiNVxPC0OgM=
github.com/zeevdiukman/go-reverseproxy v0.0.0-20250305093102-9882ad3edb31/go.mod h1:RAFNKzQy/q2eDB/2WI88dHuiVC8Qqp6iAPDZ+btXACk=
github.com/zeevdiukman/go-router v0.0.0-20250305093130-650cd1d241f5 h1:ssP7N61mi0MLfnaH/ob7xOlu+prPwULl0nZ2hPw/QdE=
github.com/zeevdiukman/go-router v0.0.0-20250305093130-650cd1d241f5/go.mod h1:wY15gRD14GOWs8j6bazZypGkMfCbSdr5cQK77MwzReA=
github.com/zeevdiukman/go-server v0.0.0-20250305093228-f3ab0096fcba h1:BHG0fbASaIBXr+JyIaIt+/x1MIH+wAB5MTj2VQ1nlnU=
github.com/zeevdiukman/go-server v0.0.0-20250305093228-f3ab0096fcba/go.mod h1:HQARDR3c1btC+vNSDekVtC1KM/tVFrJqr3yGrPN3pbo=
github.com/zeevdiukman/go-zgate v0.0.0-20250307073122-f7ca358b2107 h1:1PldMKThu/U5vkmrAJzycaXJE6XR52nVA3NAgZiPl6k=
github.com/zeevdiukman/go-zgate v0.0.0-20250307073122-f7ca358b2107/go.mod h1:b9fvxFwJrZiJ0Q2tv5enpCxL3JYUKJDUb+nVXqeAWIg=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

205
internal/auth/auth.go Normal file
View file

@ -0,0 +1,205 @@
package auth
import (
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"log"
"math/big"
"net/http"
"strings"
"github.com/golang-jwt/jwt"
"github.com/gookit/goutil/dump"
"github.com/zeevdiukman/zprox/internal/config"
"github.com/zeevdiukman/zprox/internal/session"
"golang.org/x/oauth2"
)
func init() {
gob.RegisterName("oauth2_token_pointer", &oauth2.Token{})
gob.RegisterName("rsa_public_key_pointer", &rsa.PublicKey{})
}
type TokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
RefreshExpiresIn int `json:"refresh_expires_in"`
TokenType string `json:"token_type"`
NotBeforePolicy int `json:"not-before-policy"`
SessionState string `json:"session_state"`
Scope string `json:"scope"`
}
type JWKS struct {
Keys []JSONWebKeys `json:"keys"`
}
type JSONWebKeys struct {
Kty string `json:"kty"`
Kid string `json:"kid"`
Use string `json:"use"`
N string `json:"n"`
E string `json:"e"`
X5c []string `json:"x5c"`
}
func GetPublicKey(kid string, jwksURL string) (*rsa.PublicKey, error) {
resp, err := http.Get(jwksURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var jwks JWKS
if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil {
dump.P("json.NewDecoder")
return nil, err
}
key, err := ValidateJWTissuerSignatrue(kid, jwks)
if err != nil {
return nil, err
}
nBytes, err := base64.RawURLEncoding.DecodeString(key.N)
if err != nil {
return nil, err
}
eBytes, err := base64.RawURLEncoding.DecodeString(key.E)
if err != nil {
return nil, err
}
n := new(big.Int).SetBytes(nBytes)
e := new(big.Int).SetBytes(eBytes)
publicKey := &rsa.PublicKey{
N: n,
E: int(e.Int64()),
}
return publicKey, nil
}
func ValidateJWTissuerSignatrue(kid string, jwks JWKS) (JSONWebKeys, error) {
for _, key := range jwks.Keys {
if key.Kid == kid {
return key, nil
}
}
err := errors.New("public key not found for kid: " + kid)
return JSONWebKeys{}, err
}
func FetchKeycloakPublicKey(oauth2Token *oauth2.Token, jwksURL string) (*rsa.PublicKey, error) {
jwtKID, err := GetKidFromJWT(oauth2Token.AccessToken)
if err != nil {
dump.Println(err.Error())
return nil, err
}
publicKey, err := GetPublicKey(jwtKID, jwksURL)
if err != nil {
log.Println("Error fetching public key:", err)
return nil, err
}
return publicKey, nil
}
func GetJwtClaims(r *http.Request, publicKey *rsa.PublicKey, oauth2Token *oauth2.Token) (jwt.MapClaims, error) {
// authProvider := routerData.Auth.Provider
// jwksURL := config.Data.AuthMap[authProvider].OpenID.JwksURI
// Extract the username from the token
tokenString := oauth2Token.AccessToken
claims := jwt.MapClaims{}
_, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return publicKey, nil
})
if err != nil {
err = errors.New("error parsing token: " + err.Error())
return nil, err
}
return claims, nil
}
func GetKidFromJWT(tokenString string) (string, error) {
parts := strings.Split(tokenString, ".")
if len(parts) < 2 {
return "", fmt.Errorf("invalid JWT format")
}
headerBase64 := parts[0]
headerJSON, err := base64.RawURLEncoding.DecodeString(headerBase64)
if err != nil {
return "", fmt.Errorf("failed to decode header: %v", err)
}
var header map[string]interface{}
if err := json.Unmarshal(headerJSON, &header); err != nil {
return "", fmt.Errorf("failed to unmarshal header: %v", err)
}
kid, ok := header["kid"].(string)
if !ok {
return "", fmt.Errorf("kid not found or not a string")
}
return kid, nil
}
func RedirectToLogin(authConfig *config.Auth, w http.ResponseWriter, r *http.Request) {
session.Manager.Clear(r.Context())
session.Manager.RenewToken(r.Context())
u := GetAuthCodeURL(authConfig, r)
// sessToken := session.Manager.Token(r.Context())
// sessCtx, err := session.Manager.Load(r.Context(), sessToken)
// if err != nil {
// log.Println(err.Error())
// }
// session.Manager.Put(sessCtx, "original_path", r.URL.Path)
session.Manager.Put(r.Context(), "original_path", r.URL.Path)
http.Redirect(w, r, u, http.StatusTemporaryRedirect)
}
func GetAuthCodeURL(authConfig *config.Auth, r *http.Request) string {
conf := &oauth2.Config{
ClientID: authConfig.OpenID.ClientID,
ClientSecret: authConfig.OpenID.ClientSecret,
RedirectURL: authConfig.OpenID.EndPoints.RedirectURI,
Scopes: []string{"openid", "email", "profile"},
Endpoint: oauth2.Endpoint{
AuthURL: authConfig.OpenID.EndPoints.AuthURL,
TokenURL: authConfig.OpenID.EndPoints.TokenURL,
},
}
state, err := generateState()
if err != nil {
return "/"
}
nonce, err := generateState()
if err != nil {
return "/"
}
// Adding options to the AuthCodeURL method
session.Manager.Put(r.Context(), "nonce", nonce)
session.Manager.Put(r.Context(), "state", state)
return conf.AuthCodeURL(state, oauth2.SetAuthURLParam("nonce", nonce))
}
func generateState() (string, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}

255
internal/auth/handlers.go Normal file
View file

@ -0,0 +1,255 @@
package auth
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gookit/goutil/dump"
"github.com/zeevdiukman/go-helper"
"github.com/zeevdiukman/zprox/internal/config"
"github.com/zeevdiukman/zprox/internal/session"
"golang.org/x/oauth2"
)
func CallbackHandler(authConfig *config.Auth, routerData *config.Router) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := config.Data.Context
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
insecureClient := &http.Client{Transport: tr}
ctx = context.WithValue(ctx, oauth2.HTTPClient, insecureClient)
provider, err := oidc.NewProvider(ctx, authConfig.OpenID.EndPoints.Issuer)
if err != nil {
panic(err)
}
oidcConf := &oidc.Config{
ClientID: authConfig.OpenID.ClientID,
}
verifier := provider.Verifier(oidcConf)
code := r.FormValue("code")
state := r.FormValue("state")
expectedState := session.Manager.GetString(r.Context(), "state")
if state != "" && state != expectedState {
dump.P("Wrong nonce")
return
}
// nonce := r.FormValue("nonce")
if code == "" {
dump.P("No code provided")
return
}
if state == "" {
dump.P("No state provided")
return
}
endPoint := oauth2.Endpoint{
AuthURL: authConfig.OpenID.EndPoints.AuthURL,
TokenURL: authConfig.OpenID.EndPoints.TokenURL,
}
conf := &oauth2.Config{
ClientID: authConfig.OpenID.ClientID,
ClientSecret: authConfig.OpenID.ClientSecret,
RedirectURL: authConfig.OpenID.EndPoints.RedirectURI,
Scopes: []string{"openid"},
Endpoint: endPoint,
}
oauth2Token, err := conf.Exchange(r.Context(), code)
if err != nil {
dump.Println(err.Error())
}
jwksURL := authConfig.OpenID.EndPoints.JwksURI
rsaPublicKey, err := FetchKeycloakPublicKey(oauth2Token, jwksURL)
if err != nil {
dump.Println("Error fetching public key:", err)
// http.Redirect(w, r, originalPath, http.StatusTemporaryRedirect)
status := http.StatusUnauthorized
statusText := http.StatusText(status)
w.WriteHeader(status)
w.Write([]byte(statusText))
return
}
originalPath := session.Manager.GetString(r.Context(), "original_path")
nonce := session.Manager.GetString(r.Context(), "nonce")
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
return
}
idToken, err := verifier.Verify(r.Context(), rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
if nonce != idToken.Nonce {
dump.P("ID token nonce is not as expected")
return
}
var claimsRaw json.RawMessage
if err := idToken.Claims(&claimsRaw); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var claims []byte
claimsRaw.UnmarshalJSON(claims)
dump.P(claims)
session.Manager.RenewToken(r.Context())
session.Manager.Put(r.Context(), "oauth2_token", oauth2Token)
session.Manager.Put(r.Context(), "jwks_public_key", rsaPublicKey)
//////////////////////////////////////////
// oauth2Token.SetAuthHeader(r) //?????????
//////////////////////////////////////////
http.Redirect(w, r, originalPath, http.StatusTemporaryRedirect)
return
}
}
func LoginHandler(authConfig *config.Auth, routerData *config.Router) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Add("Access-Control-Allow-Headers", "*")
w.Header().Add("Access-Control-Allow-Credentials", "true")
w.Header().Add("type", "application/json")
b, err := io.ReadAll(r.Body)
if err != nil {
log.Fatalf("Error reading body: %v", err)
}
defer r.Body.Close()
data := map[string]string{}
json.Unmarshal(b, &data)
username := data["username"]
password := data["password"]
kcData := url.Values{}
kcData.Set("grant_type", "password")
kcData.Set("client_id", authConfig.OpenID.ClientID)
kcData.Set("client_secret", authConfig.OpenID.ClientSecret)
kcData.Set("username", username)
kcData.Set("password", password)
client := helper.HttpClientWithSkipVerify()
clientReq, err := http.NewRequest("POST", authConfig.OpenID.EndPoints.TokenURL, strings.NewReader(kcData.Encode()))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
clientReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(clientReq)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response:", err)
return
}
if resp.StatusCode != http.StatusOK {
fmt.Printf("Error: Status code %d, Response: %s\n", resp.StatusCode, string(respBody))
return
}
var tokenResponse TokenResponse
err = json.Unmarshal(respBody, &tokenResponse)
if err != nil {
fmt.Println("Error unmarshaling JSON:", err)
return
}
}
}
func LogoutHandler(authConfig *config.Auth, routerData *config.Router) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if _, ok := session.Manager.Get(r.Context(), "oauth2_token").(*oauth2.Token); !ok {
// if !oauth2Token.Valid() {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(http.StatusText(http.StatusUnauthorized)))
return
// }
}
u, _ := url.Parse(authConfig.OpenID.EndPoints.LogoutURL)
// Set query parameters
// q := u.Query()
// q.Add("client_id", url.QueryEscape(authConfig.OpenID.ClientID))
// q.Add("redirect_uri", url.QueryEscape(authConfig.OpenID.PostLogoutRedirectUri))
// // q.Add("state", )
// // q.Set("redirect_uri", url.QueryEscape(authConfig.OpenID.PostLogoutRedirectUri))
// q.Add("post_logout_redirect_uri", url.QueryEscape("https://app.z.com/auth/logout"))
// u.RawQuery = q.Encode()
// // Redirect to the logout URL
// err = session.Manager.RenewToken(r.Context())
// if err != nil {
// log.Println("Error RenewToken session:", err)
// http.Error(w, "Internal Server Error", http.StatusInternalServerError)
// return
// }
// http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect)
// } else {
// w.WriteHeader(http.StatusUnauthorized)
// w.Write([]byte(http.StatusText(http.StatusUnauthorized)))
// return
// }
// err := session.Manager.Clear(r.Context())
// if err != nil {
// log.Println("Error Clearing session:", err)
// http.Error(w, "Internal Server Error", http.StatusInternalServerError)
// return
// }
// session.Manager.Remove(r.Context(), "oauth2_token")
session.Manager.Clear(r.Context())
session.Manager.RenewToken(r.Context())
http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect)
}
}
type ContextKey string
func PostLogoutHandler(authConfig *config.Auth, routerData *config.Router) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// err := session.Manager.RenewToken(r.Context())
// if err != nil {
// log.Println("Error RenewToken session:", err)
// http.Error(w, "Internal Server Error", http.StatusInternalServerError)
// return
// }
// r = r.WithContext(config.Data.Context)
err := session.Manager.Clear(r.Context())
if err != nil {
log.Println("Error Clearing session:", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// dump.P(session.Manager.Commit(r.Context()))
// ctx := context.WithValue(r.Context(), ContextKey("auth"), true)
// r = r.WithContext(ctx)
http.Redirect(w, r, authConfig.OpenID.EndPoints.LogoutURL, http.StatusTemporaryRedirect)
}
}

190
internal/auth/middleware.go Normal file
View file

@ -0,0 +1,190 @@
package auth
import (
"crypto/rsa"
"net/http"
"net/http/httputil"
"slices"
"github.com/gookit/goutil/dump"
"github.com/zeevdiukman/zprox/internal/config"
"github.com/zeevdiukman/zprox/internal/session"
"golang.org/x/oauth2"
)
func Middleware(routerData *config.Router, routeID string, w http.ResponseWriter, r *http.Request, rp *httputil.ReverseProxy) {
authConfig := config.Data.AuthMap[routerData.Auth.Provider]
// dump.P("routerData.Auth.Provider", routerData.Auth.Provider)
// dump.P("authConfig.OpenID.EndPoints.RedirectURI", authConfig.OpenID.EndPoints.RedirectURI)
// dump.P("config.Data.AuthMap[routerData.Auth.Provider]", config.Data.AuthMap[routerData.Auth.Provider].OpenID.EndPoints.RedirectURI)
if !routerData.IsAuthRouter && !IsLoggedIn(r, authConfig, routerData, routeID) {
RedirectToLogin(authConfig, w, r)
return
}
rp.ServeHTTP(w, r)
}
// func IsLoggedIn(r *http.Request, routerData *config.Router, routeID string) bool {
// if oauth2Token, ok := session.Manager.Get(r.Context(), "oauth2_token").(*oauth2.Token); ok {
// if publicKey, ok := session.Manager.Get(r.Context(), "jwks_public_key").(*rsa.PublicKey); ok {
// if claims, isValid := GetJwtClaims(r, publicKey, oauth2Token); isValid {
// allowLists := routerData.Auth.JWT.AllowLists
// for listName, list := range allowLists {
// if len(list) > 0 {
// if claim, ok := claims[listName]; ok {
// if !slices.Contains(list, claim.(string)) {
// return false
// } else {
// dump.P("Hello " + claims["name"].(string))
// }
// }
// }
// }
// }
// if !oauth2Token.Valid() {
// return false
// }
// }
// dump.P("IsLoggedIn: token is not valid")
// return false
// }
// dump.P("IsLoggedIn: no token")
// return false
// }
func IsLoggedIn(r *http.Request, authConfig *config.Auth, routerData *config.Router, routeID string) bool {
var oauth2Token *oauth2.Token
var publicKey *rsa.PublicKey
var oauth2TokenOK bool
var publicKeyOK bool
if oauth2Token, oauth2TokenOK = session.Manager.Get(r.Context(), "oauth2_token").(*oauth2.Token); !oauth2TokenOK {
// dump.P("no oauth2Token")
return false
}
if publicKey, publicKeyOK = session.Manager.Get(r.Context(), "jwks_public_key").(*rsa.PublicKey); !publicKeyOK {
dump.P("publicKey not valid")
return false
}
if !oauth2Token.Valid() {
dump.P("oauth2Token not valid")
return false
}
claims, err := GetJwtClaims(r, publicKey, oauth2Token)
if err != nil {
dump.P(err.Error())
return false
}
if err := claims.Valid(); err != nil {
dump.P(err.Error())
return false
}
// dump.P(claims)
// conf := &oauth2.Config{
// ClientID: authConfig.OpenID.ClientID,
// ClientSecret: authConfig.OpenID.ClientSecret,
// RedirectURL: authConfig.OpenID.RedirectURI,
// Scopes: []string{"openid", "email", "profile"},
// Endpoint: oauth2.Endpoint{
// AuthURL: authConfig.OpenID.AuthURL,
// TokenURL: authConfig.OpenID.TokenURL,
// },
// }
// u,_ := url.Parse(authConfig.OpenID.UserURL)
// u.
// client := conf.Client(r.Context(),oauth2Token)
// client.PostForm(authConfig.OpenID.UserURL,url.Values{})
// tokenSource := conf.TokenSource(r.Context(), oauth2Token)
// tokenSource.Token()
// if !oauth2Token.Valid() {
// timeNow := time.Now()
// if !oauth2Token.Expiry.Before(timeNow) {
// return false
// }
// openID := config.Data.AuthMap[routerData.Auth.Provider].OpenID
// conf := &oauth2.Config{
// ClientID: openID.ClientID,
// ClientSecret: openID.ClientSecret,
// RedirectURL: openID.RedirectURI,
// Scopes: []string{"openid", "email", "profile"},
// Endpoint: endPoint,
// }
// oauth2Token, err := conf.Exchange(r.Context(), code)
// if err != nil {
// return false
// log.Println(err.Error())
// }
// return false
// }
for listName, list := range routerData.Auth.JWT.AllowLists {
if len(list) > 0 {
if claim, ok := claims[listName]; ok {
if !slices.Contains(list, claim.(string)) {
return false
} else {
dump.P("Hello " + claims["name"].(string))
}
}
}
}
return true
}
// func getAccessToken(refreshToken string) (TokenResponse, error) {
// keycloakURL := os.Getenv("KEYCLOAK_URL")
// realm := os.Getenv("KEYCLOAK_REALM")
// clientID := os.Getenv("KEYCLOAK_CLIENT_ID")
// clientSecret := os.Getenv("KEYCLOAK_CLIENT_SECRET")
// tokenURL := fmt.Sprintf("%s/auth/realms/%s/protocol/openid-connect/token", keycloakURL, realm)
// data := url.Values{}
// data.Set("grant_type", "refresh_token")
// data.Set("client_id", clientID)
// data.Set("client_secret", clientSecret)
// data.Set("refresh_token", refreshToken)
// req, err := http.NewRequest("POST", tokenURL, bytes.NewBufferString(data.Encode()))
// if err != nil {
// return TokenResponse{}, err
// }
// req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// client := &http.Client{}
// resp, err := client.Do(req)
// if err != nil {
// return TokenResponse{}, err
// }
// defer resp.Body.Close()
// body, err := io.ReadAll(resp.Body)
// if err != nil {
// return TokenResponse{}, err
// }
// if resp.StatusCode != http.StatusOK {
// return TokenResponse{}, fmt.Errorf("Keycloak returned status: %d, body: %s", resp.StatusCode, string(body))
// }
// var tokenResponse TokenResponse
// err = json.Unmarshal(body, &tokenResponse)
// if err != nil {
// return TokenResponse{}, err
// }
// return tokenResponse, nil
// }

View file

@ -1,41 +1,94 @@
package config package config
import ( import (
"context"
"errors"
"log"
"os"
"path/filepath"
"regexp" "regexp"
"slices"
"strings" "strings"
"github.com/zeevdiukman/go-config" "github.com/google/uuid"
"github.com/gookit/goutil/dump"
"github.com/joho/godotenv"
"github.com/spf13/viper"
) )
const VIPER_NAME string = "config" // var routeRulesNames = []string{"Host", "Path", "PathPrefix"}
const VIPER_TYPE string = "yaml" const DEFAULT_CONFIG_TYPE = "yaml"
const VIPER_PATH string = "./assets/config/."
const SERVICES_KEY string = "http.services" var DEFAULT_CONFIG_PATH string
const ROUTERS_KEY string = "http.routers" var DEFAULT_CONFIG_NAME string
const ENTRYPOINTS_KEY string = "entrypoints"
const TLS_KEY string = "tls.certproviders" var Data *Config
func init() {
err := godotenv.Load(".env")
if err != nil {
log.Fatalf("Error loading .env file: %v", err)
}
DEFAULT_CONFIG_PATH = os.Getenv("CONFIG_PATH")
DEFAULT_CONFIG_NAME = os.Getenv("CONFIG_NAME")
Data = ConfigBuild()
}
type Config struct { type Config struct {
HTTP *HTTP Routers Routers
TLS *TLS Services Services
EntryPoints EntryPoints EntryPoints EntryPoints
TLS TLS
AuthMap AuthMap
Health *Health
Logs *Logs
Context context.Context
} }
type HTTP struct { type Logs struct {
Routers Routers ConfigBuild []string
Services Services
} }
type TLS struct { type Health struct {
CertProviders CertProviders ConfigHealth *ConfigHealth
} }
type CertProviders map[string]*CertProvider type ConfigHealth struct {
type CertProvider struct { Routers bool
Key string Services bool
Cert string EntryPoints bool
AuthBuild bool
} }
type ConfigLog map[string][]string
type Services map[string]*Service type AuthMap map[string]*Auth
type Service struct { type Auth struct {
URL string Domain string
SessionSecret string
Paths *Paths
OpenID *OpenID
}
type OpenID struct {
EndPoints EndPoints
Realm string
ClientID string
ClientSecret string
}
type EndPoints struct {
Issuer string
AuthURL string
TokenURL string
UserURL string
LogoutURL string
Config string
RedirectURI string
PostLogoutRedirectUri string
JwksURI string
}
type Paths struct {
Prefix string
Login string
Logout string
Callback string
PostLogout string
} }
type EntryPoints map[string]*EntryPoint type EntryPoints map[string]*EntryPoint
type EntryPoint struct { type EntryPoint struct {
@ -45,170 +98,377 @@ type EntryPoint struct {
type EntryPointTLS struct { type EntryPointTLS struct {
Enabled bool Enabled bool
} }
type TLS struct {
CertProviders CertProviders
}
type CertProviders map[string]*certProvider
type certProvider struct {
Cert string
Key string
}
type Routers map[string]*Router type Routers map[string]*Router
type Router struct { type Router struct {
EntryPoint string IsAuthRouter bool
Service string StripPrefix bool
Rules *Rules Priority int
TLS *RouterTLS Name string
Service string
EntryPoint string
Routes Routes
Auth RouterAuth
} }
type RouterTLS struct { type RouterAuth struct {
CertProvider string JWT *JWT
Enabled bool
Provider string
} }
type Rules struct { type JWT struct {
Raw string AllowLists map[string][]string
Map map[string]*Rule }
type Routes map[string]*Route
type Route struct {
ID string
Router string
Rule map[string]string
} }
type Rule struct { type Services map[string]string
Name string
Value string func (c *Config) InitContext(ctx context.Context) {
c.Context = ctx
} }
func (e EntryPoints) ForEach(fn func(name string, data *EntryPoint)) {
for name, data := range e {
fn(name, data)
}
}
func (r Routers) ForEach(fn func(name string, data *Router) string) {
func New() *Config { for name, data := range r {
msg := fn(name, data)
if msg == "break" {
break
}
if msg == "continue" {
continue
}
}
}
func (r Routers) ForEachByPriority(fn func(name string, data *Router)) {
slc := r.getSlicesByPriority()
for _, n := range slc {
d := r[n]
fn(n, d)
}
}
func (r Routers) getSlicesByPriority() []string {
c := config.NewConfig(VIPER_NAME, VIPER_TYPE, VIPER_PATH) routersSlice := []string{}
c.Load() r.ForEach(func(routerName string, routerData *Router) string {
routersSlice = append(routersSlice, routerName)
return ""
})
slices.SortFunc(routersSlice, func(a, b string) int {
if r[a].Priority == r[b].Priority {
return 0
}
if r[a].Priority > r[b].Priority {
return -1
}
if r[a].Priority < r[b].Priority {
return 1
}
return r[a].Priority - r[b].Priority
})
return routersSlice
appConfig := &Config{ }
HTTP: &HTTP{ func (r *Router) GetRouteByID(routeID string) *Route {
Routers: Routers{}, return r.Routes[routeID]
Services: Services{}, }
}, func (r Routes) ForEach(fn func(routeID string, v *Route) string) {
TLS: &TLS{
for routeID, v := range r {
msg := fn(routeID, v)
if msg == "break" {
break
}
if msg == "continue" {
continue
}
}
}
func (r Route) ForEachRule(fn func(routeID string, ruleName string, ruleValue string) string) {
for k, v := range r.Rule {
msg := fn(r.ID, k, v)
if msg == "break" {
break
}
}
}
func BuildAuthPaths(authData any) *Paths {
pathsData := getMap(authData, "paths")
return &Paths{
Prefix: getMapString(pathsData, "prefix"),
Login: getMapString(pathsData, "login"),
Logout: getMapString(pathsData, "logout"),
Callback: getMapString(pathsData, "callback"),
PostLogout: getMapString(pathsData, "postlogout"),
}
}
func InitConfigStruct() *Config {
config := &Config{
Routers: Routers{},
Services: Services{},
EntryPoints: EntryPoints{},
TLS: TLS{
CertProviders: CertProviders{}, CertProviders: CertProviders{},
}, },
EntryPoints: EntryPoints{}, AuthMap: AuthMap{},
} Health: &Health{
ConfigHealth: &ConfigHealth{
appConfig.InitRouters(c) Routers: false,
appConfig.InitServices(c) Services: false,
appConfig.InitEntryPoints(c) EntryPoints: false,
appConfig.InitTLS(c) AuthBuild: false,
return appConfig
}
func (r Routers) ForEach(fn func(routerName string, routerConfig *Router)) {
for k, v := range r {
fn(k, v)
}
}
func (ep EntryPoints) ForEach(fn func(entryPointName string, entryPointConfig *EntryPoint)) {
forEach(ep, fn)
}
func (s Services) ForEach(fn func(serviceName string, serviceConfig *Service)) {
forEach(s, fn)
}
func (c *Config) InitRouters(vConfig *config.Config) {
c.HTTP.Routers = make(Routers)
for k, v := range vConfig.Sub(ROUTERS_KEY).AllSettings() {
val := v.(map[string]any)
var tls string = ""
var services string = ""
var rawRules string = ""
var entryPoint string = ""
if value, ok := val["service"]; ok {
services = value.(string)
}
if value, ok := val["rules"]; ok {
rawRules = value.(string)
}
if value, ok := val["entrypoint"]; ok {
entryPoint = value.(string)
}
if value, ok := val["tls"]; ok {
tls = value.(map[string]any)["certprovider"].(string)
}
rls := &Rules{
Raw: rawRules,
Map: make(map[string]*Rule),
}
r := &Router{
Service: services,
Rules: rls,
EntryPoint: entryPoint,
TLS: &RouterTLS{CertProvider: tls},
}
r.ForRuleFunc("Domain", func(domain string) {
r.Rules.Map["Domain"] = &Rule{
Name: "Domain",
Value: domain,
}
})
r.ForRuleFunc("Path", func(path string) {
r.Rules.Map["Path"] = &Rule{
Name: "Path",
Value: path,
}
})
c.HTTP.Routers[k] = r
}
}
func (c *Config) InitServices(vConfig *config.Config) {
c.HTTP.Services = make(map[string]*Service)
for k, v := range vConfig.Sub(SERVICES_KEY).AllSettings() {
c.HTTP.Services[k] = &Service{
URL: v.(string),
}
}
}
func (c *Config) InitEntryPoints(vConfig *config.Config) {
c.EntryPoints = make(EntryPoints)
for k, v := range vConfig.Sub(ENTRYPOINTS_KEY).AllSettings() {
v := v.(map[string]any)
address := ""
enabled := false
if v, ok := v["tls"]; ok {
if v, ok := v.(map[string]any)["enabled"]; ok {
enabled = v.(bool)
}
}
if value, ok := v["address"]; ok {
address = value.(string)
}
c.EntryPoints[k] = &EntryPoint{
Address: address,
TLS: &EntryPointTLS{
Enabled: enabled,
}, },
},
Logs: &Logs{
ConfigBuild: []string{},
},
}
return config
}
func ConfigBuild() *Config {
vc := NewViperConfig()
c := InitConfigStruct()
// //Routers
// //Services
BuildRoutersConfig(vc, c, "routers")
BuildServicesConfig(vc, c, "services")
BuildCertProvidersConfig(vc, c, "tls.certproviders")
BuildEntryPointsConfig(vc, c, "entrypoints")
BuildAuthConfig(vc, c, "auth")
// if !BuildServicesConfig(viperConfig, config, "services") {
// err = errors.Join(errors.New("build services config error ="))
// }
// if !BuildRoutersConfig(viperConfig, config, "routers") {
// err = errors.Join(errors.New("build routers config error"))
// }
// // //Entrypoints
// if !BuildEntryPointsConfig(viperConfig, config, "entryPoints") {
// err = errors.Join(errors.New("build entry points config error"))
// }
// // //TLS Certificate Providers
// if !BuildCertProvidersConfig(viperConfig, config, "tls.certproviders") {
// err = errors.Join(errors.New("build cert providers config error"))
// }
// if !BuildAuthConfig(viperConfig, config, "auth") {
// // err = errors.Join(errors.New("build auth config error"))
// }
return c
}
func BuildCertProvidersConfig(viperConfig *viper.Viper, config *Config, key string) bool {
if viperConfig.Sub(key) == nil {
config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
return false
} else {
certProviders := viperConfig.Sub(key).AllSettings()
// config.TLS.CertProviders = make(CertProviders)
for k, v := range certProviders {
v := v.(map[string]any)
cert := v["cert"].(string)
key := v["key"].(string)
config.TLS.CertProviders[k] = &certProvider{
Cert: cert,
Key: key,
}
} }
return true
} }
} }
func BuildServicesConfig(viperConfig *viper.Viper, config *Config, key string) bool {
if viperConfig.Sub(key) == nil {
func (c *Config) InitTLS(vConfig *config.Config) { config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
c.TLS.CertProviders = make(map[string]*CertProvider) dump.P("Missing \"" + key + "\" in Config")
return false
for k, v := range vConfig.Sub(TLS_KEY).AllSettings() { } else {
cert := v.(map[string]any)["cert"].(string) config.Services = make(Services)
key := v.(map[string]any)["key"].(string) services := viperConfig.Sub(key).AllSettings()
c.TLS.CertProviders[k] = &CertProvider{ for k, v := range services {
Cert: cert, config.Services[k] = v.(string)
Key: key,
} }
return true
} }
} }
func forEach[K comparable, V comparable](mp map[K]V, fn func(key K, value V)) { func BuildEntryPointsConfig(viperConfig *viper.Viper, config *Config, key string) bool {
for k, v := range mp { if viperConfig.Sub(key) == nil {
fn(k, v) config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
} dump.P("Missing \"" + key + "\" in Config")
}
func (r *Router) ForRuleFunc(ruleName string, fn func(string)) { return false
rules := r.Rules.Raw } else {
rulesSlice := splitStatementByRegex(rules, "\\(`(.*?)`\\)") entryPoints := viperConfig.Sub(key).AllSettings()
for idx, rule := range rulesSlice { for k, v := range entryPoints {
rule, _ = strings.CutPrefix(rule, ".")
if rule == ruleName { entryPoint := &EntryPoint{
fn(rulesSlice[idx+1]) Address: "",
TLS: &EntryPointTLS{
Enabled: false,
},
}
v := v.(map[string]any)
if _, ok := v["address"]; ok {
entryPoint.Address = v["address"].(string)
}
if _, ok := v["tls"]; ok {
tls := v["tls"].(map[string]any)
enabled := tls["enabled"].(bool)
entryPoint.TLS = &EntryPointTLS{
Enabled: enabled,
}
}
config.EntryPoints[k] = entryPoint
} }
return true
} }
} }
func getMap(mp any, key string) map[string]any {
if val, ok := mp.(map[string]any)[key]; ok {
return val.(map[string]any)
} else {
return val.(map[string]any)
}
}
func getMapString(mp any, key string) string {
if val, ok := mp.(map[string]any)[key]; ok {
return val.(string)
} else {
return ""
}
}
func markWrapper(str string, mark []string) string {
return mark[0] + str + mark[1]
}
func BuildAuthConfig(viperConfig *viper.Viper, config *Config, key string) bool {
if viperConfig.Sub(key) == nil {
config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
dump.P("Missing \"" + key + "\" in Config")
return false
} else {
authData := viperConfig.Sub(key).AllSettings()
mark := []string{"<{{", "}}>"}
realmUrlMark := markWrapper("realm", mark)
authRootUrlMark := markWrapper("auth_root_url", mark)
targetRootUrlMark := markWrapper("target_root_url", mark)
authLocalRootUrlMark := markWrapper("auth_local_root_url", mark)
for authName, v := range authData {
auth := &Auth{}
openid := &OpenID{}
// dump.P(auth)
auth.Paths = BuildAuthPaths(v)
// endPoints := &EndPoints{}
authRootURL := getMapString(v, "auth_root_url")
targetRootURL := getMapString(v, "target_root_url")
authLocalRootURL := getMapString(v, "auth_local_root_url")
openidData := getMap(v, "openid")
endPointsData := getMap(openidData, "end_points")
// for k, v := range openidData {
// if k != "end_points" {
// v := v.(string)
// field := helper.CapitalizeFirstLetter(k)
// field = helper.CapitalizeAfterCharMulti(field, '_')
// field = strings.ReplaceAll(field, "_", "")
// if strings.HasSuffix(field, "Id") {
// field, _ = strings.CutSuffix(field, "Id")
// field += "ID"
// }
// helper.InsertStringValueIntoField(openid, field, v)
// dump.P(field, v)
// }
// }
openid.Realm = openidData["realm"].(string)
openid.ClientID = openidData["client_id"].(string)
openid.ClientSecret = openidData["client_secret"].(string)
endPointsMapStringString := make(map[string]string)
for k, v := range endPointsData {
val := v.(string)
if strings.Contains(val, realmUrlMark) {
val = strings.ReplaceAll(val, realmUrlMark, openid.Realm)
}
if strings.Contains(val, authRootUrlMark) {
val = strings.ReplaceAll(val, authRootUrlMark, authRootURL)
}
if strings.Contains(val, targetRootUrlMark) {
val = strings.ReplaceAll(val, targetRootUrlMark, targetRootURL)
}
if strings.Contains(val, authLocalRootUrlMark) {
val = strings.ReplaceAll(val, authLocalRootUrlMark, authLocalRootURL)
}
endPointsMapStringString[k] = val
}
if epData, ok := endPointsMapStringString["issuer"]; ok {
openid.EndPoints.Issuer = epData
}
if epData, ok := endPointsMapStringString["redirect_uri"]; ok {
openid.EndPoints.RedirectURI = epData
}
if epData, ok := endPointsMapStringString["post_logout_redirect_uri"]; ok {
openid.EndPoints.PostLogoutRedirectUri = epData
}
if epData, ok := endPointsMapStringString["config"]; ok {
openid.EndPoints.Config = epData
}
if epData, ok := endPointsMapStringString["authurl"]; ok {
openid.EndPoints.AuthURL = epData
}
if epData, ok := endPointsMapStringString["tokenurl"]; ok {
openid.EndPoints.TokenURL = epData
}
if epData, ok := endPointsMapStringString["userurl"]; ok {
openid.EndPoints.UserURL = epData
}
if epData, ok := endPointsMapStringString["logouturl"]; ok {
openid.EndPoints.LogoutURL = epData
}
if epData, ok := endPointsMapStringString["jwksuri"]; ok {
openid.EndPoints.JwksURI = epData
}
auth.OpenID = openid
config.AuthMap[authName] = auth
}
return true
}
}
func splitStatementByRegex(str string, regx string) []string { func splitStatementByRegex(str string, regx string) []string {
input := str input := str
@ -226,16 +486,254 @@ func splitStatementByRegex(str string, regx string) []string {
startIndex := strings.Index(input, match[0]) startIndex := strings.Index(input, match[0])
if startIndex > lastIndex { if startIndex > lastIndex {
result = append(result, input[lastIndex:startIndex]) str := input[lastIndex:startIndex]
str, _ = strings.CutPrefix(str, ".")
result = append(result, str)
} }
str := match[1]
result = append(result, match[1]) // Append the content within backticks str, _ = strings.CutPrefix(str, ".")
result = append(result, str) // Append the content within backticks
lastIndex = startIndex + len(match[0]) lastIndex = startIndex + len(match[0])
} }
if lastIndex < len(input) { if lastIndex < len(input) {
result = append(result, input[lastIndex:]) str := input[lastIndex:]
str, _ = strings.CutPrefix(str, ".")
result = append(result, str)
} }
return result return result
} }
func BuildRoutersConfig(viperConfig *viper.Viper, config *Config, key string) bool {
if viperConfig.Sub(key) == nil {
config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
dump.P("Missing \"" + key + "\" in Config")
return false
} else {
routers := viperConfig.Sub(key).AllSettings()
for routerName, d := range routers {
data := d.(map[string]any)
// var (
// routes []any
// service string
// entryPoint string
// priority int
// stripPrefix bool
// )
if _, ok := data["routes"]; !ok {
continue
}
if _, ok := data["service"]; !ok {
continue
}
if _, ok := data["entrypoint"]; !ok {
continue
}
if _, ok := data["priority"]; !ok {
data["priority"] = 1000
}
if _, ok := data["routes"]; !ok {
continue
}
if _, ok := data["stripprefix"]; !ok {
data["stripprefix"] = false
}
routes := data["routes"].([]any)
service := data["service"].(string)
entryPoint := data["entrypoint"].(string)
priority := data["priority"].(int)
stripPrefix := data["stripprefix"].(bool)
isAuthRouter := false
if _, ok := data["is_auth_router"]; ok {
isAuthRouter = data["is_auth_router"].(bool)
}
//AUTH
authEnabled := false
provider := ""
jwt := &JWT{
AllowLists: map[string][]string{},
}
if d, ok := data["auth"]; ok {
if _, ok := d.(map[string]any)["enabled"]; ok {
authEnabled = d.(map[string]any)["enabled"].(bool)
}
provider = d.(map[string]any)["provider"].(string)
if jwtData, ok := d.(map[string]any)["jwt"]; ok {
if allow, ok := jwtData.(map[string]any)["allow"]; ok {
a := allow.(map[string]any)
for f, s := range a {
field := f
slice := []string{}
for _, v := range s.([]any) {
slice = append(slice, v.(string))
}
jwt.AllowLists[field] = slice
dump.P(slice)
}
}
}
// if _, ok := d.(map[string]any)["emails"]; ok {
// for _, e := range d.(map[string]any)["emails"].([]any) {
// email := e.(string)
// emails = append(emails, email)
// }
// }
}
router := &Router{
Name: routerName,
IsAuthRouter: isAuthRouter,
Priority: priority,
Service: service,
EntryPoint: entryPoint,
StripPrefix: stripPrefix,
Routes: Routes{},
Auth: RouterAuth{
Enabled: authEnabled,
Provider: provider,
JWT: jwt,
// Emails: emails,
},
}
for _, v := range routes {
routeID := uuid.New().String()
route := &Route{
ID: routeID,
Router: routerName,
Rule: map[string]string{},
}
rawRoute := v.(string)
route.Rule["raw"] = rawRoute
routeStatmentsLogicSplit := strings.Split(rawRoute, "&&")
for _, logicPart := range routeStatmentsLogicSplit {
logicPart = strings.TrimSpace(logicPart)
routeStatmentsSlice := splitStatementByRegex(logicPart, "\\(`(.*?)`\\)")
for i, v := range routeStatmentsSlice {
if i&1 == 0 {
if i+1 < len(routeStatmentsSlice) {
route.Rule[v] = routeStatmentsSlice[i+1]
}
}
}
}
// routeStatmentsSlice := splitStatementByRegex(rawRoute, "\\(`(.*?)`\\)")
// for i, v := range routeStatmentsSlice {
// if i&1 == 0 {
// if i+1 < len(routeStatmentsSlice) {
// route.Rule[v] = routeStatmentsSlice[i+1]
// }
// }
// }
router.Routes[routeID] = route
// router.Routes = append(router.Routes, route)
}
config.Routers[routerName] = router
}
return true
}
}
// func NewViperConfig() *viper.Viper {
// vc := viper.New()
// vc.AddConfigPath(DEFAULT_CONFIG_PATH)
// vc.SetConfigType(DEFAULT_CONFIG_TYPE)
// vc.SetConfigName(DEFAULT_CONFIG_NAME)
// vc.ReadInConfig()
// fileNames, err := getFileNames(DEFAULT_CONFIG_PATH, DEFAULT_CONFIG_NAME)
// if err != nil {
// dump.Println("No extra config files" + err.Error())
// } else {
// for _, fileName := range fileNames {
// vc.SetConfigName(fileName)
// vc.MergeInConfig()
// }
// }
// return vc
// }
func NewViperConfig() *viper.Viper {
vc := viper.New()
vc.AddConfigPath(DEFAULT_CONFIG_PATH)
vc.SetConfigType(DEFAULT_CONFIG_TYPE)
vc.SetConfigName(DEFAULT_CONFIG_NAME)
err := vc.ReadInConfig()
if err != nil {
dump.P(err.Error())
}
// vc1 := viper.New()
// vc1.AddConfigPath(DEFAULT_CONFIG_PATH)
// vc1.SetConfigType(DEFAULT_CONFIG_TYPE)
// vc1.SetConfigName("auth")
// vc1.ReadInConfig()
// vc.MergeConfigMap(vc1.AllSettings())
// vc.MergeConfigMap(vc1.AllSettings())
fileNames, err := getFileNames(DEFAULT_CONFIG_PATH, DEFAULT_CONFIG_NAME)
if err != nil {
dump.Println("No extra config files" + err.Error())
} else {
for _, fileName := range fileNames {
vc.SetConfigName(fileName)
vc.MergeInConfig()
}
}
return vc
}
func getYamlFileNameExcExt(fileName string, exts ...string) (string, error) {
var fileExt string
for _, ext := range exts {
if strings.HasSuffix(fileName, ext) {
fileExt = ext
break
}
}
if name, ok := strings.CutSuffix(fileName, fileExt); ok && fileExt != "" {
return name, nil
} else {
return "", errors.New("error: no file extension found ")
}
}
func getFileNames(folderPath string, skipFileName string) ([]string, error) {
var fileNames []string
err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() { // Only add files, not directories
filename := info.Name()
filesize := info.Size()
filenameNoExt, _ := getYamlFileNameExcExt(filename, ".yaml", ".yml")
if skipFileName == filenameNoExt || filesize == 0 {
return nil
}
fileNames = append(fileNames, filenameNoExt)
}
return nil
})
if err != nil {
return nil, err
}
return fileNames, nil
}

118
internal/proxy/proxy.go Normal file
View file

@ -0,0 +1,118 @@
package proxy
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/gorilla/mux"
"github.com/zeevdiukman/zprox/internal/auth"
"github.com/zeevdiukman/zprox/internal/config"
)
// func reverseProxypHandler(config *Config, epmr map[string]*mux.Router, rp *httputil.ReverseProxy, data *Router) func(string, string) http.HandlerFunc {
// return func(service string, prefix string) http.HandlerFunc {
// return Matcher(config, epmr, data, rp, service)
// }
// }
func ReverseProxypHandler(epr *mux.Router, rp *httputil.ReverseProxy, routerData *config.Router) func(string, string) http.HandlerFunc {
return func(service string, prefix string) http.HandlerFunc {
return Matcher(epr, routerData, rp, service)
}
}
func reWrite(serviceURL string, prefix string, rp *httputil.ReverseProxy, routerData *config.Router) {
u, err := url.Parse(serviceURL)
if err != nil {
log.Println(err.Error())
}
rp.Rewrite = func(pr *httputil.ProxyRequest) {
pr.SetURL(u)
if routerData.StripPrefix {
pr.Out.URL.Path = strings.TrimPrefix(pr.Out.URL.Path, prefix)
}
// pr.SetXForwarded()
}
}
func Matcher(epr *mux.Router, routerData *config.Router, rp *httputil.ReverseProxy, serviceURL string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
authEnabled := routerData.Auth.Enabled
match := &mux.RouteMatch{}
if epr.Match(r, match) {
matchID := match.Route.GetName()
pathPrefix := routerData.Routes[matchID].Rule["PathPrefix"]
route := routerData.GetRouteByID(matchID)
reWrite(serviceURL, pathPrefix, rp, routerData)
if authEnabled {
auth.Middleware(routerData, route.ID, w, r, rp)
} else {
rp.ServeHTTP(w, r)
}
} else {
w.WriteHeader(http.StatusNotFound)
}
}
}
// func Matcher(epr *mux.Router, routerData *config.Router, rp *httputil.ReverseProxy, serviceURL string) http.HandlerFunc {
// return func(w http.ResponseWriter, req *http.Request) {
// // if _, ok := config.Data.AuthMap[routerData.Auth.Provider]; ok {
// // authPrefix := config.Data.AuthMap[routerData.Auth.Provider].Paths.Prefix
// // if strings.HasPrefix(req.URL.Path, authPrefix) {
// // dump.P("HasPrefix !")
// // rp.ServeHTTP(w, req)
// // }
// // return
// // }
// authEnabled := routerData.Auth.Enabled
// match := &mux.RouteMatch{}
// // dump.P(strings.HasPrefix(req.URL.Path, authPrefix), req.URL.Path, authPrefix)
// if epr.Match(req, match) {
// matchID := match.Route.GetName()
// pathPrefix := routerData.Routes[matchID].Rule["PathPrefix"]
// route := routerData.GetRouteByID(matchID)
// // IsProtectedRoute := ""
// // if v, ok := route.Rule["Auth"]; ok {
// // IsProtectedRoute = v
// // } else {
// // IsProtectedRoute = "true"
// // }
// // protected := true
// // if !authEnabled {
// // protected = false
// // } else {
// // if IsProtectedRoute == "false" {
// // protected = false
// // }
// // if IsProtectedRoute == "true" {
// // protected = true
// // }
// // }
// reWrite(serviceURL, pathPrefix, rp, routerData)
// dump.P(matchID)
// if authEnabled {
// // if protected {
// // if protected && !strings.HasPrefix(req.URL.Path, "/auth/") {
// auth.Middleware(routerData, route.ID)(rp, w, req)
// } else {
// rp.ServeHTTP(w, req)
// }
// } else {
// w.WriteHeader(http.StatusNotFound)
// }
// }
// }

View file

@ -0,0 +1,41 @@
package session
import (
"crypto/rsa"
"encoding/gob"
"time"
"github.com/alexedwards/scs/v2"
"golang.org/x/oauth2"
)
// type SCS struct {
// *scs.SessionManager
// }
// type Data struct {
// Ctx context.Context
// Token string
// Req *http.Request
// }
var Manager *scs.SessionManager
func init() {
gob.RegisterName("oauth2_token_pointer", &oauth2.Token{})
gob.RegisterName("rsa_public_key_pointer", &rsa.PublicKey{})
Manager = scs.New()
Manager.Lifetime = 24 * time.Hour
Manager.Cookie.Name = "session_cookie"
// Manager.Cookie.Secure = true // Set to true in production
// Manager.Store = memstore.New() // Or use another store (e.g., Redis, database)
}
// func (s *SCS) UpdateHttpRequest(r *http.Request) context.Context {
// sessToken := Manager.Token(r.Context())
// sess, err := Manager.Load(r.Context(), sessToken)
// if err != nil {
// log.Println(err.Error())
// }
// s.SessionManager = sess
// return
// }

190
main.go
View file

@ -1,165 +1,67 @@
package main package main
import ( import (
"context"
"crypto/rsa"
"encoding/gob"
"net/http" "net/http"
"net/url" "net/http/httputil"
"github.com/gorilla/mux"
"github.com/zeevdiukman/go-helper" "github.com/zeevdiukman/go-helper"
"github.com/zeevdiukman/go-reverseproxy" "github.com/zeevdiukman/zprox/internal/config"
"github.com/zeevdiukman/go-router" "github.com/zeevdiukman/zprox/internal/proxy"
"github.com/zeevdiukman/go-zgate" "golang.org/x/oauth2"
"github.com/zeevdiukman/go-zgate/pkg/config"
) )
// main is the entry point of the z application. func init() {
// It initializes and configures the application, sets up entry points, gob.RegisterName("oauth2_token_pointer", &oauth2.Token{})
// configures routers, and starts the servers. It also starts test HTTP servers gob.RegisterName("rsa_public_key_pointer", &rsa.PublicKey{})
// for demonstration purposes. }
func Init() {
config.Data.InitContext(context.Background())
}
func main() { func main() {
helper.Clear() Init()
helper.AppRunner(true, func() { helper.AppRunner(true, func() {
rp := &httputil.ReverseProxy{}
zGate := zgate.New() handlers := make(map[string]func(string, string) http.HandlerFunc)
entryPointsMuxRouters := make(map[string]*mux.Router)
//Enetry points building activeEntryPoints := make(map[string]bool)
zGate.Config.EntryPoints.ForEach(func(entryPointName string, entryPointConfig *config.EntryPoint) { config.Data.EntryPoints.ForEach(func(epName string, epData *config.EntryPoint) {
v, ok := isOKv(zGate.ActiveEntryPoints, entryPointName) activeEntryPoints[epName] = false
if ok && v { config.Data.Routers.ForEach(func(name string, data *config.Router) string {
port := zgate.StrAddressPortToInt(entryPointConfig.Address) if data.EntryPoint == epName {
newEntryPoint := zGate.EntryPoints.NewEntryPoint(entryPointName) entryPointsMuxRouters[epName] = mux.NewRouter()
newEntryPoint.Server.Port(port).Name(entryPointName) activeEntryPoints[epName] = true
newEntryPoint.Server.Router(newEntryPoint.Router)
if ok && entryPointConfig.TLS.Enabled {
zGate.EntryPoints[entryPointName].IsTLS = true
} }
} return "continue"
})
}) })
//Routers building config.Data.Routers.ForEachByPriority(func(routerName string, routerData *config.Router) {
zGate.Config.HTTP.Routers.ForEach(func(rName string, rConfig *config.Router) { isActiveEntryPoint := activeEntryPoints[routerData.EntryPoint]
epName := rConfig.EntryPoint if isActiveEntryPoint {
serviceName := rConfig.Service epr := entryPointsMuxRouters[routerData.EntryPoint]
isActive, _ := isOKv(zGate.ActiveEntryPoints, epName) // buildAuthRoutes(epr, routerData)
isEpExist := isOK(zGate.EntryPoints, epName) handlers[routerData.EntryPoint] = proxy.ReverseProxypHandler(epr, rp, routerData)
isServiceExist := isOK(zGate.Config.HTTP.Services, serviceName) buildAuthRoutes(epr, routerData) //Must be be built before the routes
ok := isActive && isEpExist && isServiceExist BuildRoutes(routerData, handlers, epr)
if ok {
entryPoint := zGate.EntryPoints[rConfig.EntryPoint]
if _, ok := entryPoint.HostRouters[rName]; !ok {
entryPoint.HostRouters = make(map[string]*router.DomainRouter)
}
applyMap := make(map[string]string)
rulesAvailable := []string{
"Domain",
"Path",
"PathPrefix",
}
for _, ruleName := range rulesAvailable {
applyMap[ruleName] = rConfig.Rules.Get(ruleName)
}
revereProxyHandler := zGate.NewReverseProxy(rConfig.Service)
revereProxyHandler.Director = func(r *http.Request) {
r = reverseproxy.StripPrefix(r, applyMap["PathPrefix"])
r = r.WithContext(zGate.Context)
host := zGate.Config.HTTP.Services[rConfig.Service].URL
target, _ := url.Parse(host)
targetQuery := target.RawQuery
r.URL.Scheme = target.Scheme
r.URL.Host = target.Host
r.URL.Path, r.URL.RawPath = reverseproxy.JoinURLPath(target, r.URL)
if targetQuery == "" || r.URL.RawQuery == "" {
r.URL.RawQuery = targetQuery + r.URL.RawQuery
} else {
r.URL.RawQuery = targetQuery + "&" + r.URL.RawQuery
}
}
route1 := entryPoint.Router.NewRoute()
subRouter := route1.Host(applyMap["Domain"]).Subrouter()
subRouter.PathPrefix(applyMap["PathPrefix"]).Handler(revereProxyHandler)
// subRouter.NewRoute().Handler(revereProxyHandler)
// readyRouter.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
// return r.URL.Path == applyMap["PathPrefix"]
// }).Handler(revereProxyHandler)
// }
// readyRouter.PathPrefix(applyMap["Path"]).Handler(revereProxyHandler)
zGate.EntryPoints[rConfig.EntryPoint].Router = entryPoint.Router
// }
// if applyMap["Domain"] && applyMap["Path"] {
// serviceURL := zGate.Config.HTTP.Services[rConfig.Service].URL
// reverseProxy := reverseproxy.New(zGate.Context, serviceURL)
// domain := rConfig.Rules.Map["Domain"].Value
// hostRouter = entryPoint.Router.NewDomainRouter(domain,"Path")
// pathPrefix := rConfig.Rules.Map["Path"].Value
// hostRouter.Path(pathPrefix).Handler(reverseProxy)
// zGate.EntryPoints[rConfig.EntryPoint].HostRouters[rName] = hostRouter
// }
} }
}) })
config.Data.EntryPoints.ForEach(func(epName string, epData *config.EntryPoint) {
zGate.EntryPoints.ForEach(func(epNAme string, zGateEntryPoint *zgate.EntryPoint) { if activeEntryPoints[epName] {
if zGateEntryPoint.IsTLS { if _, ok := entryPointsMuxRouters[epName]; ok {
zGateEntryPoint.Server.CertKey(zgate.CERTS_PATH, "z.com.cert.pem", "z.com.key.pem") router := entryPointsMuxRouters[epName]
go zGateEntryPoint.Server.ListenAndServeTLS() BuildEntryPoint(epData, router)
} else { }
go zGateEntryPoint.Server.ListenAndServe()
} }
/*
//TODO: TLS per domain
// isTLS := false
// zGate.Config.HTTP.Routers.ForEach(func(rName string, rConfig *config.Router) {
// rEntryPoint := rConfig.EntryPoint
// ep, isEpConfOK := isOK(zGate.Config.EntryPoints, rEntryPoint)
// activeEp, isActiveEpOK := isOK(zGate.ActiveEntryPoints, rEntryPoint)
// if isEpConfOK && isActiveEpOK && ep.TLS.Enabled && epNAme == rEntryPoint && activeEp {
// isTLS = true
// }
// })
// zGate.Config.HTTP.Routers.ForEach(func(rName string, rConfig *config.Router) {
// rEntryPoint := rConfig.EntryPoint
// ep, ok := isOK(zGate.Config.EntryPoints, rEntryPoint)
// if ok && ep.TLS.Enabled && epNAme == rEntryPoint {
// isTLS = true
// }
// })
// zGateEntryPoint.Server.ConnState = func(c net.Conn, cs http.ConnState) {
// msg1 := c.RemoteAddr().String()
// msg2 := "Connection state: " + strconv.Itoa(int(cs))
// fmt.Println(msg1 + "\n" + msg2)
// fmt.Println("=======================")
// log.Println()
// }
*/
}) })
helper.StartTestHTTPServer(3001, "app1") helper.StartTestHTTPServer(3001, "albert")
helper.StartTestHTTPServer(3002, "app2")
}) })
} }
func isOKv[K comparable, V comparable](mp map[K]V, key K) (V, bool) {
if v, ok := mp[key]; ok {
return v, true
} else {
return v, false
}
}
func isOK[K comparable, V comparable](mp map[K]V, key K) bool {
if _, ok := mp[key]; ok {
return true
} else {
return false
}
}

225
router.go Normal file
View file

@ -0,0 +1,225 @@
package main
import (
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/zeevdiukman/zprox/internal/auth"
"github.com/zeevdiukman/zprox/internal/config"
)
func BuildRoutes(routerData *config.Router, handlers map[string]func(string, string) http.HandlerFunc, epr *mux.Router) {
routerData.Routes.ForEach(func(routeID string, route *config.Route) string {
blockedRoutes := map[string]string{}
route.ForEachRule(func(routeID string, ruleName, ruleValue string) string {
if ruleName == "!Host" || ruleName == "!PathPrefix" || ruleName == "!Path" || ruleName == "!Headers" {
blockedRoutes[routeID] = ruleValue
return "break"
}
return ""
})
if len(blockedRoutes) > 0 {
nrBlock := epr.NewRoute().Name(routeID + "_block")
route.ForEachRule(func(routeID string, ruleName, ruleValue string) string {
switch ruleName {
case "!Host":
{
nrBlock = nrBlock.Host(ruleValue)
}
case "!PathPrefix":
{
nrBlock = nrBlock.PathPrefix(ruleValue)
}
case "!Path":
{
nrBlock = nrBlock.Path(ruleValue)
}
case "!Headers":
{
ruleValue := strings.Split(ruleValue, ":")
nrBlock = nrBlock.Headers(ruleValue[0], ruleValue[1])
}
default:
}
return ""
})
nrBlock.HandlerFunc(http.NotFound)
}
nr := epr.NewRoute().Name(routeID)
route.ForEachRule(func(routeID string, ruleName, ruleValue string) string {
switch ruleName {
case "Host":
{
nr = nr.Host(ruleValue)
}
case "PathPrefix":
{
nr = nr.PathPrefix(ruleValue)
}
case "Path":
{
nr = nr.Path(ruleValue)
}
case "Headers":
{
ruleValue := strings.Split(ruleValue, ":")
nr = nr.Headers(ruleValue[0], ruleValue[1])
}
default:
}
return ""
})
serviceURL := config.Data.Services[routerData.Service]
handler := handlers[routerData.EntryPoint]
h := handler(serviceURL, routeID)
nr.HandlerFunc(h)
return ""
})
}
func buildAuthRoutes(epmr *mux.Router, routerData *config.Router) {
if routerData.Auth.Enabled {
var authSubrouter *mux.Router
authConfig := config.Data.AuthMap[routerData.Auth.Provider]
authPrefix := authConfig.Paths.Prefix
// LoginPath := authConfig.Paths.Login
logoutPath := authConfig.Paths.Logout
callbackPath := authConfig.Paths.Callback
// PostLogoutPath := authConfig.Paths.PostLogout
authRoute := epmr.NewRoute().Name(routerData.Name)
routerData.Routes.ForEach(func(routeID string, v *config.Route) string {
if isRouteProtected, ok := v.Rule["Auth"]; (ok && isRouteProtected != "false") || !ok {
if host, ok := v.Rule["Host"]; ok && host != "" {
authSubrouter = authRoute.Host(host).PathPrefix(authPrefix + "/").Subrouter()
} else {
authSubrouter = authRoute.PathPrefix(authPrefix + "/").Subrouter()
}
authSubrouter.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
})
// authSubrouter.Path(LoginPath).HandlerFunc(auth.LoginHandler(authConfig,routerData))
authSubrouter.Path(logoutPath).HandlerFunc(auth.LogoutHandler(authConfig, routerData))
authSubrouter.Path(callbackPath).HandlerFunc(auth.CallbackHandler(authConfig, routerData))
// authSubrouter.Path(PostLogoutPath).HandlerFunc(auth.PostLogoutHandler(authConfig, routerData))
return "break"
}
return ""
})
}
}
// func buildAuthRoutes(epmr *mux.Router, routerData *config.Router, handlers map[string]func(string, string) http.HandlerFunc) {
// if routerData.Auth.Enabled {
// host := ""
// routerData.Routes.ForEach(func(routeID string, v *config.Route) string {
// if isRouteProtected, ok := v.Rule["Auth"]; (ok && isRouteProtected != "false") || !ok {
// if _, ok := v.Rule["Host"]; ok {
// host = v.Rule["Host"]
// return "break"
// }
// }
// return ""
// })
// if host != "" {
// dump.P(host)
// // var r *mux.Router
// authConfig := config.Data.AuthMap[routerData.Auth.Provider]
// authPrefix := authConfig.Paths.Prefix
// // loginPath := authConfig.Paths.Login
// // logoutPath := authConfig.Paths.Logout
// callbackPath := authConfig.Paths.Callback
// // postLogoutPath := authConfig.Paths.PostLogout
// authRoute := epmr.NewRoute().Name(routerData.Name)
// // authSubrouter = authRoute.Host(host).PathPrefix(authPrefix).Subrouter()
// authRoute = authRoute.PathPrefix(authPrefix)
// authRoute = authRoute.Path("/callback")
// authRoute.Handler(auth.CallbackHandler(authConfig, routerData))
// handler := handlers[routerData.EntryPoint]
// h := handler(serviceURL, routeID)
// nr.HandlerFunc(h)
// // authSubrouter.Path(loginPath).HandlerFunc(auth.LoginHandler(authConfig, routerData))
// // authSubrouter.Path(logoutPath).HandlerFunc(auth.LogoutHandler(authConfig, routerData))
// // authSubrouter.Path(callbackPath).HandlerFunc(auth.CallbackHandler(authConfig, routerData))
// // authSubrouter.Path(postLogoutPath).HandlerFunc(auth.PostLogoutHandler(authConfig, routerData))
// }
// // if isRouteProtected, ok := route.Rule["Auth"]; (ok && isRouteProtected != "false") || !ok {
// // if host, ok := v.Rule["Host"]; ok && host != "" {
// // dump.P(authPrefix)
// // return "break"
// // }
// // } else {
// // authSubrouter = authRoute.PathPrefix(authPrefix).Subrouter()
// // }
// // authSubrouter.Use(func(next http.Handler) http.Handler {
// // return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// // next.ServeHTTP(w, r)
// // })
// // serviceURL := config.Data.Services[routerData.Service]
// // handler := handlers[routerData.EntryPoint]
// // h := handler(serviceURL, routeID)
// // nr.HandlerFunc(h)
// // return ""
// // })
// // return "break"
// // }
// // return ""
// // })
// }
// }
// func buildAuthRoutes(epmr *mux.Router, routerData *config.Router) {
// if routerData.Auth.Enabled {
// var authSubrouter *mux.Router
// authConfig := config.Data.AuthMap[routerData.Auth.Provider]
// authPrefix := authConfig.Paths.Prefix
// LoginPath := authConfig.Paths.Login
// LogoutPath := authConfig.Paths.Logout
// CallbackPath := authConfig.Paths.Callback
// PostLogoutPath := authConfig.Paths.PostLogout
// authRoute := epmr.NewRoute().Name(routerData.Name)
// routerData.Routes.ForEach(func(routeID string, v *config.Route) string {
// if isRouteProtected, ok := v.Rule["Auth"]; (ok && isRouteProtected != "false") || !ok {
// if host, ok := v.Rule["Host"]; ok && host != "" {
// authSubrouter = authRoute.Host(host).PathPrefix(authPrefix + "/").Subrouter()
// } else {
// authSubrouter = authRoute.PathPrefix(authPrefix + "/").Subrouter()
// }
// authSubrouter.Use(func(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// next.ServeHTTP(w, r)
// })
// })
// authSubrouter.Path(LoginPath).HandlerFunc(auth.LoginHandler(authConfig))
// authSubrouter.Path(LogoutPath).HandlerFunc(auth.LogoutHandler(authConfig, routerData))
// authSubrouter.Path(CallbackPath).HandlerFunc(auth.CallbackHandler(authConfig))
// authSubrouter.Path(PostLogoutPath).HandlerFunc(auth.PostLogoutHandler(authConfig, routerData))
// return "break"
// }
// return ""
// })
// }
// }

File diff suppressed because one or more lines are too long

BIN
tmp/main

Binary file not shown.