22032025-1
This commit is contained in:
parent
2c7fbd6617
commit
8e690668a4
23 changed files with 2463 additions and 442 deletions
52
.air.toml
52
.air.toml
|
|
@ -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
BIN
_tmp/main
Binary file not shown.
|
|
@ -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
517
config.go
Normal 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
25
config/app.yml
Normal 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
76
config/auth.yml
Normal 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
13
config/config.yml
Normal 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
15
config/frontend.yml
Normal 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
32
entrypoint.go
Normal 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
27
go.mod
|
|
@ -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
41
go.sum
|
|
@ -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
205
internal/auth/auth.go
Normal 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
255
internal/auth/handlers.go
Normal 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
190
internal/auth/middleware.go
Normal 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
|
||||||
|
// }
|
||||||
|
|
@ -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
118
internal/proxy/proxy.go
Normal 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)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
41
internal/session/session.go
Normal file
41
internal/session/session.go
Normal 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
190
main.go
|
|
@ -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
225
router.go
Normal 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
BIN
tmp/main
Binary file not shown.
Loading…
Reference in a new issue