From 16db9e68a27fdc7abd01038900a952e2372603bb Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Tue, 14 Jun 2016 22:45:09 -0700 Subject: [PATCH] auth, etcdserver: separate auth checking apply from core apply --- auth/store.go | 12 ++-- etcdserver/apply.go | 125 +++++++++++++-------------------------- etcdserver/apply_auth.go | 95 +++++++++++++++++++++++++++++ etcdserver/server.go | 5 +- etcdserver/v3_server.go | 17 +++--- 5 files changed, 154 insertions(+), 100 deletions(-) create mode 100644 etcdserver/apply_auth.go diff --git a/auth/store.go b/auth/store.go index 8a0a9863e..0e59d18ad 100644 --- a/auth/store.go +++ b/auth/store.go @@ -108,10 +108,10 @@ type AuthStore interface { UsernameFromToken(token string) (string, bool) // IsPutPermitted checks put permission of the user - IsPutPermitted(header *pb.RequestHeader, key []byte) bool + IsPutPermitted(username string, key []byte) bool // IsRangePermitted checks range permission of the user - IsRangePermitted(header *pb.RequestHeader, key, rangeEnd []byte) bool + IsRangePermitted(username string, key, rangeEnd []byte) bool // IsDeleteRangePermitted checks delete-range permission of the user IsDeleteRangePermitted(username string, key, rangeEnd []byte) bool @@ -570,12 +570,12 @@ func (as *authStore) isOpPermitted(userName string, key, rangeEnd []byte, permTy return false } -func (as *authStore) IsPutPermitted(header *pb.RequestHeader, key []byte) bool { - return as.isOpPermitted(header.Username, key, nil, authpb.WRITE) +func (as *authStore) IsPutPermitted(username string, key []byte) bool { + return as.isOpPermitted(username, key, nil, authpb.WRITE) } -func (as *authStore) IsRangePermitted(header *pb.RequestHeader, key, rangeEnd []byte) bool { - return as.isOpPermitted(header.Username, key, rangeEnd, authpb.READ) +func (as *authStore) IsRangePermitted(username string, key, rangeEnd []byte) bool { + return as.isOpPermitted(username, key, rangeEnd, authpb.READ) } func (as *authStore) IsDeleteRangePermitted(username string, key, rangeEnd []byte) bool { diff --git a/etcdserver/apply.go b/etcdserver/apply.go index f13c50e52..1a4fb0883 100644 --- a/etcdserver/apply.go +++ b/etcdserver/apply.go @@ -20,7 +20,6 @@ import ( "sort" "time" - "github.com/coreos/etcd/auth" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/lease" "github.com/coreos/etcd/mvcc" @@ -50,6 +49,8 @@ type applyResult struct { // applierV3 is the interface for processing V3 raft messages type applierV3 interface { + Apply(r *pb.InternalRaftRequest) *applyResult + Put(txnID int64, p *pb.PutRequest) (*pb.PutResponse, error) Range(txnID int64, r *pb.RangeRequest) (*pb.RangeResponse, error) DeleteRange(txnID int64, dr *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) @@ -61,9 +62,11 @@ type applierV3 interface { Alarm(*pb.AlarmRequest) (*pb.AlarmResponse, error) + Authenticate(r *pb.InternalAuthenticateRequest) (*pb.AuthenticateResponse, error) + AuthEnable() (*pb.AuthEnableResponse, error) AuthDisable() (*pb.AuthDisableResponse, error) - Authenticate(ctx context.Context, username, password string) (*pb.AuthenticateResponse, error) + UserAdd(ua *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) UserChangePassword(ua *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) @@ -81,74 +84,62 @@ type applierV3backend struct { s *EtcdServer } -func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) *applyResult { +func (s *EtcdServer) newApplierV3() applierV3 { + return newAuthApplierV3( + s.AuthStore(), + newQuotaApplierV3(s, &applierV3backend{s}), + ) +} + +func (a *applierV3backend) Apply(r *pb.InternalRaftRequest) *applyResult { ar := &applyResult{} - username := r.Header.Username - - if needAdminPermission(r) && !s.AuthStore().IsAdminPermitted(username) { - ar.err = auth.ErrPermissionDenied - return ar - } + // call into a.s.applyV3.F instead of a.F so upper appliers can check individual calls switch { case r.Range != nil: - if s.AuthStore().IsRangePermitted(r.Header, r.Range.Key, r.Range.RangeEnd) { - ar.resp, ar.err = s.applyV3.Range(noTxn, r.Range) - } else { - ar.err = auth.ErrPermissionDenied - } + ar.resp, ar.err = a.s.applyV3.Range(noTxn, r.Range) case r.Put != nil: - if s.AuthStore().IsPutPermitted(r.Header, r.Put.Key) { - ar.resp, ar.err = s.applyV3.Put(noTxn, r.Put) - } else { - ar.err = auth.ErrPermissionDenied - } + ar.resp, ar.err = a.s.applyV3.Put(noTxn, r.Put) case r.DeleteRange != nil: - if s.AuthStore().IsDeleteRangePermitted(r.Header.Username, r.DeleteRange.Key, r.DeleteRange.RangeEnd) { - ar.resp, ar.err = s.applyV3.DeleteRange(noTxn, r.DeleteRange) - } else { - ar.err = auth.ErrPermissionDenied - } + ar.resp, ar.err = a.s.applyV3.DeleteRange(noTxn, r.DeleteRange) case r.Txn != nil: - ar.resp, ar.err = s.applyV3.Txn(r.Txn) + ar.resp, ar.err = a.s.applyV3.Txn(r.Txn) case r.Compaction != nil: - ar.resp, ar.physc, ar.err = s.applyV3.Compaction(r.Compaction) + ar.resp, ar.physc, ar.err = a.s.applyV3.Compaction(r.Compaction) case r.LeaseGrant != nil: - ar.resp, ar.err = s.applyV3.LeaseGrant(r.LeaseGrant) + ar.resp, ar.err = a.s.applyV3.LeaseGrant(r.LeaseGrant) case r.LeaseRevoke != nil: - ar.resp, ar.err = s.applyV3.LeaseRevoke(r.LeaseRevoke) + ar.resp, ar.err = a.s.applyV3.LeaseRevoke(r.LeaseRevoke) case r.Alarm != nil: - ar.resp, ar.err = s.applyV3.Alarm(r.Alarm) - - case r.AuthEnable != nil: - ar.resp, ar.err = s.applyV3.AuthEnable() - case r.AuthDisable != nil: - ar.resp, ar.err = s.applyV3.AuthDisable() + ar.resp, ar.err = a.s.applyV3.Alarm(r.Alarm) case r.Authenticate != nil: - ctx := context.WithValue(context.WithValue(context.TODO(), "index", s.consistIndex.ConsistentIndex()), "simpleToken", r.Authenticate.SimpleToken) - ar.resp, ar.err = s.applyV3.Authenticate(ctx, r.Authenticate.Name, r.Authenticate.Password) + ar.resp, ar.err = a.s.applyV3.Authenticate(r.Authenticate) + case r.AuthEnable != nil: + ar.resp, ar.err = a.s.applyV3.AuthEnable() + case r.AuthDisable != nil: + ar.resp, ar.err = a.s.applyV3.AuthDisable() case r.AuthUserAdd != nil: - ar.resp, ar.err = s.applyV3.UserAdd(r.AuthUserAdd) + ar.resp, ar.err = a.s.applyV3.UserAdd(r.AuthUserAdd) case r.AuthUserDelete != nil: - ar.resp, ar.err = s.applyV3.UserDelete(r.AuthUserDelete) + ar.resp, ar.err = a.s.applyV3.UserDelete(r.AuthUserDelete) case r.AuthUserChangePassword != nil: - ar.resp, ar.err = s.applyV3.UserChangePassword(r.AuthUserChangePassword) + ar.resp, ar.err = a.s.applyV3.UserChangePassword(r.AuthUserChangePassword) case r.AuthUserGrantRole != nil: - ar.resp, ar.err = s.applyV3.UserGrantRole(r.AuthUserGrantRole) + ar.resp, ar.err = a.s.applyV3.UserGrantRole(r.AuthUserGrantRole) case r.AuthUserGet != nil: - ar.resp, ar.err = s.applyV3.UserGet(r.AuthUserGet) + ar.resp, ar.err = a.s.applyV3.UserGet(r.AuthUserGet) case r.AuthUserRevokeRole != nil: - ar.resp, ar.err = s.applyV3.UserRevokeRole(r.AuthUserRevokeRole) + ar.resp, ar.err = a.s.applyV3.UserRevokeRole(r.AuthUserRevokeRole) case r.AuthRoleAdd != nil: - ar.resp, ar.err = s.applyV3.RoleAdd(r.AuthRoleAdd) + ar.resp, ar.err = a.s.applyV3.RoleAdd(r.AuthRoleAdd) case r.AuthRoleGrantPermission != nil: - ar.resp, ar.err = s.applyV3.RoleGrantPermission(r.AuthRoleGrantPermission) + ar.resp, ar.err = a.s.applyV3.RoleGrantPermission(r.AuthRoleGrantPermission) case r.AuthRoleGet != nil: - ar.resp, ar.err = s.applyV3.RoleGet(r.AuthRoleGet) + ar.resp, ar.err = a.s.applyV3.RoleGet(r.AuthRoleGet) case r.AuthRoleRevokePermission != nil: - ar.resp, ar.err = s.applyV3.RoleRevokePermission(r.AuthRoleRevokePermission) + ar.resp, ar.err = a.s.applyV3.RoleRevokePermission(r.AuthRoleRevokePermission) case r.AuthRoleDelete != nil: - ar.resp, ar.err = s.applyV3.RoleDelete(r.AuthRoleDelete) + ar.resp, ar.err = a.s.applyV3.RoleDelete(r.AuthRoleDelete) default: panic("not implemented") } @@ -503,7 +494,7 @@ func (a *applierV3backend) Alarm(ar *pb.AlarmRequest) (*pb.AlarmResponse, error) switch m.Alarm { case pb.AlarmType_NOSPACE: plog.Infof("alarm disarmed %+v", ar) - a.s.applyV3 = newQuotaApplierV3(a.s, &applierV3backend{a.s}) + a.s.applyV3 = a.s.newApplierV3() default: plog.Errorf("unimplemented alarm deactivation (%+v)", m) } @@ -550,8 +541,9 @@ func (a *applierV3backend) AuthDisable() (*pb.AuthDisableResponse, error) { return &pb.AuthDisableResponse{}, nil } -func (a *applierV3backend) Authenticate(ctx context.Context, username, password string) (*pb.AuthenticateResponse, error) { - return a.s.AuthStore().Authenticate(ctx, username, password) +func (a *applierV3backend) Authenticate(r *pb.InternalAuthenticateRequest) (*pb.AuthenticateResponse, error) { + ctx := context.WithValue(context.WithValue(context.TODO(), "index", a.s.consistIndex.ConsistentIndex()), "simpleToken", r.SimpleToken) + return a.s.AuthStore().Authenticate(ctx, r.Name, r.Password) } func (a *applierV3backend) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) { @@ -727,36 +719,3 @@ func compareInt64(a, b int64) int { func isGteRange(rangeEnd []byte) bool { return len(rangeEnd) == 1 && rangeEnd[0] == 0 } - -func needAdminPermission(r *pb.InternalRaftRequest) bool { - switch { - case r.AuthEnable != nil: - return true - case r.AuthDisable != nil: - return true - case r.AuthUserAdd != nil: - return true - case r.AuthUserDelete != nil: - return true - case r.AuthUserChangePassword != nil: - return true - case r.AuthUserGrantRole != nil: - return true - case r.AuthUserGet != nil: - return true - case r.AuthUserRevokeRole != nil: - return true - case r.AuthRoleAdd != nil: - return true - case r.AuthRoleGrantPermission != nil: - return true - case r.AuthRoleGet != nil: - return true - case r.AuthRoleRevokePermission != nil: - return true - case r.AuthRoleDelete != nil: - return true - default: - return false - } -} diff --git a/etcdserver/apply_auth.go b/etcdserver/apply_auth.go new file mode 100644 index 000000000..3da6463ac --- /dev/null +++ b/etcdserver/apply_auth.go @@ -0,0 +1,95 @@ +// 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 etcdserver + +import ( + "github.com/coreos/etcd/auth" + pb "github.com/coreos/etcd/etcdserver/etcdserverpb" +) + +type authApplierV3 struct { + applierV3 + as auth.AuthStore + user string +} + +func newAuthApplierV3(as auth.AuthStore, base applierV3) *authApplierV3 { + return &authApplierV3{base, as, ""} +} + +func (aa *authApplierV3) Apply(r *pb.InternalRaftRequest) *applyResult { + user := r.Header.Username + if needAdminPermission(r) && !aa.as.IsAdminPermitted(user) { + return &applyResult{err: auth.ErrPermissionDenied} + } + aa.user = user + ret := aa.applierV3.Apply(r) + aa.user = "" + return ret +} + +func (aa *authApplierV3) Put(txnID int64, r *pb.PutRequest) (*pb.PutResponse, error) { + if !aa.as.IsPutPermitted(aa.user, r.Key) { + return nil, auth.ErrPermissionDenied + } + return aa.applierV3.Put(txnID, r) +} + +func (aa *authApplierV3) Range(txnID int64, r *pb.RangeRequest) (*pb.RangeResponse, error) { + if !aa.as.IsRangePermitted(aa.user, r.Key, r.RangeEnd) { + return nil, auth.ErrPermissionDenied + } + return aa.applierV3.Range(txnID, r) +} + +func (aa *authApplierV3) DeleteRange(txnID int64, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) { + if !aa.as.IsDeleteRangePermitted(aa.user, r.Key, r.RangeEnd) { + return nil, auth.ErrPermissionDenied + } + return aa.applierV3.DeleteRange(txnID, r) +} + +func needAdminPermission(r *pb.InternalRaftRequest) bool { + switch { + case r.AuthEnable != nil: + return true + case r.AuthDisable != nil: + return true + case r.AuthUserAdd != nil: + return true + case r.AuthUserDelete != nil: + return true + case r.AuthUserChangePassword != nil: + return true + case r.AuthUserGrantRole != nil: + return true + case r.AuthUserGet != nil: + return true + case r.AuthUserRevokeRole != nil: + return true + case r.AuthRoleAdd != nil: + return true + case r.AuthRoleGrantPermission != nil: + return true + case r.AuthRoleGet != nil: + return true + case r.AuthRoleRevokePermission != nil: + return true + case r.AuthRoleDelete != nil: + return true + default: + return false + } +} diff --git a/etcdserver/server.go b/etcdserver/server.go index 695c5bae3..86b87481d 100644 --- a/etcdserver/server.go +++ b/etcdserver/server.go @@ -1072,7 +1072,7 @@ func (s *EtcdServer) applyEntryNormal(e *raftpb.Entry) { // set the consistent index of current executing entry s.consistIndex.setConsistentIndex(e.Index) - ar := s.applyV3Request(&raftReq) + ar := s.applyV3.Apply(&raftReq) s.setAppliedIndex(e.Index) if ar.err != ErrNoSpace || len(s.alarmStore.Get(pb.AlarmType_NOSPACE)) > 0 { s.w.Trigger(id, ar) @@ -1311,8 +1311,7 @@ func (s *EtcdServer) Backend() backend.Backend { func (s *EtcdServer) AuthStore() auth.AuthStore { return s.authStore } func (s *EtcdServer) restoreAlarms() error { - s.applyV3 = newQuotaApplierV3(s, &applierV3backend{s}) - + s.applyV3 = s.newApplierV3() as, err := alarm.NewAlarmStore(s) if err != nil { return err diff --git a/etcdserver/v3_server.go b/etcdserver/v3_server.go index 0df019af4..2404c349a 100644 --- a/etcdserver/v3_server.go +++ b/etcdserver/v3_server.go @@ -19,7 +19,6 @@ import ( "strings" "time" - "github.com/coreos/etcd/auth" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/lease" "github.com/coreos/etcd/lease/leasehttp" @@ -76,19 +75,21 @@ type Authenticator interface { } func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) { + var result *applyResult + var err error + if r.Serializable { user, err := s.usernameFromCtx(ctx) if err != nil { return nil, err } - hdr := &pb.RequestHeader{Username: user} - if !s.AuthStore().IsRangePermitted(hdr, r.Key, r.RangeEnd) { - return nil, auth.ErrPermissionDenied - } - return s.applyV3.Range(noTxn, r) + result = s.applyV3.Apply( + &pb.InternalRaftRequest{ + Header: &pb.RequestHeader{Username: user}, + Range: r}) + } else { + result, err = s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Range: r}) } - - result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Range: r}) if err != nil { return nil, err }