From 6a4b2c9d15a91144a4e1e87d4ac16e34756ca366 Mon Sep 17 00:00:00 2001 From: Pouyan Azari Date: Wed, 19 Jun 2019 17:55:29 +0200 Subject: [PATCH] added no_auth type, login targets and logout possibility --- CHANGELOG | 8 +++++ README.md | 45 +++++++++++++++++++----- collector.go | 86 +++++++++++++++++++++++++++++++++++++++------ main.go | 2 +- misc/login.yml.dist | 7 ++-- 5 files changed, 126 insertions(+), 22 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c0666ce..1e26b8f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,13 @@ # Change Log +## [0.0.2] 2019-06-19 + +- Added target as own parameter different from the url +- Added submit_type parameter which can be used for buttons or forms that can be clicked or submitted +- Some fixes in the application it self +- Allow logout to also be initiated, when the xpath exists +- Allow No Auth, when the page does not have any auth + ## [0.0.1] 2019-06-18 - First release \ No newline at end of file diff --git a/README.md b/README.md index 8c4a6a2..01c031a 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ kind of login forms. The following parameters should be set: | parameter | help | |----------------|----------------| | login_type | simple_form | +| target | The target that is searched for to find the config| | url | The url that login form is included| | username | The username that should be used for the login| | password | The password that should be used for the login| @@ -86,6 +87,8 @@ The following parameters are optional: | parameter | help | |--------------------|----------------| | expected_text_xpath| The expected text xpath (must be unique), if not given the whole text is searched for the string| +| submit_type | The type of submission that should be used it can be click or submit, default is submit| +| logout_xpath | The xpath that should be used for the logout button| ### Shibboleth @@ -98,6 +101,7 @@ be set for the Shibboleth: | parameter | help | |----------------|----------------| | login_type | shibboleth | +| target | The target that is searched for to find the config| | url | The url that login form is included| | username | The username that should be used for the login| | password | The password that should be used for the login| @@ -111,6 +115,7 @@ The following parameters are optional: | password_xpath | The xpath address of the password field (must be unique) for Shibboleth default value is the `//input[@id='password']`| | submit_xpath | The xpath address of the submit button (must be unique) for Shibboleth default value is the `//button[@class='aai_login_button']`| | expected_text_xpath| The expected text xpath (must be unique), if not given the whole text is searched for the string| +| submit_type | The type of submission that should be used it can be click or submit, default is submit| ### Basic Auth @@ -121,6 +126,7 @@ following parameters should be set for the application: |----------------|----------------| | login_type | basic_auth | | url | The url that login form is included| +| target | The target that is searched for to find the config| | username | The username that should be used for the login| | password | The password that should be used for the login| | expected_text | The text that is expected to be there| @@ -141,6 +147,7 @@ should be set: |----------------|----------------| | login_type | password_only | | url | The url that login form is included| +| target | The target that is searched for to find the config| | password | The password that should be used for the login| | password_xpath | The xpath address of the password field (must be unique)| | expected_text | The text that is expected to be there| @@ -150,6 +157,8 @@ The following parameters are optional | parameter | help | |--------------------|----------------| | expected_text_xpath| The expected text xpath (must be unique), if not given the whole text is searched for the string| +| submit_type | The type of submission that should be used it can be click or submit, default is submit| +| logout_xpath | The xpath that should be used for the logout button| ### API @@ -162,6 +171,7 @@ parameters should be set: |----------------|----------------| | login_type | api | | url | The url that login form is included| +| target | The target that is searched for to find the config| | password | This could be key, password or token that should be used| | password_xpath | This is the parameter that should be used for the token or password| | expected_text | The text that is expected to be there| @@ -174,6 +184,25 @@ The following parameters are optional: | username_xpath | The parameter that is used for the username| | method | The method that should be used for the call default is POST| +### No Auth + +When the page is only protected by IP address firewall or does not +have any authentication, but still should be checked for a given text +this method can be used. + +| parameter | help | +|----------------|----------------| +| login_type | no_auth | +| url | The url that login form is included| +| target | The target that is searched for to find the config| +| expected_text | The text that is expected to be there| + +These parameters are optional: + +| parameter | help | +|--------------------|----------------| +| expected_text_xpath| The expected text xpath (must be unique), if not given the whole text is searched for the string| + ## Configuring Prometheus This exporter works the same way as @@ -209,11 +238,10 @@ In `login_targets.json` the following settings would be enough: "group": "apps", "host": "hostname", "ip": "ip_address", - "job": "login_exporter", - "module": "http_2xx" + "job": "login_exporter" }, "targets": [ - "target_url_which_is_defined_in_login.yml_before" + "target_which_is_defined_in_login.yml_before" ] } ] @@ -222,10 +250,11 @@ In `login_targets.json` the following settings would be enough: ## Development This application is open-source and can be extended. This repository -is mirror of our home owned repository, so the data here can not be -merged. But pull requests are still welcome. It will extract the data -and add them manually to our internal repo. You still can fork this -repository and add your changes too. +is a mirror of our home owned repository, as a result the pull request +here can not be directly merged. But pull requests are still welcome. +I will extract the patch and add it manually to our internal repo, when +it is acceptable patch. You still can fork this repository and add your +changes too. ### Build @@ -238,7 +267,7 @@ go get go build -o ./login_exporter ``` -Or If you want to create a binary for several platforms at once, you can +Or if you want to create a binary for several platforms at once, you can use the `go_build.sh` script. ## Change Log diff --git a/collector.go b/collector.go index bb5d6d4..325c041 100644 --- a/collector.go +++ b/collector.go @@ -28,6 +28,7 @@ type LoginConfigs struct { /// that is used to read the yaml files. type SingleLoginConfig struct { Url string `yaml:"url"` + Target string `yaml:"target"` Username string `yaml:"username"` Password string `yaml:"password"` Certificate string `yaml:"certificate"` @@ -41,6 +42,8 @@ type SingleLoginConfig struct { SSLCheck bool `yaml:"ssl_check"` Debug bool `yaml:"debug"` Method string `yaml:"method"` + SubmitType string `yaml:"submit_type"` + LogoutXpath string `yaml:"logout_xpath"` } /// getChromeOptions Returns the options for the chrome driver that is used @@ -149,7 +152,7 @@ func getLogger() *log.Logger { /// loginSimpleForm Logs in the simple for using the username, password and the submit button func loginSimpleForm(page *agouti.Page, urlText string, usernameXpath string, passwordXpath string, submitXpath string, - username string, password string) { + username string, password string, submitType string) { err := page.Navigate(urlText) if err != nil { logger.WithFields( @@ -178,7 +181,11 @@ func loginSimpleForm(page *agouti.Page, urlText string, usernameXpath string, pa }).Warningln(err.Error()) } submitField := page.FindByXPath(submitXpath) - err = submitField.Submit() + if submitType == "click" { + err = submitField.Click() + } else { + err = submitField.Submit() + } if err != nil { logger.WithFields( log.Fields{ @@ -190,7 +197,7 @@ func loginSimpleForm(page *agouti.Page, urlText string, usernameXpath string, pa /// loginShibboleth Logs in the shibboleth system using the given username and password func loginShibboleth(page *agouti.Page, urlText string, username string, password string, usernameXpath string, - passwordXpath string, submitXpath string) { + passwordXpath string, submitXpath string, submitType string) { err := page.Navigate(urlText) if err != nil { logger.WithFields( @@ -222,8 +229,15 @@ func loginShibboleth(page *agouti.Page, urlText string, username string, passwor "part": "password_field", }).Warningln(err.Error()) } + if submitType == "" { + submitType = "click" + } submitField := page.FindByXPath(submitXpath) - err = submitField.Click() + if submitType == "click" { + err = submitField.Click() + } else { + err = submitField.Submit() + } if err != nil { logger.WithFields( log.Fields{ @@ -300,8 +314,33 @@ func loginBasicAuth(page *agouti.Page, urlText string, username string, password } } +// logOut Logs out of the given page using the xpath that is given for the logout. +func logOut(page *agouti.Page, logoutXpath string, submitType string) { + logoutField := page.FindByXPath(logoutXpath) + if submitType == "click" { + err := logoutField.Click() + if err != nil { + logger.WithFields( + log.Fields{ + "subsystem": "logout", + "part": "click", + }).Warningln(err.Error()) + } + } else { + err := logoutField.Submit() + if err != nil { + logger.WithFields( + log.Fields{ + "subsystem": "logout", + "part": "submit", + }).Warningln(err.Error()) + } + } +} + /// loginPasswordOnly Logs in the given website with only password and the xpath to find the field -func loginPasswordOnly(page *agouti.Page, urlText string, passwordXPath string, submitXpath string, password string) { +func loginPasswordOnly(page *agouti.Page, urlText string, passwordXPath string, submitXpath string, password string, + submitType string) { err := page.Navigate(urlText) if err != nil { logger.WithFields( @@ -320,7 +359,11 @@ func loginPasswordOnly(page *agouti.Page, urlText string, passwordXPath string, }).Warningln(err.Error()) } submitField := page.FindByXPath(submitXpath) - err = submitField.Submit() + if submitType == "click" { + err = submitField.Click() + } else { + err = submitField.Submit() + } if err != nil { logger.WithFields( log.Fields{ @@ -341,6 +384,7 @@ func checkExpected(page *agouti.Page, expectedXPath string, expectedText string) "subsystem": "check_expected", "part": "xpath_data", }).Warningln(err.Error()) + return false } return expectedFieldText == expectedText } @@ -351,10 +395,23 @@ func checkExpected(page *agouti.Page, expectedXPath string, expectedText string) "subsystem": "check_expected", "part": "match_all_data", }).Warningln(err.Error()) + return false } return strings.Contains(content, expectedText) } +/// getNoLogin The no login function that only returns the page without any submissions +func getNoLogin(page *agouti.Page, urlText string) { + err := page.Navigate(urlText) + if err != nil { + logger.WithFields( + log.Fields{ + "subsystem": "driver", + "part": "navigation_error", + }).Warningln(err.Error()) + } +} + /// checkExpectedResponse Checks if the expected string exists in the http.Response func checkExpectedResponse(response *http.Response, expectedText string) bool { content, err := ioutil.ReadAll(response.Body) @@ -383,17 +440,16 @@ func getStatus(config SingleLoginConfig) (status bool, elapsed float64) { err = page.SetPageLoad(timeout * 1000) } - // Start counting milliseconds start := time.Now() switch config.LoginType { case "simple_form": loginSimpleForm(page, config.Url, config.UsernameXpath, config.PasswordXpath, config.SubmitXpath, - config.Username, config.Password) + config.Username, config.Password, config.SubmitType) status = checkExpected(page, config.ExpectedTextXpath, config.ExpectedText) break case "shibboleth": loginShibboleth(page, config.Url, config.Username, config.Password, config.UsernameXpath, - config.PasswordXpath, config.SubmitXpath) + config.PasswordXpath, config.SubmitXpath, config.SubmitType) status = checkExpected(page, config.ExpectedTextXpath, config.ExpectedText) break case "basic_auth": @@ -401,7 +457,7 @@ func getStatus(config SingleLoginConfig) (status bool, elapsed float64) { status = checkExpected(page, config.ExpectedTextXpath, config.ExpectedText) break case "password_only": - loginPasswordOnly(page, config.Url, config.PasswordXpath, config.SubmitXpath, config.Password) + loginPasswordOnly(page, config.Url, config.PasswordXpath, config.SubmitXpath, config.Password, config.SubmitType) status = checkExpected(page, config.ExpectedTextXpath, config.ExpectedText) break case "api": @@ -409,10 +465,18 @@ func getStatus(config SingleLoginConfig) (status bool, elapsed float64) { config.Method) status = checkExpectedResponse(response, config.ExpectedText) break + case "no_auth": + getNoLogin(page, config.Url) + status = checkExpected(page, config.ExpectedTextXpath, config.ExpectedText) + break } end := time.Now() elapsed = end.Sub(start).Seconds() - // Stop counting milliseconds + + // logout if the value is set + if config.LogoutXpath != "" { + logOut(page, config.LogoutXpath, config.SubmitType) + } if err == nil { err = page.CloseWindow() diff --git a/main.go b/main.go index 658196b..035597b 100644 --- a/main.go +++ b/main.go @@ -66,7 +66,7 @@ func probeHandler(w http.ResponseWriter, r *http.Request, configs LoginConfigs) /// findTargetInConfig Finds the given target in login configs func findTargetInConfig(configs LoginConfigs, target string) (SingleLoginConfig, error) { for _, config := range configs.Configs { - if config.Url == target { + if config.Target == target { return config, nil } } diff --git a/misc/login.yml.dist b/misc/login.yml.dist index 2741cbe..6c6e8f8 100644 --- a/misc/login.yml.dist +++ b/misc/login.yml.dist @@ -1,5 +1,6 @@ targets: - - url: "" + - url: "the-url-that-should-be-called" + target: "the-target-that-is-used-to-find-conf" username: "" password: "" certificate: "" @@ -11,4 +12,6 @@ targets: expected_text: "" ssl_check: no debug: no - method: "POST" \ No newline at end of file + method: "POST" + submit_type: "click" + logout_xpath: "" \ No newline at end of file