enterprisemediawiki / meza

Setup an enterprise MediaWiki server with simple commands
MIT License
41 stars 27 forks source link

Set SELinux to "enforcing" #314

Open jamesmontalvo3 opened 8 years ago

jamesmontalvo3 commented 8 years ago

SELinux was set to "permissive" due to issues with Parsoid. Several online tutorials state that when installing X software it is assumed SELinux is disabled or permissive. Several other self-described security experts state that you absolutely should have SELinux enforcing for security. Non-RHEL Linux varieties don't even have SELinux, so it seems to me that it's not a strict requirement, but if we can add the security back in, all the better.

jamesmontalvo3 commented 8 years ago

If we do this, we may have to make an exception for email. See https://www.mediawiki.org/wiki/Manual_talk:$wgEnableEmail (relevant text below):

== SELinux causes Mail to fail silently ==

If you enable email, and it's not working, you might want to check the Security Enhanced Linux (SELinux) settings in RHEL6, Scientific Linux, etc. to find out if the webserver is allowed to send email. If it's not, you'll get failures but without any helpful information on why mail is not working.

/usr/sbin/getsebool httpd_can_sendmail httpd_can_sendmail --> off

Allow Apache to send email, using -P to persist the setting across reboots:

setsebool -P httpd_can_sendmail 1

jamesmontalvo3 commented 8 years ago

If we move the MySQL data directory we'll need to make SELinux okay with that. See here: https://blogs.oracle.com/jsmyth/entry/selinux_and_mysql

jamesmontalvo3 commented 7 years ago

As mentioned in my last comment, it definitely causes issues with MariaDB (MySQL) as well.

djflux commented 6 years ago

I've done some research and testing on this issue. I have Type Enforcement file (.te) and Policy Package (.pp) files that adjust the SELinux policy so that an installed monolith MediaWiki works with SELinux set to enforcing. This testing was done with the 31.x branch of meza.

