Skip to content

Commit

Permalink
clientv3: allow setting JWT directly
Browse files Browse the repository at this point in the history
etcd supports using signed JWTs in a verify-only mode where the server
has access to only a public key and therefore can not create tokens but
can validate them. For this to work a client must not call Authenticate
and must instead submit a pre-signed JWT with their request. The server
will validate this token, extract the username from it, and may allow
the client access.

This change allows setting the JWT directly and not setting a username
and password. If a JWT is provided the client will no longer call
Authenticate, which would not work anyhow. It also provides a public
method UpdateAuthToken to allow a user of the client to update their
auth token, for example, if it expires.

In this flow all token lifecycle management is handled outside of the
client as a concern of the client user.

Signed-off-by: Mike Crute <[email protected]>
  • Loading branch information
mcrute committed Oct 21, 2023
1 parent 7f2936d commit 176ae2b
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 7 deletions.
29 changes: 22 additions & 7 deletions client/v3/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ type Client struct {
// Username is a user name for authentication.
Username string
// Password is a password for authentication.
Password string
Password string
// Token is a JWT used for authentication instead of a password.
Token string

authTokenBundle credentials.PerRPCCredentialsBundle

callOpts []grpc.CallOption
Expand Down Expand Up @@ -262,9 +265,21 @@ func (c *Client) Dial(ep string) (*grpc.ClientConn, error) {
return c.dial(creds, grpc.WithResolvers(resolver.New(ep)))
}

// UpdateAuthToken allows updating the JWT auth token held by the
// client. It is safe to call this function concurrently with other
// operations.
func (c *Client) UpdateAuthToken(token string) {
c.authTokenBundle.UpdateAuthToken(token)
}

func (c *Client) getToken(ctx context.Context) error {
var err error // return last error in a case of fail

if c.Token != "" {
c.UpdateAuthToken(c.Token)
return nil
}

if c.Username == "" || c.Password == "" {
return nil
}
Expand All @@ -277,7 +292,7 @@ func (c *Client) getToken(ctx context.Context) error {
}
return err
}
c.authTokenBundle.UpdateAuthToken(resp.Token)
c.UpdateAuthToken(resp.Token)
return nil
}

Expand Down Expand Up @@ -386,11 +401,11 @@ func newClient(cfg *Config) (*Client, error) {
return nil, err
}

if cfg.Username != "" && cfg.Password != "" {
client.Username = cfg.Username
client.Password = cfg.Password
client.authTokenBundle = credentials.NewPerRPCCredentialBundle()
}
client.Username = cfg.Username
client.Password = cfg.Password
client.Token = cfg.Token
client.authTokenBundle = credentials.NewPerRPCCredentialBundle()

if cfg.MaxCallSendMsgSize > 0 || cfg.MaxCallRecvMsgSize > 0 {
if cfg.MaxCallRecvMsgSize > 0 && cfg.MaxCallSendMsgSize > cfg.MaxCallRecvMsgSize {
return nil, fmt.Errorf("gRPC message recv limit (%d bytes) must be greater than send limit (%d bytes)", cfg.MaxCallRecvMsgSize, cfg.MaxCallSendMsgSize)
Expand Down
3 changes: 3 additions & 0 deletions client/v3/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ type Config struct {
// Password is a password for authentication.
Password string `json:"password"`

// Token is a JWT used for authentication instead of a password.
Token string `json:"token"`

// RejectOldCluster when set will refuse to create a client against an outdated cluster.
RejectOldCluster bool `json:"reject-old-cluster"`

Expand Down

0 comments on commit 176ae2b

Please sign in to comment.