diff --git a/auth/range_perm_cache.go b/auth/range_perm_cache.go index 8f4d9f5db68..a6989aaa2ee 100644 --- a/auth/range_perm_cache.go +++ b/auth/range_perm_cache.go @@ -38,11 +38,18 @@ func getMergedPerms(tx backend.BatchTx, userName string) *unifiedRangePermission for _, perm := range role.KeyPermission { var ivl adt.Interval + var rangeEnd string + + if len(perm.RangeEnd) == 1 && perm.RangeEnd[0] == 0 { + rangeEnd = "" + } else { + rangeEnd = string(perm.RangeEnd) + } if len(perm.RangeEnd) != 0 { - ivl = adt.NewStringInterval(string(perm.Key), string(perm.RangeEnd)) + ivl = adt.NewStringAffineInterval(string(perm.Key), string(rangeEnd)) } else { - ivl = adt.NewStringPoint(string(perm.Key)) + ivl = adt.NewStringAffinePoint(string(perm.Key)) } switch perm.PermType { @@ -66,7 +73,11 @@ func getMergedPerms(tx backend.BatchTx, userName string) *unifiedRangePermission } func checkKeyInterval(cachedPerms *unifiedRangePermissions, key, rangeEnd string, permtyp authpb.Permission_Type) bool { - ivl := adt.NewStringInterval(key, rangeEnd) + if len(rangeEnd) == 1 && rangeEnd[0] == '\x00' { + rangeEnd = "" + } + + ivl := adt.NewStringAffineInterval(key, rangeEnd) switch permtyp { case authpb.READ: return cachedPerms.readPerms.Contains(ivl) @@ -79,7 +90,7 @@ func checkKeyInterval(cachedPerms *unifiedRangePermissions, key, rangeEnd string } func checkKeyPoint(cachedPerms *unifiedRangePermissions, key string, permtyp authpb.Permission_Type) bool { - pt := adt.NewStringPoint(key) + pt := adt.NewStringAffinePoint(key) switch permtyp { case authpb.READ: return cachedPerms.readPerms.Intersects(pt) diff --git a/auth/range_perm_cache_test.go b/auth/range_perm_cache_test.go index 0665e5d7971..8629e73a48a 100644 --- a/auth/range_perm_cache_test.go +++ b/auth/range_perm_cache_test.go @@ -29,17 +29,17 @@ func TestRangePermission(t *testing.T) { want bool }{ { - []adt.Interval{adt.NewStringInterval("a", "c"), adt.NewStringInterval("x", "z")}, + []adt.Interval{adt.NewStringAffineInterval("a", "c"), adt.NewStringAffineInterval("x", "z")}, "a", "z", false, }, { - []adt.Interval{adt.NewStringInterval("a", "f"), adt.NewStringInterval("c", "d"), adt.NewStringInterval("f", "z")}, + []adt.Interval{adt.NewStringAffineInterval("a", "f"), adt.NewStringAffineInterval("c", "d"), adt.NewStringAffineInterval("f", "z")}, "a", "z", true, }, { - []adt.Interval{adt.NewStringInterval("a", "d"), adt.NewStringInterval("a", "b"), adt.NewStringInterval("c", "f")}, + []adt.Interval{adt.NewStringAffineInterval("a", "d"), adt.NewStringAffineInterval("a", "b"), adt.NewStringAffineInterval("c", "f")}, "a", "f", true, }, diff --git a/e2e/ctl_v3_auth_test.go b/e2e/ctl_v3_auth_test.go index 61e982144f9..27207d8ac53 100644 --- a/e2e/ctl_v3_auth_test.go +++ b/e2e/ctl_v3_auth_test.go @@ -37,6 +37,7 @@ func TestCtlV3AuthMemberUpdate(t *testing.T) { testCtl(t, authTestMemberUpda func TestCtlV3AuthCertCN(t *testing.T) { testCtl(t, authTestCertCN, withCfg(configClientTLSCertAuth)) } func TestCtlV3AuthRevokeWithDelete(t *testing.T) { testCtl(t, authTestRevokeWithDelete) } func TestCtlV3AuthInvalidMgmt(t *testing.T) { testCtl(t, authTestInvalidMgmt) } +func TestCtlV3AuthFromKeyPerm(t *testing.T) { testCtl(t, authTestFromKeyPerm) } func authEnableTest(cx ctlCtx) { if err := authEnable(cx); err != nil { @@ -199,7 +200,7 @@ func authRoleUpdateTest(cx ctlCtx) { // revoke the newly granted key cx.user, cx.pass = "root", "root" - if err := ctlV3RoleRevokePermission(cx, "test-role", "hoo", ""); err != nil { + if err := ctlV3RoleRevokePermission(cx, "test-role", "hoo", "", false); err != nil { cx.t.Fatal(err) } @@ -613,3 +614,54 @@ func authTestInvalidMgmt(cx ctlCtx) { cx.t.Fatal("revoking the role root from the user root must not be allowed") } } + +func authTestFromKeyPerm(cx ctlCtx) { + if err := authEnable(cx); err != nil { + cx.t.Fatal(err) + } + + cx.user, cx.pass = "root", "root" + authSetupTestUser(cx) + + // grant keys after z to test-user + cx.user, cx.pass = "root", "root" + if err := ctlV3RoleGrantPermission(cx, "test-role", grantingPerm{true, true, "z", "\x00", false}); err != nil { + cx.t.Fatal(err) + } + + // try the granted open ended permission + cx.user, cx.pass = "test-user", "pass" + for i := 0; i < 10; i++ { + key := fmt.Sprintf("z%d", i) + if err := ctlV3Put(cx, key, "val", ""); err != nil { + cx.t.Fatal(err) + } + } + largeKey := "" + for i := 0; i < 10; i++ { + largeKey += "\xff" + if err := ctlV3Put(cx, largeKey, "val", ""); err != nil { + cx.t.Fatal(err) + } + } + + // try a non granted key + if err := ctlV3PutFailPerm(cx, "x", "baz"); err != nil { + cx.t.Fatal(err) + } + + // revoke the open ended permission + cx.user, cx.pass = "root", "root" + if err := ctlV3RoleRevokePermission(cx, "test-role", "z", "", true); err != nil { + cx.t.Fatal(err) + } + + // try the revoked open ended permission + cx.user, cx.pass = "test-user", "pass" + for i := 0; i < 10; i++ { + key := fmt.Sprintf("z%d", i) + if err := ctlV3PutFailPerm(cx, key, "val"); err != nil { + cx.t.Fatal(err) + } + } +} diff --git a/e2e/ctl_v3_role_test.go b/e2e/ctl_v3_role_test.go index 5ef43b2a502..340446a10a9 100644 --- a/e2e/ctl_v3_role_test.go +++ b/e2e/ctl_v3_role_test.go @@ -103,7 +103,10 @@ func ctlV3RoleGrantPermission(cx ctlCtx, rolename string, perm grantingPerm) err cmdArgs := append(cx.PrefixArgs(), "role", "grant-permission") if perm.prefix { cmdArgs = append(cmdArgs, "--prefix") + } else if len(perm.rangeEnd) == 1 && perm.rangeEnd[0] == '\x00' { + cmdArgs = append(cmdArgs, "--from-key") } + cmdArgs = append(cmdArgs, rolename) cmdArgs = append(cmdArgs, grantingPermToArgs(perm)...) @@ -117,12 +120,19 @@ func ctlV3RoleGrantPermission(cx ctlCtx, rolename string, perm grantingPerm) err return err } -func ctlV3RoleRevokePermission(cx ctlCtx, rolename string, key, rangeEnd string) error { +func ctlV3RoleRevokePermission(cx ctlCtx, rolename string, key, rangeEnd string, fromKey bool) error { cmdArgs := append(cx.PrefixArgs(), "role", "revoke-permission") cmdArgs = append(cmdArgs, rolename) cmdArgs = append(cmdArgs, key) + expStr := "" if len(rangeEnd) != 0 { cmdArgs = append(cmdArgs, rangeEnd) + expStr = fmt.Sprintf("Permission of range [%s, %s) is revoked from role %s", key, rangeEnd, rolename) + } else if fromKey { + cmdArgs = append(cmdArgs, "--from-key") + expStr = fmt.Sprintf("Permission of range [%s, is revoked from role %s", key, rolename) + } else { + expStr = fmt.Sprintf("Permission of key %s is revoked from role %s", key, rolename) } proc, err := spawnCmd(cmdArgs) @@ -130,7 +140,6 @@ func ctlV3RoleRevokePermission(cx ctlCtx, rolename string, key, rangeEnd string) return err } - expStr := fmt.Sprintf("Permission of key %s is revoked from role %s", key, rolename) _, err = proc.Expect(expStr) return err } @@ -161,5 +170,10 @@ func grantingPermToArgs(perm grantingPerm) []string { if len(perm.rangeEnd) == 0 { return []string{permstr, perm.key} } + + if len(perm.rangeEnd) == 1 && perm.rangeEnd[0] == '\x00' { + return []string{permstr, perm.key} + } + return []string{permstr, perm.key, perm.rangeEnd} } diff --git a/etcdctl/ctlv3/command/printer_simple.go b/etcdctl/ctlv3/command/printer_simple.go index 2d0bf1a3e69..96e1032e6e1 100644 --- a/etcdctl/ctlv3/command/printer_simple.go +++ b/etcdctl/ctlv3/command/printer_simple.go @@ -141,7 +141,11 @@ func (s *simplePrinter) RoleGet(role string, r v3.AuthRoleGetResponse) { printRange := func(perm *v3.Permission) { sKey := string(perm.Key) sRangeEnd := string(perm.RangeEnd) - fmt.Printf("\t[%s, %s)", sKey, sRangeEnd) + if strings.Compare(sRangeEnd, "\x00") != 0 { + fmt.Printf("\t[%s, %s)", sKey, sRangeEnd) + } else { + fmt.Printf("\t[%s, ", sKey) + } if strings.Compare(v3.GetPrefixRangeEnd(sKey), sRangeEnd) == 0 { fmt.Printf(" (prefix %s)", sKey) } @@ -188,7 +192,11 @@ func (s *simplePrinter) RoleRevokePermission(role string, key string, end string fmt.Printf("Permission of key %s is revoked from role %s\n", key, role) return } - fmt.Printf("Permission of range [%s, %s) is revoked from role %s\n", key, end, role) + if strings.Compare(end, "\x00") != 0 { + fmt.Printf("Permission of range [%s, %s) is revoked from role %s\n", key, end, role) + } else { + fmt.Printf("Permission of range [%s, is revoked from role %s\n", key, role) + } } func (s *simplePrinter) UserAdd(name string, r v3.AuthUserAddResponse) { diff --git a/etcdctl/ctlv3/command/role_command.go b/etcdctl/ctlv3/command/role_command.go index 28d12f24596..f75a8efceb4 100644 --- a/etcdctl/ctlv3/command/role_command.go +++ b/etcdctl/ctlv3/command/role_command.go @@ -24,6 +24,7 @@ import ( var ( grantPermissionPrefix bool + permFromKey bool ) // NewRoleCommand returns the cobra command for "role". @@ -83,16 +84,21 @@ func newRoleGrantPermissionCommand() *cobra.Command { } cmd.Flags().BoolVar(&grantPermissionPrefix, "prefix", false, "grant a prefix permission") + cmd.Flags().BoolVar(&permFromKey, "from-key", false, "grant a permission of keys that are greater than or equal to the given key using byte compare") return cmd } func newRoleRevokePermissionCommand() *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "revoke-permission [endkey]", Short: "Revokes a key from a role", Run: roleRevokePermissionCommandFunc, } + + cmd.Flags().BoolVar(&permFromKey, "from-key", false, "grant a permission of keys that are greater than or equal to the given key using byte compare") + + return cmd } // roleAddCommandFunc executes the "role add" command. @@ -168,9 +174,20 @@ func roleGrantPermissionCommandFunc(cmd *cobra.Command, args []string) { if grantPermissionPrefix { ExitWithError(ExitBadArgs, fmt.Errorf("don't pass both of --prefix option and range end to grant permission command")) } + + if permFromKey { + ExitWithError(ExitBadArgs, fmt.Errorf("don't pass both of --from-key option and range end to grant permission command")) + } + rangeEnd = args[3] } else if grantPermissionPrefix { + if permFromKey { + ExitWithError(ExitBadArgs, fmt.Errorf("don't pass both of --from-key option and --prefix option to grant permission command")) + } + rangeEnd = clientv3.GetPrefixRangeEnd(args[2]) + } else if permFromKey { + rangeEnd = "\x00" } resp, err := mustClientFromCmd(cmd).Auth.RoleGrantPermission(context.TODO(), args[0], args[2], rangeEnd, perm) @@ -190,6 +207,8 @@ func roleRevokePermissionCommandFunc(cmd *cobra.Command, args []string) { rangeEnd := "" if 3 <= len(args) { rangeEnd = args[2] + } else if permFromKey { + rangeEnd = "\x00" } resp, err := mustClientFromCmd(cmd).Auth.RoleRevokePermission(context.TODO(), args[0], args[1], rangeEnd)