New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[KIALI-670]Token #218
[KIALI-670]Token #218
Conversation
4b4b24e
to
a1f0ced
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is pretty simple but powerful implementation of the token management. LGTM!
config/security/token.go
Outdated
func (ti *TokenInfo) ToBase64() string { | ||
bytes, err := json.Marshal(ti) | ||
if err != nil { | ||
log.Errorf("Failed to marshal TokenInfo.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you return an error here?
handlers/root.go
Outdated
RespondWithJSONIndent(w, http.StatusInternalServerError, nil) | ||
return | ||
} | ||
token := security.NewAuthToken(u+p, time.Now().Local().Add(time.Hour*time.Duration(config.Get().Token.ExpirationHours)), config.Get().Token.Secret) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the classical username+password check missing here, before generating the token?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to generate token from username + password. The check is u, p, ok := r.BasicAuth()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But r.BasicAuth doesn't actually check anything, does it? I mean, we still need to make sure it matches somehow the username and password we have in our config file, conf.Server.Credentials.Username
and conf.Server.Credentials.Password
config/config.go
Outdated
@@ -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 = strings.TrimSpace(getDefaultString(EnvTokenSecret, "kiali")) | |||
c.Token.ExpirationHours = getDefaultInt(EnvTokenExpirationHours, 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Higher default for convenience, like 10 hours?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
config/security/token.go
Outdated
} | ||
} | ||
|
||
func (at *AuthToken) GetTokenInfo(secret string) (*TokenInfo, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to find a flaw, but it seems good :)
However I wonder if we could use an existing JWT-like implementation that would be more widely tested & trusted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keycloak not support golang I didn't find any JWT implementation in golang only some methods of thirds....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also prefer an existing JWT implementation.
This page list several ones for golang: https://jwt.io/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is while we add the authentication against openshift that will be this Sprint, I don't know if it's a good idea spent time in work in a full JWT implementation @israel-hdez
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmm there are only libraries of thirds...I am gonna check them
@lucasponce @jotak @xeviknal review the token authentication please |
@aljesusg overall looks good. I'd like others to review as well as security is critical. |
I did changes @xeviknal @lucasponce @jotak I don't know if it's necessary to spent time implementing jwt token when this is going to change soon. But If you think different, I'll do it |
handlers/root.go
Outdated
RespondWithJSONIndent(w, http.StatusInternalServerError, nil) | ||
return | ||
} | ||
token, err := security.NewAuthToken(u+p, time.Now().Local().Add(time.Hour*time.Duration(config.Get().Token.ExpirationHours)), config.Get().Token.Secret) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why my previous comment went outdated, I was saying:
r.BasicAuth
doesn't actually check anything. I mean, we still need to make sure it matches somehow the username and password we have in our config file, conf.Server.Credentials.Username and conf.Server.Credentials.Password . Else I bet you can login with any credential :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know If I am udnerstanding you.
In server.go L-87 is checking that the username and password are the same
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
}
h is called in the handler where proxyHandler have the config file credentials
proxyHandler := serverAuthProxyHandler{
credentials: security.Credentials{
Username: conf.Server.Credentials.Username,
Password: conf.Server.Credentials.Password,
},
trueHandler: router,
}
http.HandleFunc("/", proxyHandler.handler)
So if a try to access with another credentials fail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok my bad, I didn't see the whole picture. If the authorization handler is still called in addition to the new one then I suppose it's fine! 👍
Maybe we should add in another PR the Expired time because there is a JIRA for that https://issues.jboss.org/browse/KIALI-533 . What do you think @israel-hdez @jotak @xeviknal @lucasponce ? |
server/server.go
Outdated
@@ -78,7 +78,13 @@ 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 r.Header.Get("Kiali-Token") != "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JWT is usually used (not a requirement, but i have seen that most services do this way) in the header 'Authorization' and usually has the format "Bearer the_token"
See jwt.io [1] and jira docs, wikipedia, etc.
I think we should comply unless there is a reason we can't do that.
[1]
Whenever the user wants to access a protected route or resource, the user agent should send the JWT, typically in the Authorization header using the Bearer schema. The content of the header should look like the following:
Authorization: Bearer <token>
config/token.go
Outdated
fmt.Println("Here") | ||
return err | ||
} | ||
if token.Valid { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are missing an step. Validate the "alg" claim. (taken from here)
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
If not validated, one could create a JWT with the "alg = none" and bypass your protection.
alg none means that the jwt is unsecured, but is still a valid JWT, and the RFC requires [1] that the libs implement that one.
See this post [2] for more info.
The none algorithm is a curious addition to JWT. It is intended to be used for situations where the integrity of the token has already been verified. Interestingly enough, it is one of only two algorithms that are mandatory to implement (the other being HS256).
[1] https://tools.ietf.org/html/rfc7519#section-8
[2]https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also configure validMethods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it seems that none
is disabled by default https://github.com/dgrijalva/jwt-go/blob/master/none.go#L28
But anyway, we should only allow tokens signed with SigningMethodHS256
|
||
type TokenGenerated struct { | ||
Token string `json:"token"` | ||
ExpiredAt string `json:"expired_at"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Expiration Time
is a standard JWT claim [1], I don't think we should duplicate this value, but when parsing we could "unwrap" into a more friendly/verbose format if we want to use the contained data.
Besides, "Token" is signed and encoded, not encrypted, if consumers want to know their contents (claims) they could decode (using base64) the second segment or use a JWT lib, and for debugging purposes or quickly having a look at their values, you can use this site: https://jwt.io/ and paste the token there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We wanna set a expiration time only @josejulio in the claim against the configuration file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker, but this field is duplicated, set once in the token (encoded) and once in the answer of GenerateToken
(verbose).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you see in the handlerRoot you can see that Token generated is the object returned to the user, in tokenclaim is the info to generate the token and the second one the output to the user where I inform him about when the token will be expired
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, i see that, i only mean to say that the expiresAt is also self-contained in the token (the unix timestamp) and the consumer can use that too, we don't have to pull out every data from the claims.
Again, not a blocker, but would like to make that clear in case we want to add more data to the claims.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it by https://issues.jboss.org/browse/KIALI-533 for the sessions time out, but you are right about get the expired against the token
Is just matter of adding the proper claim to the JWT (check my other comments) and the validation is done by the JWT lib |
Thanks @josejulio for comments, is still in WIP ^^ |
This is ready with ExpiredAt working too. Can you review this @jotak @lucasponce @xeviknal ? |
config/token.go
Outdated
"fmt" | ||
"github.com/dgrijalva/jwt-go" | ||
"github.com/kiali/kiali/log" | ||
"time" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor, but @jmazzitelli will be happy if we order the imports in
standard imports
LINE
third party imports
LINE
kiali imports
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed, we should put this in the future style code guide ^^ Thanks @lucasponce
config/token.go
Outdated
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { | ||
// Token is either expired or not active yet | ||
log.Debugf("Timing is everything") | ||
return errors.New("Timing is everything") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Timing is everything" sounds like extracted from Thanos quotes. :-)
Not a blocker, it's funny.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hahahaa Sorry ^^ I should put anothet kind of message xD
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe "Token expired ... Timing is everything"
6f53174
to
824ec38
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but I would let @josejulio to take a look before to merge.
handlers/root.go
Outdated
RespondWithJSONIndent(w, http.StatusInternalServerError, u) | ||
return | ||
} | ||
fmt.Printf("Token " + u) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This print shouldn't be a log.info if it is intentional print not debug line ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
handlers/root.go
Outdated
fmt.Printf("Token " + u) | ||
token, error := config.GenerateToken(u) | ||
if error != nil { | ||
fmt.Printf("Error fine %+v", error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
routing/routes.go
Outdated
@@ -30,6 +30,12 @@ func NewRoutes() (r *Routes) { | |||
"/api", | |||
handlers.Root, | |||
}, | |||
{ // another way to get to root, both show status |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/api/token gets a token or status ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed comment
server/server.go
Outdated
@@ -8,6 +8,7 @@ import ( | |||
"github.com/kiali/kiali/config/security" | |||
"github.com/kiali/kiali/log" | |||
"github.com/kiali/kiali/routing" | |||
"strings" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong import order
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed ^^ Jetbrains autoimport ^^
server/server.go
Outdated
if strings.Contains(r.Header.Get("Authorization"), "Bearer") { | ||
err := config.ValidateToken(strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")) | ||
if err != nil { | ||
log.Debugf("Error token %+v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would move this to Info/Warn.
I guess I would like to see in the log bad authentication entries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to Warning
config/config.go
Outdated
if retValString == "" { | ||
retVal = defaultValue | ||
} else { | ||
if num, err := strconv.ParseInt(retValString, 2, 64); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In base 2? Is it intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope ^^ mindblind
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed^^
@@ -92,6 +95,11 @@ type Products struct { | |||
Jaeger JaegerConfig `yaml:"jaeger,omitempty"` | |||
} | |||
|
|||
type Token struct { | |||
Secret []byte `yaml:"secret,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... Probably we should add a warning in Readme regarding filesystem permissions now that the config file can have the secret.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the Documentation about the Secret
@israel-hdez @josejulio can we merge this now? |
Thanks @josejulio |
Thanks for the good work @aljesusg ! |
NEED #223
Changed to JWT Working in expired at