diff --git a/auth/authpb/auth.pb.go b/auth/authpb/auth.pb.go index 4fcb774ef..33f65ce28 100644 --- a/auth/authpb/auth.pb.go +++ b/auth/authpb/auth.pb.go @@ -10,6 +10,7 @@ It has these top-level messages: User + Role */ package authpb @@ -39,8 +40,18 @@ func (m *User) Reset() { *m = User{} } func (m *User) String() string { return proto.CompactTextString(m) } func (*User) ProtoMessage() {} +// Role is a single entry in the bucket authRoles +type Role struct { + Name []byte `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (m *Role) Reset() { *m = Role{} } +func (m *Role) String() string { return proto.CompactTextString(m) } +func (*Role) ProtoMessage() {} + func init() { proto.RegisterType((*User)(nil), "authpb.User") + proto.RegisterType((*Role)(nil), "authpb.Role") } func (m *User) Marshal() (data []byte, err error) { size := m.Size() @@ -81,6 +92,32 @@ func (m *User) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *Role) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Role) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Name != nil { + if len(m.Name) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintAuth(data, i, uint64(len(m.Name))) + i += copy(data[i:], m.Name) + } + } + return i, nil +} + func encodeFixed64Auth(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -129,6 +166,18 @@ func (m *User) Size() (n int) { return n } +func (m *Role) Size() (n int) { + var l int + _ = l + if m.Name != nil { + l = len(m.Name) + if l > 0 { + n += 1 + l + sovAuth(uint64(l)) + } + } + return n +} + func sovAuth(x uint64) (n int) { for { n++ @@ -273,6 +322,87 @@ func (m *User) Unmarshal(data []byte) error { } return nil } +func (m *Role) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuth + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Role: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Role: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuth + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthAuth + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = append(m.Name[:0], data[iNdEx:postIndex]...) + if m.Name == nil { + m.Name = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuth(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthAuth + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipAuth(data []byte) (n int, err error) { l := len(data) iNdEx := 0 diff --git a/auth/authpb/auth.proto b/auth/authpb/auth.proto index e0ab5c54c..00f94e71d 100644 --- a/auth/authpb/auth.proto +++ b/auth/authpb/auth.proto @@ -15,3 +15,8 @@ message User { bytes password = 2; int64 tombstone = 3; } + +// Role is a single entry in the bucket authRoles +message Role { + bytes name = 2; +} diff --git a/auth/store.go b/auth/store.go index 42f6ab5dc..39cf96bb8 100644 --- a/auth/store.go +++ b/auth/store.go @@ -25,14 +25,17 @@ import ( ) var ( - enableFlagKey = []byte("authEnabled") + enableFlagKey = []byte("authEnabled") + authBucketName = []byte("auth") authUsersBucketName = []byte("authUsers") + authRolesBucketName = []byte("authRoles") plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "auth") ErrUserAlreadyExist = errors.New("auth: user already exists") ErrUserNotFound = errors.New("auth: user not found") + ErrRoleAlreadyExist = errors.New("auth: role already exists") ) type AuthStore interface { @@ -50,6 +53,9 @@ type AuthStore interface { // UserChangePassword changes a password of a user UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) + + // RoleAdd adds a new role + RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) } type authStore struct { @@ -163,12 +169,39 @@ func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*p return &pb.AuthUserChangePasswordResponse{}, nil } +func (as *authStore) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) { + tx := as.be.BatchTx() + tx.Lock() + defer tx.Unlock() + + _, vs := tx.UnsafeRange(authRolesBucketName, []byte(r.Name), nil, 0) + if len(vs) != 0 { + return nil, ErrRoleAlreadyExist + } + + newRole := &authpb.Role{ + Name: []byte(r.Name), + } + + marshaledRole, err := newRole.Marshal() + if err != nil { + return nil, err + } + + tx.UnsafePut(authRolesBucketName, []byte(r.Name), marshaledRole) + + plog.Noticef("Role %s is created", r.Name) + + return &pb.AuthRoleAddResponse{}, nil +} + func NewAuthStore(be backend.Backend) *authStore { tx := be.BatchTx() tx.Lock() tx.UnsafeCreateBucket(authBucketName) tx.UnsafeCreateBucket(authUsersBucketName) + tx.UnsafeCreateBucket(authRolesBucketName) tx.Unlock() be.ForceCommit() diff --git a/clientv3/auth.go b/clientv3/auth.go index c4e57809a..fceea770b 100644 --- a/clientv3/auth.go +++ b/clientv3/auth.go @@ -25,6 +25,7 @@ type ( AuthUserAddResponse pb.AuthUserAddResponse AuthUserDeleteResponse pb.AuthUserDeleteResponse AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse + AuthRoleAddResponse pb.AuthRoleAddResponse ) type Auth interface { @@ -39,6 +40,9 @@ type Auth interface { // UserChangePassword changes a password of a user. UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error) + + // RoleAdd adds a new user to an etcd cluster. + RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) } type auth struct { @@ -76,3 +80,8 @@ func (auth *auth) UserChangePassword(ctx context.Context, name string, password resp, err := auth.remote.UserChangePassword(ctx, &pb.AuthUserChangePasswordRequest{Name: name, Password: password}) return (*AuthUserChangePasswordResponse)(resp), err } + +func (auth *auth) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) { + resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name}) + return (*AuthRoleAddResponse)(resp), err +} diff --git a/etcdctl/ctlv3/command/role_command.go b/etcdctl/ctlv3/command/role_command.go new file mode 100644 index 000000000..6055b3593 --- /dev/null +++ b/etcdctl/ctlv3/command/role_command.go @@ -0,0 +1,56 @@ +// Copyright 2016 Nippon Telegraph and Telephone Corporation. +// +// 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 command + +import ( + "fmt" + + "github.com/spf13/cobra" + "golang.org/x/net/context" +) + +// NewRoleCommand returns the cobra command for "role". +func NewRoleCommand() *cobra.Command { + ac := &cobra.Command{ + Use: "role ", + Short: "role related command", + } + + ac.AddCommand(newRoleAddCommand()) + + return ac +} + +func newRoleAddCommand() *cobra.Command { + return &cobra.Command{ + Use: "add ", + Short: "add a new role", + Run: roleAddCommandFunc, + } +} + +// roleAddCommandFunc executes the "role add" command. +func roleAddCommandFunc(cmd *cobra.Command, args []string) { + if len(args) != 1 { + ExitWithError(ExitBadArgs, fmt.Errorf("role add command requires role name as its argument.")) + } + + _, err := mustClientFromCmd(cmd).Auth.RoleAdd(context.TODO(), args[0]) + if err != nil { + ExitWithError(ExitError, err) + } + + fmt.Printf("Role %s created\n", args[0]) +} diff --git a/etcdctl/ctlv3/ctl.go b/etcdctl/ctlv3/ctl.go index d3e5894f7..015ca22b8 100644 --- a/etcdctl/ctlv3/ctl.go +++ b/etcdctl/ctlv3/ctl.go @@ -80,6 +80,7 @@ func init() { command.NewAuthCommand(), command.NewElectCommand(), command.NewUserCommand(), + command.NewRoleCommand(), ) } diff --git a/etcdserver/api/v3rpc/auth.go b/etcdserver/api/v3rpc/auth.go index fa3bdf964..b07112f3f 100644 --- a/etcdserver/api/v3rpc/auth.go +++ b/etcdserver/api/v3rpc/auth.go @@ -47,8 +47,11 @@ func (as *AuthServer) Authenticate(ctx context.Context, r *pb.AuthenticateReques } func (as *AuthServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) { - plog.Info("not implemented yet") - return nil, nil + resp, err := as.authenticator.RoleAdd(ctx, r) + if err != nil { + return nil, togRPCError(err) + } + return resp, nil } func (as *AuthServer) RoleDelete(ctx context.Context, r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error) { diff --git a/etcdserver/api/v3rpc/rpctypes/error.go b/etcdserver/api/v3rpc/rpctypes/error.go index 2a2f51a11..5e174b4b3 100644 --- a/etcdserver/api/v3rpc/rpctypes/error.go +++ b/etcdserver/api/v3rpc/rpctypes/error.go @@ -39,4 +39,5 @@ var ( ErrUserAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name already exists") ErrUserNotFound = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name not found") + ErrRoleAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name already exists") ) diff --git a/etcdserver/api/v3rpc/util.go b/etcdserver/api/v3rpc/util.go index f882abe00..982627a00 100644 --- a/etcdserver/api/v3rpc/util.go +++ b/etcdserver/api/v3rpc/util.go @@ -41,6 +41,8 @@ func togRPCError(err error) error { return rpctypes.ErrUserAlreadyExist case auth.ErrUserNotFound: return rpctypes.ErrUserNotFound + case auth.ErrRoleAlreadyExist: + return rpctypes.ErrRoleAlreadyExist default: return grpc.Errorf(codes.Internal, err.Error()) } diff --git a/etcdserver/apply.go b/etcdserver/apply.go index 02d2586e2..9a38454db 100644 --- a/etcdserver/apply.go +++ b/etcdserver/apply.go @@ -57,6 +57,7 @@ type applierV3 interface { UserAdd(ua *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) UserChangePassword(ua *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) + RoleAdd(ua *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) } type applierV3backend struct { @@ -90,6 +91,8 @@ func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) *applyResult { ar.resp, ar.err = s.applyV3.UserDelete(r.AuthUserDelete) case r.AuthUserChangePassword != nil: ar.resp, ar.err = s.applyV3.UserChangePassword(r.AuthUserChangePassword) + case r.AuthRoleAdd != nil: + ar.resp, ar.err = s.applyV3.RoleAdd(r.AuthRoleAdd) default: panic("not implemented") } @@ -489,6 +492,10 @@ func (a *applierV3backend) UserChangePassword(r *pb.AuthUserChangePasswordReques return a.s.AuthStore().UserChangePassword(r) } +func (a *applierV3backend) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) { + return a.s.AuthStore().RoleAdd(r) +} + type quotaApplierV3 struct { applierV3 q Quota diff --git a/etcdserver/etcdserverpb/raft_internal.pb.go b/etcdserver/etcdserverpb/raft_internal.pb.go index 1368692b6..7497bcd07 100644 --- a/etcdserver/etcdserverpb/raft_internal.pb.go +++ b/etcdserver/etcdserverpb/raft_internal.pb.go @@ -35,7 +35,8 @@ type InternalRaftRequest struct { AuthUserAdd *AuthUserAddRequest `protobuf:"bytes,11,opt,name=auth_user_add" json:"auth_user_add,omitempty"` AuthUserDelete *AuthUserDeleteRequest `protobuf:"bytes,12,opt,name=auth_user_delete" json:"auth_user_delete,omitempty"` AuthUserChangePassword *AuthUserChangePasswordRequest `protobuf:"bytes,13,opt,name=auth_user_change_password" json:"auth_user_change_password,omitempty"` - Alarm *AlarmRequest `protobuf:"bytes,14,opt,name=alarm" json:"alarm,omitempty"` + AuthRoleAdd *AuthRoleAddRequest `protobuf:"bytes,14,opt,name=auth_role_add" json:"auth_role_add,omitempty"` + Alarm *AlarmRequest `protobuf:"bytes,15,opt,name=alarm" json:"alarm,omitempty"` } func (m *InternalRaftRequest) Reset() { *m = InternalRaftRequest{} } @@ -193,16 +194,26 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) { } i += n12 } - if m.Alarm != nil { + if m.AuthRoleAdd != nil { data[i] = 0x72 i++ - i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size())) - n13, err := m.Alarm.MarshalTo(data[i:]) + i = encodeVarintRaftInternal(data, i, uint64(m.AuthRoleAdd.Size())) + n13, err := m.AuthRoleAdd.MarshalTo(data[i:]) if err != nil { return 0, err } i += n13 } + if m.Alarm != nil { + data[i] = 0x7a + i++ + i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size())) + n14, err := m.Alarm.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n14 + } return i, nil } @@ -305,6 +316,10 @@ func (m *InternalRaftRequest) Size() (n int) { l = m.AuthUserChangePassword.Size() n += 1 + l + sovRaftInternal(uint64(l)) } + if m.AuthRoleAdd != nil { + l = m.AuthRoleAdd.Size() + n += 1 + l + sovRaftInternal(uint64(l)) + } if m.Alarm != nil { l = m.Alarm.Size() n += 1 + l + sovRaftInternal(uint64(l)) @@ -776,6 +791,39 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error { } iNdEx = postIndex case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AuthRoleAdd", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRaftInternal + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRaftInternal + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AuthRoleAdd == nil { + m.AuthRoleAdd = &AuthRoleAddRequest{} + } + if err := m.AuthRoleAdd.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 15: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Alarm", wireType) } diff --git a/etcdserver/etcdserverpb/raft_internal.proto b/etcdserver/etcdserverpb/raft_internal.proto index 097791b84..85fec87dd 100644 --- a/etcdserver/etcdserverpb/raft_internal.proto +++ b/etcdserver/etcdserverpb/raft_internal.proto @@ -29,8 +29,9 @@ message InternalRaftRequest { AuthUserAddRequest auth_user_add = 11; AuthUserDeleteRequest auth_user_delete = 12; AuthUserChangePasswordRequest auth_user_change_password = 13; + AuthRoleAddRequest auth_role_add = 14; - AlarmRequest alarm = 14; + AlarmRequest alarm = 15; } message EmptyResponse { diff --git a/etcdserver/etcdserverpb/rpc.pb.go b/etcdserver/etcdserverpb/rpc.pb.go index eb3b725ed..3b72f5d69 100644 --- a/etcdserver/etcdserverpb/rpc.pb.go +++ b/etcdserver/etcdserverpb/rpc.pb.go @@ -1341,6 +1341,7 @@ func (m *AuthUserRevokeRequest) String() string { return proto.CompactTextString func (*AuthUserRevokeRequest) ProtoMessage() {} type AuthRoleAddRequest struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } func (m *AuthRoleAddRequest) Reset() { *m = AuthRoleAddRequest{} } @@ -4664,6 +4665,12 @@ func (m *AuthRoleAddRequest) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if len(m.Name) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRpc(data, i, uint64(len(m.Name))) + i += copy(data[i:], m.Name) + } return i, nil } @@ -5934,6 +5941,10 @@ func (m *AuthUserRevokeRequest) Size() (n int) { func (m *AuthRoleAddRequest) Size() (n int) { var l int _ = l + l = len(m.Name) + if l > 0 { + n += 1 + l + sovRpc(uint64(l)) + } return n } @@ -11314,6 +11325,35 @@ func (m *AuthRoleAddRequest) Unmarshal(data []byte) error { return fmt.Errorf("proto: AuthRoleAddRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRpc + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(data[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRpc(data[iNdEx:]) diff --git a/etcdserver/etcdserverpb/rpc.proto b/etcdserver/etcdserverpb/rpc.proto index 5ac8f5827..b31bf51a8 100644 --- a/etcdserver/etcdserverpb/rpc.proto +++ b/etcdserver/etcdserverpb/rpc.proto @@ -515,6 +515,7 @@ message AuthUserRevokeRequest { } message AuthRoleAddRequest { + string name = 1; } message AuthRoleGetRequest { diff --git a/etcdserver/v3demo_server.go b/etcdserver/v3demo_server.go index eea9f52c6..d9ecd59ec 100644 --- a/etcdserver/v3demo_server.go +++ b/etcdserver/v3demo_server.go @@ -56,6 +56,7 @@ type Authenticator interface { UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) + RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) } func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) { @@ -233,6 +234,14 @@ func (s *EtcdServer) UserChangePassword(ctx context.Context, r *pb.AuthUserChang return result.resp.(*pb.AuthUserChangePasswordResponse), result.err } +func (s *EtcdServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) { + result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleAdd: r}) + if err != nil { + return nil, err + } + return result.resp.(*pb.AuthRoleAddResponse), result.err +} + func (s *EtcdServer) processInternalRaftRequest(ctx context.Context, r pb.InternalRaftRequest) (*applyResult, error) { r.ID = s.reqIDGen.Next()