Skip to content
This repository has been archived by the owner on Aug 29, 2022. It is now read-only.

Commit

Permalink
add metrics for ldap service (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
rajatjindal authored Feb 20, 2018
1 parent 6a9ebc7 commit 39d647a
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 3 deletions.
48 changes: 48 additions & 0 deletions auth/token_issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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))
}
Expand Down
48 changes: 48 additions & 0 deletions auth/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -23,14 +66,17 @@ 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
}

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
Expand All @@ -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")
Expand All @@ -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)
}
15 changes: 12 additions & 3 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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))

Expand Down
56 changes: 56 additions & 0 deletions ldap/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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)
}

Expand All @@ -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)
}

Expand All @@ -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)
}
}
Expand Down

0 comments on commit 39d647a

Please sign in to comment.