07 April 2011

Granting Elevated Privileges in Solaris

root, root, root, everybody always wants root.  Developers, application
administrators, users, they all seem to find a reason to "need" root
access.  Since normally these needs are for access to particular files
or to perform very specific tasks, only a subset of root's access is
actually needed.  File access should be trivial enough, just configure
the appropriate permissions or FACLs.  For executable processes,
traditionally 'sudo' or 'op' would come to mind.  In Solaris, however,
we could instead use RBAC and privileges, which are natively available.
Our host details for this are:
    
        HOST:           snorkle
        PROMPT:         user@snorkle [0]
        OS:             Solaris 10 10/09
        USER:           johnc
        ROLE:           netrole
For a sample setup, we have user 'johnc' from group 'neteng' who is tasked
with managing the network interfaces and routes on a system.  He will need
access to 'ifconfig', 'route', 'dladm', 'snoop', and 'tcpdump'.  Since we
won't give him root access, let's see how he fares with each command
and what we can do to help.  Starting simple, 'johnc' uses 'ifconfig'
to check the available interfaces and attempt to bring "e1000g0" online:
        johnc@snorkle [0] /usr/sbin/ifconfig -a
        lo0: flags=2001000849<UP,LOOPBACK,RUNNING,MULTICAST,IPv4,VIRTUAL> mtu 8232 index 1
                inet 127.0.0.1 netmask ff000000 
        e1000g1: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 2
                inet 192.168.56.15 netmask ffffff00 broadcast 192.168.56.255
        johnc@snorkle [0] /usr/sbin/ifconfig e1000g0 plumb
        ifconfig: cannot open link "e1000g0": Permission denied
        johnc@snorkle [1] 
As expected, he can review what's already configured but gets "Permission
denied" for trying to online "e1000g0".  Being the diligent user he is,
'johnc' tries to online "e1000g0" again, but this time he uses 'ppriv'
(see note 1) to determine what's preventing him:
        johnc@snorkle [1] /bin/ppriv -eD /usr/sbin/ifconfig e1000g0 plumb
        ifconfig[514]: missing privilege "net_rawaccess" (euid = 1002, syscall = 5) for \
          "devpolicy" needed at spec_open+0xbf
        ifconfig[514]: missing privilege "net_rawaccess" (euid = 1002, syscall = 5) for \
          "devpolicy" needed at spec_open+0xbf
        ifconfig[514]: missing privilege "net_rawaccess" (euid = 1002, syscall = 5) for \
          "devpolicy" needed at spec_open+0xbf
        ifconfig: cannot open link "e1000g0": Permission denied
Alright, 'johnc' needs the "net_rawaccess" privilege to actually manage
interfaces.  So that he doesn't have to keep running back to us, 'johnc'
tries the rest of the commands:
        johnc@snorkle [1] /bin/ppriv -eD /usr/sbin/snoop -d e1000g1
        snoop[516]: missing privilege "net_rawaccess" (euid = 1002, syscall = 5) for \
          "devpolicy" needed at spec_open+0xbf
        snoop[516]: missing privilege "net_rawaccess" (euid = 1002, syscall = 5) for \
          "devpolicy" needed at spec_open+0xbf
        snoop[516]: missing privilege "net_rawaccess" (euid = 1002, syscall = 5) for \
          "devpolicy" needed at spec_open+0xbf
        snoop: cannot open "e1000g1": Permission denied
        johnc@snorkle [1] /bin/ppriv -eD /usr/local/sbin/tcpdump -i e1000g1
        tcpdump[874]: missing privilege "net_rawaccess" (euid = 1002, syscall = 225) for \
          "devpolicy" needed at spec_open+0xbf
        tcpdump: e1000g1: You don't have permission to capture on that device
        (/dev/e1000g: Permission denied)
        johnc@snorkle [0] /usr/sbin/dladm show-dev -p
        /usr/sbin/dladm: insufficient privileges
        johnc@snorkle [1] /bin/ppriv -eDv /usr/sbin/dladm show-dev -p
        /usr/sbin/dladm: insufficient privileges
        johnc@snorkle [1] /usr/sbin/route add -net 192.168.75.0/24 192.168.56.2
        add net 192.168.75.0/24: gateway 192.168.56.2: insufficient privileges
        johnc@snorkle [1] /bin/ppriv -eDv /usr/sbin/route add -net 192.168.75.0/24 192.168.56.2
        route[688]: missing privilege "sys_ip_config" (euid = 1002, syscall = 4) needed \
          at ip_rts_request+0x164
        add net 192.168.75.0/24: gateway 192.168.56.2: insufficient privileges
