Skip to content

Commit c99cf0c

Browse files
committed
etcdserver: allow non-admin to fetch member list and alarms
In some environments, etcd members do not have stable hostnames or IP addresses. During maintenance, all etcd nodes may be replaced, resulting in new hostnames and IPs for every member. In that case, clients such as Patroni can lose access to the cluster entirely if they are not allowed to refresh the member list. Allow non-admin users to fetch the member list so they can rediscover updated member endpoints after such topology changes. Signed-off-by: Wei Fu <fuweid89@gmail.com> (cherry picked from commit a2987fd) Signed-off-by: Wei Fu <fuweid89@gmail.com>
1 parent 8a1830c commit c99cf0c

5 files changed

Lines changed: 40 additions & 28 deletions

File tree

server/etcdserver/api/v3rpc/auth.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,19 @@ func (aa *AuthAdmin) isPermitted(ctx context.Context) error {
185185

186186
return aa.ag.AuthStore().IsAdminPermitted(authInfo)
187187
}
188+
189+
func (aa *AuthAdmin) requireAuthInfo(ctx context.Context) error {
190+
if !aa.ag.AuthStore().IsAuthEnabled() {
191+
return nil
192+
}
193+
194+
authInfo, err := aa.ag.AuthInfoFromCtx(ctx)
195+
if err != nil {
196+
return err
197+
}
198+
199+
if authInfo == nil {
200+
return auth.ErrUserEmpty
201+
}
202+
return nil
203+
}

server/etcdserver/api/v3rpc/maintenance.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,15 @@ func (ams *authMaintenanceServer) HashKV(ctx context.Context, r *pb.HashKVReques
350350
}
351351

352352
func (ams *authMaintenanceServer) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error) {
353-
if err := ams.isPermitted(ctx); err != nil {
354-
return nil, togRPCError(err)
353+
switch ar.GetAction() {
354+
case pb.AlarmRequest_GET:
355+
if err := ams.requireAuthInfo(ctx); err != nil {
356+
return nil, togRPCError(err)
357+
}
358+
default:
359+
if err := ams.isPermitted(ctx); err != nil {
360+
return nil, togRPCError(err)
361+
}
355362
}
356363
return ams.maintenanceServer.Alarm(ctx, ar)
357364
}

server/etcdserver/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1677,7 +1677,7 @@ func (s *EtcdServer) MemberList(ctx context.Context, r *pb.MemberListRequest) ([
16771677
}
16781678
}
16791679

1680-
if err := s.checkMembershipOperationPermission(ctx); err != nil {
1680+
if err := s.requireAuthInfo(ctx); err != nil {
16811681
return nil, err
16821682
}
16831683
return s.cluster.Members(), nil

tests/common/auth_test.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -691,9 +691,14 @@ func TestAuthMemberList(t *testing.T) {
691691
require.NoErrorf(t, setupAuth(cc, []authRole{testRole}, []authUser{rootUser, testUser}), "failed to enable auth")
692692
rootAuthClient := testutils.MustClient(clus.Client(WithAuth(rootUserName, rootPassword)))
693693
testUserAuthClient := testutils.MustClient(clus.Client(WithAuth(testUserName, testPassword)))
694+
anonAuthClient := testutils.MustClient(clus.Client())
694695

695696
_, err := testUserAuthClient.MemberList(ctx, false)
696-
require.ErrorContains(t, err, PermissionDenied)
697+
require.NoError(t, err)
698+
699+
_, err = anonAuthClient.MemberList(ctx, false)
700+
require.Error(t, err)
701+
require.ErrorContains(t, err, "etcdserver: user name is empty")
697702

698703
_, err = rootAuthClient.MemberList(ctx, false)
699704
require.NoError(t, err)
@@ -994,6 +999,7 @@ func TestAuthAlarm(t *testing.T) {
994999
require.NoErrorf(t, setupAuth(cc, []authRole{testRole}, []authUser{rootUser, testUser}), "failed to enable auth")
9951000
rootAuthClient := testutils.MustClient(clus.Client(WithAuth(rootUserName, rootPassword)))
9961001
testUserAuthClient := testutils.MustClient(clus.Client(WithAuth(testUserName, testPassword)))
1002+
anonAuthClient := testutils.MustClient(clus.Client())
9971003

9981004
for i := 0; ; i++ {
9991005
err := rootAuthClient.Put(ctx,
@@ -1006,9 +1012,6 @@ func TestAuthAlarm(t *testing.T) {
10061012
break
10071013
}
10081014

1009-
_, err := testUserAuthClient.AlarmList(ctx)
1010-
require.ErrorContains(t, err, PermissionDenied)
1011-
10121015
memberID := uint64(0)
10131016

10141017
for i := 0; i < 10; i++ {
@@ -1023,6 +1026,12 @@ func TestAuthAlarm(t *testing.T) {
10231026
}
10241027
require.NotEqualf(t, uint64(0), memberID, "expect to find alarm with non-zero member ID")
10251028

1029+
_, err := anonAuthClient.AlarmList(ctx)
1030+
require.ErrorContains(t, err, "etcdserver: user name is empty")
1031+
1032+
_, err = testUserAuthClient.AlarmList(ctx)
1033+
require.NoError(t, err)
1034+
10261035
_, err = testUserAuthClient.AlarmDisarm(ctx, &clientv3.AlarmMember{
10271036
MemberID: memberID,
10281037
Alarm: pb.AlarmType_NOSPACE,

tests/e2e/ctl_v3_auth_test.go

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
package e2e
1616

1717
import (
18-
"context"
1918
"fmt"
2019
"os"
2120
"strings"
@@ -296,26 +295,7 @@ func authTestEndpointHealth(cx ctlCtx) {
296295
require.NoError(cx.t, ctlV3RoleGrantPermission(cx, "test-role", grantingPerm{true, true, "health", "", false}))
297296

298297
cx.user, cx.pass = "test-user", "pass"
299-
func(cx ctlCtx) {
300-
cmdArgs := append(cx.PrefixArgs(), "endpoint", "health")
301-
lines := make([]expect.ExpectedResponse, cx.epc.Cfg.ClusterSize)
302-
for i := range lines {
303-
lines[i] = expect.ExpectedResponse{
304-
Value: cx.epc.Procs[i].EndpointsGRPC()[0] + " is unhealthy: failed to commit proposal: Unable to fetch the alarm list",
305-
}
306-
}
307-
308-
proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap)
309-
require.NoErrorf(cx.t, err, "failed to spawn endpoint health command")
310-
defer func() {
311-
require.Errorf(cx.t, proc.Close(), "endpoint health command should reject all non-root users")
312-
}()
313-
314-
for _, line := range lines {
315-
_, lerr := proc.ExpectWithContext(context.TODO(), line)
316-
require.NoErrorf(cx.t, lerr, "endpoint health should fail with permission denied error")
317-
}
318-
}(cx)
298+
require.NoErrorf(cx.t, ctlV3EndpointHealth(cx), "endpointStatusTest ctlV3EndpointHealth error")
319299

320300
cmdArgs := append(cx.PrefixArgs(), "endpoint", "health", "--user=root:root", "--cluster")
321301
proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap)

0 commit comments

Comments
 (0)