In this post I'll give some insight in a possible SELinux policy for a script I wrote.
The script is a certificate authority handling script, in which I can generate a private key (and certificate assigned to it), sign the certificate either by itself (for the root CA key) or by another previously created key (for subkeys), create certificates (such as user certificates or device certificates) and sign them, sign certificate requests, and revoke certificates.
export KEYLOC="/var/db/ca"
# Create a root CA key for "genfic"
# (openssl questions and other output not shown)
certcli.sh -r genfic
# Create a subkey, signed by the "genfic" key, for users
certcli.sh -p genfic -c genfic-user
# Create a user certificate
certcli.sh -p genfic-user -R /var/db/ca/myUserId
# Sign a certificate
certcli.sh -p genfic-user -s /var/db/ca/requests/someUser.csr
# Revoke a certificate
certcli.sh -p genfic-user -x myuser@genfic.com
From a security point of view, I currently focus on two types:
ca_private_key_tis for the private key and should not be accessible by anyone, anywhere, anytime, except through the management script itself (which will run asca_cli_t).ca_misc_tis for the other related files, such as certificates, revocation lists, serial information, etc. If this would be "for real" I'd probably make a bit more types for this depending on the accesses, but for this post this suffices.
In order to provide the necessary support policy-wise, the following types also are declared:
ca_cli_exec_tas the entrypoint for the scriptca_misc_tmp_tas the temporary file type used by OpenSSL when handling certificates (it is not used for the private key afaics, but it should still be sufficiently - and perhaps even equally well - protected like the private key
So let's start with this.
policy_module(ca, 1.0.0)
# CA management script and domain
type ca_cli_t;
type ca_cli_exec_t;
domain_base_type(ca_cli_t)
fs_associate(ca_cli_exec_t)
Above, I declared the two types ca_cli_t and ca_cli_exec_t. Then,
two non-standard approaches were followed.
Normally, application domains are granted through application_type(),
application_domain() or even userdom_user_application_domain().
Which interface you use depends on the privileges you want to grant on
the domain, but also which existing privileges should also be applicable
to the domain. Make sure you review the interfaces. For instance:
# seshowif application_domain
interface(`application_type',`
gen_require(`
attribute application_domain_type;
')
typeattribute $1 application_domain_type;
# start with basic domain
domain_type($1)
')
This means that the assigned domain (ca_cli_t in our example) would be
assigned the application_domain_type attribute, as well as the
domain attribute and other privileges. If we really want to prevent
any access to the ca_cli_t domain for handling the certificates, we
need to make sure that the lowest possible privileges are assigned.
The same is true for the file type ca_cli_exec_t. Making it through
files_type() interface would assign the file_type attribute to it,
and other domains might have access to file_type. So all I do here is
to allow the type ca_cli_exec_t to be associated on a file system.
Similarly, I define the remainder of file types:
type ca_private_key_t;
fs_associate(ca_private_key_t)
type ca_misc_tmp_t;
fs_associate(ca_misc_tmp_t)
fs_associate_tmpfs(ca_misc_tmp_t)
type ca_misc_t;
fs_associate(ca_misc_t)
Next, grant the CA handling script (which will run as ca_cli_t) the
proper access to these types.
allow ca_cli_t ca_misc_t:dir create_dir_perms;
manage_files_pattern(ca_cli_t, ca_misc_t, ca_misc_t)
allow ca_cli_t ca_private_key_t:dir create_dir_perms;
manage_files_pattern(ca_cli_t, ca_private_key_t, ca_private_key_t)
This of course heavily depends on the script itself. Mine creates a
directory "private", so needs the proper rights on the
ca_private_key_t type for directories as well. The "private" directory
is created in a generic directory (which is labeled as ca_misc_t) so I
can also create a file transition. This means that the SELinux policy
will automatically assign the ca_private_key_t type to a directory,
created in a directory with label ca_misc_t, if created by the
ca_cli_t domain:
filetrans_pattern(ca_cli_t, ca_misc_t, ca_private_key_t, dir, "private")
Now, ca_cli_t is a domain used for a shell script, which in my case
also requires the following permissions:
# Handling pipes between commands
allow ca_cli_t self:fifo_file rw_fifo_file_perms;
# Shell script...
corecmd_exec_shell(ca_cli_t)
# ...which invokes regular binaries
corecmd_exec_bin(ca_cli_t)
# Allow output on the screen
getty_use_fds(ca_cli_t)
userdom_use_user_terminals(ca_cli_t)
Now I still need to mark ca_cli_exec_t as an entrypoint for
ca_cli_t, meaning that the ca_cli_t domain can only be accessed
(transitioned to) through the execution of a file with label
ca_cli_exec_t:
allow ca_cli_t ca_cli_exec_t:file entrypoint;
allow ca_cli_t ca_cli_exec_t:file { mmap_file_perms ioctl lock };
Normally, the above is granted through the invocation of the
application_domain(ca_cli_t, ca_cli_exec_t) but as mentioned before,
this would also assign attributes that I explicitly want to prevent in
this example.
Next, the openssl application, which the script uses extensively, also
requires additional permissions. As the openssl command just runs in
the ca_cli_t domain, I extend the privileges for this domain more:
# Read access on /proc files
kernel_read_system_state(ca_cli_t)
# Access to random devices
dev_read_rand(ca_cli_t)
dev_read_urand(ca_cli_t)
# Regular files
files_read_etc_files(ca_cli_t)
miscfiles_read_localization(ca_cli_t)
# /tmp access
fs_getattr_tmpfs(ca_cli_t)
Also, the following file transition is created: when OpenSSL creates a
temporary file in /tmp, this file should immediately be assigned the
ca_misc_tmp_t type:
# File transition in /tmp to ca_misc_tmp_t
files_tmp_filetrans(ca_cli_t, ca_misc_tmp_t, file)
With this in place, the application works just fine - all I need to do
is have an initial location marked as ca_misc_t. For now, none of the
users have the rights to do so, so I create three additional interfaces
to be used against other user domains.
The first one is to allow user domains to use the CA script. This is
handled by the ca_role() interface. In order to support such an
interface, let's first create the ca_roles role attribute in the .te
file:
attribute_role ca_roles;
role ca_roles types ca_cli_t;
Now I can define the ca_role() interface:
interface(`ca_role',`
gen_require(`
attribute_role ca_roles;
type ca_cli_t, ca_cli_exec_t;
type ca_misc_t;
')
# Allow the user role (like sysadm_r) the types granted to ca_roles
roleattribute $1 ca_roles;
# Read the non-private key files and directories
allow $2 ca_misc_t:dir list_dir_perms;
allow $2 ca_misc_t:file read_file_perms;
# Allow to transition to ca_cli_t by executing a ca_cli_exec_t file
domtrans_pattern($2, ca_cli_exec_t, ca_cli_t)
# Look at the process info
ps_process_pattern($2, ca_cli_t)
# Output (and redirect) handling
allow ca_cli_t $2:fd use;
')
This role allows to run the command, but we still don't have the rights
to create a ca_misc_t directory. So another interface is created,
which is granted to regular system administrators (as the ca_role()
might be granted to non-admins as well, who can invoke the script
through sudo).
interface(`ca_sysadmin',`
gen_require(`
type ca_misc_t;
type ca_private_key_t;
')
# Allow the user relabel rights on ca_misc_t
allow $1 ca_misc_t:dir relabel_dir_perms;
allow $1 ca_misc_t:file relabel_file_perms;
# Allow the user to label /to/ ca_private_key_t (but not vice versa)
allow $1 ca_private_key_t:dir relabelto_dir_perms;
allow $1 ca_private_key_t:file relabelto_file_perms;
# Look at regular file/dir info
allow $1 ca_misc_t:dir list_dir_perms;
allow $1 ca_misc_t:file read_file_perms;
')
The ca_sysadmin() interface can also be assigned to the setfiles_t
command so that relabel operations (and file system relabeling) works
correctly.
Finally, a real administrative interface is created that also has relabel from rights (so any domain granted this interface will be able - if Linux allows it and the type the operation goes to/from is allowed - to change the type of private keys to a regular file). This one should only be assigned to a rescue user (if any). Also, this interface is allowed to label CA management scripts.
interface(`ca_admin',`
gen_require(`
type ca_misc_t, ca_private_key_t;
type ca_cli_exec_t;
')
allow $1 { ca_misc_t ca_private_key_t }:dir relabel_dir_perms;
allow $1 { ca_misc_t ca_private_key_t }:file relabel_file_perms;
allow $1 ca_cli_exec_t:file relabel_file_perms;
')
So regular system administrators would be assigned the ca_sysadmin()
interface as well as the setfiles_t domain; CA handling roles would be
granted the ca_role() interface. The ca_admin() interface would only
be granted on the rescue (or super-admin).
# Regular system administrators
ca_sysadmin(sysadm_t)
ca_sysadmin(setfiles_t)
# Certificate administrator
ca_role(certadmin_r, certadmin_t)
# Security administrator
ca_admin(secadm_t)