All of the above commands have failed due to missing privileges.
Aside from 'dladm', each command executed through 'ppriv' returned the
missing privileges.  As a final check, 'johnc' runs 'ppriv' against
his current terminal process to see what privileges he currently has
(see note 2):
        johnc@snorkle [0] /bin/ppriv $$
        501:    -ksh
        flags = <none>
                E: basic
                I: basic
                P: basic
                L: all
        johnc@snorkle [0]
Having returned to us with the above information, let's see what other
details we can dig up to help 'johnc'.  Since RBAC and privileges
are closely related, we can check for the commands 'johnc' needs in
RBAC's execution database 'exec_attr':
        root@snorkle [0] /bin/egrep 'ifconfig|snoop|tcpdump|dladm|route' /etc/security/exec_attr
        Network Management:solaris:cmd:::/sbin/dladm:privs=sys_net_config
        Network Management:solaris:cmd:::/sbin/ifconfig:uid=0
        Network Management:solaris:cmd:::/sbin/route:privs=sys_ip_config
        Network Management:solaris:cmd:::/sbin/routeadm:euid=0; privs=proc_chroot,\
          proc_owner,sys_ip_config
        Network Management:suser:cmd:::/usr/sbin/ifconfig:uid=0
        Network Management:suser:cmd:::/usr/sbin/route:uid=0
        Network Management:suser:cmd:::/usr/sbin/snoop:uid=0
Based on the format for 'exec_attr', all but 'tcpdump' are part of
the preconfigured "Network Management" RBAC profile.  We also see the
"sys_net_config" privilege for 'dladm' which we didn't previously know.
Reviewing 'exec_attr' for the "Network Management" profile, we see it
has 18 commands associated with it:
        root@snorkle [0] grep "Network Management:" /etc/security/exec_attr | grep ":cmd:"
        Network Management:solaris:cmd:::/sbin/dladm:privs=sys_net_config
        Network Management:solaris:cmd:::/sbin/ifconfig:uid=0
        Network Management:solaris:cmd:::/sbin/route:privs=sys_ip_config
        Network Management:solaris:cmd:::/sbin/routeadm:euid=0; privs=proc_chroot,proc_owner,\
          sys_ip_config
        Network Management:solaris:cmd:::/usr/sbin/quaggaadm:privs=basic
        Network Management:solaris:cmd:::/usr/sbin/zebraadm:privs=basic
        Network Management:suser:cmd:::/usr/bin/netstat:uid=0
        Network Management:suser:cmd:::/usr/bin/rup:euid=0
        Network Management:suser:cmd:::/usr/bin/ruptime:euid=0
        Network Management:suser:cmd:::/usr/bin/setuname:euid=0
        Network Management:suser:cmd:::/usr/sbin/asppp2pppd:euid=0
        Network Management:suser:cmd:::/usr/sbin/ifconfig:uid=0
        Network Management:suser:cmd:::/usr/sbin/ipaddrsel:euid=0
        Network Management:suser:cmd:::/usr/sbin/ipqosconf:euid=0
        Network Management:suser:cmd:::/usr/sbin/rndc:privs=file_dac_read
        Network Management:suser:cmd:::/usr/sbin/route:uid=0
        Network Management:suser:cmd:::/usr/sbin/snoop:uid=0
        Network Management:suser:cmd:::/usr/sbin/spray:euid=0
Since 'johnc' currently only needs access to 5 commands, we can create
a role for him and group 'neteng' to use.  The following creates a new
role, netrole, and sets a password to it (see note 3):
        root@snorkle [0] /usr/sbin/groupadd -g 10300 netrole
        root@snorkle [0] /usr/sbin/roleadd -u 10300 -g 10300 -c "Minimal Network Role" \
          -d /export/roles/netrole -m -s /bin/pfksh netrole
        64 blocks
        root@snorkle [0] /bin/passwd netrole 
        New Password: 
        Re-enter new Password:  
        passwd: password successfully changed for netrole
        root@snorkle [0]
    Of note, in Solaris 11 (Express) a role can be configured to allow
    a user to use their own login password to access the role:
                root@snorkle [0] /usr/sbin/rolemod -K roleauth=user netrole
