diff --git a/auth/token_issuer.go b/auth/token_issuer.go index 03bd04b..9409699 100644 --- a/auth/token_issuer.go +++ b/auth/token_issuer.go @@ -5,6 +5,7 @@ import ( goldap "github.com/go-ldap/ldap" "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" "github.com/proofpoint/kubernetes-ldap/ldap" "github.com/proofpoint/kubernetes-ldap/token" "strings" @@ -21,9 +22,53 @@ type LDAPTokenIssuer struct { UsernameAttribute string } +var ( + newTokenRequests = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_new_token_requests", + Help: "Total number of requests to get new token.", + }, + ) + noauthTokenRequests = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_noauth_token_requests", + Help: "Total number of requests to get new token without username or password.", + }, + ) + unauthTokenRequests = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_failed_ldap_auth", + Help: "Total number of requests to get new token where ldap auth failed.", + }, + ) + errorSigningToken = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_error_signing_tokens", + Help: "Total number of requests where signing new token failed.", + }, + ) + successfulTokens = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_successful_tokens_generated", + Help: "Total number of requests where tokens were successfully issued.", + }, + ) +) + +//RegisterIssueTokenMetrics registers the metrics for the token generation +func RegisterIssueTokenMetrics() { + prometheus.MustRegister(newTokenRequests) + prometheus.MustRegister(noauthTokenRequests) + prometheus.MustRegister(unauthTokenRequests) + prometheus.MustRegister(errorSigningToken) + prometheus.MustRegister(successfulTokens) +} + func (lti *LDAPTokenIssuer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + newTokenRequests.Inc() user, password, ok := req.BasicAuth() if !ok { + noauthTokenRequests.Inc() resp.Header().Add("WWW-Authenticate", `Basic realm="kubernetes ldap"`) resp.WriteHeader(http.StatusUnauthorized) return @@ -32,6 +77,7 @@ func (lti *LDAPTokenIssuer) ServeHTTP(resp http.ResponseWriter, req *http.Reques // Authenticate the user via LDAP ldapEntry, err := lti.LDAPAuthenticator.Authenticate(user, password) if err != nil { + unauthTokenRequests.Inc() glog.Errorf("Error authenticating user: %v", err) resp.WriteHeader(http.StatusUnauthorized) return @@ -43,11 +89,13 @@ func (lti *LDAPTokenIssuer) ServeHTTP(resp http.ResponseWriter, req *http.Reques // Sign token and return signedToken, err := lti.TokenSigner.Sign(token) if err != nil { + errorSigningToken.Inc() glog.Errorf("Error signing token: %v", err) resp.WriteHeader(http.StatusInternalServerError) return } + successfulTokens.Inc() resp.Header().Add("Content-Type", "text/plain") resp.Write([]byte(signedToken)) } diff --git a/auth/webhook.go b/auth/webhook.go index 4647cee..25ae01a 100644 --- a/auth/webhook.go +++ b/auth/webhook.go @@ -5,9 +5,52 @@ import ( "net/http" "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" "github.com/proofpoint/kubernetes-ldap/token" ) +var ( + verifyTokenRequests = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_verify_token_requests", + Help: "Total number of requests to verify token.", + }, + ) + invalidMethodRequests = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_invalid_http_method_requests", + Help: "Total number of requests to verify token which were not HTTP Post.", + }, + ) + invalidJSONBody = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_invalid_token_request_format", + Help: "Total number of requests to verify token with invalid verify token request format.", + }, + ) + invalidTokenRequests = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_invalid_token", + Help: "Total number of requests to verify token with invalid token.", + }, + ) + successfulVerification = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_successful_verify_token_requests", + Help: "Total number of requests where verify token request succeeded.", + }, + ) +) + +//RegisterVerifyTokenMetrics registers the metrics for the token generation +func RegisterVerifyTokenMetrics() { + prometheus.MustRegister(verifyTokenRequests) + prometheus.MustRegister(invalidMethodRequests) + prometheus.MustRegister(invalidTokenRequests) + prometheus.MustRegister(invalidJSONBody) + prometheus.MustRegister(successfulVerification) +} + // TokenWebhook responds to requests from the K8s authentication webhook type TokenWebhook struct { tokenVerifier token.Verifier @@ -23,7 +66,9 @@ func NewTokenWebhook(verifier token.Verifier) *TokenWebhook { // ServeHTTP verifies the incoming token and sends the user's info // back if the token is valid. func (tw *TokenWebhook) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + verifyTokenRequests.Inc() if req.Method != http.MethodPost { + invalidMethodRequests.Inc() resp.WriteHeader(http.StatusMethodNotAllowed) return } @@ -31,6 +76,7 @@ func (tw *TokenWebhook) ServeHTTP(resp http.ResponseWriter, req *http.Request) { trr := &TokenReviewRequest{} err := json.NewDecoder(req.Body).Decode(trr) if err != nil { + invalidJSONBody.Inc() glog.Errorf("Error unmarshalling request: %v", err) resp.WriteHeader(http.StatusInternalServerError) return @@ -40,6 +86,7 @@ func (tw *TokenWebhook) ServeHTTP(resp http.ResponseWriter, req *http.Request) { // Verify token token, err := tw.tokenVerifier.Verify(trr.Spec.Token) if err != nil { + invalidTokenRequests.Inc() glog.Errorf("Token is invalid: %v", err) resp.WriteHeader(http.StatusUnauthorized) resp.Header().Add("Content-Type", "text/plain") @@ -63,6 +110,7 @@ func (tw *TokenWebhook) ServeHTTP(resp http.ResponseWriter, req *http.Request) { return } + successfulVerification.Inc() resp.Header().Add("Content-Type", "application/json") resp.Write(respJSON) } diff --git a/cmd/serve.go b/cmd/serve.go index bc04f8e..56c1eeb 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -22,13 +22,13 @@ import ( "os" "github.com/golang/glog" + "github.com/mitchellh/go-homedir" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/proofpoint/kubernetes-ldap/auth" "github.com/proofpoint/kubernetes-ldap/ldap" "github.com/proofpoint/kubernetes-ldap/token" - "github.com/spf13/cobra" - - "github.com/mitchellh/go-homedir" "github.com/spf13/cast" + "github.com/spf13/cobra" "github.com/spf13/viper" "time" ) @@ -69,10 +69,17 @@ var RootCmd = &cobra.Command{ /authenticate - to verify the token`, Run: func(cmd *cobra.Command, args []string) { validate() + registerMetrics() serve() }, } +func registerMetrics() { + auth.RegisterIssueTokenMetrics() + auth.RegisterVerifyTokenMetrics() + ldap.RegisterLDAPClientMetrics() +} + // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { @@ -237,6 +244,8 @@ func serve() error { // Endpoint for token issuance after LDAP auth http.Handle("/ldapAuth", ldapTokenIssuer) + //for prometheus metrics + http.Handle("/metrics", promhttp.Handler()) glog.Infof("Serving on %s", fmt.Sprintf(":%d", serverPort)) diff --git a/ldap/client.go b/ldap/client.go index a17dba4..e41334b 100644 --- a/ldap/client.go +++ b/ldap/client.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/go-ldap/ldap" + "github.com/prometheus/client_golang/prometheus" ) // Authenticator authenticates a user against an LDAP directory @@ -26,11 +27,61 @@ type Client struct { TLSConfig *tls.Config } +var ( + ldapConnectionError = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_ldap_connection_error", + Help: "Total number of LDAP connection errors.", + }, + ) + ldapBindingError = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_ldap_binding_error", + Help: "Total number of LDAP binding errors.", + }, + ) + userSearchFailed = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_user_search_failed", + Help: "Total number of LDAP user search failures.", + }, + ) + noUserFound = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_no_user_found", + Help: "Total number of times user was not found in LDAP.", + }, + ) + multipleUsersFound = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_multiple_user_found", + Help: "Total number of times multiple user(s) were found in LDAP.", + }, + ) + invalidUserCredentials = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "kubernetes_ldap_invalid_credentials_error", + Help: "Total number of times invalid user credentials were used.", + }, + ) +) + +//RegisterLDAPClientMetrics registers the metrics for the token generation +func RegisterLDAPClientMetrics() { + prometheus.MustRegister(ldapConnectionError) + prometheus.MustRegister(ldapBindingError) + prometheus.MustRegister(userSearchFailed) + prometheus.MustRegister(noUserFound) + prometheus.MustRegister(multipleUsersFound) + prometheus.MustRegister(invalidUserCredentials) +} + // Authenticate a user against the LDAP directory. Returns an LDAP entry if password // is valid, otherwise returns an error. func (c *Client) Authenticate(username, password string) (*ldap.Entry, error) { conn, err := c.dial() if err != nil { + ldapConnectionError.Inc() return nil, fmt.Errorf("Error opening LDAP connection: %v", err) } defer conn.Close() @@ -43,6 +94,7 @@ func (c *Client) Authenticate(username, password string) (*ldap.Entry, error) { } if err != nil { + ldapBindingError.Inc() return nil, fmt.Errorf("Error binding user to LDAP server: %v", err) } @@ -51,13 +103,16 @@ func (c *Client) Authenticate(username, password string) (*ldap.Entry, error) { // Do a search to ensure the user exists within the BaseDN scope res, err := conn.Search(req) if err != nil { + userSearchFailed.Inc() return nil, fmt.Errorf("Error searching for user %s: %v", username, err) } switch { case len(res.Entries) == 0: + noUserFound.Inc() return nil, fmt.Errorf("No result for the search filter '%s'", req.Filter) case len(res.Entries) > 1: + multipleUsersFound.Inc() return nil, fmt.Errorf("Multiple entries found for the search filter '%s': %+v", req.Filter, res.Entries) } @@ -67,6 +122,7 @@ func (c *Client) Authenticate(username, password string) (*ldap.Entry, error) { if c.SearchUserDN != "" && c.SearchUserPassword != "" { err = conn.Bind(res.Entries[0].DN, password) if err != nil { + invalidUserCredentials.Inc() return nil, fmt.Errorf("Error binding user %s, invalid credentials: %v", username, err) } }