Merge pull request #8302 from mitake/token-ttl

auth: a new option for configuring TTL of jwt tokens
release-3.4
Gyuho Lee 2018-02-27 20:50:37 -08:00 committed by GitHub
commit ac50ef0812
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 8 deletions

View File

@ -361,8 +361,8 @@ Follow the instructions when using these flags.
## Auth flags ## Auth flags
### --auth-token ### --auth-token
+ Specify a token type and token specific options, especially for JWT. Its format is "type,var1=val1,var2=val2,...". Possible type is 'simple' or 'jwt'. Possible variables are 'sign-method' for specifying a sign method of jwt (its possible values are 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'PS256', 'PS384', or 'PS512'), 'pub-key' for specifying a path to a public key for verifying jwt, and 'priv-key' for specifying a path to a private key for signing jwt. + Specify a token type and token specific options, especially for JWT. Its format is "type,var1=val1,var2=val2,...". Possible type is 'simple' or 'jwt'. Possible variables are 'sign-method' for specifying a sign method of jwt (its possible values are 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'PS256', 'PS384', or 'PS512'), 'pub-key' for specifying a path to a public key for verifying jwt, 'priv-key' for specifying a path to a private key for signing jwt, and 'ttl' for specifying TTL of jwt tokens.
+ Example option of JWT: '--auth-token jwt,pub-key=app.rsa.pub,priv-key=app.rsa,sign-method=RS512' + Example option of JWT: '--auth-token jwt,pub-key=app.rsa.pub,priv-key=app.rsa,sign-method=RS512,ttl=10m'
+ default: "simple" + default: "simple"
## Experimental flags ## Experimental flags

View File

