Roles Review

A role is a bundle of GRANTed privileges which can be assigned to users or which can take the place of users. When there are hundreds or thousands of users, administration becomes a horrible burden if the DBMS doesn't support roles. Our online ANSI-standard book has syntax descriptions. Other DBMSs do support roles, and MySQL has had a worklog task "WL#988 Roles" for many years. Earlier attempts to implement them included a Google Summer of Code project and a MySQL tool released by Google.

Now another google-summer-coder, Vicențiu Ciorbaru, has put together something which will be in MariaDB. MariaDB's official blog says this is "a big thing". I'll compare the MySQL specification to the MariaDB feature.

Unfortunately Oracle made WL#988 Roles a hidden task about a year and a half ago -- you can't find it by looking on dev.mysql.com/worklog. Fortunately the Wayback Machine had crawled the page in 2012 -- you can find it by looking on http://web.archive.org/web/20120508072213/http://forge.mysql.com/worklog/task.php?id=988. Remember that worklog pages are tabbed, and you must click the "High Level Architecture" button to see most of the specification.

I'm using MariaDB 10.0.8 built from source. This is an early version and changes will happen before General Availability happens. Although MariaDB has its own worklog task which I'll refer to as MDEV-4397, I prefer WL#988 for obvious reasons. For this review I'll use the same order and same section names as WL#988 high-level architecture. It will be hard to follow unless you read WL#988 first.

CREATE ROLE

"CREATE ROLE role_1;" works. Good.

"CREATE ROLE IF NOT EXISTS role_2;" does not work. I'm ambivalent about this. I don't like the IF NOT EXISTS clause myself, but I thought it was the way of the future, according to another MySQL Worklog task, WL#3129 Consistent clauses in CREATE and DROP.

Here's the first bug: I get a crash if I use a bad name:

MariaDB [test]> CREATE ROLE ``;
ERROR 2013 (HY000): Lost connection to MySQL server during query

Notes about role names

MariaDB allows role names to be wildcards or privilege names, for example

MariaDB [test]> CREATE ROLE super;
Query OK, 0 rows affected (0.00 sec)
MariaDB [d]> create role '%';
Query OK, 0 rows affected (0.00 sec)

I regard that as a flaw. Somewhere, ages hence, somebody will get confused because these names have special meanings.

DROP ROLE

"DROP ROLE role_1;" works. Good.

"DROP ROLE IF EXISTS role_1;" does not work. Once again I'm ambivalent. WL#988 specifies that a NOT EXISTS clause should be permissible. On the other hand, "DROP USER IF EXISTS u;" doesn't work either.