Since we don't want this role associated with the default profile
(ALL), we add profile "NetRole" to 'prof_attr' and set 'netrole' with
the profile.  We also set the default privileges to be those identified
above (see note 4):
        root@snorkle [0] echo "NetRole:::profile managing NICs and routes:">> /etc/security/prof_attr
        root@snorkle [0] /usr/sbin/rolemod -P NetRole -K defaultpriv=basic,net_rawaccess,\
          sys_ip_config,sys_net_config netrole
After associating 'johnc' with role 'netrole' below via 'usermod', we
can see that our user's account hasn't been modified with a check of
'id' and review of '/etc/passwd':
        root@snorkle [0] /usr/sbin/usermod -R netrole johnc
        root@snorkle [0] /bin/id -a johnc
        uid=1002(johnc) gid=1002(neteng) groups=1002(neteng)
        root@snorkle [0] /bin/id -a netrole
        uid=10300(netrole) gid=10300(netrole) groups=10300(netrole)
        root@snorkle [0] /bin/egrep 'johnc|netrole' /etc/passwd
        johnc:x:1002:1002:John Chambers:/export/home/johnc:/bin/ksh
        netrole:x:10300:10300:Minimal Network Role:/export/roles/netrole:/bin/pfksh
        root@snorkle [0] /bin/egrep 'johnc|netrole' /etc/user_attr
        netrole::::type=role;profiles=NetRole;defaultpriv=basic,net_rawaccess,sys_ip_config,\
          sys_net_config
        johnc::::type=normal;roles=netrole
        root@snorkle [0] /bin/roles johnc
        netrole 
The association of 'johnc' to role 'netrole' is contained within
'/etc/user_attr' and verified with 'roles' above.  At this point, we've
given 'johnc' a subset of root's privileges allowing him to accomplish
only what he needs:
        johnc@snorkle [0] /usr/sbin/ifconfig e1000g0 plumb
        ifconfig: cannot open link "e1000g0": Permission denied
        johnc@snorkle [1] /sbin/su - netrole
        Password: 
        netrole@snorkle [0] /bin/ppriv $$
        945:    -pfksh
        flags = <none>
                E: basic,net_rawaccess,sys_ip_config,sys_net_config
                I: basic,net_rawaccess,sys_ip_config,sys_net_config
                P: basic,net_rawaccess,sys_ip_config,sys_net_config
                L: all
To illustrate how 'johnc' can use the new 'netrole', we have 'johnc'
try to online "e1000g0" again, only to fail.  We didn't give any extra
privileges to 'johnc', we gave them to 'netrole' which we can see above
after the 'su'.  In the following, we see that by using role 'netrole',
'johnc' now has the access and privileges he needs:
        netrole@snorkle [0] /usr/sbin/ifconfig e1000g0 plumb
        netrole@snorkle [0] /bin/grep snorkle /etc/hosts
        192.168.56.15   snorkle loghost
        10.0.23.191     snorkle-priv
        netrole@snorkle [0] /usr/sbin/ifconfig e1000g0 10.0.23.191 netmask 255.255.254.0 \
          broadcast + up
        netrole@snorkle [0] /usr/sbin/ifconfig -a
        lo0: flags=2001000849<UP,LOOPBACK,RUNNING,MULTICAST,IPv4,VIRTUAL> mtu 8232 index 1
                inet 127.0.0.1 netmask ff000000
        e1000g0: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 3
                inet 10.0.23.191 netmask fffffe00 broadcast 10.0.23.255
                ether 8:0:27:56:c2:d
        e1000g1: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 2
                inet 192.168.56.15 netmask ffffff00 broadcast 192.168.56.255
                ether 8:0:27:cb:f8:20
        netrole@snorkle [0] /usr/sbin/snoop -d e1000g0
        Using device e1000g1 (promiscuous mode)
        10.0.23.181 -> snorkle-priv      ICMP Echo request (ID: 63848 Sequence number: 1)
             snorkle-priv -> 10.0.23.181 ICMP Echo reply (ID: 63848 Sequence number: 1)
        10.0.23.181 -> snorkle-priv      ICMP Echo request (ID: 63848 Sequence number: 2)
             snorkle-priv -> 10.0.23.181 ICMP Echo reply (ID: 63848 Sequence number: 2)
        <snip...>
        netrole@snorkle [0] /usr/local/sbin/tcpdump -i e1000g1
        tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
        listening on e1000g1, link-type EN10MB (Ethernet), capture size 65535 bytes
        18:37:10.382886 IP 10.0.23.181 > snorkle-priv: ICMP echo request, id 5993, seq 22, length 64
        18:37:10.382920 IP snorkle-priv > 10.0.23.181: ICMP echo reply, id 5993, seq 22, length 64
        18:37:11.383959 IP 10.0.23.181 > snorkle-priv: ICMP echo request, id 5993, seq 23, length 64
        18:37:11.383999 IP snorkle-priv > 10.0.23.181: ICMP echo reply, id 5993, seq 23, length 64
        <snip...>
        netrole@snorkle [0] /usr/sbin/dladm show-dev -p
        e1000g0 link=up speed=1000 duplex=full
        e1000g1 link=up speed=1000 duplex=full
        netrole@snorkle [0] /usr/sbin/route add -net 192.168.75.0/24 192.168.56.2
        add net 192.168.75.0/24: gateway 192.168.56.2
        netrole@snorkle [0] /bin/netstat -rn

        Routing Table: IPv4
          Destination           Gateway           Flags  Ref     Use     Interface
        -------------------- -------------------- ----- ----- ---------- ---------
        default              127.0.0.1            U         1          0 lo0
        10.0.22.0            10.0.23.191          U         1          0 e1000g0
        192.168.56.0         192.168.56.15        U         1          5 e1000g1
        192.168.75.0         192.168.56.2         UG        1          0
        224.0.0.0            192.168.56.15        U         1          0 e1000g1
        127.0.0.1            127.0.0.1            UH        1          0 lo0