@ -18,6 +18,7 @@ import (
"context" "context"
"crypto/rsa" "crypto/rsa"
"io/ioutil" "io/ioutil"
"time"
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
) )
@ -26,6 +27,7 @@ type tokenJWT struct {
signMethod string signMethod string
signKey *rsa.PrivateKey signKey *rsa.PrivateKey
verifyKey *rsa.PublicKey verifyKey *rsa.PublicKey
ttl time.Duration
} }
func (t *tokenJWT) enable() {} func (t *tokenJWT) enable() {}
@ -70,6 +72,7 @@ func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64)
jwt.MapClaims{ jwt.MapClaims{
"username": username, "username": username,
"revision": revision, "revision": revision,
"exp": time.Now().Add(t.ttl).Unix(),
}) })
token, err := tk.SignedString(t.signKey) token, err := tk.SignedString(t.signKey)
@ -83,7 +86,7 @@ func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64)
return token, err return token, err
} }
func prepareOpts(opts map[string]string) (jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath string, err error) { func prepareOpts(opts map[string]string) (jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath string, ttl time.Duration, err error) {
for k, v := range opts { for k, v := range opts {
switch k { switch k {
case "sign-method": case "sign-method":
@ -92,24 +95,36 @@ func prepareOpts(opts map[string]string) (jwtSignMethod, jwtPubKeyPath, jwtPrivK
jwtPubKeyPath = v jwtPubKeyPath = v
case "priv-key": case "priv-key":
jwtPrivKeyPath = v jwtPrivKeyPath = v
case "ttl":
ttl, err = time.ParseDuration(v)
if err != nil {
plog.Errorf("failed to parse ttl option (%s)", err)
return "", "", "", 0, ErrInvalidAuthOpts
}
default: default:
plog.Errorf("unknown token specific option: %s", k) plog.Errorf("unknown token specific option: %s", k)
return "", "", "", ErrInvalidAuthOpts return "", "", "", 0, ErrInvalidAuthOpts
} }
} }
if len(jwtSignMethod) == 0 { if len(jwtSignMethod) == 0 {
return "", "", "", ErrInvalidAuthOpts return "", "", "", 0, ErrInvalidAuthOpts
} }
return jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, nil return jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, ttl, nil
} }
func newTokenProviderJWT(opts map[string]string) (*tokenJWT, error) { func newTokenProviderJWT(opts map[string]string) (*tokenJWT, error) {
jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, err := prepareOpts(opts) jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, ttl, err := prepareOpts(opts)
if err != nil { if err != nil {
return nil, ErrInvalidAuthOpts return nil, ErrInvalidAuthOpts
} }
t := &tokenJWT{} if ttl == 0 {
ttl = 5 * time.Minute
}
t := &tokenJWT{
ttl: ttl,
}
t.signMethod = jwtSignMethod t.signMethod = jwtSignMethod

View File

@ -78,6 +78,11 @@ var (
initialToken: "new", initialToken: "new",
clientCertAuthEnabled: true, clientCertAuthEnabled: true,
} }
configJWT = etcdProcessClusterConfig{
clusterSize: 1,
initialToken: "new",
authTokenOpts: "jwt,pub-key=../integration/fixtures/server.crt,priv-key=../integration/fixtures/server.key.insecure,sign-method=RS256,ttl=1s",
}
) )
func configStandalone(cfg etcdProcessClusterConfig) *etcdProcessClusterConfig { func configStandalone(cfg etcdProcessClusterConfig) *etcdProcessClusterConfig {
@ -117,6 +122,7 @@ type etcdProcessClusterConfig struct {
quotaBackendBytes int64 quotaBackendBytes int64
noStrictReconfig bool noStrictReconfig bool
initialCorruptCheck bool initialCorruptCheck bool
authTokenOpts string
} }
// newEtcdProcessCluster launches a new cluster from etcd processes, returning // newEtcdProcessCluster launches a new cluster from etcd processes, returning
@ -238,6 +244,11 @@ func (cfg *etcdProcessClusterConfig) etcdServerProcessConfigs() []*etcdServerPro
} }
args = append(args, cfg.tlsArgs()...) args = append(args, cfg.tlsArgs()...)
if cfg.authTokenOpts != "" {
args = append(args, "--auth-token", cfg.authTokenOpts)
}
etcdCfgs[i] = &etcdServerProcessConfig{ etcdCfgs[i] = &etcdServerProcessConfig{
execPath: cfg.execPath, execPath: cfg.execPath,
args: args, args: args,

View File

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"os" "os"
"testing" "testing"
"time"
"github.com/coreos/etcd/clientv3" "github.com/coreos/etcd/clientv3"
) )
@ -58,6 +59,7 @@ func TestCtlV3AuthSnapshot(t *testing.T) { testCtl(t, authTestSnapshot) }
func TestCtlV3AuthCertCNAndUsername(t *testing.T) { func TestCtlV3AuthCertCNAndUsername(t *testing.T) {
testCtl(t, authTestCertCNAndUsername, withCfg(configClientTLSCertAuth)) testCtl(t, authTestCertCNAndUsername, withCfg(configClientTLSCertAuth))
} }
func TestCtlV3AuthJWTExpire(t *testing.T) { testCtl(t, authTestJWTExpire, withCfg(configJWT)) }
func authEnableTest(cx ctlCtx) { func authEnableTest(cx ctlCtx) {
if err := authEnable(cx); err != nil { if err := authEnable(cx); err != nil {
@ -1073,3 +1075,24 @@ func authTestCertCNAndUsername(cx ctlCtx) {
cx.t.Error(err) cx.t.Error(err)
} }
} }
func authTestJWTExpire(cx ctlCtx) {
if err := authEnable(cx); err != nil {
cx.t.Fatal(err)
}
cx.user, cx.pass = "root", "root"
authSetupTestUser(cx)
// try a granted key
if err := ctlV3Put(cx, "hoo", "bar", ""); err != nil {
cx.t.Error(err)
}
// wait an expiration of my JWT token
<-time.After(3 * time.Second)
if err := ctlV3Put(cx, "hoo", "bar", ""); err != nil {
cx.t.Error(err)
}
}