mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
Merge pull request #5361 from mitake/auth-v3-token-credential
RFC: *: attach auth token as a gRPC credential
This commit is contained in:
commit
7014f6861d
@ -52,9 +52,6 @@ type Auth interface {
|
|||||||
// AuthDisable disables auth of an etcd cluster.
|
// AuthDisable disables auth of an etcd cluster.
|
||||||
AuthDisable(ctx context.Context) (*AuthDisableResponse, error)
|
AuthDisable(ctx context.Context) (*AuthDisableResponse, error)
|
||||||
|
|
||||||
// Authenticate does authenticate with given user name and password.
|
|
||||||
Authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error)
|
|
||||||
|
|
||||||
// UserAdd adds a new user to an etcd cluster.
|
// UserAdd adds a new user to an etcd cluster.
|
||||||
UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error)
|
UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error)
|
||||||
|
|
||||||
@ -100,11 +97,6 @@ func (auth *auth) AuthDisable(ctx context.Context) (*AuthDisableResponse, error)
|
|||||||
return (*AuthDisableResponse)(resp), rpctypes.Error(err)
|
return (*AuthDisableResponse)(resp), rpctypes.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *auth) Authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error) {
|
|
||||||
resp, err := auth.remote.Authenticate(ctx, &pb.AuthenticateRequest{Name: name, Password: password})
|
|
||||||
return (*AuthenticateResponse)(resp), rpctypes.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (auth *auth) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) {
|
func (auth *auth) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) {
|
||||||
resp, err := auth.remote.UserAdd(ctx, &pb.AuthUserAddRequest{Name: name, Password: password})
|
resp, err := auth.remote.UserAdd(ctx, &pb.AuthUserAddRequest{Name: name, Password: password})
|
||||||
return (*AuthUserAddResponse)(resp), rpctypes.Error(err)
|
return (*AuthUserAddResponse)(resp), rpctypes.Error(err)
|
||||||
@ -146,3 +138,29 @@ func StrToPermissionType(s string) (PermissionType, error) {
|
|||||||
}
|
}
|
||||||
return PermissionType(-1), fmt.Errorf("invalid permission type: %s", s)
|
return PermissionType(-1), fmt.Errorf("invalid permission type: %s", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type authenticator struct {
|
||||||
|
conn *grpc.ClientConn // conn in-use
|
||||||
|
remote pb.AuthClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *authenticator) authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error) {
|
||||||
|
resp, err := auth.remote.Authenticate(ctx, &pb.AuthenticateRequest{Name: name, Password: password})
|
||||||
|
return (*AuthenticateResponse)(resp), rpctypes.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *authenticator) close() {
|
||||||
|
auth.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAuthenticator(endpoint string, opts []grpc.DialOption) (*authenticator, error) {
|
||||||
|
conn, err := grpc.Dial(endpoint, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &authenticator{
|
||||||
|
conn: conn,
|
||||||
|
remote: pb.NewAuthClient(conn),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -65,6 +65,11 @@ type Client struct {
|
|||||||
// newconnc is closed on successful connect and set to a fresh channel
|
// newconnc is closed on successful connect and set to a fresh channel
|
||||||
newconnc chan struct{}
|
newconnc chan struct{}
|
||||||
lastConnErr error
|
lastConnErr error
|
||||||
|
|
||||||
|
// Username is a username of authentication
|
||||||
|
Username string
|
||||||
|
// Password is a password of authentication
|
||||||
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new etcdv3 client from a given configuration.
|
// New creates a new etcdv3 client from a given configuration.
|
||||||
@ -132,6 +137,20 @@ func (c *Client) Errors() (errs []error) {
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type authTokenCredential struct {
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cred authTokenCredential) RequireTransportSecurity() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cred authTokenCredential) GetRequestMetadata(ctx context.Context, s ...string) (map[string]string, error) {
|
||||||
|
return map[string]string{
|
||||||
|
"token": cred.token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Dial establishes a connection for a given endpoint using the client's config
|
// Dial establishes a connection for a given endpoint using the client's config
|
||||||
func (c *Client) Dial(endpoint string) (*grpc.ClientConn, error) {
|
func (c *Client) Dial(endpoint string) (*grpc.ClientConn, error) {
|
||||||
opts := []grpc.DialOption{
|
opts := []grpc.DialOption{
|
||||||
@ -160,6 +179,21 @@ func (c *Client) Dial(endpoint string) (*grpc.ClientConn, error) {
|
|||||||
}
|
}
|
||||||
opts = append(opts, grpc.WithDialer(f))
|
opts = append(opts, grpc.WithDialer(f))
|
||||||
|
|
||||||
|
if c.Username != "" && c.Password != "" {
|
||||||
|
auth, err := newAuthenticator(endpoint, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer auth.close()
|
||||||
|
|
||||||
|
resp, err := auth.authenticate(c.ctx, c.Username, c.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = append(opts, grpc.WithPerRPCCredentials(authTokenCredential{token: resp.Token}))
|
||||||
|
}
|
||||||
|
|
||||||
conn, err := grpc.Dial(endpoint, opts...)
|
conn, err := grpc.Dial(endpoint, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -183,9 +217,10 @@ func newClient(cfg *Config) (*Client, error) {
|
|||||||
c := credentials.NewTLS(cfg.TLS)
|
c := credentials.NewTLS(cfg.TLS)
|
||||||
creds = &c
|
creds = &c
|
||||||
}
|
}
|
||||||
|
|
||||||
// use a temporary skeleton client to bootstrap first connection
|
// use a temporary skeleton client to bootstrap first connection
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
conn, err := cfg.RetryDialer(&Client{cfg: *cfg, creds: creds, ctx: ctx})
|
conn, err := cfg.RetryDialer(&Client{cfg: *cfg, creds: creds, ctx: ctx, Username: cfg.Username, Password: cfg.Password})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -199,6 +234,11 @@ func newClient(cfg *Config) (*Client, error) {
|
|||||||
newconnc: make(chan struct{}),
|
newconnc: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.Username != "" && cfg.Password != "" {
|
||||||
|
client.Username = cfg.Username
|
||||||
|
client.Password = cfg.Password
|
||||||
|
}
|
||||||
|
|
||||||
go client.connMonitor()
|
go client.connMonitor()
|
||||||
|
|
||||||
client.Cluster = NewCluster(client)
|
client.Cluster = NewCluster(client)
|
||||||
|
@ -43,6 +43,12 @@ type Config struct {
|
|||||||
|
|
||||||
// Logger is the logger used by client library.
|
// Logger is the logger used by client library.
|
||||||
Logger Logger
|
Logger Logger
|
||||||
|
|
||||||
|
// Username is a username of authentication
|
||||||
|
Username string
|
||||||
|
|
||||||
|
// Password is a password of authentication
|
||||||
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
type yamlConfig struct {
|
type yamlConfig struct {
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
// Copyright 2016 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package integration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/coreos/etcd/clientv3"
|
|
||||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
|
||||||
"github.com/coreos/etcd/integration"
|
|
||||||
"github.com/coreos/etcd/pkg/testutil"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAuthError(t *testing.T) {
|
|
||||||
defer testutil.AfterTest(t)
|
|
||||||
|
|
||||||
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
|
|
||||||
defer clus.Terminate(t)
|
|
||||||
|
|
||||||
authapi := clientv3.NewAuth(clus.RandClient())
|
|
||||||
|
|
||||||
_, err := authapi.UserAdd(context.TODO(), "foo", "bar")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = authapi.Authenticate(context.TODO(), "foo", "bar111")
|
|
||||||
if err != rpctypes.ErrAuthFailed {
|
|
||||||
t.Fatalf("expected %v, got %v", rpctypes.ErrAuthFailed, err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -70,7 +70,7 @@ func epHealthCommandFunc(cmd *cobra.Command, args []string) {
|
|||||||
dt := dialTimeoutFromCmd(cmd)
|
dt := dialTimeoutFromCmd(cmd)
|
||||||
cfgs := []*v3.Config{}
|
cfgs := []*v3.Config{}
|
||||||
for _, ep := range endpoints {
|
for _, ep := range endpoints {
|
||||||
cfg, err := newClientCfg([]string{ep}, dt, sec)
|
cfg, err := newClientCfg([]string{ep}, dt, sec, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ExitWithError(ExitBadArgs, err)
|
ExitWithError(ExitBadArgs, err)
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bgentry/speakeasy"
|
||||||
"github.com/coreos/etcd/clientv3"
|
"github.com/coreos/etcd/clientv3"
|
||||||
"github.com/coreos/etcd/pkg/flags"
|
"github.com/coreos/etcd/pkg/flags"
|
||||||
"github.com/coreos/etcd/pkg/transport"
|
"github.com/coreos/etcd/pkg/transport"
|
||||||
@ -40,6 +42,8 @@ type GlobalFlags struct {
|
|||||||
|
|
||||||
OutputFormat string
|
OutputFormat string
|
||||||
IsHex bool
|
IsHex bool
|
||||||
|
|
||||||
|
User string
|
||||||
}
|
}
|
||||||
|
|
||||||
type secureCfg struct {
|
type secureCfg struct {
|
||||||
@ -51,6 +55,11 @@ type secureCfg struct {
|
|||||||
insecureSkipVerify bool
|
insecureSkipVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type authCfg struct {
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
}
|
||||||
|
|
||||||
var display printer = &simplePrinter{}
|
var display printer = &simplePrinter{}
|
||||||
|
|
||||||
func initDisplayFromCmd(cmd *cobra.Command) {
|
func initDisplayFromCmd(cmd *cobra.Command) {
|
||||||
@ -76,14 +85,15 @@ func mustClientFromCmd(cmd *cobra.Command) *clientv3.Client {
|
|||||||
}
|
}
|
||||||
dialTimeout := dialTimeoutFromCmd(cmd)
|
dialTimeout := dialTimeoutFromCmd(cmd)
|
||||||
sec := secureCfgFromCmd(cmd)
|
sec := secureCfgFromCmd(cmd)
|
||||||
|
auth := authCfgFromCmd(cmd)
|
||||||
|
|
||||||
initDisplayFromCmd(cmd)
|
initDisplayFromCmd(cmd)
|
||||||
|
|
||||||
return mustClient(endpoints, dialTimeout, sec)
|
return mustClient(endpoints, dialTimeout, sec, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustClient(endpoints []string, dialTimeout time.Duration, scfg *secureCfg) *clientv3.Client {
|
func mustClient(endpoints []string, dialTimeout time.Duration, scfg *secureCfg, acfg *authCfg) *clientv3.Client {
|
||||||
cfg, err := newClientCfg(endpoints, dialTimeout, scfg)
|
cfg, err := newClientCfg(endpoints, dialTimeout, scfg, acfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ExitWithError(ExitBadArgs, err)
|
ExitWithError(ExitBadArgs, err)
|
||||||
}
|
}
|
||||||
@ -96,7 +106,7 @@ func mustClient(endpoints []string, dialTimeout time.Duration, scfg *secureCfg)
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClientCfg(endpoints []string, dialTimeout time.Duration, scfg *secureCfg) (*clientv3.Config, error) {
|
func newClientCfg(endpoints []string, dialTimeout time.Duration, scfg *secureCfg, acfg *authCfg) (*clientv3.Config, error) {
|
||||||
// set tls if any one tls option set
|
// set tls if any one tls option set
|
||||||
var cfgtls *transport.TLSInfo
|
var cfgtls *transport.TLSInfo
|
||||||
tlsinfo := transport.TLSInfo{}
|
tlsinfo := transport.TLSInfo{}
|
||||||
@ -138,6 +148,12 @@ func newClientCfg(endpoints []string, dialTimeout time.Duration, scfg *secureCfg
|
|||||||
if scfg.insecureSkipVerify && cfg.TLS != nil {
|
if scfg.insecureSkipVerify && cfg.TLS != nil {
|
||||||
cfg.TLS.InsecureSkipVerify = true
|
cfg.TLS.InsecureSkipVerify = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if acfg != nil {
|
||||||
|
cfg.Username = acfg.username
|
||||||
|
cfg.Password = acfg.password
|
||||||
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,3 +229,30 @@ func keyAndCertFromCmd(cmd *cobra.Command) (cert, key, cacert string) {
|
|||||||
|
|
||||||
return cert, key, cacert
|
return cert, key, cacert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func authCfgFromCmd(cmd *cobra.Command) *authCfg {
|
||||||
|
userFlag, err := cmd.Flags().GetString("user")
|
||||||
|
if err != nil {
|
||||||
|
ExitWithError(ExitBadArgs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if userFlag == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg authCfg
|
||||||
|
|
||||||
|
splitted := strings.SplitN(userFlag, ":", 2)
|
||||||
|
if len(splitted) == 0 {
|
||||||
|
cfg.username = userFlag
|
||||||
|
cfg.password, err = speakeasy.Ask("Password: ")
|
||||||
|
if err != nil {
|
||||||
|
ExitWithError(ExitError, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.username = splitted[0]
|
||||||
|
cfg.password = splitted[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg
|
||||||
|
}
|
||||||
|
@ -68,7 +68,7 @@ func makeMirrorCommandFunc(cmd *cobra.Command, args []string) {
|
|||||||
insecureTransport: mminsecureTr,
|
insecureTransport: mminsecureTr,
|
||||||
}
|
}
|
||||||
|
|
||||||
dc := mustClient([]string{args[0]}, dialTimeout, sec)
|
dc := mustClient([]string{args[0]}, dialTimeout, sec, nil)
|
||||||
c := mustClientFromCmd(cmd)
|
c := mustClientFromCmd(cmd)
|
||||||
|
|
||||||
err := makeMirror(context.TODO(), c, dc)
|
err := makeMirror(context.TODO(), c, dc)
|
||||||
|
@ -57,6 +57,7 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CertFile, "cert", "", "identify secure client using this TLS certificate file")
|
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CertFile, "cert", "", "identify secure client using this TLS certificate file")
|
||||||
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.KeyFile, "key", "", "identify secure client using this TLS key file")
|
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.KeyFile, "key", "", "identify secure client using this TLS key file")
|
||||||
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CAFile, "cacert", "", "verify certificates of TLS-enabled secure servers using this CA bundle")
|
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CAFile, "cacert", "", "verify certificates of TLS-enabled secure servers using this CA bundle")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&globalFlags.User, "user", "", "username[:password] for authentication (prompt if password is not supplied)")
|
||||||
|
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
command.NewGetCommand(),
|
command.NewGetCommand(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user