diff --git a/.air.toml b/.air.toml index a924b80..1f3f809 100644 --- a/.air.toml +++ b/.air.toml @@ -5,16 +5,16 @@ tmp_dir = "tmp" [build] args_bin = [] bin = "./tmp/main" - cmd = "go build -o ./tmp/main ./cmd/server/." + cmd = "go build -buildvcs=false -o ./tmp/main ./cmd/server/" delay = 1000 - exclude_dir = ["assets", "tmp", "vendor", "testdata","docker"] + exclude_dir = ["assets", "tmp", "vendor", "testdata"] exclude_file = [] exclude_regex = ["_test.go"] exclude_unchanged = false follow_symlink = false full_bin = "" include_dir = [] - include_ext = ["go", "tpl", "tmpl", "html","yml","yaml"] + include_ext = ["go", "tpl", "tmpl", "html","yml"] include_file = [] kill_delay = "0s" log = "build-errors.log" diff --git a/.air.toml.bkup b/.air.toml.bkup new file mode 100644 index 0000000..a924b80 --- /dev/null +++ b/.air.toml.bkup @@ -0,0 +1,52 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ./cmd/server/." + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata","docker"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html","yml","yaml"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + silent = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..55240e5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Process", + "type": "go", + "request": "attach", + "mode": "local", + "processId": 0 + } + ] +} \ No newline at end of file diff --git a/cmd/server/handlers.go b/cmd/server/handlers.go index 9f1cb53..968d924 100644 --- a/cmd/server/handlers.go +++ b/cmd/server/handlers.go @@ -10,87 +10,85 @@ import ( ) func CallbackHandler(w http.ResponseWriter, r *http.Request) { - config.Wrapper(func(c *config.Config) { - // ctx := context.Background() - query := r.URL.Query() + configData := config.Get() - code := query.Get("code") - state := query.Get("state") + // ctx := context.Background() + query := r.URL.Query() - verifier := app.SessionManager.GetString(r.Context(), "code_verifier") - if verifier == "" { - http.Error(w, "Code verifier not found in session", http.StatusBadRequest) - return - } - expectedState := app.SessionManager.GetString(r.Context(), "state") - if state != expectedState { - http.Error(w, "Invalid state parameter", http.StatusBadRequest) - return - } - // originalURL, err := decodeState(state) - // if err != nil { - // dump.P(err.Error()) - // http.Error(w, "Invalid state", http.StatusBadRequest) - // return - // } - originalPath := app.SessionManager.GetString(r.Context(), "original_path") + code := query.Get("code") + state := query.Get("state") - authName := c.GetAuthNameByDomain(r.Host) - token, fullResponse, e := exchangeCode(code, verifier, c, authName) - if e != nil { - dump.Println("exchangeCode: " + e.Error()) - } + verifier := appData.SessionManager.GetString(r.Context(), "code_verifier") + if verifier == "" { + http.Error(w, "Code verifier not found in session", http.StatusBadRequest) + return + } + expectedState := appData.SessionManager.GetString(r.Context(), "state") + if state != expectedState { + http.Error(w, "Invalid state parameter", http.StatusBadRequest) + return + } + originalURL, err := decodeState(state) + if err != nil { + dump.P(err.Error()) + http.Error(w, "Invalid state", http.StatusBadRequest) + return + } + dump.P("Original_Path: " + originalURL) + // originalPath := appData.SessionManager.GetString(r.Context(), "original_path") - app.SessionManager.Put(r.Context(), "access_token", token.AccessToken) - app.SessionManager.Put(r.Context(), "full_token", fullResponse) + authName := configData.GetAuthNameByDomain(r.Host) + token, fullResponse, e := exchangeCode(code, verifier, authName) + if e != nil { + dump.Println("exchangeCode: " + e.Error()) + } - // SetAuthHeader(w, token.AccessToken) - http.Redirect(w, r, originalPath, http.StatusFound) - // http.Redirect(w, r, originalURL, http.StatusFound) - }) + appData.SessionManager.Put(r.Context(), "access_token", token.AccessToken) + appData.SessionManager.Put(r.Context(), "full_token", fullResponse) + + // SetAuthHeader(w, token.AccessToken) + // http.Redirect(w, r, originalPath, http.StatusFound) + http.Redirect(w, r, originalURL, http.StatusFound) } func LogoutHandler(w http.ResponseWriter, r *http.Request) { - config.Wrapper(func(c *config.Config) { + configData := config.Get() - //TODO: only after returninig, delete the session! - app.SessionManager.Remove(r.Context(), "access_token") - app.SessionManager.Remove(r.Context(), "full_token") + //TODO: only after returninig, delete the session! + appData.SessionManager.Remove(r.Context(), "access_token") + appData.SessionManager.Remove(r.Context(), "full_token") - authName := c.DataMaps.DomainToAuth[r.Host] - a := c.Auth[authName] - u := a.OpenID.EndPoints.Logout - http.Redirect(w, r, u, http.StatusFound) - }) + authName := configData.DataMaps.DomainToAuth[r.Host] + a := configData.AuthMap[authName] + u := a.OpenID.EndPoints.Logout + http.Redirect(w, r, u, http.StatusFound) } func LoginHandler(w http.ResponseWriter, r *http.Request) { - config.Wrapper(func(c *config.Config) { + configData := config.Get() - authName := c.DataMaps.DomainToAuth[r.Host] + authName := configData.DataMaps.DomainToAuth[r.Host] - // state := helper.RandStringByBits(64) - nonce := helper.RandStringByBits(64) - authURL, _ := url.Parse(c.Auth[authName].OpenID.EndPoints.Auth) - query := authURL.Query() + // state := helper.RandStringByBits(64) + nonce := helper.RandStringByBits(64) + authURL, _ := url.Parse(configData.AuthMap[authName].OpenID.EndPoints.Auth) + query := authURL.Query() - codeVerifier, _ := generateCodeVerifier() - codeChallenge := generateCodeChallenge(codeVerifier) - - originalPath := app.SessionManager.GetString(r.Context(), "original_path") - state := generateState(url.QueryEscape(originalPath)) - query.Set("client_id", c.Auth[authName].OpenID.ClientID) - query.Set("response_type", "code") - query.Set("scope", "openid") - query.Set("redirect_uri", c.Auth[authName].OpenID.RedirectURI) - query.Set("code_challenge", codeChallenge) - query.Set("code_challenge_method", "S256") - query.Set("state", state) - query.Set("nonce", nonce) - authURL.RawQuery = query.Encode() - app.SessionManager.Put(r.Context(), "state", state) - app.SessionManager.Put(r.Context(), "code_verifier", codeVerifier) - http.Redirect(w, r, authURL.String(), http.StatusFound) - }) + codeVerifier, _ := generateCodeVerifier() + codeChallenge := generateCodeChallenge(codeVerifier) + originalPath := appData.SessionManager.GetString(r.Context(), "original_path") + state := generateState(url.QueryEscape(originalPath)) + query.Set("client_id", configData.AuthMap[authName].OpenID.ClientID) + query.Set("response_type", "code") + query.Set("scope", "openid") + query.Set("redirect_uri", configData.AuthMap[authName].OpenID.RedirectURI) + query.Set("code_challenge", codeChallenge) + query.Set("code_challenge_method", "S256") + query.Set("state", state) + query.Set("nonce", nonce) + authURL.RawQuery = query.Encode() + appData.SessionManager.Put(r.Context(), "state", state) + appData.SessionManager.Put(r.Context(), "code_verifier", codeVerifier) + http.Redirect(w, r, authURL.String(), http.StatusFound) } diff --git a/cmd/server/main.go b/cmd/server/main.go index ddef881..02b7388 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -36,87 +36,94 @@ type EntryPoint struct { type ReverseProxies map[string]ReverseProxy type ReverseProxy *httputil.ReverseProxy -var app = logic.NewApp() +var ( + appData *logic.App = logic.NewApp() + + // configData *config.Config +) func main() { helper.AppRunner(func() { - config.Wrapper(func(c *config.Config) { - groups := logic.NewGroups() - mainRouter := router.New() - groups.ForEach(func(k string, g *logic.Group) { - groupSubRouter := mainRouter.Mux.NewRoute().Subrouter() - groupSubRouter.Use(Domain_Middleware) - for k := range g.ReverseProxies { - rpConfig := c.ReverseProxies[k] - domain := rpConfig.Domain - proxy := reverse_proxy.New(rpConfig.Host) - proxy.Name = domain - newRoute := groupSubRouter.NewRoute() - subRouter := newRoute.Host(domain).Subrouter() - if rpConfig.Auth != "" { - if _, ok := c.Auth[rpConfig.Auth]; !ok { - err := errors.New("Error: Auth " + rpConfig.Auth + " not exist!") - panic(err.Error()) - } - pths := c.Auth[rpConfig.Auth].Paths - authRoute := subRouter.NewRoute() - subRouter.Use(Middleware_SetHeaders) - authSubRouter := authRoute.PathPrefix(pths.Prefix).Subrouter() - authSubRouter.Path(pths.Login).Handler(http.HandlerFunc(LoginHandler)) - authSubRouter.Path(pths.Logout).Handler(http.HandlerFunc(LogoutHandler)) - authSubRouter.Path(pths.Callback).Handler(http.HandlerFunc(CallbackHandler)) - subRouter.Use(authMiddleware) + configData := config.Get() + //////////TESTING AREA////////////////// + + //////////////////////////////////////// + groups := logic.NewGroups() + mainRouter := router.New() + groups.ForEach(func(groupName string, g *logic.Group) { + mainRouter.AddGroupRouter(groupName) + mainRouter.GetGroupRouter(groupName).Mux.Use(Domain_Middleware) + for k := range g.ReverseProxies { + rpConfig := configData.ReverseProxies[k] + domain := rpConfig.Domain + proxy := reverse_proxy.New(rpConfig.Host) + proxy.Name = domain + mainRouter.AddHostSubRouter(groupName, domain) + if rpConfig.Auth != "" { + if _, ok := configData.AuthMap[rpConfig.Auth]; !ok { + err := errors.New("Error: Auth " + rpConfig.Auth + " not exist!") + panic(err.Error()) } - subRouter.PathPrefix("/").Handler(proxy.Httputil) - - // Filter out static file requests first + pths := configData.AuthMap[rpConfig.Auth].Paths + hostRouterMux := mainRouter.GetHostSubRouter(groupName, domain).Mux + hostRouterMux.Use(Middleware_SetHeaders) + hostRouterMux.Use(authMiddleware) + authRoute := hostRouterMux.NewRoute() + authSubRouter := authRoute.PathPrefix(pths.Prefix).Subrouter() + authSubRouter.Path(pths.Login).Handler(http.HandlerFunc(LoginHandler)) + authSubRouter.Path(pths.Logout).Handler(http.HandlerFunc(LogoutHandler)) + authSubRouter.Path(pths.Callback).Handler(http.HandlerFunc(CallbackHandler)) } + mainRouter.GetHostSubRouter(groupName, domain).Mux.PathPrefix("/").Handler(proxy.Httputil) + // Filter out static file requests first - if len(g.ReverseProxies) > 0 { - tlsConfig := &tls.Config{ + } - GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { - // crt, key := "", "" + if len(g.ReverseProxies) > 0 { + tlsConfig := &tls.Config{ - crt, key := c.GetCertsPairByDomain(info.ServerName) + GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { + // crt, key := "", "" - if crt == "" && key == "" { - // crt = c.TLS.Certs["default"].Cert - // key = c.TLS.Certs["default"].Key - // panic("Error: TLS cert and key not found!") + crt, key := configData.GetCertsPairByDomain(info.ServerName) + + if crt == "" && key == "" { + // crt = configData.TLS.Certs["default"].Cert + // key = configData.TLS.Certs["default"].Key + // panic("Error: TLS cert and key not found!") - } - cert, err := loadCertificate(crt, key) - if err != nil { - return nil, err - } - return &cert, nil - }, - } - server := &http.Server{ - Addr: ":" + g.Port, - Handler: app.SessionManager.LoadAndSave(groupSubRouter), - TLSConfig: tlsConfig, - } - var err error - go func() { - ipAddr := helper.GetIP() - log.Println("Test server is running at http://" + ipAddr + ":" + g.Port) - if g.TLS { - err = server.ListenAndServeTLS("", "") - } else { - err = server.ListenAndServe() } + cert, err := loadCertificate(crt, key) if err != nil { - log.Println(err.Error()) + return nil, err } - }() + return &cert, nil + }, } + server := &http.Server{ + Addr: ":" + g.Port, + Handler: appData.SessionManager.LoadAndSave(mainRouter.GetGroupRouter(groupName).Mux), + TLSConfig: tlsConfig, + } + var err error + go func() { + ipAddr := helper.GetIP() + log.Println("Test server is running at http://" + ipAddr + ":" + g.Port) + if g.TLS { + err = server.ListenAndServeTLS("", "") + } else { + err = server.ListenAndServe() + } + if err != nil { + log.Println(err.Error()) + } + }() + } - }) - helper.StartTestHTTPServer(3000) }) + helper.StartTestHTTPServer(3000, "app") + helper.StartTestHTTPServer(3001, "oded") }) } @@ -130,67 +137,69 @@ func main() { func authMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + configData := config.Get() + currentPath := r.URL.Path + authName := configData.GetAuthNameByDomain(r.Host) + loginPath := configData.AuthMap[authName].Paths.Prefix + configData.AuthMap[authName].Paths.Login + logoutPath := configData.AuthMap[authName].Paths.Prefix + configData.AuthMap[authName].Paths.Logout + callbackPath := configData.AuthMap[authName].Paths.Prefix + configData.AuthMap[authName].Paths.Callback + // TODO: mark auth reverse proxy in yaml + proxyName := configData.GetProxyNameByDomain(r.Host) + if configData.ReverseProxies[proxyName].IsAuthServer { + next.ServeHTTP(w, r) + return + } - config.Wrapper(func(c *config.Config) { - currentPath := r.URL.Path - authName := c.GetAuthNameByDomain(r.Host) - loginPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Login - logoutPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Logout - callbackPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Callback - // TODO: mark auth reverse proxy in yaml + switch currentPath { + case loginPath: + { + // fmt.Fprintln(w, "LOGIN") + next.ServeHTTP(w, r) - if r.Host == "keycloak.z.com" { + } + case logoutPath: + { + next.ServeHTTP(w, r) + // return + } + case callbackPath: + { + + next.ServeHTTP(w, r) + // return + } + default: + { + + accessToken := appData.SessionManager.GetString(r.Context(), "access_token") + if accessToken == "" { + + authName := configData.DataMaps.DomainToAuth[r.Host] + http.Redirect(w, r, configData.AuthMap[authName].Paths.Prefix+configData.AuthMap[authName].Paths.Login, http.StatusFound) + return + } + // auth.SetAuthHeader(w, accessToken) + a := configData.AuthMap[authName] + pths := a.Paths + prefix := pths.Prefix + login := pths.Login + logout := pths.Logout + loginPath := prefix + login + logoutPath := prefix + logout + if loginPath == r.URL.Path || logoutPath == r.URL.Path { + next.ServeHTTP(w, r) + // return + } + + tokenOk := IsAuthorizedJWT(accessToken, authName) + + if !tokenOk { + http.Redirect(w, r, loginPath, http.StatusFound) + return + } next.ServeHTTP(w, r) } - switch currentPath { - case loginPath: - { - // fmt.Fprintln(w, "LOGIN") - next.ServeHTTP(w, r) - - } - case logoutPath: - { - next.ServeHTTP(w, r) - // return - } - case callbackPath: - { - - next.ServeHTTP(w, r) - // return - } - default: - { - - accessToken := app.SessionManager.GetString(r.Context(), "access_token") - if accessToken == "" { - authName := c.DataMaps.DomainToAuth[r.Host] - http.Redirect(w, r, c.Auth[authName].Paths.Prefix+c.Auth[authName].Paths.Login, http.StatusFound) - return - } - // auth.SetAuthHeader(w, accessToken) - a := c.Auth[authName] - pths := a.Paths - prefix := pths.Prefix - login := pths.Login - logout := pths.Logout - loginPath := prefix + login - logoutPath := prefix + logout - if loginPath == r.URL.Path || logoutPath == r.URL.Path { - next.ServeHTTP(w, r) - // return - } - - tokenOk := IsAuthorizedJWT(accessToken, c, "default") - if !tokenOk { - http.Redirect(w, r, loginPath, http.StatusFound) - return - } - next.ServeHTTP(w, r) - } - } - }) + } }) } @@ -200,8 +209,8 @@ func Domain_Middleware(next http.Handler) http.Handler { // c := config.Get() // requestedPath := r.URL.Path - // authName := c.GetAuthNameByDomain(r.Host) - // auth := c.Auth[authName] + // authName := configData.GetAuthNameByDomain(r.Host) + // auth := configData.AuthMap[authName] // excludedPaths := []string{ // auth.Paths.Prefix + auth.Paths.Login, // auth.Paths.Prefix + auth.Paths.Callback, @@ -210,7 +219,7 @@ func Domain_Middleware(next http.Handler) http.Handler { // contains := helper.IsSliceContains(excludedPaths, requestedPath) // contains := slices.Contains(excludedPaths, requestedPath) // if !contains { - // app.SessionManager.Put(r.Context(), "original_path", requestedPath) + // appData.SessionManager.Put(r.Context(), "original_path", requestedPath) // } // dump.P(requestedPath) @@ -303,12 +312,22 @@ func decodeState(encodedState string) (string, error) { } return stateData["redirect_uri"], nil } -func exchangeCode(code string, verifier string, c *config.Config, authName string) (*TokenResponse, string, error) { + +func exchangeCode(code string, verifier string, authName string) (*TokenResponse, string, error) { + configData := config.Get() + clientID := configData.AuthMap[authName].OpenID.ClientID + clientSecert := configData.AuthMap[authName].OpenID.ClientSecert + redirectURI := configData.AuthMap[authName].OpenID.RedirectURI + // if strings.Contains(redirectURI,"<{{dynamic}}>") { + // configData.GenerateDynamicRedirectUri() + // } + + // GenerateDynamicRedirectUri data := url.Values{} data.Set("grant_type", "authorization_code") - data.Set("client_id", c.Auth[authName].OpenID.ClientID) - data.Set("client_secret", c.Auth[authName].OpenID.ClientSecert) - data.Set("redirect_uri", c.Auth[authName].OpenID.RedirectURI) + data.Set("client_id", clientID) + data.Set("client_secret", clientSecert) + data.Set("redirect_uri", redirectURI) data.Set("code", code) data.Set("scope", "openid zapp") if verifier != "" { @@ -318,7 +337,7 @@ func exchangeCode(code string, verifier string, c *config.Config, authName strin TLSClientConfig: &tls.Config{InsecureSkipVerify: DEVELOPMENT}, } client := &http.Client{Transport: tr} - u := c.Auth[authName].OpenID.EndPoints.Token + u := configData.AuthMap[authName].OpenID.EndPoints.Token r, _ := http.NewRequest(http.MethodPost, u, strings.NewReader(data.Encode())) r.Header.Add("Content-Type", "application/x-www-form-urlencoded") resp, err := client.Do(r) @@ -358,7 +377,9 @@ func generateCodeChallenge(verifier string) string { return base64.RawURLEncoding.EncodeToString(hash[:]) } -func IsAuthorizedJWT(rawAccessToken string, c *config.Config, authName string) bool { +func IsAuthorizedJWT(rawAccessToken string, authName string) bool { + configData := config.Get() + tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -367,14 +388,14 @@ func IsAuthorizedJWT(rawAccessToken string, c *config.Config, authName string) b Transport: tr, } ctx := oidc.ClientContext(context.Background(), client) - provider, err := oidc.NewProvider(ctx, c.Auth[authName].OpenID.EndPoints.Issuer) + provider, err := oidc.NewProvider(ctx, configData.AuthMap[authName].OpenID.EndPoints.Issuer) if err != nil { dump.Println("authorisation failed while getting the provider: " + err.Error()) return false } oidcConfig := &oidc.Config{ - ClientID: c.Auth[authName].OpenID.ClientID, + ClientID: configData.AuthMap[authName].OpenID.ClientID, } verifier := provider.Verifier(oidcConfig) idToken, err := verifier.Verify(ctx, rawAccessToken) @@ -394,22 +415,22 @@ func IsAuthorizedJWT(rawAccessToken string, c *config.Config, authName string) b /////////////////////////////////// /////////////////////////////////// -func isStaticFileRequest(path string) bool { - // Check for common static file prefixes - if strings.HasPrefix(path, "/static/") || strings.HasPrefix(path, "/assets/") { - return true - } +// func isStaticFileRequest(path string) bool { +// // Check for common static file prefixes +// if strings.HasPrefix(path, "/static/") || strings.HasPrefix(path, "/assets/") { +// return true +// } - // Check for common static file extensions - staticExtensions := []string{ - ".css", ".js", ".jpg", ".jpeg", ".png", ".gif", ".svg", ".ico", - } +// // Check for common static file extensions +// staticExtensions := []string{ +// ".css", ".js", ".jpg", ".jpeg", ".png", ".gif", ".svg", ".ico", +// } - for _, ext := range staticExtensions { - if strings.HasSuffix(path, ext) { - return true - } - } +// for _, ext := range staticExtensions { +// if strings.HasSuffix(path, ext) { +// return true +// } +// } - return false -} +// return false +// } diff --git a/config.yml b/config.yml index e81ac4b..d4652ae 100644 --- a/config.yml +++ b/config.yml @@ -8,7 +8,6 @@ reverse_proxies: enabled: true certs: default auth_server: true - app: domain: app.z.com host: http://127.0.0.1:3000 @@ -16,7 +15,16 @@ reverse_proxies: tls: enabled: true certs: default - auth: default + auth: app_auth + oded: + domain: oded.z.com + host: http://127.0.0.1:3001 + entry_point: https + tls: + enabled: true + certs: default + auth: oded_auth + tls: certs: @@ -32,7 +40,7 @@ entry_points: port: 80 auth: - default: + app_auth: paths: prefix: /auth login: /login @@ -43,19 +51,25 @@ auth: realm: dev client_id: dev_client client_secret: dWhSJgARBAuBAXN7sUTpqpIq2sKQdugs - redirect_uri: https://app.z.com/auth/callback + redirect_uri: <{{dynamic}}>/auth/callback post_logout_redirect_uri: https://app.z.com/auth/logout - config_path: /realms/{{realm}}/.well-known/openid-configuration - # config_fields: - # - issuer - # - authorization_endpoint - # - token_endpoint - # - introspection_endpoint - # - userinfo_endpoint - # - end_session_endpoint - # - jwks_uri - # issuer: http://127.0.0.1:8080/realms/dev - + config_path: /realms/<{{realm}}>/.well-known/openid-configuration + oded_auth: + paths: + prefix: /auth + login: /login + logout: /logout + callback: /callback + open_id: + host: http://127.0.0.1:8080 + realm: dev + client_id: dev_client + client_secret: dWhSJgARBAuBAXN7sUTpqpIq2sKQdugs + # redirect_uri: https://oded.z.com/auth/callback + redirect_uri: <{{dynamic}}>/auth/callback + post_logout_redirect_uri: https://oded.z.com/auth/logout + # post_logout_redirect_uri: <{{dynamic}}>/auth/logout + config_path: /realms/<{{realm}}>/.well-known/openid-configuration # scope: openid profile email diff --git a/internal/config/config.go b/internal/config/config.go index 1ce23b1..9a7f7ee 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -15,20 +15,28 @@ const Viper_File_Name string = "config" const Viper_File_Type string = "yaml" const Viper_File_Path string = "." -var c *Config +var ( + configData *Config +) func init() { - helper.New().Screen.Clear() - c = createConfig() - + h := helper.New() + h.Screen.Clear() + configData = createConfig() } + func Wrapper(fn func(c *Config)) { - fn(c) + fn(configData) } func Get() *Config { - return c + + return configData +} +func Get2() *Config { + return configData } func createConfig() *Config { + c := &Config{} c.initViper() c.setViperOptions(Viper_File_Name, Viper_File_Type, Viper_File_Path) @@ -76,6 +84,19 @@ func (rp *ReverseProxies) ForEach(fn func(rpName string, rpConfig *ReverseProxy) } } +func (rp *ReverseProxies) ForEachReturn(fn func(rpName string, rpConfig *ReverseProxy) (res ForEachReturn)) *ForEachReturn { + res := ForEachReturn{} + for rpName, rpData := range *rp { + res = append(fn(rpName, &rpData), res) + } + return &res +} + +// func (rp AuthMap) ForEach(fn func(authName string, authConfig Auth)) { +// for authName, authConfig := range rp { +// fn(authName, authConfig) +// } +// } func (c *Config) fetchEndPointsByAuthName(authName string) map[string]any { configURL := c.KeycloakWellknownURL(authName) resp := helper.IsFetchOK[EndPoints](configURL, "", http.Get) @@ -89,10 +110,10 @@ func (c *Config) fetchEndPointsByAuthName(authName string) map[string]any { } func (c *Config) initOpenIDEndPoints() { - for authName := range c.Auth { + for authName := range c.AuthMap { data := c.fetchEndPointsByAuthName(authName) - c.Auth[authName].OpenID.EndPoints = &EndPoints{ + c.AuthMap[authName].OpenID.EndPoints = &EndPoints{ Issuer: data["issuer"].(string), Auth: data["authorization_endpoint"].(string), Introspection: data["introspection_endpoint"].(string), @@ -107,17 +128,35 @@ func (c *Config) initOpenIDEndPoints() { } func (c *Config) KeycloakWellknownURL(authName string) string { - if _, ok := c.Auth[authName]; !ok { + if _, ok := c.AuthMap[authName]; !ok { return "" } - hostUrl := c.Auth[authName].OpenID.Host - realm := c.Auth[authName].OpenID.Realm - configPath := c.Auth[authName].OpenID.ConfigPath + hostUrl := c.AuthMap[authName].OpenID.Host + realm := c.AuthMap[authName].OpenID.Realm + configPath := c.AuthMap[authName].OpenID.ConfigPath u := hostUrl - u += strings.ReplaceAll(configPath, "{{realm}}", realm) + u += strings.ReplaceAll(configPath, "<{{realm}}>", realm) return u } +func (c *Config) GenerateDynamicRedirectUri(proxyName string, authName string) string { + if _, ok := c.AuthMap[authName]; !ok { + return "" + } + + proto := "http" + + domain := c.ReverseProxies[proxyName].Domain + tls := c.ReverseProxies[proxyName].TLS.Enabled + if tls { + proto = "https" + } + redirectURI := c.AuthMap[authName].OpenID.RedirectURI + u := redirectURI + u = strings.ReplaceAll(redirectURI, "<{{dynamic}}>", proto+"://"+domain) + + return u +} func (c *Config) initDomainToProxyNameMap() { mp := make(map[string]string) c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) { @@ -126,17 +165,22 @@ func (c *Config) initDomainToProxyNameMap() { c.DataMaps.DomainToProxy = mp } func (c *Config) initDomainToProxyAuthName() { + authMap := map[string]int{} - for authName := range c.Auth { + for authName := range c.AuthMap { authMap[authName] = 1 } + mp := make(map[string]string) c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) { if v, ok := authMap[rpConfig.Auth]; ok && v == 1 { mp[rpConfig.Domain] = rpConfig.Auth + } + }) c.DataMaps.DomainToAuth = mp + } func (c *Config) initDomainToCertName() { crtMap := map[string]int{} @@ -155,6 +199,7 @@ func (c *Config) initDomainToCertName() { func (c *Config) GetAuthNameByDomain(domain string) string { return c.DataMaps.DomainToAuth[domain] } + func (c *Config) GetProxyNameByDomain(domain string) string { return c.DataMaps.DomainToProxy[domain] } @@ -174,5 +219,25 @@ func (c *Config) GetCertsPairByDomain(domain string) (string, string) { } return crt, key - +} + +func (c *Config) GetProxiesSliceByAuthName(authName string) []string { + slc := &[]string{} + c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) { + isOK := true + if authName != rpConfig.Auth { + isOK = false + } + if _, ok := c.AuthMap[authName]; !ok { + isOK = false + } + + if isOK { + helper.AppendToPointer(slc, rpName) + } + }) + return *slc +} +func (c *Config) GetAuthNameByProxyName(proxyName string) string { + return c.ReverseProxies[proxyName].Auth } diff --git a/internal/config/types.go b/internal/config/types.go deleted file mode 100644 index 5f14c3a..0000000 --- a/internal/config/types.go +++ /dev/null @@ -1,93 +0,0 @@ -package config - -import "github.com/spf13/viper" - -type Config struct { - Viper *viper.Viper - DataMaps DataMaps - ReverseProxies ReverseProxies `mapstructure:"reverse_proxies"` - TLS TLS `mapstructure:"tls"` - EntryPoints EntryPoints `mapstructure:"entry_points"` - Auth map[string]*AuthInstance `mapstructure:"auth"` -} - -type DataMaps struct { - DomainToProxy map[string]string - DomainToAuth map[string]string - DomainToCert map[string]string -} - -// AUTH -// type Auth map[string]AuthInstance - -type AuthInstance struct { - Paths Paths `mapstructure:"paths"` - OpenID OpenID `mapstructure:"open_id"` -} -type Paths struct { - Prefix string `mapstructure:"prefix"` - Login string `mapstructure:"login"` - Logout string `mapstructure:"logout"` - Callback string `mapstructure:"callback"` -} -type OpenID struct { - Host string `mapstructure:"host"` - Realm string `mapstructure:"realm"` - ClientID string `mapstructure:"client_id"` - ClientSecert string `mapstructure:"client_secret"` - RedirectURI string `mapstructure:"redirect_uri"` - PostLogoutRedirectURI string `mapstructure:"post_logout_redirect_uri"` - ConfigPath string `mapstructure:"config_path"` - EndPoints *EndPoints -} - -type EndPoints struct { - Issuer string - Auth string - Introspection string - Token string - UserInfo string - Logout string - JwksUri string -} - -// type OpenIdEndPoints struct { -// Issuer string -// Authorization string -// Token string -// Introspection string -// UserInfo string -// EndSession string -// } - -// ReverseProxies - -type ReverseProxies map[string]ReverseProxy - -type ReverseProxy struct { - Domain string `mapstructure:"domain"` - Host string `mapstructure:"host"` - EntryPoint string `mapstructure:"entry_point"` - TLS TLS_RP `mapstructure:"tls"` - Auth string `mapstructure:"auth"` -} -type TLS_RP struct { - Enabled bool `mapstructure:"enabled"` - Certs string `mapstructure:"certs"` -} - -// TLS -type TLS struct { - Certs map[string]Certs `mapstructure:"certs"` -} -type Certs struct { - Cert string `mapstructure:"cert"` - Key string `mapstructure:"key"` -} - -// EntryPoints -type EntryPoints map[string]EntryPoint -type EntryPoint struct { - Port string `mapstructure:"port"` - TLS bool `mapstructure:"tls"` -} diff --git a/internal/config/types_auth_.go b/internal/config/types_auth_.go new file mode 100644 index 0000000..271260a --- /dev/null +++ b/internal/config/types_auth_.go @@ -0,0 +1,36 @@ +package config + +type AuthMap map[string]*Auth + +type Auth struct { + Paths Paths `mapstructure:"paths"` + OpenID OpenID `mapstructure:"open_id"` +} + +type Paths struct { + Prefix string `mapstructure:"prefix"` + Login string `mapstructure:"login"` + Logout string `mapstructure:"logout"` + Callback string `mapstructure:"callback"` +} + +type OpenID struct { + Host string `mapstructure:"host"` + Realm string `mapstructure:"realm"` + ClientID string `mapstructure:"client_id"` + ClientSecert string `mapstructure:"client_secret"` + RedirectURI string `mapstructure:"redirect_uri"` + PostLogoutRedirectURI string `mapstructure:"post_logout_redirect_uri"` + ConfigPath string `mapstructure:"config_path"` + EndPoints *EndPoints +} + +type EndPoints struct { + Issuer string + Auth string + Introspection string + Token string + UserInfo string + Logout string + JwksUri string +} diff --git a/internal/config/types_config.go b/internal/config/types_config.go new file mode 100644 index 0000000..d9729f4 --- /dev/null +++ b/internal/config/types_config.go @@ -0,0 +1,20 @@ +package config + +import "github.com/spf13/viper" + +type Config struct { + Viper *viper.Viper + DataMaps DataMaps + ReverseProxies ReverseProxies `mapstructure:"reverse_proxies"` + TLS TLS `mapstructure:"tls"` + EntryPoints EntryPoints `mapstructure:"entry_points"` + AuthMap AuthMap `mapstructure:"auth"` +} + +type DataMaps struct { + DomainToProxy map[string]string + DomainToAuth map[string]string + DomainToCert map[string]string +} + +type ForEachReturn []any diff --git a/internal/config/types_entry_points.go b/internal/config/types_entry_points.go new file mode 100644 index 0000000..6cf3102 --- /dev/null +++ b/internal/config/types_entry_points.go @@ -0,0 +1,8 @@ +package config + +type EntryPoints map[string]EntryPoint + +type EntryPoint struct { + Port string `mapstructure:"port"` + TLS bool `mapstructure:"tls"` +} diff --git a/internal/config/types_reverse_proxy.go b/internal/config/types_reverse_proxy.go new file mode 100644 index 0000000..58ef9b8 --- /dev/null +++ b/internal/config/types_reverse_proxy.go @@ -0,0 +1,12 @@ +package config + +type ReverseProxies map[string]ReverseProxy + +type ReverseProxy struct { + Domain string `mapstructure:"domain"` + Host string `mapstructure:"host"` + EntryPoint string `mapstructure:"entry_point"` + TLS TLS_RP `mapstructure:"tls"` + Auth string `mapstructure:"auth"` + IsAuthServer bool `mapstructure:"auth_server"` +} diff --git a/internal/config/types_tls.go b/internal/config/types_tls.go new file mode 100644 index 0000000..6b47800 --- /dev/null +++ b/internal/config/types_tls.go @@ -0,0 +1,15 @@ +package config + +type TLS struct { + Certs map[string]Certs `mapstructure:"certs"` +} + +type Certs struct { + Cert string `mapstructure:"cert"` + Key string `mapstructure:"key"` +} + +type TLS_RP struct { + Enabled bool `mapstructure:"enabled"` + Certs string `mapstructure:"certs"` +} diff --git a/internal/router/router.go b/internal/router/router.go index 7934fc2..c528c9c 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -1,24 +1,77 @@ package router -import "github.com/gorilla/mux" +import ( + "github.com/gorilla/mux" + "zeevdiukman.com/zprox/pkg/helper" +) func New() *MainRouter { r := &MainRouter{} - r.SetMux() + r.setMux() return r } -func (r *MainRouter) SetMux() { +func (r *MainRouter) setMux() { r.Mux = mux.NewRouter() } +func (mainRouter *MainRouter) AddGroupRouter(groupName string) *GroupRouter { + s := mainRouter.Mux.NewRoute().Subrouter() + groupRouter := &GroupRouter{ + Mux: s, + } + + newMap := make(GroupRouters) + helper.MapIter(mainRouter.GroupRouters, func(grName string, gr *GroupRouter) { + newMap[grName] = gr + }) + newMap[groupName] = groupRouter + + mainRouter.GroupRouters = newMap + return groupRouter +} +func (mainRouter *MainRouter) GetGroupRouter(groupName string) *GroupRouter { + return mainRouter.GroupRouters[groupName] +} +func (mainRouter *MainRouter) GetHostSubRouter(groupName string, domain string) *SubRouter { + return mainRouter.GroupRouters[groupName].SubRouters[domain] +} +func (mainRouter *MainRouter) AddHostSubRouter(groupName string, domain string) *SubRouter { + groupSubRouter := mainRouter.GroupRouters[groupName] + newRoute := groupSubRouter.Mux.NewRoute() + hostSubRouter := &SubRouter{ + Mux: newRoute.Host(domain).Subrouter(), + } + newMap := make(SubRouters) + helper.MapIter(mainRouter.GroupRouters[groupName].SubRouters, func(grName string, gr *SubRouter) { + newMap[grName] = gr + }) + newMap[domain] = hostSubRouter + mainRouter.GroupRouters[groupName].SubRouters = newMap + return hostSubRouter +} + +func (mainRouter *MainRouter) AddGroupSubRouter(groupName string, subRouterName string) *SubRouter { + gsr := mainRouter.GetGroupRouter(groupName).Mux.NewRoute().Subrouter() + subRouter := &SubRouter{ + Mux: gsr, + } + mainRouter.GroupRouters[groupName].SubRouters[subRouterName] = subRouter + return subRouter +} type MainRouter struct { - Subrouters Subrouters + GroupRouters GroupRouters + Mux *mux.Router +} + +type GroupRouters map[string]*GroupRouter + +type GroupRouter struct { + SubRouters SubRouters Mux *mux.Router } -type Subrouters map[string]SubRouter + +type SubRouters map[string]*SubRouter type SubRouter struct { - Name string - Group string - *mux.Router + Mux *mux.Router } diff --git a/pkg/helper/helper.go b/pkg/helper/helper.go index aba6ec0..859b8e2 100644 --- a/pkg/helper/helper.go +++ b/pkg/helper/helper.go @@ -275,7 +275,7 @@ func CapitalizeFirstLetter(input string) string { return string(runes) // Convert rune slice back to string } -func MapIter[K comparable, V comparable](m map[K]V, fn func(K, V)) { +func MapIter[K string, V any](m map[K]V, fn func(K, V)) { for p, d := range m { fn(p, d) } @@ -456,25 +456,25 @@ func AppRunner(runApp func()) { <-ctx.Done() //do stuff after ending wg.Wait() - fmt.Println("BYE BYE!") + log.Println("AppRunner stopped") os.Exit(0) } -func StartTestHTTPServer(port int) { +func StartTestHTTPServer(port int, name string) { p := strconv.Itoa(port) go func() { log.Println("Test server is running at http://" + GetIP() + ":" + p) r := mux.NewRouter() r.Path("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "/ OK") + fmt.Fprintln(w, r.Host+"/ OK") }) r.Path("/test1").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "/test1 OK") + fmt.Fprintln(w, r.Host+"/test1 OK") }) r.Path("/test2").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "/test2 OK") + fmt.Fprintln(w, r.Host+"/test2 OK") }) - err := http.ListenAndServe(":3000", r) + err := http.ListenAndServe(":"+p, r) if err != nil { log.Println(err.Error()) } @@ -510,3 +510,7 @@ func GetIP(prefix ...string) string { } return prfx } + +func AppendToPointer[T any](slc *[]T, val T) { + *slc = append(*slc, val) +} diff --git a/tmp/build-errors.log b/tmp/build-errors.log index 028e360..883239f 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 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 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 1 \ No newline at end of file diff --git a/tmp/main b/tmp/main index 4396bf2..70476bd 100755 Binary files a/tmp/main and b/tmp/main differ