iControl REST Fine-Grained Role Based Access Control
Introduction
F5's role based access control (RBAC) mechanism allows a BIG-IP administrator to assign appropriate access privileges to the users (see Manual Chapter: User Roles). For example, with the
operator
role, the user is specifically allowed to enable or disable nodes and pool members. The mechanism is generally the best way to manage users easily and consistently, however, finer access management may be required from time to time: e.g., allow only to view the stats of a predefined set of virtuals. Restricting access is especially important in iControl REST because users can remotely and directly access the system.
The existing roles and their access permissions are defined in
/mgmt/shared/authz/roles
and /mgmt/shared/authz/resource-groups
. You can add custom roles with custom permissions for particular users to these resources (iControl REST endpoints). The resources are described in BIG-IQ Systems REST API References, however, they are not exactly easy to follow. To fill the gap, this article describes a method to configure users and roles in a much finer manner.
Note
This method is only applicable to iControl REST: It does not apply to Configuration Utility or ssh.
The method relies on the resources for local users (i.e.,
/mgmt/tm/auth/user
and /mgmt/shared/authz/users
). It does not work for remote authentication mechanisms such as RADIUS or Active Directory.
Setup
In this example, a local user "foo" and a custom role "testRole" are created. The role allows users to
GET
(list) only the virtual server "vs". The access method and resource are defined in "testResourceGroup", that is referenced from the "testRole". No other operation such as PATCH
(modify) or DELETE is permitted. Also, no other resource such as another virtual is allowed. All the REST calls are done using curl: You may want to consider using other methods such as F5 Python SDK .
1. Create a new user
The following curl command creates the user "foo". The password is "foo", the description is "Foo Bar", and the role is "operator" for all the partitions.
curl -sku admin:<pass> https://<mgmtIP>/mgmt/tm/auth/user -X POST -H "Content-Type: application/json" \ -d '{"name":"foo", "password":"foo", "description":"Foo Bar", "partitionAccess":[ { "name":"all-partitions", "role":"operator"} ] }'
You get the following JSON object:
{ "description": "Foo Bar", "encryptedPassword": "$6$jwL4UUxv$IUrzWGEUsyJaXlOB2oyyTPflHFdvDBXb6f3f/No4KNfQb.V6bpQZBgvxl3KkBDXGtpttej79DDphEGRh8d4iA/", "fullPath": "foo", "generation": 1023, "kind": "tm:auth:user:userstate", "name": "foo", "partitionAccess": [ { "name": "all-partitions", "nameReference": { "link": "https://localhost/mgmt/tm/auth/partition/all-partitions?ver=13.1.1.2" }, "role": "operator" } ], "selfLink": "https://localhost/mgmt/tm/auth/user/satoshi?ver=13.1.1.2" }
The role is important. When the access privileges conflict between the role and the fine grained RBAC, the stricter authorization is chosen. For example, if the RBAC is configured to allow PATCH or POST but the user's role is guest (no alteration allowed), the user won't be able to perform these methods.
You can create a user from tmsh or Configuration Utility if that's your preferred method.
2. (13.1.0 and later) Remove the user from /mgmt/shared/authz/roles/iControl_REST_API_User
/mgmt/shared/authz/roles/iControl_REST_API_User
From v13.1.0, a newly created local user is automatically added to
iControl_REST_API_User
, which grants access to iControl REST without any setup. To avoid assigning multiple permissions, you need to remove the user reference from /mgmt/shared/authz/roles/iControl_REST_API_User
.
First, get the data from
/mgmt/shared/authz/roles/iControl_REST_API_User
and redirect it to a file.
curl -sku admin:<pass> https://<mgmtIP>/mgmt/shared/authz/roles/iControl_REST_API_User \ | python -m json.tool > file
Edit the file. The file contains a line for the user "foo" like this.
"name": "iControl_REST_API_User", "userReferences": [ { "link": "https://localhost/mgmt/shared/authz/users/foo" }, ....
Remove the line including the opening and ending curly brackets plus the comma. Save the file.
Overwrite the current data by putting the file to the endpoint.
curl -sku admin:<pass> https://<mgmtIP>/mgmt/shared/authz/roles/iControl_REST_API_User -X PUT -d@file
3. Create a custom resource-group
A resource-group consists of a set of resources and methods. In this example, the resource-group is named "testResourceGroup", which allows a role to perform
GET
request to the resource /mgmt/tm/ltm/virtual/vs
. "testResourceGroup" is later used in the custom role.
curl -sku admin:<pass> https://<mgmtIP>/mgmt/shared/authz/resource-groups -X POST -H "Content-Type: application/json" \ -d '{"name":"testResourceGroup", "resources":[ {"restMethod":"GET", "resourceMask":"/mgmt/tm/ltm/virtual/vs" } ]}'
You get the following JSON object.
{ "id": "fe7a1ebc-3e61-30aa-8a5d-c7721f7c6ce2", "name": "testResourceGroup", "resources": [ { "resourceMask": "/mgmt/tm/ltm/virtual/vs", "restMethod": "GET" } ], "generation": 1, "lastUpdateMicros": 1521682571723849, "kind": "shared:authz:resource-groups:roleresourcegroupstate", "selfLink": "https://localhost/mgmt/shared/authz/resource-groups/fe7a1ebc-3e61-30aa-8a5d-c7721f7c6ce2" }
Note that a resource-group entry is keyed by "id", not "name". The id is automatically generated.
As you can see, the resources field is a list of multiple objects, each containing the endpoint and the method. To add more permissions to the resource-group, add objects to the list.
4. Create a custom role
Create a role "testRole" with the user "foo" and the resource-groups "testResourceGroup". This makes the user to become a member of the role "testRole", hence allows the users to perform
GET
only to the /mgmt/tm/ltm/virtual/vs
.
curl -sku admin:<pass> https://<mgmtIP>/mgmt/shared/authz/roles -X POST -H "Content-Type: application/json" \ -d '{"name":"testRole", "userReferences":[ {"link":"https://localhost/mgmt/shared/authz/users/foo"} ], "resourceGroupReferences":[{"link":"https://localhost/mgmt/shared/authz/resource-groups/fe7a1ebc-3e61-30aa-8a5d-c7721f7c6ce2"}]}'
Please note that the reference to the user foo in the userReferences field is different from Step #1: The user created was
/mgmt/tm/auth/user/foo
while the reference is /mgmt/shared/authz/users/foo
. The /mgmt/shared/authz/users/foo is automatically created. J
ust use /mgmt/shared/authz/users/
+ user name
.
Again, please observe that the resource-groups reference is not by name but by ID. See the selfLink in the step #3.
You get the following JSON object.
{ "name": "testRole", "userReferences": [ { "link": "https://localhost/mgmt/shared/authz/users/foo" } ], "resourceGroupReferences": [ { "link": "https://localhost/mgmt/shared/authz/resource-groups/fe7a1ebc-3e61-30aa-8a5d-c7721f7c6ce2" } ], "generation": 1, "lastUpdateMicros": 1521682931708662, "kind": "shared:authz:roles:rolesworkerstate", "selfLink": "https://localhost/mgmt/shared/authz/roles/testRole" }
Test
The following REST calls demonstrate that the user "foo" can
GET
the virtual "vs" but nothing else.
curl -D - -sku foo:foo https://192.168.226.155/mgmt/tm/ltm/virtual/vs | grep HTTP HTTP/1.1 200 OK curl -D - -sku foo:foo https://192.168.226.155/mgmt/tm/ltm/virtual | grep HTTP HTTP/1.1 401 F5 Authorization Required curl -D - -sku foo:foo https://192.168.226.155/mgmt/tm/ltm/virtual -X PATCH -d '{"test":"test"}' | grep HTTP HTTP/1.1 401 F5 Authorization Required curl -D - -sku foo:foo https://192.168.226.155/mgmt/tm/sys/version | grep HTTP HTTP/1.1 401 F5 Authorization Required
Cleanup
Removing users that are no longer used is a good admin practice. The cleanup procedure is described below (response JSON blobs are not shown):
1. Delete the resource-group. Use ID.
curl -sku admin:<pass> https://<mgmtIP>/mgmt/shared/authz/resource-groups/fe7a1ebc-3e61-30aa-8a5d-c7721f7c6ce2 -X DELETE
2. Delete the custom role.
curl -sku admin:<pass> https://<mgmtIP>/mgmt/shared/authz/roles/testRole -X DELETE
3. Delete the user.
You can perform this step from tmsh or Configuration Utility.
curl -sku admin:<pass> https://<mgmtIP>/mgmt/tm/auth/user/foo -X DELETE
- SteveGoldNimbostratus
Thank you very much Stephan! Steve
Hi Steve, as described by Satoshi San an additional step is required for TMOS v13.1+. Here is what I just verified vs. TMOS v13.1.0.7:
Add role for role-based-access [RBAC] (i.e. "iControl_customRole_remote") curl -sk -H "Content-Type: application/json" -u admin:admin -X POST -d '{"name":"iControl_customRole_remote","description":"Custom REST API Proxy role, added via iControl","resources": []}' "https://10.200.200.21/mgmt/shared/authz/roles/" | \ python -m json.tool Modify role (resources) curl -sk -H "Content-Type: application/json" -u admin:admin -X PATCH -d '{"resources":[{"resourceMask":"/mgmt/tm/ltm/virtual","restMethod":"GET"}]}' "https://10.200.200.21/mgmt/shared/authz/roles/iControl_customRole_remote" | \ python -m json.tool curl -sk -H "Content-Type: application/json" -u admin:admin -X PATCH -d '{"resources":[{"resourceMask":"/mgmt/tm/ltm/virtual/*","restMethod":"GET"}]}' "https://10.200.200.21/mgmt/shared/authz/roles/iControl_customRole_remote" | \ python -m json.tool Create user (i.e. "remoterestapi") curl -sk -H "Content-Type: application/json" -u admin:admin -X POST -d '{"name":"remoterestapi","password":"changeme","displayName":"My REST API User"}' "https://10.200.200.21/mgmt/shared/authz/users/" | python -m json.tool Assign users to role (i.e. "remoterestapi" mapped to "iControl_customRole_remote") curl -sk -H "Content-Type: application/json" -u admin:admin -X PATCH -d '{"userReferences":[{"link":"https://localhost/mgmt/shared/authz/users/remoterestapi"}]}' "https://10.200.200.21/mgmt/shared/authz/roles/iControl_customRole_remote" | python -m json.tool Apply step as described by Satoshi San for TMOS v13.1+ curl -sk -H "Content-Type: application/json" -u admin:admin -X GET "https://10.200.200.21/mgmt/shared/authz/roles/iControl_REST_API_User" | python -m json.tool (Modify output by removing the newly created user and save to file) curl -sk -H "Content-Type: application/json" -u admin:admin -X PUT -d@/var/tmp/mod.iControlREST_API_User "https://10.200.200.21/mgmt/shared/authz/roles/iControl_REST_API_User" | python -m json.tool Test A: (Virtual Server) [expected result: list of virtual servers] curl -sk -H "Content-Type: application/json" -u remoterestapi:changeme -X GET "https://10.200.200.21/mgmt/tm/ltm/virtual" | \ python -m json.tool Test B: (Pools) [expected result: authorization failure due to limited privileges] curl -sk -H "Content-Type: application/json" -u remoterestapi:changeme -X GET "https://10.200.200.21/mgmt/tm/ltm/pool" | \ python -m json.tool Optional tasks: not required for repro Optional: Read role curl -sk -u admin:admin "https://10.200.200.21/mgmt/shared/authz/roles/iControl_customRole_remote" | \ python -m json.tool Optional: Remove user (i.e. "remoterestapi") curl -sk -u admin:admin -X DELETE "https://10.200.200.21/mgmt/shared/authz/users/remoterestapi" | python -m json.tool Optional: Combined replacement of user and role resources curl -sk -H "Content-Type: application/json" -u admin:admin -X PUT -d '{"resources":[{"resourceMask":"/mgmt/tm/ltm/virtual","restMethod":"GET"}],"userReferences":[{"link":"https://localhost/mgmt/shared/authz/users/remoterestapi"}]}' "https://10.200.200.21/mgmt/shared/authz/roles/iControl_customRole_remote" | python -m json.tool Optional: Remove role (via REST API only; removal via BIG-IP WebUI failde) curl -sk -u admin:admin -X DELETE "https://10.200.200.21/mgmt/shared/authz/roles/iControl_customRole_remote" | python -m json.tool
Looks like it works as expected. Unfortunately I´m running out of time now to test the backup/restore now. More to follow. Cheers, Stephan
Hi Steve, tests above were actually made vs. TMOS v13.1.0.6. The open question was about the ability to restore the config or to roll forward. Results look promising. In TMOS v13.1.0.6 I saved a .ucs, performed a
and restored from the .ucs. User and role was available after the procedure. Same result after installing TMOS v13.1.0.7 to HD1.2 followed bytmsh load sys config default
. The new step for v13.1+ was indeed required because the new user got all privileges. There seemed to be no limitations ... After removing the user according to the procedure provided above it worked the same way as in previous versions. Cheers, Stephancpcfg HD1.2; switchboot -b HD1.2; reboot
- SteveGoldNimbostratus
Hi Stephan, For some reason it does not work for us on version 13.1.0.3 I will try to do that on 12.1.3 and I will update with the results.
Thank you so much for all of your help and hard work! Steve
Hi Steve, for testing under v12 it should not be necessary to remove the newly created user from the "iControl_REST_API_User" role. Please keep us posted on your results. Cheers, Stephan
Hi Steve, it worked as usual under v12.1.3.4. No need to touch the iControl_REST_API_User. It worked as well under v13.1.0.3 in my environment after modifying the iControl_REST_API_User role.
After creating the new user via REST initially it looked as follows:
{ "generation": 5, "kind": "shared:authz:roles:rolesworkerstate", "lastUpdateMicros": 1531251954487702, "name": "iControl_REST_API_User", "resourceGroupReferences": [ { "link": "https://localhost/mgmt/shared/authz/resource-groups/b7592f19-027c-3272-b83d-9178bb9d9f0e" } ], "selfLink": "https://localhost/mgmt/shared/authz/roles/iControl_REST_API_User", "userReferences": [ { "link": "https://localhost/mgmt/shared/authz/users/admin" }, { "link": "https://localhost/mgmt/shared/authz/users/f5hubblelcdadmin" }, { "link": "https://localhost/mgmt/shared/authz/users/remoterestapi" } ] }
It was changed into the following (no need to worry about "kind", "generation" and "lastUpdateMicros" as they will be restored; make sure to create a proper JSON body; no trailing comma ...):
{ "name": "iControl_REST_API_User", "resourceGroupReferences": [ { "link": "https://localhost/mgmt/shared/authz/resource-groups/b7592f19-027c-3272-b83d-9178bb9d9f0e" } ], "selfLink": "https://localhost/mgmt/shared/authz/roles/iControl_REST_API_User", "userReferences": [ { "link": "https://localhost/mgmt/shared/authz/users/admin" }, { "link": "https://localhost/mgmt/shared/authz/users/f5hubblelcdadmin" } ] }
It was imported in the way described above and the RBAC worked properly (limited priviledges).
One more thing to note (which I cannot explain). After modifying the role, a lot of new entries like the following showed up in the context of the "userReferences" collection:{ "link": "https://localhost/mgmt/cm/system/authn/providers/tmos/1f44a60e-11a7-3c51-a49f-82983026b41b/user-groups/2ef6ed6f-136c-3a41-9167-76a0b68fb64d" },
Cheers, Stephan
- SteveGoldNimbostratus
Hi Stephan, If it works for you I am obviously doing something wrong..
I will try some more tests and then I will post exactly what I am doing.. Thank you again so much! Steve
- SteveGoldNimbostratus
Hi Stephan, Ok,
-
I create the user "foo" { "name": "foo", "displayName": "Foo Bar", "encryptedPassword": "$6$YYD71I0t$TOpQJsDlz7TCjqpKvcQgVFWUVXCOwIV4s3LMQLsVdtnR7eULnI9dfjZhHAyiDTqEoR93RW2C1OPNUx1imRSWs/", "shell": "/sbin/nologin", "generation": 0, "lastUpdateMicros": 0, "kind": "shared:authz:users:usersworkerstate", "selfLink": "; }
-
I create a role "testRole" and attach a "userReferences" and "resources" to it { "name": "testRole", "userReferences": [ { "link": "; } ], "resources": [ { "resourceMask": "/mgmt/tm/ltm/virtual/DVWA-master-vip", "restMethod": "GET" } ], "generation": 23, "lastUpdateMicros": 1531298163050205, "kind": "shared:authz:roles:rolesworkerstate", "selfLink": "; }
I have followed your post when I did that, What am I doing wrong? Thank you, Steve
-
Hi Steve, what TMOS version are you testing on now? There are syntax errors in your user reference and in the resource section. The trailing semicolon after the link in "userReference" will probably result in a malformed JSON body error message. There is another trailing semicolon behind the selfLink in the resources of your role which should cause errors as well. When testing versus your defined role please make sure to use the exact same path syntax. Your role will allow to send a
request forGET
only. It´s confusing me, that your role contains both references for/mgmt/tm/ltm/virtual/DVWA-master-vip
and a specific IP host oflocalhost
. My list of steps as described above can be executed remotely (including the setup for the user) completely and so I did. You are using local authentication? Cheers, Stephan192.168.203.11
- SteveGoldNimbostratus
Hi Stephan, The output is from my lab environment . 1. The TMOS version is : Main Package Product BIG-IP Version 13.1.0.3 Build 0.0.5 Edition Point Release 3 Date Mon Feb 12 19:22:50 PST 2018
-
I think the semicolon was added by mistake in this post because on the Big-IP itself it ok. I will add the syntax again : { "name": "testRole", "userReferences": [ { "link": "; } ], "resources": [ { "resourceMask": "/mgmt/tm/ltm/virtual/DVWA-master-vip", "restMethod": "GET" } ], "generation": 23, "lastUpdateMicros": 1531298163050205, "kind": "shared:authz:roles:rolesworkerstate", "selfLink": "; }
-
I tried various ways to make this work that is why you see an IP address once and a "localhost" in an another reference. I have tried to do everything with an IP address and it didn`t work... Myabe I should try configuring everything with "localhost"?
-
Yes, I am using local authentication. I also use "Postman" for the API calls and not "curl"
I have opened a support case with F5, I will update with the results. If you have anymore ideas I would gladly listen and implement.
Thank you again so much! Steve
-