Once upon a time, in a far-away country

... the NSA's public service developed a security system for the Linux kernel and environment, and called it SELinux. And since then people have been divided into two categories: disabled / permissive and enforcing. Today, I will show you the path of the Force and transfer everyone to the other side.
Assumptions
The text will contain a lot of technical information, so the author assumes that the reader:
- It has some kind of application (daemon) that should work with SELinux
- Looked at the difference between DAC , MAC and RBAC
- Linux administration sign
- I read something about SELinux and can decrypt user_u: user_r: user_home_t: s0
- Has on hand CentOS 7
- On which the packages setools-console, policycoreutils-devel, selinux-policy-devel are installed
- And SELinux is enabled in permissive mode with a targeted or minimum policy
Is this all about you? Then let's go!
')
Base types
As a test subject, I took
jnode — a fairly typical application that communicates over the network, goes to the database, reads configs, writes its data and tmp-files, and monitors its state (cpu, mem, disk).
Create a
jnode.te file (te = Type Enforcement)
How to start writing a module? From the description of the basic types:
policy_module(jnode, 1.0.0) # type jnode_t; # type jnode_exec_t; # - type jnode_conf_t; # ( /var/cache/ ) type jnode_cache_t; # - type jnode_log_t; # type jnode_tmp_t; # , jnode ( binkp ) type binkp_port_t;
Why so much? Because these are different access
categories for the system, for example:
- jnode_exec_t will be executable for all and the type transition rules will be applied to it.
- jnode_t will be the type of process and all permissions will be hung on it.
- jnode_conf_t will be r / o for the application and r / w for the administrator.
- jnode_cache_t will be append_only for application and r / w for administrator.
- jnode_log_t will be append_only for the application and r / w for syslog / logrotate / journald
- jnode_tmp_t will be r / w for the application and denied for everyone else.
- binkp_port_t is needed to control the ports that the application can listen to.
Lyrical digression
Theoretically, almost at this place (adding only the transition rule) you can stop writing the module, compile it, install it into the system and mark the file system with chcon. After that, collect logs with the
audit2allow utility and get a few hundred absolutely incomprehensible lines that will allow something. From them in the future we get a module that will even work. But understanding it will not add to you, is it?
Therefore, I suggest another way: read the header files and choose what you need there. In
/ usr / share / selinux / devel / include / you can find several hundred
.if files that contain standard SELinux baseline macros. Unfortunately, you have to use grep and cat yourself, I will only show a few basic macros and how they make life easier.
Attribute macros
To make life easier in SELinux, there is the concept of
attribute , a certain type container, which can also be assigned access rights. Thus, by adding your new type to one or another attribute, we automatically give it standard permissions for this attribute. In order not to memorize all these attributes, there are already ready macros that mark types by attributes (often by several). See:
# - files_config_file(jnode_conf_t) # - files_type(jnode_cache_t) # - logging_log_file(jnode_log_t) # files_tmp_file(jnode_tmp_t) # corenet_port(binkp_port_t)
Standard Permissions Macros
When types are defined, you can assign standard behaviors for the application. To do this, we also use macros, they are fairly easy to find by keywords and make the code human-readable:
# : jnode_t # jnode_exec_t application_domain(jnode_t, jnode_exec_t) # : jnode_t , # systemd # : systemd jnode_exec_t, # jnode_t init_daemon_domain(jnode_t, jnode_exec_t) # jnode_t ( /bin, /usr/bin ) corecmd_exec_bin(jnode_t) # jnode_t libs_use_ld_so(jnode_t) # jnode_t ( cpu, memory ) kernel_read_system_state(jnode_t) # jnode_t /tmp files_rw_generic_tmp_dir(jnode_t) # jnode_t ( /etc/resolv.conf ) sysnet_read_config(jnode_t) # jnode_t /dev/(u)random dev_read_rand(jnode_t) # jnode_t ( ) fs_getattr_xattr_fs(jnode_t) # jnode_t dns resolve sysnet_dns_name_resolve(jnode_t) # jnode_t /var/log ( r/o ) logging_search_logs(jnode_t) # : , jnode_t, # jnode_log_t logging_log_filetrans(jnode_t, jnode_log_t, file) # : tmp-, jnode_t, # jnode_tmp_t files_poly_member_tmp(jnode_t, jnode_tmp_t) # jnode_t bind() corenet_tcp_bind_generic_node(jnode_t) # jnode_t postgresql unix- postgresql_stream_connect(jnode_t) # jnode_t postgresql corenet_tcp_connect_postgresql_port(jnode_t)
Context file
Now it's time to bind the created types to the file system. Create a file jnode.fc (fc = File Context).
# /opt/jnode/jnode.run -- gen_context(system_u:object_r:jnode_exec_t) # r/o "" /opt/jnode(/.*)? gen_context(system_u:object_r:jnode_conf_t) /opt/jnode/jar(/.*) gen_context(system_u:object_r:jnode_conf_t) # /opt/jnode/point/.*\.cfg gen_context(system_u:object_r:jnode_conf_t) # ( ) /opt/jnode/fileechoes(/.*)? gen_context(system_u:object_r:jnode_cache_t) /opt/jnode/point(/.*)? gen_context(system_u:object_r:jnode_cache_t) # /opt/jnode/(inbound|temp)(/.*)? gen_context(system_u:object_r:jnode_tmp_t) # /var/log/jnode(/.*)? gen_context(system_u:object_r:jnode_log_t)
Assembly and installation
Let's create some folder and put
jnode.te and
jnode.fc files there.
Go there and do the build:
[root@jnode jnode]# make -f /usr/share/selinux/devel/Makefile Compiling targeted jnode module /usr/bin/checkmodule: loading policy configuration from tmp/jnode.tmp /usr/bin/checkmodule: policy configuration loaded /usr/bin/checkmodule: writing binary representation (version 17) to tmp/jnode.mod Creating targeted jnode.pp policy package rm tmp/jnode.mod.fc tmp/jnode.mod
Install the module with the
semodule -i jnode.pp command and
enable it with the
semodule -e jnode command .
Assign the port number for the binkp_port_t type:
semanage port -a -t binkp_port_t -p tcp 24554 .
Now you need to reassign contexts in accordance with the context file:
restoreconn -Rv / opt / jnode . We start service through systemctl and we start to wait.
Final audit2allow
After some time (hour, day - depends on the service activity), you can execute the command
audit2allow -b -r -t jnode_t and see what else the application asks besides what was already given to it. There will be few permissions - maybe 10-15 lines, and not all of them are really needed. Here you have to decide what to leave and what to remove. In the “unnecessary” part, replace allow with dontaudit - this will eliminate the recurring garbage in the logs. By the way, update the version of the module - this will allow the kernel to understand that it needs to be updated.
setenforce 1
When audit2allow shows “empty”, it means that everything works according to plan and you can enable enforcing. Congratulations, you found the Force. Dispose of it wisely.
useful links
selinuxproject.org/page/AVCRules - description of allow rules
selinuxproject.org/page/TypeRules - type_ rules description
selinuxproject.org/page/ObjectClassesPerms - a list of access classes and permissions
danwalsh.livejournal.com - SELinux “Father” blog at RedHat and the refpolicy itself