Skip to content

Commit

Permalink
Merge pull request #218 from aljesusg/token_auth
Browse files Browse the repository at this point in the history
[KIALI-670]Token
  • Loading branch information
josejulio committed May 29, 2018
2 parents 702fd4d + ec21214 commit dec255b
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 2 deletions.
19 changes: 19 additions & 0 deletions README.adoc
Expand Up @@ -72,6 +72,10 @@ make test

== Running

=== Secrets

*Note*: There is a secret variable in the configuration called *TOKEN_SECRET*, be careful with this secret.

=== Running on OpenShift

==== Setting up OpenShift
Expand Down Expand Up @@ -414,6 +418,21 @@ products:
jaeger:
service: VALUE
----

|`TOKEN_SECRET`
|The token secret for generate. This configuration is ignored if `TOKEN_SECRET` is set. (default is `kiali`)
[source,yaml]
----
token:
secret: VALUE
----
|`TOKEN_EXPIRATION_AT`
|The token expired in VALUE seconds. This configuration is ignored if `TOKEN_EXPIRATION_AT` is set. (default is 10 hours => 36000)
[source,yaml]
----
token:
expiration_hours: VALUE
----
|===

== Additional Notes
Expand Down
28 changes: 28 additions & 0 deletions config/config.go
Expand Up @@ -46,6 +46,9 @@ const (

EnvServiceFilterLabelName = "SERVICE_FILTER_LABEL_NAME"
EnvVersionFilterLabelName = "VERSION_FILTER_LABEL_NAME"

EnvTokenSecret = "TOKEN_SECRET"
EnvTokenExpirationAt = "TOKEN_EXPIRATION_AT"
)

// Global configuration for the application.
Expand Down Expand Up @@ -92,6 +95,11 @@ type Products struct {
Jaeger JaegerConfig `yaml:"jaeger,omitempty"`
}

type Token struct {
Secret []byte `yaml:"secret,omitempty"`
ExpirationAt int64 `yaml:"expiration,omitempty"`
}

// Config defines full YAML configuration.
type Config struct {
Identity security.Identity `yaml:",omitempty"`
Expand All @@ -100,6 +108,7 @@ type Config struct {
ServiceFilterLabelName string `yaml:"service_filter_label_name,omitempty"`
VersionFilterLabelName string `yaml:"version_filter_label_name,omitempty"`
Products Products `yaml:"products,omitempty"`
Token Token `yaml:"token,omitempty"`
}

// NewConfig creates a default Config struct
Expand Down Expand Up @@ -144,6 +153,10 @@ func NewConfig() (c *Config) {
c.Products.Istio.IstioSidecarAnnotation = strings.TrimSpace(getDefaultString(EnvIstioSidecarAnnotation, "sidecar.istio.io/status"))
c.Products.Istio.UrlServiceVersion = strings.TrimSpace(getDefaultString(EnvIstioUrlServiceVersion, "http://istio-pilot:9093/version"))

// Token Configuration
c.Token.Secret = []byte(strings.TrimSpace(getDefaultString(EnvTokenSecret, "kiali")))
c.Token.ExpirationAt = getDefaultInt64(EnvTokenExpirationAt, 36000)

return
}

Expand Down Expand Up @@ -180,6 +193,21 @@ func getDefaultInt(envvar string, defaultValue int) (retVal int) {
return
}

func getDefaultInt64(envvar string, defaultValue int64) (retVal int64) {
retValString := os.Getenv(envvar)
if retValString == "" {
retVal = defaultValue
} else {
if num, err := strconv.ParseInt(retValString, 10, 64); err != nil {
log.Warningf("Invalid number for envvar [%v]. err=%v", envvar, err)
retVal = defaultValue
} else {
retVal = num
}
}
return
}

func getDefaultBool(envvar string, defaultValue bool) (retVal bool) {
retValString := os.Getenv(envvar)
if retValString == "" {
Expand Down
76 changes: 76 additions & 0 deletions config/token.go
@@ -0,0 +1,76 @@
package config

import (
"errors"
"fmt"
"time"

"github.com/dgrijalva/jwt-go"

"github.com/kiali/kiali/log"
)

// Structured version of Claims Section, as referenced at
// https://tools.ietf.org/html/rfc7519#section-4.1
// See examples for how to use this with your own claim types
type TokenClaim struct {
User string `json:"username"`
jwt.StandardClaims
}

type TokenGenerated struct {
Token string `json:"token"`
ExpiredAt string `json:"expired_at"`
}

/*
Generate the token with a Expiraton of <ExpiresAt> seconds
*/
func GenerateToken(username string) (TokenGenerated, error) {
timeExpire := time.Now().Add(time.Second * time.Duration(Get().Token.ExpirationAt))
claim := TokenClaim{
username,
jwt.StandardClaims{
ExpiresAt: timeExpire.Unix(),
},
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
ss, err := token.SignedString(Get().Token.Secret)
if err != nil {
return TokenGenerated{}, err
}

return TokenGenerated{Token: ss, ExpiredAt: timeExpire.String()}, nil
}

func ValidateToken(tokenString string) error {
token, err := jwt.ParseWithClaims(tokenString, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
return Get().Token.Secret, nil
})
if err != nil {
return err
}
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return errors.New(fmt.Sprintf("Unexpected signing method: ", token.Header["alg"]))
}
if token.Valid {
return nil
} else if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
log.Debugf("That's not even a token")
return errors.New("That's not even a token")
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
log.Debugf("Token expired ... Timing is everything")
return errors.New("Token expired ... Timing is everything")
} else {
log.Debugf("Couldn't handle this token:", err)
return err
}
} else {
log.Debugf("Couldn't handle this token:", err)
return err
}
return nil
}
15 changes: 15 additions & 0 deletions handlers/root.go
Expand Up @@ -3,6 +3,7 @@ package handlers
import (
"net/http"

"github.com/kiali/kiali/config"
"github.com/kiali/kiali/status"
)