DROP ROLE does not take effect immediately. If a user has enabled role_x, then that user continues to have the privileges of role_x even after role_x is dropped. I regard that as a flaw. [UPDATE: it's normal, see the comments.]

Here's the second bug: DROP USER crashes if I specify a role name:

MariaDB [test]> CREATE ROLE role_55;
Query OK, 0 rows affected (0.00 sec)

MariaDB [test]> DROP USER role_55;
ERROR 2013 (HY000): Lost connection to MySQL server during query

GRANT privilege TO role_name

"GRANT SELECT ON t TO role_1;" works. Good.

"GRANT PROXY ON a TO role_1;" works too. I'm ambivalent yet again. As WL#988 says, it's meaningless to grant a privilege to a role if it only can affect a user. There will always be an argument between people who think this must mean the user is making a mistake, and people who think it doesn't do any harm.

The privilege change does not take effect immediately. Consider what WL#988 says:

"The privilege change takes effect for the next statement that any affected user executes. (A user is affected if he/she has an enabled role that is affected by the privilege change.)

That seems reasonable -- after all, if I say "GRANT ... TO user_x" then user_x gets the privilege immediately. But it doesn't happen for roles. If I say "GRANT ... TO role_x", then role_x gets the privilege -- but users who have enabled role_x do not. I regard this as a flaw. [UPDATE: my test was bad, I was not granting the same type of privilege to both the role and the user, see the comments.]

Here's the third bug: if a user and a role have the same name, GRANT only works on the role. If I say

CREATE USER u_1;
CREATE ROLE u_1;
GRANT INSERT ON *.* TO u_1;
SELECT host,user,insert_priv,is_role FROM mysql.user WHERE mysql.user.user='u_1';

I'd expect that INSERT would be granted to both the user and the role. It's not, it's granted only to the role. Moral: users and roles should not have the same names!

GRANT role_name TO user_name

"GRANT role_1 TO user_1;" works. Good. "GRANT role_1 ON *.* TO user_1;" would be good too but an [ON *.*] clause should never be necessary.

"GRANT role_1 TO user_1 WITH GRANT OPTION;" does not work. I'm ambivalent yet again. What we actually have is an ability to say "GRANT role_1 TO user_1 WITH ADMIN OPTION;" and that's okay (WITH ADMIN OPTION is a standard clause), but I'm not sure it's a good idea that it's the default.

"GRANT role_1, role_2 to user_1;" does not work. I regard this as a flaw, but it might have been tough to implement. (UPDATE: 2016-03-20: It is indeed tough to implement. Two months after this blog comment was made, since the manual said that it would work, this flaw was reported as a bug: MDEV-5772 Granting multiple roles in single statement does not work. But two years later it was still unresolved.)

Who has GRANT role_name privileges?

As WL#988 puts it:

Some People will say that, if Peter said CREATE ROLE Role1, Peter should automatically have GRANT Role1 privilege. The analogy is with the way that we grant for routines. This is what would happen with Oracle.
Other People will say that, no, the only way for Peter to get GRANT Role1 privileges is if somebody grants to Peter. The analogy is with the way that we grant for tables.

The implementer decided to follow the advice of Some People for this one. Good.

GRANT role_name TO role_name

"GRANT role_1 TO role_2;" works. Good.

"GRANT role_1 TO role_1;" does not work -- and that's good too. This is a special instance of what WL#988 calls a "cyclical GRANT". Nobody would want that.

GRANT CREATE / ALTER / DROP ROLE

"GRANT CREATE ROLE ..." does not work. I regard that as a flaw.

WL#988 has quite a bit of verbiage about how there has to be a CREATE ROLE privilege, and why. MariaDB sidesteps -- the decision was that all you need is a CREATE USER privilege. Although I cannot think of a way that this could lead to security breaches, I am fairly sure that cases exist where administrators who want to allow user-creation do not want to allow role-creation, and vice versa.

REVOKE

"REVOKE role_1 FROM user_1;" works. Good.

SET ROLE

"SET ROLE role_1;" works. And it only works if somebody previously said "GRANT role_1 TO user_1;" for the user that's doing the SET ROLE. Good.

"SET role 'role_1';" works, that is, the role name can be a string literal. But "SET @x='role_1'; SET ROLE @x;" does not work. I regard that as a flaw because it's a lack-of-orthogonality thing. By that I mean: if a string literal is okay in a certain place in an SQL statement, then The User On The Clapham Omnibus will expect that a string variable would be okay in the same place.

"SET ROLE role_1,role_2;" does not work. That's because MariaDB, unlike Oracle, doesn't allow assignment of multiple roles. And that's what WL#988 asks for, so no complaints here.

SET ROLE DEFAULT does not work. I regard that as a flaw. Yes, I recognize there is no non-null default, but that just means that SET ROLE DEFAULT should have the same effect as SET ROLE NONE.

Big Example

The big example didn't work due to incompatible syntax, but I see that there is some evidence that testing has been done. Good.

CURRENT_ROLE

"SELECT CURRENT_ROLE;" works. Good. As expected, it's possible to find out what the last "SET ROLE" statement did.

"SELECT CURRENT_ROLE();" also works. I regard this as a flaw but I suppose it was inevitable. After all, "SELECT CURRENT_USER();" works.

CURRENT_ROLE is not on the current reserved words list and this will cause a minor incompatibility with MySQL.

ALTER ROLE

"ALTER ROLE role_1 ..." does not work. Good.

ALTER ROLE exists in Oracle for changing the IDENTIFIED BY clause, but MariaDB doesn't support the IDENTIFIED BY clause, so ALTER ROLE would have nothing to do.

RENAME ROLE

"RENAME ROLE role_1 TO role_2;" does not work. Good.

Although "RENAME USER user_1 TO user_2;" works, WL#988 reminds us that renaming a role is going to have cascading effects that wouldn't exist for renaming a user.

SHOW PRIVILEGES

"SHOW PRIVILEGES;" is the same as it used to be. Good.

Since MariaDB doesn't have a CREATE ROLE privilege, it doesn't have to list it.

SET DEFAULT ROLE

"SET DEFAULT ROLE role_1 TO user_1;" does not work. I regard this as a flaw.

It's a good thing if, when user_1 connects, user_1 automatically gets the privileges associated with a default role. It's less good if user_1 has to do her own SET ROLE whenever she connects, probably with mysql --init_command='SET ROLE role_1'. The point of roles was supposed to be the elimination of administrator hassle, and --init-command won't help there.

The Initial State

The initial state when a user connects is, effectively, SET ROLE NONE. That'll have to do for now.

SHOW

"SHOW GRANTS FOR user_1;" works and shows roles too. Good.

"SHOW GRANTS FOR CURRENT_ROLE;" also works. WL#988 failed to suggest this obvious addition, so maybe I should call it: better than good.

INFORMATION_SCHEMA

"SELECT * FROM INFORMATION_SCHEMA.ENABLED_ROLES;" works. Good.

"SELECT * FROM INFORMATION_SCHEMA.APPLICABLE_ROLES;" also works. Also good.

And it does show contained roles. That is, "If role1 is contained in role2, then both roles appear." However: It only shows the roles that have been granted for the current user, it does not show all roles. I regard that as a flaw.
[UPDATE: In an earlier version of this blog this observation was in the wrong place.] [UPDATE again: I missed what the cause was, the real problem was that I couldn't grant to public -- but that's another bug. See comments.]

Command line options

"--role=role_name" doesn't work (on the command line). Good.

The way to go for setting a role name at connect time is to support defaults. Any other "solution" would just get in the way.

For Replication

As WL#988 says, "The plan is to ignore replication till the last minute." I can't tell whether MariaDB is following such a plan.

Plan for Backup

Backup is simplified by the fact that users and roles come from the same table in the mysql database. I did not test it, so let's say: assumed good.

Stored Procedures and Definer Privileges

After "SET ROLE role_1; CREATE PROCEDURE p () SQL SECURITY DEFINER SELECT CURRENT_ROLE; CALL p();" the result is NULL. Good.

The idea is supposed to be that, within an SQL SECURITY DEFINER routine, an implicit SET ROLE NONE takes place temporarily. It's great that the implementer caught on to this rather obscure point.

Logging in with a Role Name

After "CREATE ROLE 'role_5@localhost';" or "CREATE ROLE role_5;" I was not able to log in as role_5. Good.

However, "CREATE DEFINER=role_5 PROCEDURE p2() SET @x=5;" works. I regard that as a flaw. There should at least be a warning that the DEFINER clause is being used with a nonexistent user. I get the impression from MDEV-4397 that this is deliberate behaviour, so don't expect a fix.

DML statements and role privileges

The Oracle-style restriction for CREATE VIEW isn't there. Good.

Other worklog tasks affected by roles

Well, WL#988 anticipated that roles would come first, and pluggable authentication support would come later. It didn't happen that way. No problem.

Glossary

It doesn't seem that the terminology differs from what's expected. Good.

New columns in mysql.user

When I say "CREATE ROLE role_1;" the effect is that a new row goes into the mysql.user table. The mysql.user table definition had to be adjusted to take that into account. But I don't think it's all good.

WL#988 expected that the length of a role name would be 16 characters, but mysql.user.user is now CHAR(80). Well, that was a flaw, but just an old bug that wasn't caused by introduction of roles.

The mysql.user.host column is blank (''). I'd expected it would be '%'. I suppose that this was intended as a way to disambiguate users and roles that had the same names, but that's not working well anyway. The column should be '%' because that means "any or all hosts".

There is one new column at the end, named is_role, which can be 'Y' or 'N'. I regard that as a flaw. The future-considerations way would have been to add a new column named user_type, which could be NULL or 'Role' -- or something else that somebody will dream up in a future release. The way it is now, if somebody dreams up another type later, they'll have to add yet another column in mysql.user.

Storing role names

The implementer picked "Alternative #2: (loosely based on a suggestion from Monty Widenius)". So there's a new table in the mysql database:
CREATE TABLE mysql.roles_mapping (Host CHAR(60), User CHAR(80), Role CHAR(80), Admin_option ENUM('N','Y'))
... And it works. Good. Although ROLE was a reserved word in SQL-99, and although WL#988 suggested an additional column and different names, it works.
However, storing the WITH ADMIN OPTION value should only be in one place and that place is mysql.user, so mysql.roles.mapping should not contain a column named Admin_option. I regard this as a flaw. [UPDATE: not it is not a flaw, see the comments.]

The Decisions about Options

The early decisions of Monty Widenius were: we don't want passwords, we do want defaults, we don't want multiple current roles, and PUBLIC should not be a role. That's what ended up happening, except that there are no defaults. Good.

Overall

For all of the bugs, and for some of the flaws, there's no worry -- they'll probably disappear. The MariaDB advantage here is the release-early policy, because there's lots of advance time if there's a decision to make a few corrections or course changes.

The MariaDB disadvantage is illustrated by its worklog task for roles, MDEV-4397. I'm biased, but I think it's fair to say that MySQL's WL#988 "high level architecture" specification covered the necessary ground and MariaDB's MDEV-4397 did not. This means Oracle made the right decision when it "hid" its worklog task. Too bad it didn't work out this particular time.

, January 12, 2014. Category: MySQL / MariaDB.

About pgulutzan

Co-author of four computer books. Software Architect at MySQL/Sun/Oracle from 2003-2011, and at HP for a little while after that. Currently with Ocelot Computer Services Inc. in Edmonton Canada.

4 Comments

  1. Sergei Golubchik says:

    Thanks, Peter, for writing a review of the Roles feature in MariaDB!

    I have to admit, I hope you will write it, and I'm happy you did :)

    Thanks for discovering all these crashes, they will be fixed in the next MariaDB release, of course.

    Now, let me reply to other your observations:

    * "CREATE ROLE IF NOT EXISTS" doesn't work, because it doesn't work for users either. I felt it would be inconsistent to support "NOT EXISTS" for roles, but not for users. And doing WL#3129 in its entirety was certainly beyond the scope of the GSoC project.

    * Role names follow same rules as user names. One can have a user of the name 'super' or a user of the name '%' (just tried).

    * DROP ROLE and GRANT do not take effect immediately if the role is enabled - this is partially true. Global privileges aren't immediately changed, privileges on the current database aren't immediately changed. Privileges on other databases, on tables and columns are changed immediately. Exactly the same happens when you grant/revoke privileges to/from a user that is currently connected. This is unfortunate, but fixing how privileges are cached in the THD is a separate task, beyond the scope of the GSoC project.

    * if user a role have the same name - generally we say that a user with a wildcard host is written as 'name@%', while a role is always 'name' (no host part). For backward compatibility we try to resolve any ambiguous 'name' first as a role name and, if that fails, as a user name. So, role names take precedence over user names. One is advised to use unambigous syntax for users, always with a host part, even if only with @%.

    * WITH GRANT OPTION for roles is not standard, we implemented the standard syntax instead, I though you'd be pleased :)

    * Default roles are not standard and they were not part of the GSoC project, we planned to do them in a later release. This is still the case.

    * You didn't comment on the fact that "SHOW GRANTS" is now different from "SHOW GRANTS FOR CURRENT_USER". Did you notice that? Is it good or a flaw in your opinion?

    * What is the flaw in I_S.ENABLED_ROLES? What do you mean "it does not show all roles"? What should it show?

    * Replication - it is supposed to work, see the test in the mysql-test roles suite.

    * "However, storing the WITH ADMIN OPTION value should only be in one place and that place is mysql.user, so mysql.roles.mapping should not contain a column named Admin_option." - not quite. We have implemented "ADMIN OPTION" the standard way. It's an *option* not a privilege, like "GRANT OPTION". It doesn't allow you to grant any role to anyone, but only roles that were granted to you (or any of the enabled roles) with the ADMIN OPTION. That is, "ADMIN OPTION" is a property of the individual {role, grantee} pair, not a global privilege. I thought you'd be pleased :)

    And thanks again for your thourough review of this feature!

    • pgulutzan says:

      Hello and thanks for commenting,

      Re CREATE ROLE IF NOT EXISTS: yes, I acknowledge that some of what I said would be way beyond what a Google-Summer-of-Code project could do.
      Re naming a role SUPER: sure you can name a user SUPER too, I was only worried that somebody would confuse GRANT SUPER with GRANT SUPER ON *.*
      Re DROP ROLE and GRANT again: you are correct, and I think maybe t granted a global privilege to the role but granted a table privilege to the user.
      Re users and roles with the same name: I think we'll disagree forever.
      Re WITH GRANT OPTION: sure I'm pleased, when am I ever displeased?
      Re default roles: a delay is understandable.
      Re SHOW GRANTS: I guess you mean that SHOW GRANTS shows grants for the user's enabled role, and you're right I hadn't noticed. I regard this as a good thing.
      Re INFORMATION_SCHEMA.ENABLED_ROLES: Sorry, I meant INFORMATION_SCHEMA.APPLICABLE_ROLES. I've moved the comment to the correct place.
      Re Replication: Oh, so you didn't follow the WL#988 specification "The plan is to ignore replication till the last minute." I guess that's acceptable.
      Re storing the ADMIN OPTION: not for exactly the first time, you're right I'm wrong.
      I have added statements marked "[UPDATE: ...]" to the blog post to take the above into account.

      Please pass on "Happy New Year" greetings from both Trudy Pelzer and me to all at MariaDB or SkySQL.

      • Sergei Golubchik says:

        Thanks.

        I still don't understand what do you mean by "INFORMATION_SCHEMA.APPLICABLE_ROLES does not show all roles".

        According to the definition of INFORMATION_SCHEMA.APPLICABLE_ROLES it should show roles granted to the current user or PUBLIC, roles granted to one of the enabled roles, or (recursively) roles granted to roles mentioned above. As far as I understand it means that it should show granted to user and contained roles, but not "all roles".

        • pgulutzan says:

          I had, as root, said "CREATE ROLE role_44;" and then "GRANT role_44 TO PUBLIC;" and then (as a different user) seen that role_44 was not in INFORMATION_SCHEMA.APPLICABLE_ROLES. But now I see that the GRANT must have failed without my noticing. But am I to blame for not looking? Nah, it's easier to blame MariaDB. Why does "GRANT role_44 TO PUBLIC;" fail? And -- more interesting because it looks like a behaviour change -- why does "GRANT SELECT ON *.* TO PUBLIC;" fail? Grants to ordinary users don't fail.