Skip to content
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

Merged
merged 2 commits into from May 29, 2018
Merged

[KIALI-670]Token #218

merged 2 commits into from May 29, 2018

Conversation

aljesusg
Copy link
Collaborator

@aljesusg aljesusg commented May 28, 2018

NEED #223

Changed to JWT Working in expired at

get_token
request

@aljesusg aljesusg force-pushed the token_auth branch 2 times, most recently from 4b4b24e to a1f0ced Compare May 28, 2018 09:55
xeviknal
xeviknal previously approved these changes May 28, 2018
Copy link
Member

@xeviknal xeviknal left a 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!

@aljesusg aljesusg changed the title [WIP]Token [KIALI-670]Token May 28, 2018
func (ti *TokenInfo) ToBase64() string {
bytes, err := json.Marshal(ti)
if err != nil {
log.Errorf("Failed to marshal TokenInfo.")
Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Collaborator Author

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()

Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

}
}

func (at *AuthToken) GetTokenInfo(secret string) (*TokenInfo, error) {
Copy link
Contributor

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.

Copy link
Collaborator Author

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....

Copy link
Member

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/

Copy link
Collaborator Author

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

Copy link
Collaborator Author

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

@aljesusg
Copy link
Collaborator Author

aljesusg commented May 28, 2018

@lucasponce @jotak @xeviknal review the token authentication please

auth
token

@jotak
Copy link
Contributor

jotak commented May 28, 2018

@aljesusg overall looks good. I'd like others to review as well as security is critical.

@aljesusg
Copy link
Collaborator Author

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)
Copy link
Contributor

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 :)

Copy link
Collaborator Author

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

screenshot from 2018-05-28 13-44-36

Copy link
Contributor

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! 👍

@aljesusg
Copy link
Collaborator Author

Changed to JWT Working in expired at

get_token
request

@aljesusg
Copy link
Collaborator Author

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") != "" {
Copy link
Member

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 {
Copy link
Member

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/

Copy link
Member

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

Copy link
Member

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"`
Copy link
Member

@josejulio josejulio May 28, 2018

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.

[1] https://tools.ietf.org/html/rfc7519#section-4.1.4

Copy link
Collaborator Author

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

Copy link
Member

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).

Copy link
Collaborator Author

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

Copy link
Member

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.

Copy link
Collaborator Author

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

@josejulio
Copy link
Member

Maybe we should add in another PR the Expired time because there is a JIRA for that https://issues.jboss.org/browse/KIALI-533 .

Is just matter of adding the proper claim to the JWT (check my other comments) and the validation is done by the JWT lib

@aljesusg
Copy link
Collaborator Author

Thanks @josejulio for comments, is still in WIP ^^

@aljesusg aljesusg removed the do not merge A PR is not ready to merge label May 29, 2018
@aljesusg
Copy link
Collaborator Author

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"
Copy link
Contributor

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

Copy link
Collaborator Author

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")
Copy link
Contributor

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.

Copy link
Collaborator Author

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

Copy link
Collaborator Author

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"

@aljesusg aljesusg force-pushed the token_auth branch 2 times, most recently from 6f53174 to 824ec38 Compare May 29, 2018 12:26
lucasponce
lucasponce previously approved these changes May 29, 2018
Copy link
Contributor

@lucasponce lucasponce left a 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)
Copy link
Contributor

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 ?

Copy link
Collaborator Author

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

@@ -30,6 +30,12 @@ func NewRoutes() (r *Routes) {
"/api",
handlers.Root,
},
{ // another way to get to root, both show status
Copy link
Contributor

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 ?

Copy link
Collaborator Author

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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong import order

Copy link
Collaborator Author

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)
Copy link
Contributor

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.

Copy link
Collaborator Author

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 {
Copy link
Contributor

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope ^^ mindblind

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed^^

josejulio
josejulio previously approved these changes May 29, 2018
@@ -92,6 +95,11 @@ type Products struct {
Jaeger JaegerConfig `yaml:"jaeger,omitempty"`
}

type Token struct {
Secret []byte `yaml:"secret,omitempty"`
Copy link
Member

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.

Copy link
Collaborator Author

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

@aljesusg
Copy link
Collaborator Author

@israel-hdez @josejulio can we merge this now?

@josejulio josejulio merged commit dec255b into kiali:master May 29, 2018
@aljesusg
Copy link
Collaborator Author

Thanks @josejulio

@jotak
Copy link
Contributor

jotak commented May 29, 2018

Thanks for the good work @aljesusg !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants