diff --git a/.air.toml b/.air.toml deleted file mode 100644 index 7da9f98..0000000 --- a/.air.toml +++ /dev/null @@ -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 diff --git a/_tmp/main b/_tmp/main deleted file mode 100755 index 5416201..0000000 Binary files a/_tmp/main and /dev/null differ diff --git a/assets/config/config.yml b/assets/config/config.yml deleted file mode 100644 index 73417b8..0000000 --- a/assets/config/config.yml +++ /dev/null @@ -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 diff --git a/config.go b/config.go new file mode 100644 index 0000000..c4312d3 --- /dev/null +++ b/config.go @@ -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 +// } diff --git a/config/app.yml b/config/app.yml new file mode 100644 index 0000000..1e1d2c4 --- /dev/null +++ b/config/app.yml @@ -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 \ No newline at end of file diff --git a/config/auth.yml b/config/auth.yml new file mode 100644 index 0000000..66e2ecf --- /dev/null +++ b/config/auth.yml @@ -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 \ No newline at end of file diff --git a/config/config.yml b/config/config.yml new file mode 100644 index 0000000..2ca82ca --- /dev/null +++ b/config/config.yml @@ -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 diff --git a/config/frontend.yml b/config/frontend.yml new file mode 100644 index 0000000..638cc73 --- /dev/null +++ b/config/frontend.yml @@ -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 \ No newline at end of file diff --git a/assets/certs/z.com.cert.pem b/crt.pem similarity index 100% rename from assets/certs/z.com.cert.pem rename to crt.pem diff --git a/entrypoint.go b/entrypoint.go new file mode 100644 index 0000000..c21887c --- /dev/null +++ b/entrypoint.go @@ -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()) + } + }() + } +} diff --git a/go.mod b/go.mod index e0e3928..6079db6 100644 --- a/go.mod +++ b/go.mod @@ -2,13 +2,17 @@ module github.com/zeevdiukman/zprox 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 ( 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/magiconair/properties v1.8.7 // 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/cast v1.6.0 // 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/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/multierr v1.9.0 // indirect + golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) 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/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/zeevdiukman/go-config v0.0.0-20250305101848-6cef80370123 github.com/zeevdiukman/go-helper v0.0.0-20250305091316-396bd5057e2f - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect ) diff --git a/go.sum b/go.sum index ca30873..a3790ac 100644 --- a/go.sum +++ b/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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 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/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +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/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= 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/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/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/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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.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.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.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/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/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/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/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 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= +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/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +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 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/auth/auth.go b/internal/auth/auth.go new file mode 100644 index 0000000..270d9e1 --- /dev/null +++ b/internal/auth/auth.go @@ -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 +} diff --git a/internal/auth/handlers.go b/internal/auth/handlers.go new file mode 100644 index 0000000..f6d207c --- /dev/null +++ b/internal/auth/handlers.go @@ -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) + + } +} diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go new file mode 100644 index 0000000..dd86175 --- /dev/null +++ b/internal/auth/middleware.go @@ -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 +// } diff --git a/internal/config/config.go b/internal/config/config.go index d1ecee5..c19b368 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,41 +1,94 @@ package config import ( + "context" + "errors" + "log" + "os" + "path/filepath" "regexp" + "slices" "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" -const VIPER_TYPE string = "yaml" -const VIPER_PATH string = "./assets/config/." -const SERVICES_KEY string = "http.services" -const ROUTERS_KEY string = "http.routers" -const ENTRYPOINTS_KEY string = "entrypoints" -const TLS_KEY string = "tls.certproviders" +// var routeRulesNames = []string{"Host", "Path", "PathPrefix"} +const DEFAULT_CONFIG_TYPE = "yaml" + +var DEFAULT_CONFIG_PATH string +var DEFAULT_CONFIG_NAME string + +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 { - HTTP *HTTP - TLS *TLS + Routers Routers + Services Services EntryPoints EntryPoints + TLS TLS + AuthMap AuthMap + Health *Health + Logs *Logs + Context context.Context } -type HTTP struct { - Routers Routers - Services Services +type Logs struct { + ConfigBuild []string } -type TLS struct { - CertProviders CertProviders +type Health struct { + ConfigHealth *ConfigHealth } -type CertProviders map[string]*CertProvider -type CertProvider struct { - Key string - Cert string +type ConfigHealth struct { + Routers bool + Services bool + EntryPoints bool + AuthBuild bool } +type ConfigLog map[string][]string -type Services map[string]*Service -type Service struct { - URL string +type AuthMap map[string]*Auth +type Auth struct { + 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 EntryPoint struct { @@ -45,170 +98,377 @@ type EntryPoint struct { 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 { - EntryPoint string - Service string - Rules *Rules - TLS *RouterTLS + IsAuthRouter bool + StripPrefix bool + Priority int + Name string + Service string + EntryPoint string + Routes Routes + Auth RouterAuth } -type RouterTLS struct { - CertProvider string +type RouterAuth struct { + JWT *JWT + Enabled bool + Provider string } -type Rules struct { - Raw string - Map map[string]*Rule +type JWT struct { + AllowLists map[string][]string +} +type Routes map[string]*Route + +type Route struct { + ID string + Router string + Rule map[string]string } -type Rule struct { - Name string - Value string +type Services map[string]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) - c.Load() + 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 - appConfig := &Config{ - HTTP: &HTTP{ - Routers: Routers{}, - Services: Services{}, - }, - TLS: &TLS{ +} +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) 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{}, }, - EntryPoints: EntryPoints{}, - } - - appConfig.InitRouters(c) - appConfig.InitServices(c) - appConfig.InitEntryPoints(c) - appConfig.InitTLS(c) - - 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, + AuthMap: AuthMap{}, + Health: &Health{ + ConfigHealth: &ConfigHealth{ + Routers: false, + Services: false, + EntryPoints: false, + AuthBuild: false, }, + }, + 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) { - c.TLS.CertProviders = make(map[string]*CertProvider) - - for k, v := range vConfig.Sub(TLS_KEY).AllSettings() { - cert := v.(map[string]any)["cert"].(string) - key := v.(map[string]any)["key"].(string) - c.TLS.CertProviders[k] = &CertProvider{ - Cert: cert, - Key: key, + 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 forEach[K comparable, V comparable](mp map[K]V, fn func(key K, value V)) { - for k, v := range mp { - fn(k, v) - } -} +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") -func (r *Router) ForRuleFunc(ruleName string, fn func(string)) { - rules := r.Rules.Raw - rulesSlice := splitStatementByRegex(rules, "\\(`(.*?)`\\)") - for idx, rule := range rulesSlice { - rule, _ = strings.CutPrefix(rule, ".") - if rule == ruleName { - fn(rulesSlice[idx+1]) + 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 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 { input := str @@ -226,16 +486,254 @@ func splitStatementByRegex(str string, regx string) []string { startIndex := strings.Index(input, match[0]) if startIndex > lastIndex { - result = append(result, input[lastIndex:startIndex]) + str := input[lastIndex:startIndex] + str, _ = strings.CutPrefix(str, ".") + result = append(result, str) } - - result = append(result, match[1]) // Append the content within backticks + 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) { - result = append(result, input[lastIndex:]) + 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) + + // 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 +} diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go new file mode 100644 index 0000000..ccf66cb --- /dev/null +++ b/internal/proxy/proxy.go @@ -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) +// } +// } +// } diff --git a/internal/session/session.go b/internal/session/session.go new file mode 100644 index 0000000..8b4ca35 --- /dev/null +++ b/internal/session/session.go @@ -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 +// } diff --git a/assets/certs/z.com.key.pem b/key.pem similarity index 100% rename from assets/certs/z.com.key.pem rename to key.pem diff --git a/main.go b/main.go index b4af9b3..097f569 100644 --- a/main.go +++ b/main.go @@ -1,165 +1,67 @@ package main import ( + "context" + "crypto/rsa" + "encoding/gob" "net/http" - "net/url" + "net/http/httputil" + "github.com/gorilla/mux" "github.com/zeevdiukman/go-helper" - "github.com/zeevdiukman/go-reverseproxy" - "github.com/zeevdiukman/go-router" - "github.com/zeevdiukman/go-zgate" - "github.com/zeevdiukman/go-zgate/pkg/config" + "github.com/zeevdiukman/zprox/internal/config" + "github.com/zeevdiukman/zprox/internal/proxy" + "golang.org/x/oauth2" ) -// main is the entry point of the z application. -// It initializes and configures the application, sets up entry points, -// configures routers, and starts the servers. It also starts test HTTP servers -// for demonstration purposes. +func init() { + gob.RegisterName("oauth2_token_pointer", &oauth2.Token{}) + gob.RegisterName("rsa_public_key_pointer", &rsa.PublicKey{}) +} + +func Init() { + config.Data.InitContext(context.Background()) + +} + func main() { - helper.Clear() - + Init() helper.AppRunner(true, func() { - - zGate := zgate.New() - - //Enetry points building - zGate.Config.EntryPoints.ForEach(func(entryPointName string, entryPointConfig *config.EntryPoint) { - v, ok := isOKv(zGate.ActiveEntryPoints, entryPointName) - if ok && v { - port := zgate.StrAddressPortToInt(entryPointConfig.Address) - newEntryPoint := zGate.EntryPoints.NewEntryPoint(entryPointName) - newEntryPoint.Server.Port(port).Name(entryPointName) - newEntryPoint.Server.Router(newEntryPoint.Router) - if ok && entryPointConfig.TLS.Enabled { - zGate.EntryPoints[entryPointName].IsTLS = true + rp := &httputil.ReverseProxy{} + handlers := make(map[string]func(string, string) http.HandlerFunc) + entryPointsMuxRouters := make(map[string]*mux.Router) + activeEntryPoints := make(map[string]bool) + config.Data.EntryPoints.ForEach(func(epName string, epData *config.EntryPoint) { + activeEntryPoints[epName] = false + config.Data.Routers.ForEach(func(name string, data *config.Router) string { + if data.EntryPoint == epName { + entryPointsMuxRouters[epName] = mux.NewRouter() + activeEntryPoints[epName] = true } - } + return "continue" + }) }) - //Routers building - zGate.Config.HTTP.Routers.ForEach(func(rName string, rConfig *config.Router) { - epName := rConfig.EntryPoint - serviceName := rConfig.Service - isActive, _ := isOKv(zGate.ActiveEntryPoints, epName) - isEpExist := isOK(zGate.EntryPoints, epName) - isServiceExist := isOK(zGate.Config.HTTP.Services, serviceName) - ok := isActive && isEpExist && isServiceExist - 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.Routers.ForEachByPriority(func(routerName string, routerData *config.Router) { + isActiveEntryPoint := activeEntryPoints[routerData.EntryPoint] + if isActiveEntryPoint { + epr := entryPointsMuxRouters[routerData.EntryPoint] + // buildAuthRoutes(epr, routerData) + handlers[routerData.EntryPoint] = proxy.ReverseProxypHandler(epr, rp, routerData) + buildAuthRoutes(epr, routerData) //Must be be built before the routes + BuildRoutes(routerData, handlers, epr) } }) - - zGate.EntryPoints.ForEach(func(epNAme string, zGateEntryPoint *zgate.EntryPoint) { - if zGateEntryPoint.IsTLS { - zGateEntryPoint.Server.CertKey(zgate.CERTS_PATH, "z.com.cert.pem", "z.com.key.pem") - go zGateEntryPoint.Server.ListenAndServeTLS() - } else { - go zGateEntryPoint.Server.ListenAndServe() + config.Data.EntryPoints.ForEach(func(epName string, epData *config.EntryPoint) { + if activeEntryPoints[epName] { + if _, ok := entryPointsMuxRouters[epName]; ok { + router := entryPointsMuxRouters[epName] + BuildEntryPoint(epData, router) + } } - /* - //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(3002, "app2") + helper.StartTestHTTPServer(3001, "albert") }) } -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 - } -} diff --git a/router.go b/router.go new file mode 100644 index 0000000..7122f7a --- /dev/null +++ b/router.go @@ -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 "" +// }) +// } +// } diff --git a/tmp/build-errors.log b/tmp/build-errors.log index 520294b..930879e 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +1 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file +exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/tmp/main b/tmp/main index eabe3bd..629bff9 100755 Binary files a/tmp/main and b/tmp/main differ