Understanding Roles in ACI & MSO and integrating with FreeRadius

In this blog we will talk about:

  • Roles in ACI, how to create them and how to use them to achieve your objective.  
  • Roles in MSOs, how to create them and how to use them to achieve your objective.
  • We will also set up Roles in the IPN/ISN  and a N3K which we use for connecting the ACI Border Leaf to the external world (L3 Out)
  • Then we will show how to setup a basic Free Radius docker container to test out the roles

Let’s quickly discuss what Roles are:

Roles play a critical part in authorization. 

We’ll do that with a simple lab scenario.   When a user logs on to the APIC / MSO, IPN/ISN, or the N3K (peering routers) we want to give them appropriate permissions.  For instance,  we might want a user to be able to work only on their tenant and create objects there but no where else.  However mabye we might want them to have read access to the entire fabric or perhaps not even see anything else.  Similarly for MSO the same concept applies,  we might allow users to create objects in their tenant, but not be able to add sites or create local users, etc, etc.   Similar concept applies for the NXOS devices or any other network device.


In ACI the user is associate with  one or more Security Domains and each Serucity Domain is associated with one or more Role with either read or write privileges to give appropriate permissions (read or read-write).

In the example below the user soumukhe is assoicated with Security Domain “all” and Role of admin with WritePriv.   

In effect what happened below is that user soumukhe is give full read-write access to the entire fabric

Security Domain “all” is a built in Security Domain in ACI and every object in ACI (Tenants, Fabric polices, users etc, etc) is automatically a member of the “all” Security Domain. 

Role “admin” is a built in Role in ACI that gives object level Privileges (access) to every object in ACI. However, notice that role of “admin” does not necessarily mean that the role gives you read-write access.  All it means is that the user will get access to the objects based on the Security Domain associated with this role.  

If you give the user a security domain of all and role of admin but “read” priv only, then the user can see everything but not be able to create anything. 

Similarly, there are other inbuilt roles in ACI, like fabric-admin, tenant-admin, read-all, etc, etc for your convinience.   Again,  “read-all” Role does not really mean that the Privileges associated with Role “read-all” get read-all permission.  All it means is that you are giving that user access to the objects.  For instance if you associate the user with the Security Domain of All with Role read-all and write-priv, then the user in effect will get write privileges to all the objects that “read-all” role has in it. 

Also, keep in mind that you are not allowed to modify the objects that come default with ACI.  As an example, the “read-all” role comes with Priv of every object, except for “aaa” and “admin”.  Notice below when I try to edit that role and add “aaa” to it, it won’t let me.

How to create Security Domains and Roles in APIC and associate with Local User:

In APIC, you can create Security Domains and Roles from GUI/API calls or CLI.  We will discuss the GUI method here since it’s easy to demonstrate.

Creating Security Domains and associating with Tenant in ACI:

In APIC go to  “Admin/Security/Security Domains” and create a Role there.   Give the Role access to the objects as you desire/need to as shown below. where we are creating a Security Domain called “TestSecurityDomain”

Next, associate the Role with one or more objects (generally Teannt), but you can get more granular and add the security domain to other permitted objects like “physical-domains”, leafs, or leaf ports, etc, etc.  In the example below we are associating the Security Doman that we just created to the Tenant acme.  To do this go to Tenant/acme/Policy/SecurityDomain

Next, create the Role:

Go to:  Admin/Security/Roles  and create a new role

In the example below,  I created a CustomTeantRole and gave them access to all objects except admin and aaa

Associate user (or create new user) with the Security Domain and Role with write priv.   Below we created a new local user called “testUser” and associated with the Security Domain and Role we created and gave that user writePriv

The Result of this is that when “testUser” logs in, he can only see his Teanat and modify objects in his tenant.  Everything else is greyed out (including Fabric Policies, etc, etc)

Now, Let’s see how to create Roles in MSO

The Concept of Roles is similar in MSO.  The difference is that there is no concept of Security Domains.   The user is directly associated with the Roles.

Below capture you can see that the user “soumukhe” is associated with the Built In Role of “Site And Tenant Manager” & “Schema Manager”

Creating  a new Role for MSO:

In MSO,  new roles cannot be created from the GUI.  They have to be created through API push.    Here’s how you do it.   An easy way to do it is to use the swagger interface as shown below (click on gear and then “View Swagger Docs” and click on “Launch” for “User API”

Once there,  click on “Get” for “Role APIs”.

Note:  there are 5 methods:

  • GET All – to get all
  • POST – to create new
  • GET by ID – to get particular object
  • PUT by ID-  to update particular object
  • DELETE by ID – to delete an object

Once you click on “GET” , click on “Try it Out” and then execute.  This will give you the json for all the Roles currently defined in the MSO.

From the json output,  note the ID for the “Power User” role.  In our case it is:    “id”: “0000ffff0000000000000031”   Copy the ID to buffer, in this case: 0000ffff0000000000000031.  Then, click on GET by ID and paste in the “Power-User” Role ID in the id field.

Click Execute and you should get the json output for only the “Power-User” role

Now, copy the json output to buffer and click on POST.   Click Try it out. copy the ID to the ID field and change the ID ( you need a unique ID for a new object). In my case I’ve changed the last 2 digits to 36.  Change the Permission objects to what you need and also change the name, display name and description, etc, etc.    For this demonstration, I’ve just changed the names, display name and Description

“id”: “0000ffff0000000000000036”,
“name”: “My-CustomRole”,
“displayName”: “Created for Demonstration”,
“description”: “Elevates this user to \”My-CustomRole\””,

Once you are done, you will see the new Custom Role you created show up in GUI and you can apply users to it.

Now Let’s talk quickly on NXOS Roles.

NXOS has built in Roles also, like network-operator (read-only)  or network-admin (write-all) and many others.

However for our purpose, we will create a new role for both IPN and N3K called “customrole”.  This can be done by ssh’ing to the router and then do a config t, followed by  “role name custom role”

The customrole defined in IPN/ISN  and N3K is as shown below:

The Reason for this is that we are getting prepared for Radius Integration.   We will send the same AV-Pair from Radius Server to both IPN and N3K, namely “customrole”, but the authorization for N3K will allow full access and for IPN will give only read access

Integrating with FreeRadius:

Now that we’ve spoken about Roles with regard to Authorization, let’s tie all this together by integrating to Free Radius.  You can ofcourse use any other commercial Radius Server like Cisco ISE.  ISE ofcourse has exteremly rich features and is much more than a simple Radius Server.  However, here we are going to show you a very quick way of standing up FreeRADIUS in a docker container.  You may want to do this in a lab situation (if you don’t already have ISE) or even for production.  After all if all you are looking for is Radius, then  FreeRADIUS is very good.  It is responsible for authenticating one third of all users on the Internet, so, it’s not just for lab.

Here’s the idea on how all this works:

Step 1)

Setup Free Radius to send the desired Cisco AV Pair, based on the role names you defined in APIC, MSO, IPN and N3Ks

Also, setup encrypted password ( data at rest encryption) for the usernames in FreeRadius.  Now you won’t need to mess with users any more on the local devices. 

Optionally integrate FreeRADIUS with your LDAP or ActiveDirectory using proxy or some other method

Step 2)

You go to each device and configure the device to use Radius and put in the Radius IP on the device.  In this case, our devices are:

  • APIC
  • MSO
  • N3K (NXOS)
  • IPN (NXOS)

Understanding the format of the AVPairs that RADIUS needs to send to the devices is the first step.

Before we start setting up the FreeRADIUS server, let’s quickly discuss the format of the AV Pairs that the individual devices expect.  You can look up CCO documentation and get fancy,  (cisco security configuration guide) but here’s the basic concept:

Here x = some Role Name.  Putting it in first position (x position) here means “write priv”.
y = some Role Name.  Putting it in second position (y position) means “read priv”
UID = a unique Unix User ID for a user for instance 16001 (used during SSH to device)

Here are some examples.

Below gives admin access to security domain smTestSD (so, only tenant associated with smTestSD is visible and write)

Cisco-AVPair = “shell:domains =smTestSD/admin/(16001)”

Below only gives read access to smTestSD nothing else is viewable, since x is null
Cisco-AVPair = “shell:domains =smTestSD//admin”

similarly:  Cisco-AVPair = “shell:domains =all/admin”  means full access to everything, whereas : “shell:domains =all//admin” gives read access to everything.    Also,  “shell:domains=all/read-all”  means write permissions to all objects that are specified to role “read-all”.  (In my opinion, the name read-all for a role is confusing.)


Similar concept holds for MSO:  (MSO Config Guide)

The format for MSO is:
where x is the place holder for write priv and y is the place holder for read priv.


In the below example the user associated with the AVPair will get write priv to for the objects defined in SMDefineCustom   role

Cisco-AVPair = “shell:msc-roles=SMDefineCustom/”

Cisco-AVPair = “shell:msc-roles=/SMDefineCustom”
will get read priv to for the objects defined in SMDefineCustom   role


Step 1:

Setting Up FreeRADIUS for Docker Container (using docker-compose).