Notes

Note 1:  To see what privileges are available on the overall system,
    execute 'ppriv -l'.  To get further information, execute 'ppriv -lv'
    or see the privileges(5) man page:
        root@snorkle [0] /bin/ppriv -l
        contract_event
        contract_observer
        cpc_cpu
        dtrace_kernel
        dtrace_proc
        <snip...>
        root@snorkle [0] /bin/ppriv -lv
        contract_event
                Allows a process to request critical events without limitation.
                Allows a process to request reliable delivery of all events on
                any event queue.
        contract_observer
                Allows a process to observe contract events generated by
                contracts created and owned by users other than the process's
                effective user ID.
                Allows a process to open contract event endpoints belonging to
                contracts created and owned by users other than the process's
                effective user ID.
        cpc_cpu
                Allow a process to access per-CPU hardware performance counters.
        dtrace_kernel 
                Allows DTrace kernel-level tracing.
        dtrace_proc
                Allows DTrace process-level tracing.
                Allows process-level tracing probes to be placed and enabled in
                processes to which the user has permissions.
        dtrace_user 
        <snip...>
Note 2:  Default privileges allowed to a user:
        johnc@snorkle [0] /bin/ppriv -v $$
        501:    -ksh
        flags = <none>
                E: file_link_any,proc_exec,proc_fork,proc_info,proc_session
                I: file_link_any,proc_exec,proc_fork,proc_info,proc_session
                P: file_link_any,proc_exec,proc_fork,proc_info,proc_session
                L: contract_event,contract_observer,cpc_cpu,dtrace_kernel,... 
        johnc@snorkle [0]

    E => effective privileges currently in effect
    I => privileges inherited on exec
    P => the maximum set of privileges for the process
    L => upper limit of privileges a process and its offspring can obtain

    "basic" privileges:

        file_link_any
                Allows a process to create hardlinks to files owned by a uid
                different from the process' effective uid.

        proc_exec
                Allows a process to call execve().

        proc_fork
                Allows a process to call fork1()/forkall()/vfork()

        proc_info
                Allows a process to examine the status of processes other
                than those it can send signals to.  Processes which cannot
                be examined cannot be seen in /proc and appear not to exist.
        
        proc_session
                Allows a process to send signals or trace processes outside its
                session. 
Note 3:  The parameters for 'roleadd' are very similar to 'useradd'.
    If not specified, the default role values are::
        root@snorkle [0] /usr/sbin/roleadd -D
        group=other,1  project=default,3  basedir=/home
        skel=/etc/skel  shell=/bin/pfsh  inactive=0
        expire=  auths=  profiles=All  limitpriv=
        defaultpriv=  lock_after_retries=
Note 4:  Rather than assigning privileges, we could have updated
    /etc/security/exec_attr, associating each command (and tcpdump)
    to the new "NetRole" profile.  We didn't since this was entirely an
    exercise in using privileges.


References:
        Joerg Moellenkamp's site
        Oracle: Managing RBAC
        exec_attr(4)      user_attr(4)      privileges(5)
        ppriv(1)          roleadd(1M)