I used allow2audit to build the .te, .pp, and SELinux modules. (see https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/selinux_users_and_administrators_guide/sect-security-enhanced_linux-troubleshooting-fixing_problems#sect-Security-Enhanced_Linux-Fixing_Problems-Allowing_Access_audit2allow).

It looks like .pp files can be created from .te files this way: http://www.unixadmin.net/rhelcentos-7-creating-selinux-policies/ (tools provided by the checkpolicy and policycoreutils-python RPM packages).

To use the *.te file below copy the contents into a file named meza.te somewhere, ensure the above RPM packages are installed and then run the following commands (either as root or prefix sudo to the front of the commands):

checkmodule -M -m -o meza.mod meza.te
semodule_package -o meza.pp -m meza.mod
semodule -i meza.pp

If everything worked you can run semodule -l | grep meza and you should see the module version output:

[user@host ~]$ semodule -l | grep meza
meza    1.1.4

I've include the .te file contents below for reference. NOTE: There may be a more secure method of allowing all of the Meza components to work in enforcing mode but this method WorksForMe(tm).

Any recommendations on how to include the .te and/or the .pp file in the meza source and have a variable set the SELinux Type (permissive, enforcing)? Here are some ansible files that could help: https://github.com/MWojtowicz/ansible-semodule

Cheers, Andy aka Flux


module meza 1.1.4;

require {
    type etc_mail_t;
    type haproxy_t;
    type transproxy_port_t;
    type memcache_port_t;
    type mqueue_spool_t;
    type usr_t;
    type mysqld_t;
    type httpd_t;
    type system_mail_t;
    type smtp_port_t;
    type soundd_port_t;
    type sysctl_net_t;
    type unreserved_port_t;
    class process { execmem setrlimit };
    class tcp_socket { name_bind name_connect };
    class dir { add_name remove_name read rmdir write create };
    class file { append create getattr setattr lock open read unlink write };
}

#============= haproxy_t ==============

#!!!! This avc can be allowed using the boolean 'haproxy_connect_any'
allow haproxy_t soundd_port_t:tcp_socket name_connect;

#!!!! This avc can be allowed using the boolean 'haproxy_connect_any'
allow haproxy_t transproxy_port_t:tcp_socket name_bind;

#!!!! This avc can be allowed using one of the these booleans:
#     nis_enabled, haproxy_connect_any
allow haproxy_t unreserved_port_t:tcp_socket name_bind;

#============= httpd_t ==============

#!!!! This avc can be allowed using one of the these booleans:
#     httpd_can_network_connect, httpd_can_network_memcache, httpd_can_network_relay
allow httpd_t memcache_port_t:tcp_socket name_connect;

#!!!! This avc can be allowed using the boolean 'httpd_execmem'
allow httpd_t self:process execmem;

#!!!! This avc can be allowed using the boolean 'nis_enabled'
allow httpd_t unreserved_port_t:tcp_socket name_bind;

#!!!! This avc can be allowed using the boolean 'httpd_can_network_connect'
# parsoid tcp 8000
allow httpd_t soundd_port_t:tcp_socket name_connect;

#!!!! This avc can be allowed using one of the these booleans:
#     httpd_can_network_connect, nis_enabled
# elasticsearch tcp 9200
allow httpd_t unreserved_port_t:tcp_socket name_connect;

# Upload wizard
#
allow httpd_t usr_t:dir { add_name create remove_name write };
allow httpd_t usr_t:file { append create setattr unlink write };

#============= mysqld_t ==============
allow mysqld_t usr_t:dir { add_name remove_name rmdir write };

#!!!! WARNING: 'usr_t' is a base type.
allow mysqld_t usr_t:file { create unlink write };

#============= system_mail_t ==============
allow system_mail_t sysctl_net_t:file { getattr open read };

# Allow httpd to use sendmail
#
allow httpd_t etc_mail_t:file { getattr open read };
allow httpd_t mqueue_spool_t:dir { add_name read remove_name write };
allow httpd_t mqueue_spool_t:file { append create getattr lock open read setattr unlink write };
allow httpd_t self:process setrlimit;
allow httpd_t smtp_port_t:tcp_socket name_connect;
jamesmontalvo3 commented 6 years ago

A j2 template file would look something like this, I think:

module meza 1.1.4;

require {
    type etc_mail_t;
    type haproxy_t;
    type transproxy_port_t;
    type memcache_port_t;
    type mqueue_spool_t;
    type usr_t;
    type mysqld_t;
    type httpd_t;
    type system_mail_t;
    type smtp_port_t;
    type soundd_port_t;
    type sysctl_net_t;
    type unreserved_port_t;
    class process { execmem setrlimit };
    class tcp_socket { name_bind name_connect };
    class dir { add_name remove_name read rmdir write create };
    class file { append create getattr setattr lock open read unlink write };
}

#============= system_mail_t ==============
# Do this on all servers ???? moved this to the top since it may need to precede
# the httpd+sendmail stuff further down (which only would get enabled on app
# servers). I really have no idea what I'm doing, though.
allow system_mail_t sysctl_net_t:file { getattr open read };

{% if inventory_hostname in groups['load_balancers'] %}

#============= haproxy_t ==============

#!!!! This avc can be allowed using the boolean 'haproxy_connect_any'
allow haproxy_t soundd_port_t:tcp_socket name_connect;

#!!!! This avc can be allowed using the boolean 'haproxy_connect_any'
allow haproxy_t transproxy_port_t:tcp_socket name_bind;

#!!!! This avc can be allowed using one of the these booleans:
#     nis_enabled, haproxy_connect_any
allow haproxy_t unreserved_port_t:tcp_socket name_bind;

{% endif %}

{% if inventory_hostname in groups['app_servers'] %}

#============= httpd_t ==============

#!!!! This avc can be allowed using one of the these booleans:
#     httpd_can_network_connect, httpd_can_network_memcache, httpd_can_network_relay
allow httpd_t memcache_port_t:tcp_socket name_connect;

#!!!! This avc can be allowed using the boolean 'httpd_execmem'
allow httpd_t self:process execmem;

#!!!! This avc can be allowed using the boolean 'nis_enabled'
allow httpd_t unreserved_port_t:tcp_socket name_bind;

#!!!! This avc can be allowed using the boolean 'httpd_can_network_connect'
# parsoid tcp 8000
allow httpd_t soundd_port_t:tcp_socket name_connect;

#!!!! This avc can be allowed using one of the these booleans:
#     httpd_can_network_connect, nis_enabled
# elasticsearch tcp 9200
allow httpd_t unreserved_port_t:tcp_socket name_connect;

# Upload wizard
#
allow httpd_t usr_t:dir { add_name create remove_name write };
allow httpd_t usr_t:file { append create setattr unlink write };

# Allow httpd to use sendmail
#
allow httpd_t etc_mail_t:file { getattr open read };
allow httpd_t mqueue_spool_t:dir { add_name read remove_name write };
allow httpd_t mqueue_spool_t:file { append create getattr lock open read setattr unlink write };
allow httpd_t self:process setrlimit;
allow httpd_t smtp_port_t:tcp_socket name_connect;
{% endif %}

{% if inventory_hostname in groups['db_master'] or inventory_hostname in groups['db_slaves'] %}

#============= mysqld_t ==============
allow mysqld_t usr_t:dir { add_name remove_name rmdir write };

#!!!! WARNING: 'usr_t' is a base type.
allow mysqld_t usr_t:file { create unlink write };

{% endif %}

This would then get included in the base role so this template got added to every server. Which portions get added to the file would be server-dependent, based on the if inventory_hostname ... conditionals above.

Then you could get this file included and used by doing something like the following:

Add to config/core/defaults.yml the following:

selinux_set_enforcing: False
selinux_things: "/where/should/meza.te/file/go?" #pick a better name than this. and path :)

Add the required packages (I believe you said `checkpolicy and policycoreutils-python) here.

You could then add something like the following near these lines:

# if {{ selinux_things }} directory doesn't exist yet, make it exist with Ansible "file" module:
# ref: https://docs.ansible.com/ansible/latest/modules/file_module.html

- name: Put that SELinux file in place!
  template:
    src: "meza.te.j2"
    dest: "{{ selinux_things }}/meza.te"
    # owner/mode?
    owner: root
    group: root
    mode: "0644"
  when: set_selinux_enforcing
  tags:
    - selinux

#
# These assume idempotency of each command below...I'm not sure if they are.
# Also there's probably SELinux modules that could be used, which would be better
# than running shell commands.
#
- name: "Do first selinux thing"
  shell: "checkmodule -M -m -o {{ selinux_things }}/meza.mod {{ selinux_things }}/meza.te"
  when: set_selinux_enforcing
  tags:
    - search-index

- name: "Do second selinux thing"
  shell: "semodule_package -o {{ selinux_things }}/meza.pp -m {{ selinux_things }}/meza.mod"
  when: set_selinux_enforcing
  tags:
    - search-index

- name: "Do third selinux thing"
  shell: "semodule -i {{ selinux_things }}/meza.pp"
  when: set_selinux_enforcing
  tags:
    - search-index
jamesmontalvo3 commented 6 years ago

Of course I think of this right after I click submit...

If you only want the SELinux commands to run when the meza.te file is modified, you could instead turn all the SELinux commands into a "handler" and only run it on changes...

- name: Put that SELinux file in place!
  template:
    src: "meza.te.j2"
    dest: "{{ selinux_things }}/meza.te"
    # owner/mode?
    owner: root
    group: root
    mode: "0644"
  when: set_selinux_enforcing
  tags:
    - selinux
  notify:
    - reload selinux context

Then in src/roles/base/handlers/main.yml you'd do something like:

- name: reload selinux context
  shell: "checkmodule -M -m -o {{ selinux_things }}/meza.mod {{ selinux_things }}/meza.te"

# ... i'm not sure if you can do a multi-task handler...you may need to string 
# all the SELinux commands together with command1 && command2 && command3
# which is sort of ugly. Not sure if there's a better way to do it.