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

auth: a new option for configuring TTL of jwt tokens #8302

Merged
merged 2 commits into from
Feb 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Documentation/op-guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,8 @@ Follow the instructions when using these flags.
## Auth flags

### --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.
+ Example option of JWT: '--auth-token jwt,pub-key=app.rsa.pub,priv-key=app.rsa,sign-method=RS512'
+ 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,ttl=10m'
+ default: "simple"

## Experimental flags
Expand Down
27 changes: 21 additions & 6 deletions auth/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"crypto/rsa"
"io/ioutil"
"time"

jwt "github.com/dgrijalva/jwt-go"
)
Expand All @@ -26,6 +27,7 @@ type tokenJWT struct {
signMethod string
signKey *rsa.PrivateKey
verifyKey *rsa.PublicKey
ttl time.Duration
}

func (t *tokenJWT) enable() {}
Expand Down Expand Up @@ -70,6 +72,7 @@ func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64)
jwt.MapClaims{
"username": username,
"revision": revision,
"exp": time.Now().Add(t.ttl).Unix(),
})

token, err := tk.SignedString(t.signKey)
Expand All @@ -83,7 +86,7 @@ func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64)
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 {
switch k {
case "sign-method":
Expand All @@ -92,24 +95,36 @@ func prepareOpts(opts map[string]string) (jwtSignMethod, jwtPubKeyPath, jwtPrivK
jwtPubKeyPath = v
case "priv-key":
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:
plog.Errorf("unknown token specific option: %s", k)
return "", "", "", ErrInvalidAuthOpts
return "", "", "", 0, ErrInvalidAuthOpts
}
}
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) {
jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, err := prepareOpts(opts)
jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, ttl, err := prepareOpts(opts)
if err != nil {
return nil, ErrInvalidAuthOpts
}

t := &tokenJWT{}
if ttl == 0 {
ttl = 5 * time.Minute
}

t := &tokenJWT{
ttl: ttl,
}

t.signMethod = jwtSignMethod

Expand Down
11 changes: 11 additions & 0 deletions e2e/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ var (
initialToken: "new",
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 {
Expand Down Expand Up @@ -117,6 +122,7 @@ type etcdProcessClusterConfig struct {
quotaBackendBytes int64
noStrictReconfig bool
initialCorruptCheck bool
authTokenOpts string
}

// newEtcdProcessCluster launches a new cluster from etcd processes, returning
Expand Down Expand Up @@ -238,6 +244,11 @@ func (cfg *etcdProcessClusterConfig) etcdServerProcessConfigs() []*etcdServerPro
}

args = append(args, cfg.tlsArgs()...)

if cfg.authTokenOpts != "" {
args = append(args, "--auth-token", cfg.authTokenOpts)
}

etcdCfgs[i] = &etcdServerProcessConfig{
execPath: cfg.execPath,
args: args,
Expand Down
23 changes: 23 additions & 0 deletions e2e/ctl_v3_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"os"
"testing"
"time"

"github.com/coreos/etcd/clientv3"
)
Expand Down Expand Up @@ -58,6 +59,7 @@ func TestCtlV3AuthSnapshot(t *testing.T) { testCtl(t, authTestSnapshot) }
func TestCtlV3AuthCertCNAndUsername(t *testing.T) {
testCtl(t, authTestCertCNAndUsername, withCfg(configClientTLSCertAuth))
}
func TestCtlV3AuthJWTExpire(t *testing.T) { testCtl(t, authTestJWTExpire, withCfg(configJWT)) }

func authEnableTest(cx ctlCtx) {
if err := authEnable(cx); err != nil {
Expand Down Expand Up @@ -1073,3 +1075,24 @@ func authTestCertCNAndUsername(cx ctlCtx) {
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)
}
}