Expand All @@ -13,3 +14,17 @@ func Root(w http.ResponseWriter, r *http.Request) {
func getStatus(w http.ResponseWriter, r *http.Request) {
RespondWithJSONIndent(w, http.StatusOK, status.Get())
}

func GetToken(w http.ResponseWriter, r *http.Request) {
u, _, ok := r.BasicAuth()
if !ok {
RespondWithJSONIndent(w, http.StatusInternalServerError, u)
return
}
token, error := config.GenerateToken(u)
if error != nil {
RespondWithJSONIndent(w, http.StatusInternalServerError, error)
return
}
RespondWithJSONIndent(w, http.StatusOK, token)
}
6 changes: 6 additions & 0 deletions routing/routes.go
Expand Up @@ -30,6 +30,12 @@ func NewRoutes() (r *Routes) {
"/api",
handlers.Root,
},
{ // Request the token
"Status",
"GET",
"/api/token",
handlers.GetToken,
},
{ // another way to get to root, both show status
"Status",
"GET",
Expand Down
10 changes: 8 additions & 2 deletions server/server.go
Expand Up @@ -3,6 +3,7 @@ package server
import (
"fmt"
"net/http"
"strings"

"github.com/kiali/kiali/config"
"github.com/kiali/kiali/config/security"
Expand Down Expand Up @@ -77,8 +78,13 @@ type serverAuthProxyHandler struct {
func (h *serverAuthProxyHandler) handler(w http.ResponseWriter, r *http.Request) {
statusCode := http.StatusOK

// before we handle any requests, make sure the user is authenticated
if h.credentials.Username != "" || h.credentials.Password != "" {
if strings.Contains(r.Header.Get("Authorization"), "Bearer") {
err := config.ValidateToken(strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer "))
if err != nil {
log.Warning("Error token %+v", err)
statusCode = http.StatusUnauthorized
}
} else if h.credentials.Username != "" || h.credentials.Password != "" {
u, p, ok := r.BasicAuth()
if !ok || h.credentials.Username != u || h.credentials.Password != p {
statusCode = http.StatusUnauthorized
Expand Down

0 comments on commit dec255b

Please sign in to comment.