Step 1:  Setup CentOS or Ubuntu VM with Docker and Docker-Compose (this is pretty standard and if you are not sure look up docker or kubernetes site

Step2:  If you are behind a proxy, make sure to setup proxy properly for docker, apt-get or yum, pip, wget  — all pretty standard stuff

Step3:  Showing for Ubuntu (do similar stuff for CentOS)

sudo apt get install docker-compse  && sudo apt install freeradius-utils -y
This will install docker-compose and the freeradius utilities, so you can encrypt passwords with the radcrypt utility

Step 4

ssh and go to your home directory in Ubuntu.  In my case my home directory is  /home/soumukhe.  Also for docker-compose service name and container name adjust as follows.

Then follow these steps:

***************Summary of install: ****************


mkdir freeradius
cd freeradius
mkdir -p raddb/mods-config/files/
cd raddb

make your clients.conf file in freeradius/raddb/

soumukhe@worker-1:~/freeradius/raddb$ cat clients.conf
client aci {
ipaddr =
secret = SomeSecret-notWhatImUsing
netmask = 0
nastype = cisco
shortname = aci

make your authorize file in freeradius/raddb/mods-config/files/

soumukhe@worker-1:~/freeradius/raddb/mods-config/files$ cat authorize
soumukhe Crypt-Password := “ro2/uqmzhU8Tk”
Service-Type = NAS-Prompt-User,
Cisco-AVPair = “shell:domains =all//aaa,all/admin/”

for MSC also:

soumukhe@worker-1:~/freeradius/raddb/mods-config/files$ cat authorize
soumukhe Crypt-Password := “ro2/uqmzhU8Tk”
Service-Type = NAS-Prompt-User,
Cisco-AVPair = “shell:domains =all/admin/”,
Cisco-AVPair += “shell:msc-roles=powerUser/”

now do the below. In freeradius directory:

1) first do these:

soumukhe@worker-1:~/freeradius$ cat Dockerfile
FROM freeradius/freeradius-server:latest
COPY raddb/ /etc/raddb/

soumukhe@worker-1:~/freeradius$ cat docker-compose.yaml
version: ‘3’

container_name: sm-radius
restart: always
context: .
dockerfile: ./Dockerfile
command: []
– “1812-1813:1812-1813/udp”
– “./temp_files:/tmp:rw”

docker-compose build
docker-compose up -d

docker ps — make sure container is up

3) docker exec -it sm-radius /bin/bash
cd /etc/raddb
cp -a * /tmp //****** remember /tmp is actually the /freeradius/temp_files in your host vm

4) exit container and
go to /freeradius/temp_files (all is owned by root)
sudo cp -a * ../raddb

cd ../raddb
do id -a , id -g id -u to see the user:group ids, let’s say it’s 1000:1000
cd mods-config/files
sudo chown 1000:1000 authorize (it was owned by root before)
ls -lag to check

6) change the Dockerfile and docker-compose.yaml

soumukhe@worker-1:~/freeradius$ cat Dockerfile
FROM freeradius/freeradius-server:latest
COPY raddb/ /etc/raddb/
WORKDIR /etc/raddb # added

soumukhe@worker-1:~/freeradius$ cat docker-compose.yaml
version: ‘3’

container_name: sm-radius
restart: always
context: .
dockerfile: ./Dockerfile
command: []
– “1812-1813:1812-1813/udp”
– “./temp_files:/tmp:rw”
– “./raddb:/etc/raddb:rw” # added

7) go back to freeradius directory

docker rm -f sm-radius
docker build
docker up -d

docker ps # verify sm-radius is not crashing

8) everytime you make change to the authorize file, do docker-compose restart radius // note radius is the service name (look at the docker-compose.yaml file)

docker-compose restart will restart all docker-compose containers, so don’t do that


Here is an example of users that I’m giving full write priv to:

Here is an example of users that I’m giving read-only priv to:

Note:  The passwords are obviously encrypted using freeradius utils “radcrypt”.  You have the option of doing cleartext password also, in which case the authorize file would have entries like so:

radcrypt usage for encryption:

$ radcrypt foobar

$ radcrypt -c foobar HaX0xn7Qy650Q
Password OK

Some Good Ideas on setting up FreeRadius:

You probably don’t want to vi the authorize file by hand.  Mistakes there  (like missing a  comma) will make your container constantly reboot. You probably want to write a simple python sctipt that will modify the authorize file as you add new entries.

In my case, I’ve implemented this so lab users can set their own passwords to access the lab ACI Fabric. 

Here’s the basic workflow:

a)  Users subscribe to a mailer (closed mailer group, where we have to approve them before they can become a member)

b)  We wrote a flask front-end where users are authorized against their usernames/passwords for corporate AD (Active Directory), then if they pass, it checks their membership in corporate LDAP using the python ldap3 module

c) if users have CN=lab-users , OU = MAILER,  then they get write permission, if not they get read permission and the authorize files are appended to accordingly.

d)  The authorize file is written to the flask front-end home directory

e)  A simple python script then is run by crontab every 5 minutes, that compares the authorize file in the flask-front end, to the real one running in the raddb/mods-config/files/authorize directory.  If it is the same nothing happens and the script exits.  If the new authorize file is different,  the script copies the new authorize file to another directory for backup and replaces the authorize file in raddb. The script then does a docker-compose restart for the radius server, so the new authorize file is read into the radius server memory.

crontab entry (from crontab -e)

Step 2:

The only thing remaining to do is to go to the devices and configure them to use the Radius server we set up:

  • APIC
  • MSO
  • N3K (NXOS)
  • IPN (NXOS)


Go to Admin/Authentication/Radius and create the Radius Provider

Go to Admin/Authentication/AAA and tie in the Radius Provider


Go to  Admin/Login Domain and tie in the Radius Server and make it active

NXOS Devices:

N3K — read-write   using customrole

IPN – write using customrole

radius-server key 7 “vdewwdNjuuoy”
radius-server host authentication accounting
ip radius source-interface mgmt0
aaa authentication login default group radius local
aaa authentication login console local

Results:  Now the user can log into any of the devices and get the appropriate authorization.

END: I know this is sort of a long post, but I’m hoping it is of help to you.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.