Merge pull request #6881 from mitake/auth-v3-cn
authenticate clients based on certificate CommonName in v3 APIrelease-3.2
commit
89bb9048dd
|
@ -30,7 +30,9 @@ import (
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/coreos/pkg/capnslog"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -159,6 +161,9 @@ type AuthStore interface {
|
||||||
|
|
||||||
// AuthInfoFromCtx gets AuthInfo from gRPC's context
|
// AuthInfoFromCtx gets AuthInfo from gRPC's context
|
||||||
AuthInfoFromCtx(ctx context.Context) (*AuthInfo, error)
|
AuthInfoFromCtx(ctx context.Context) (*AuthInfo, error)
|
||||||
|
|
||||||
|
// AuthInfoFromTLS gets AuthInfo from TLS info of gRPC's context
|
||||||
|
AuthInfoFromTLS(ctx context.Context) *AuthInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
type authStore struct {
|
type authStore struct {
|
||||||
|
@ -950,6 +955,28 @@ func (as *authStore) isValidSimpleToken(token string, ctx context.Context) bool
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (as *authStore) AuthInfoFromTLS(ctx context.Context) *AuthInfo {
|
||||||
|
peer, ok := peer.FromContext(ctx)
|
||||||
|
if !ok || peer == nil || peer.AuthInfo == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsInfo := peer.AuthInfo.(credentials.TLSInfo)
|
||||||
|
for _, chains := range tlsInfo.State.VerifiedChains {
|
||||||
|
for _, chain := range chains {
|
||||||
|
cn := chain.Subject.CommonName
|
||||||
|
plog.Debugf("found common name %s", cn)
|
||||||
|
|
||||||
|
return &AuthInfo{
|
||||||
|
Username: cn,
|
||||||
|
Revision: as.Revision(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (as *authStore) AuthInfoFromCtx(ctx context.Context) (*AuthInfo, error) {
|
func (as *authStore) AuthInfoFromCtx(ctx context.Context) (*AuthInfo, error) {
|
||||||
md, ok := metadata.FromContext(ctx)
|
md, ok := metadata.FromContext(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -34,6 +34,7 @@ func TestCtlV3AuthMemberRemove(t *testing.T) {
|
||||||
testCtl(t, authTestMemberRemove, withQuorum(), withNoStrictReconfig())
|
testCtl(t, authTestMemberRemove, withQuorum(), withNoStrictReconfig())
|
||||||
}
|
}
|
||||||
func TestCtlV3AuthMemberUpdate(t *testing.T) { testCtl(t, authTestMemberUpdate) }
|
func TestCtlV3AuthMemberUpdate(t *testing.T) { testCtl(t, authTestMemberUpdate) }
|
||||||
|
func TestCtlV3AuthCertCN(t *testing.T) { testCtl(t, authTestCertCN, withCfg(configClientTLSCertAuth)) }
|
||||||
|
|
||||||
func authEnableTest(cx ctlCtx) {
|
func authEnableTest(cx ctlCtx) {
|
||||||
if err := authEnable(cx); err != nil {
|
if err := authEnable(cx); err != nil {
|
||||||
|
@ -549,3 +550,36 @@ func authTestMemberUpdate(cx ctlCtx) {
|
||||||
cx.t.Fatal(err)
|
cx.t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func authTestCertCN(cx ctlCtx) {
|
||||||
|
if err := ctlV3User(cx, []string{"add", "etcd", "--interactive=false"}, "User etcd created", []string{""}); err != nil {
|
||||||
|
cx.t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := spawnWithExpect(append(cx.PrefixArgs(), "role", "add", "test-role"), "Role test-role created"); err != nil {
|
||||||
|
cx.t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ctlV3User(cx, []string{"grant-role", "etcd", "test-role"}, "Role test-role is granted to user etcd", nil); err != nil {
|
||||||
|
cx.t.Fatal(err)
|
||||||
|
}
|
||||||
|
cmd := append(cx.PrefixArgs(), "role", "grant-permission", "test-role", "readwrite", "foo")
|
||||||
|
if err := spawnWithExpect(cmd, "Role test-role updated"); err != nil {
|
||||||
|
cx.t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// grant a new key
|
||||||
|
if err := ctlV3RoleGrantPermission(cx, "test-role", grantingPerm{true, true, "hoo", "", false}); err != nil {
|
||||||
|
cx.t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// try a granted key
|
||||||
|
cx.user, cx.pass = "", ""
|
||||||
|
if err := ctlV3Put(cx, "hoo", "bar", ""); err != nil {
|
||||||
|
cx.t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// try a non granted key
|
||||||
|
cx.user, cx.pass = "", ""
|
||||||
|
if err := ctlV3PutFailPerm(cx, "baz", "bar"); err == nil {
|
||||||
|
cx.t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -106,6 +106,13 @@ var (
|
||||||
isPeerTLS: true,
|
isPeerTLS: true,
|
||||||
initialToken: "new",
|
initialToken: "new",
|
||||||
}
|
}
|
||||||
|
configClientTLSCertAuth = etcdProcessClusterConfig{
|
||||||
|
clusterSize: 1,
|
||||||
|
proxySize: 0,
|
||||||
|
clientTLS: clientTLS,
|
||||||
|
initialToken: "new",
|
||||||
|
clientCertAuthEnabled: true,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func configStandalone(cfg etcdProcessClusterConfig) *etcdProcessClusterConfig {
|
func configStandalone(cfg etcdProcessClusterConfig) *etcdProcessClusterConfig {
|
||||||
|
|
|
@ -47,6 +47,7 @@ type RaftStatusGetter interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthGetter interface {
|
type AuthGetter interface {
|
||||||
|
AuthInfoFromCtx(ctx context.Context) (*auth.AuthInfo, error)
|
||||||
AuthStore() auth.AuthStore
|
AuthStore() auth.AuthStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +153,7 @@ type authMaintenanceServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ams *authMaintenanceServer) isAuthenticated(ctx context.Context) error {
|
func (ams *authMaintenanceServer) isAuthenticated(ctx context.Context) error {
|
||||||
authInfo, err := ams.ag.AuthStore().AuthInfoFromCtx(ctx)
|
authInfo, err := ams.ag.AuthInfoFromCtx(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1022,7 +1022,7 @@ func (s *EtcdServer) checkMembershipOperationPermission(ctx context.Context) err
|
||||||
// in the state machine layer
|
// in the state machine layer
|
||||||
// However, both of membership change and role management requires the root privilege.
|
// However, both of membership change and role management requires the root privilege.
|
||||||
// So careful operation by admins can prevent the problem.
|
// So careful operation by admins can prevent the problem.
|
||||||
authInfo, err := s.AuthStore().AuthInfoFromCtx(ctx)
|
authInfo, err := s.AuthInfoFromCtx(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -617,7 +617,7 @@ func (s *EtcdServer) RoleDelete(ctx context.Context, r *pb.AuthRoleDeleteRequest
|
||||||
// doSerialize handles the auth logic, with permissions checked by "chk", for a serialized request "get". Returns a non-nil error on authentication failure.
|
// doSerialize handles the auth logic, with permissions checked by "chk", for a serialized request "get". Returns a non-nil error on authentication failure.
|
||||||
func (s *EtcdServer) doSerialize(ctx context.Context, chk func(*auth.AuthInfo) error, get func()) error {
|
func (s *EtcdServer) doSerialize(ctx context.Context, chk func(*auth.AuthInfo) error, get func()) error {
|
||||||
for {
|
for {
|
||||||
ai, err := s.AuthStore().AuthInfoFromCtx(ctx)
|
ai, err := s.AuthInfoFromCtx(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -652,7 +652,7 @@ func (s *EtcdServer) processInternalRaftRequestOnce(ctx context.Context, r pb.In
|
||||||
ID: s.reqIDGen.Next(),
|
ID: s.reqIDGen.Next(),
|
||||||
}
|
}
|
||||||
|
|
||||||
authInfo, err := s.AuthStore().AuthInfoFromCtx(ctx)
|
authInfo, err := s.AuthInfoFromCtx(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -802,3 +802,14 @@ func (s *EtcdServer) linearizableReadNotify(ctx context.Context) error {
|
||||||
return ErrStopped
|
return ErrStopped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *EtcdServer) AuthInfoFromCtx(ctx context.Context) (*auth.AuthInfo, error) {
|
||||||
|
if s.Cfg.ClientCertAuthEnabled {
|
||||||
|
authInfo := s.AuthStore().AuthInfoFromTLS(ctx)
|
||||||
|
if authInfo != nil {
|
||||||
|
return authInfo, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.AuthStore().AuthInfoFromCtx(ctx)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue