From bd73a086ec385929cee96eed937b8cc354e51a1e Mon Sep 17 00:00:00 2001
From: Gleb Kashkin <g.kashkin@tarantool.org>
Date: Mon, 4 Sep 2023 17:07:23 +0000
Subject: [PATCH] config: revoke privs for default users and roles

All user-defined users and roles are not being removed and their
privileges are not being revoked when this user or role is removed
from config. This is done to prevent extreme repercussions of
misconfiguration, e.g. empty config is provided to cluster and it
breaks up.

Default users and roles are not supposed to be changed, so this rule
does not apply to them. Now all of non-default privileges will be
revoked if such user or role is removed from config.

Default users:
* guest
* admin

Default roles:
* super
* public
* replication

Part of #8967

NO_DOC=documentation request will be filed manually for the whole
       credentials
---
 ...creds-restore-defaults-for-default-user.md |  4 +
 src/box/lua/config/applier/credentials.lua    | 29 ++++++
 .../credentials_applier_test.lua              | 91 +++++++++++++++++++
 3 files changed, 124 insertions(+)
 create mode 100644 changelogs/unreleased/gh-8967-creds-restore-defaults-for-default-user.md

diff --git a/changelogs/unreleased/gh-8967-creds-restore-defaults-for-default-user.md b/changelogs/unreleased/gh-8967-creds-restore-defaults-for-default-user.md
new file mode 100644
index 0000000000..8ac282d66b
--- /dev/null
+++ b/changelogs/unreleased/gh-8967-creds-restore-defaults-for-default-user.md
@@ -0,0 +1,4 @@
+## feature/config
+
+* Non-default privileges are now revoked from default users and roles
+  when they are removed from the config (gh-8967).
diff --git a/src/box/lua/config/applier/credentials.lua b/src/box/lua/config/applier/credentials.lua
index e9ce2832aa..03171f40f9 100644
--- a/src/box/lua/config/applier/credentials.lua
+++ b/src/box/lua/config/applier/credentials.lua
@@ -493,6 +493,35 @@ local function apply(config)
         return
     end
 
+    -- Tarantool has the following roles and users present by default on every
+    -- instance:
+    --
+    -- Default roles:
+    -- * super
+    -- * public
+    -- * replication
+    --
+    -- Default users:
+    -- * guest
+    -- * admin
+    --
+    -- These roles and users have according privileges pre-granted by design.
+    -- Credentials applier adds such privileges with `priviliges_add_default()`
+    -- when syncing. So, for the excessive (non-default) privs to be removed,
+    -- these roles and users must be present inside configuration at least in a
+    -- form of an empty table. Otherwise, the privileges will be left unchanged,
+    -- similar to all used-defined roles and users.
+
+    credentials.roles = credentials.roles or {}
+    credentials.roles['super'] = credentials.roles['super'] or {}
+    credentials.roles['public'] = credentials.roles['public'] or {}
+    credentials.roles['replication'] = credentials.roles['replication'] or {}
+
+    credentials.users = credentials.users or {}
+    credentials.users['guest'] = credentials.users['guest'] or {}
+    credentials.users['admin'] = credentials.users['admin'] or {}
+
+    -- Create roles and users and synchronise privileges for them.
     create_roles(credentials.roles)
     create_users(credentials.users)
 end
diff --git a/test/config-luatest/credentials_applier_test.lua b/test/config-luatest/credentials_applier_test.lua
index be2de2be8d..4ba7f28552 100644
--- a/test/config-luatest/credentials_applier_test.lua
+++ b/test/config-luatest/credentials_applier_test.lua
@@ -636,3 +636,94 @@ g.test_remove_user_role = function(g)
         verify_2 = verify,
     })
 end
+
+g.test_restore_defaults_for_default_user = function(g)
+    -- Verify that if the default users and roles are not present in config
+    -- their excessive privileges are revoked (restored to built-in defaults).
+
+    helpers.reload_success_case(g, {
+        options = {
+            credentials = {
+                roles = {
+                    dummy = { },
+                    super = {
+                        roles = { 'dummy' },
+                    },
+                    public = {
+                        roles = { 'dummy' },
+                    },
+                    replication = {
+                        roles = { 'dummy' },
+                    },
+                },
+                users = {
+                    guest = {
+                        roles = { 'super', 'dummy' }
+                    },
+                    admin = {
+                        roles = { 'dummy' }
+                    },
+                }
+            }
+        },
+        verify = function()
+            local internal =
+                    require('internal.config.applier.credentials')._internal
+
+            local default_identities = {{
+                'user', 'admin',
+            }, {
+                'user', 'guest',
+            }, {
+                'role', 'super',
+            }, {
+                'role', 'public',
+            }, {
+                'role', 'replication',
+            },}
+
+            for _, id in ipairs(default_identities) do
+                local user_or_role, name = unpack(id)
+
+                local perm = box.schema[user_or_role].info(name)
+                perm = internal.privileges_from_box(perm)
+
+                t.assert_equals(perm['role']['dummy'], {execute = true})
+            end
+        end,
+        options_2 = {
+            credentials = {
+                users = {
+                    guest = {
+                        roles = { 'super' }
+                    }
+                }
+            }
+        },
+        verify_2 = function()
+            local internal =
+                    require('internal.config.applier.credentials')._internal
+
+            local default_identities = {{
+                'user', 'admin',
+            }, {
+                'user', 'guest',
+            }, {
+                'role', 'super',
+            }, {
+                'role', 'public',
+            }, {
+                'role', 'replication',
+            },}
+
+            for _, id in ipairs(default_identities) do
+                local user_or_role, name = unpack(id)
+
+                local perm = box.schema[user_or_role].info(name)
+                perm = internal.privileges_from_box(perm)
+
+                t.assert_not_equals(perm['role']['dummy'], {execute = true})
+            end
+        end,
+    })
+end
-- 
GitLab