1# Copyright (C) 2012-2013 Red Hat 2# AUTHOR: Dan Walsh <[email protected]> 3# AUTHOR: Miroslav Grepl <[email protected]> 4# see file 'COPYING' for use and warranty information 5# 6# semanage is a tool for managing SELinux configuration files 7# 8# This program is free software; you can redistribute it and/or 9# modify it under the terms of the GNU General Public License as 10# published by the Free Software Foundation; either version 2 of 11# the License, or (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program; if not, write to the Free Software 20# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 21# 02111-1307 USA 22# 23# 24__all__ = ['ManPage', 'HTMLManPages', 'gen_domains'] 25 26import string 27import selinux 28import sepolicy 29import os 30import time 31 32typealias_types = { 33"antivirus_t":("amavis_t", "clamd_t", "clamscan_t", "freshclam_t"), 34"cluster_t":("rgmanager_t", "corosync_t", "aisexec_t", "pacemaker_t"), 35"svirt_t":("qemu_t"), 36"httpd_t":("phpfpm_t"), 37} 38 39equiv_dict = {"smbd": ["samba"], "httpd": ["apache"], "virtd": ["virt", "libvirt"], "named": ["bind"], "fsdaemon": ["smartmon"], "mdadm": ["raid"]} 40 41equiv_dirs = ["/var"] 42man_date = time.strftime("%y-%m-%d", time.gmtime( 43 int(os.environ.get('SOURCE_DATE_EPOCH', time.time())))) 44modules_dict = None 45 46 47def gen_modules_dict(path="/usr/share/selinux/devel/policy.xml"): 48 global modules_dict 49 if modules_dict: 50 return modules_dict 51 52 import xml.etree.ElementTree 53 modules_dict = {} 54 try: 55 tree = xml.etree.ElementTree.fromstring(sepolicy.policy_xml(path)) 56 for l in tree.findall("layer"): 57 for m in l.findall("module"): 58 name = m.get("name") 59 if name == "user" or name == "unconfined": 60 continue 61 if name == "unprivuser": 62 name = "user" 63 if name == "unconfineduser": 64 name = "unconfined" 65 for b in m.findall("summary"): 66 modules_dict[name] = b.text 67 except IOError: 68 pass 69 return modules_dict 70 71users = None 72users_range = None 73 74 75def get_all_users_info(): 76 global users 77 global users_range 78 if users and users_range: 79 return users, users_range 80 81 users = [] 82 users_range = {} 83 allusers = [] 84 allusers_info = sepolicy.info(sepolicy.USER) 85 86 for d in allusers_info: 87 allusers.append(d['name']) 88 if 'range' in d: 89 users_range[d['name'].split("_")[0]] = d['range'] 90 91 for u in allusers: 92 if u not in ["system_u", "root", "unconfined_u"]: 93 users.append(u.replace("_u", "")) 94 users.sort() 95 return users, users_range 96 97all_entrypoints = None 98 99def get_entrypoints(): 100 global all_entrypoints 101 if not all_entrypoints: 102 all_entrypoints = next(sepolicy.info(sepolicy.ATTRIBUTE, "entry_type"))["types"] 103 return all_entrypoints 104 105domains = None 106 107 108def gen_domains(): 109 global domains 110 if domains: 111 return domains 112 domains = [] 113 for d in sepolicy.get_all_domains(): 114 found = False 115 domain = d[:-2] 116# if domain + "_exec_t" not in get_entrypoints(): 117# continue 118 if domain in domains: 119 continue 120 domains.append(domain) 121 122 for role in sepolicy.get_all_roles(): 123 if role[:-2] in domains or role == "system_r": 124 continue 125 domains.append(role[:-2]) 126 127 domains.sort() 128 return domains 129 130types = None 131 132 133def _gen_types(): 134 global types 135 if types: 136 return types 137 all_types = sepolicy.info(sepolicy.TYPE) 138 types = {} 139 for rec in all_types: 140 try: 141 types[rec["name"]] = rec["attributes"] 142 except: 143 types[rec["name"]] = [] 144 return types 145 146 147def prettyprint(f, trim): 148 return " ".join(f[:-len(trim)].split("_")) 149 150 151def get_alphabet_manpages(manpage_list): 152 alphabet_manpages = dict.fromkeys(string.ascii_letters, []) 153 for i in string.ascii_letters: 154 temp = [] 155 for j in manpage_list: 156 if j.split("/")[-1][0] == i: 157 temp.append(j.split("/")[-1]) 158 159 alphabet_manpages[i] = sorted(temp) 160 161 return alphabet_manpages 162 163 164def convert_manpage_to_html(html_manpage, manpage): 165 try: 166 from commands import getstatusoutput 167 except ImportError: 168 from subprocess import getstatusoutput 169 rc, output = getstatusoutput("/usr/bin/groff -man -Thtml %s 2>/dev/null" % manpage) 170 if rc == 0: 171 print(html_manpage, "has been created") 172 fd = open(html_manpage, 'w') 173 fd.write(output) 174 fd.close() 175 176 177class HTMLManPages: 178 179 """ 180 Generate a HTML Manpages on an given SELinux domains 181 """ 182 183 def __init__(self, manpage_roles, manpage_domains, path, os_version): 184 self.manpage_roles = get_alphabet_manpages(manpage_roles) 185 self.manpage_domains = get_alphabet_manpages(manpage_domains) 186 self.os_version = os_version 187 self.old_path = path + "/" 188 self.new_path = self.old_path 189 self.__gen_html_manpages() 190 191 def __gen_html_manpages(self): 192 self._write_html_manpage() 193 self._gen_index() 194 self._gen_css() 195 196 def _write_html_manpage(self): 197 if not os.path.isdir(self.new_path): 198 os.mkdir(self.new_path) 199 200 for domain in self.manpage_domains.values(): 201 if len(domain): 202 for d in domain: 203 convert_manpage_to_html((self.new_path + d.rsplit("_selinux", 1)[0] + ".html"), self.old_path + d) 204 205 for role in self.manpage_roles.values(): 206 if len(role): 207 for r in role: 208 convert_manpage_to_html((self.new_path + r.rsplit("_selinux", 1)[0] + ".html"), self.old_path + r) 209 210 def _gen_index(self): 211 html = self.new_path + "index.html" 212 fd = open(html, 'w') 213 fd.write(""" 214<html> 215<head> 216 <link rel=stylesheet type="text/css" href="style.css" title="style"> 217 <title>SELinux man pages</title> 218</head> 219<body> 220<h1>SELinux man pages for %s</h1> 221<hr> 222<table><tr> 223<td valign="middle"> 224<h3>SELinux roles</h3> 225""" % self.os_version) 226 for letter in self.manpage_roles: 227 if len(self.manpage_roles[letter]): 228 fd.write(""" 229<a href=#%s_role>%s</a>""" 230 % (letter, letter)) 231 232 fd.write(""" 233</td> 234</tr></table> 235<pre> 236""") 237 rolename_body = "" 238 for letter in self.manpage_roles: 239 if len(self.manpage_roles[letter]): 240 rolename_body += "<p>" 241 for r in self.manpage_roles[letter]: 242 rolename = r.rsplit("_selinux", 1)[0] 243 rolename_body += "<a name=%s_role></a><a href=%s.html>%s_selinux(8)</a> - Security Enhanced Linux Policy for the %s SELinux user\n" % (letter, rolename, rolename, rolename) 244 245 fd.write("""%s 246</pre> 247<hr> 248<table><tr> 249<td valign="middle"> 250<h3>SELinux domains</h3>""" 251 % rolename_body) 252 253 for letter in self.manpage_domains: 254 if len(self.manpage_domains[letter]): 255 fd.write(""" 256<a href=#%s_domain>%s</a> 257 """ % (letter, letter)) 258 259 fd.write(""" 260</td> 261</tr></table> 262<pre> 263""") 264 domainname_body = "" 265 for letter in self.manpage_domains: 266 if len(self.manpage_domains[letter]): 267 domainname_body += "<p>" 268 for r in self.manpage_domains[letter]: 269 domainname = r.rsplit("_selinux", 1)[0] 270 domainname_body += "<a name=%s_domain></a><a href=%s.html>%s_selinux(8)</a> - Security Enhanced Linux Policy for the %s SELinux processes\n" % (letter, domainname, domainname, domainname) 271 272 fd.write("""%s 273</pre> 274</body> 275</html> 276""" % domainname_body) 277 278 fd.close() 279 print("%s has been created" % html) 280 281 def _gen_css(self): 282 style_css = self.old_path + "style.css" 283 fd = open(style_css, 'w') 284 fd.write(""" 285html, body { 286 background-color: #fcfcfc; 287 font-family: arial, sans-serif; 288 font-size: 110%; 289 color: #333; 290} 291 292h1, h2, h3, h4, h5, h5 { 293 color: #2d7c0b; 294 font-family: arial, sans-serif; 295 margin-top: 25px; 296} 297 298a { 299 color: #336699; 300 text-decoration: none; 301} 302 303a:visited { 304 color: #4488bb; 305} 306 307a:hover, a:focus, a:active { 308 color: #07488A; 309 text-decoration: none; 310} 311 312a.func { 313 color: red; 314 text-decoration: none; 315} 316a.file { 317 color: red; 318 text-decoration: none; 319} 320 321pre.code { 322 background-color: #f4f0f4; 323// font-family: monospace, courier; 324 font-size: 110%; 325 margin-left: 0px; 326 margin-right: 60px; 327 padding-top: 5px; 328 padding-bottom: 5px; 329 padding-left: 8px; 330 padding-right: 8px; 331 border: 1px solid #AADDAA; 332} 333 334.url { 335 font-family: serif; 336 font-style: italic; 337 color: #440064; 338} 339""") 340 341 fd.close() 342 print("%s has been created" % style_css) 343 344 345class ManPage: 346 347 """ 348 Generate a Manpage on an SELinux domain in the specified path 349 """ 350 modules_dict = None 351 enabled_str = ["Disabled", "Enabled"] 352 manpage_domains = [] 353 manpage_roles = [] 354 355 def __init__(self, domainname, path="/tmp", root="/", source_files=False, html=False): 356 self.html = html 357 self.source_files = source_files 358 self.root = root 359 self.portrecs = sepolicy.gen_port_dict()[0] 360 self.domains = gen_domains() 361 self.all_domains = sepolicy.get_all_domains() 362 self.all_attributes = sepolicy.get_all_attributes() 363 self.all_bools = sepolicy.get_all_bools() 364 self.all_port_types = sepolicy.get_all_port_types() 365 self.all_roles = sepolicy.get_all_roles() 366 self.all_users = get_all_users_info()[0] 367 self.all_users_range = get_all_users_info()[1] 368 self.all_file_types = sepolicy.get_all_file_types() 369 self.role_allows = sepolicy.get_all_role_allows() 370 self.types = _gen_types() 371 372 if self.source_files: 373 self.fcpath = self.root + "file_contexts" 374 else: 375 self.fcpath = self.root + selinux.selinux_file_context_path() 376 377 self.fcdict = sepolicy.get_fcdict(self.fcpath) 378 379 os.makedirs(path, exist_ok=True) 380 381 self.path = path 382 383 if self.source_files: 384 self.xmlpath = self.root + "policy.xml" 385 else: 386 self.xmlpath = self.root + "/usr/share/selinux/devel/policy.xml" 387 self.booleans_dict = sepolicy.gen_bool_dict(self.xmlpath) 388 389 self.domainname, self.short_name = sepolicy.gen_short_name(domainname) 390 391 self.type = self.domainname + "_t" 392 self._gen_bools() 393 self.man_page_path = "%s/%s_selinux.8" % (path, self.domainname) 394 self.fd = open(self.man_page_path, 'w') 395 if self.domainname + "_r" in self.all_roles: 396 self.__gen_user_man_page() 397 if self.html: 398 self.manpage_roles.append(self.man_page_path) 399 else: 400 if self.html: 401 self.manpage_domains.append(self.man_page_path) 402 self.__gen_man_page() 403 self.fd.close() 404 405 for k in equiv_dict.keys(): 406 if k == self.domainname: 407 for alias in equiv_dict[k]: 408 self.__gen_man_page_link(alias) 409 410 def _gen_bools(self): 411 self.bools = [] 412 self.domainbools = [] 413 types = [self.type] 414 if self.domainname in equiv_dict: 415 for t in equiv_dict[self.domainname]: 416 if t + "_t" in self.all_domains: 417 types.append(t + "_t") 418 419 for t in types: 420 domainbools, bools = sepolicy.get_bools(t) 421 self.bools += bools 422 self.domainbools += domainbools 423 424 self.bools.sort() 425 self.domainbools.sort() 426 427 def get_man_page_path(self): 428 return self.man_page_path 429 430 def __gen_user_man_page(self): 431 self.role = self.domainname + "_r" 432 if not self.modules_dict: 433 self.modules_dict = gen_modules_dict(self.xmlpath) 434 435 try: 436 self.desc = self.modules_dict[self.domainname] 437 except: 438 self.desc = "%s user role" % self.domainname 439 440 if self.domainname in self.all_users: 441 self.attributes = next(sepolicy.info(sepolicy.TYPE, (self.type)))["attributes"] 442 self._user_header() 443 self._user_attribute() 444 self._can_sudo() 445 self._xwindows_login() 446 # until a new policy build with login_userdomain attribute 447 #self.terminal_login() 448 self._network() 449 self._booleans() 450 self._home_exec() 451 self._transitions() 452 else: 453 self._role_header() 454 self._booleans() 455 456 self._port_types() 457 self._mcs_types() 458 self._writes() 459 self._footer() 460 461 def __gen_man_page_link(self, alias): 462 path = "%s/%s_selinux.8" % (self.path, alias) 463 self.fd = open("%s/%s_selinux.8" % (self.path, alias), 'w') 464 self.fd.write(".so man8/%s_selinux.8" % self.domainname) 465 self.fd.close() 466 print(path) 467 468 def __gen_man_page(self): 469 self.anon_list = [] 470 471 self.attributes = {} 472 self.ptypes = [] 473 self._get_ptypes() 474 475 for domain_type in self.ptypes: 476 try: 477 if typealias_types[domain_type]: 478 fd = self.fd 479 man_page_path = self.man_page_path 480 for t in typealias_types[domain_type]: 481 self._typealias_gen_man(t) 482 self.fd = fd 483 self.man_page_path = man_page_path 484 except KeyError: 485 continue 486 self.attributes[domain_type] = next(sepolicy.info(sepolicy.TYPE, ("%s") % domain_type))["attributes"] 487 488 self._header() 489 self._entrypoints() 490 self._process_types() 491 self._mcs_types() 492 self._booleans() 493 self._nsswitch_domain() 494 self._port_types() 495 self._writes() 496 self._file_context() 497 self._public_content() 498 self._footer() 499 500 def _get_ptypes(self): 501 for f in self.all_domains: 502 if f.startswith(self.short_name) or f.startswith(self.domainname): 503 self.ptypes.append(f) 504 505 def _typealias_gen_man(self, t): 506 self.man_page_path = "%s/%s_selinux.8" % (self.path, t[:-2]) 507 self.ports = [] 508 self.booltext = "" 509 self.fd = open(self.man_page_path, 'w') 510 self._typealias(t[:-2]) 511 self._footer() 512 self.fd.close() 513 514 def _typealias(self,typealias): 515 self.fd.write('.TH "%(typealias)s_selinux" "8" "%(date)s" "%(typealias)s" "SELinux Policy %(typealias)s"' 516 % {'typealias':typealias, 'date': man_date}) 517 self.fd.write(r""" 518.SH "NAME" 519%(typealias)s_selinux \- Security Enhanced Linux Policy for the %(typealias)s processes 520.SH "DESCRIPTION" 521 522%(typealias)s_t SELinux domain type is now associated with %(domainname)s domain type (%(domainname)s_t). 523""" % {'typealias':typealias, 'domainname':self.domainname}) 524 525 self.fd.write(r""" 526Please see 527 528.B %(domainname)s_selinux 529 530man page for more details. 531""" % {'domainname':self.domainname}) 532 533 def _header(self): 534 self.fd.write('.TH "%(domainname)s_selinux" "8" "%(date)s" "%(domainname)s" "SELinux Policy %(domainname)s"' 535 % {'domainname': self.domainname, 'date': man_date}) 536 self.fd.write(r""" 537.SH "NAME" 538%(domainname)s_selinux \- Security Enhanced Linux Policy for the %(domainname)s processes 539.SH "DESCRIPTION" 540 541Security-Enhanced Linux secures the %(domainname)s processes via flexible mandatory access control. 542 543The %(domainname)s processes execute with the %(domainname)s_t SELinux type. You can check if you have these processes running by executing the \fBps\fP command with the \fB\-Z\fP qualifier. 544 545For example: 546 547.B ps -eZ | grep %(domainname)s_t 548 549""" % {'domainname': self.domainname}) 550 551 def _format_boolean_desc(self, b): 552 desc = self.booleans_dict[b][2][0].lower() + self.booleans_dict[b][2][1:] 553 if desc[-1] == ".": 554 desc = desc[:-1] 555 return desc 556 557 def _gen_bool_text(self): 558 booltext = "" 559 for b, enabled in self.domainbools + self.bools: 560 if b.endswith("anon_write") and b not in self.anon_list: 561 self.anon_list.append(b) 562 else: 563 if b not in self.booleans_dict: 564 continue 565 booltext += """ 566.PP 567If you want to %s, you must turn on the %s boolean. %s by default. 568 569.EX 570.B setsebool -P %s 1 571 572.EE 573""" % (self._format_boolean_desc(b), b, self.enabled_str[enabled], b) 574 return booltext 575 576 def _booleans(self): 577 self.booltext = self._gen_bool_text() 578 579 if self.booltext != "": 580 self.fd.write(""" 581.SH BOOLEANS 582SELinux policy is customizable based on least access required. %s policy is extremely flexible and has several booleans that allow you to manipulate the policy and run %s with the tightest access possible. 583 584""" % (self.domainname, self.domainname)) 585 586 self.fd.write(self.booltext) 587 588 def _nsswitch_domain(self): 589 nsswitch_types = [] 590 nsswitch_booleans = ['authlogin_nsswitch_use_ldap', 'kerberos_enabled'] 591 nsswitchbooltext = "" 592 for k in self.attributes.keys(): 593 if "nsswitch_domain" in self.attributes[k]: 594 nsswitch_types.append(k) 595 596 if len(nsswitch_types): 597 self.fd.write(""" 598.SH NSSWITCH DOMAIN 599""") 600 for b in nsswitch_booleans: 601 nsswitchbooltext += """ 602.PP 603If you want to %s for the %s, you must turn on the %s boolean. 604 605.EX 606.B setsebool -P %s 1 607.EE 608""" % (self._format_boolean_desc(b), (", ".join(nsswitch_types)), b, b) 609 610 self.fd.write(nsswitchbooltext) 611 612 def _process_types(self): 613 if len(self.ptypes) == 0: 614 return 615 self.fd.write(r""" 616.SH PROCESS TYPES 617SELinux defines process types (domains) for each process running on the system 618.PP 619You can see the context of a process using the \fB\-Z\fP option to \fBps\bP 620.PP 621Policy governs the access confined processes have to files. 622SELinux %(domainname)s policy is very flexible allowing users to setup their %(domainname)s processes in as secure a method as possible. 623.PP 624The following process types are defined for %(domainname)s: 625""" % {'domainname': self.domainname}) 626 self.fd.write(""" 627.EX 628.B %s 629.EE""" % ", ".join(self.ptypes)) 630 self.fd.write(""" 631.PP 632Note: 633.B semanage permissive -a %(domainname)s_t 634can be used to make the process type %(domainname)s_t permissive. SELinux does not deny access to permissive process types, but the AVC (SELinux denials) messages are still generated. 635""" % {'domainname': self.domainname}) 636 637 def _port_types(self): 638 self.ports = [] 639 for f in self.all_port_types: 640 if f.startswith(self.short_name) or f.startswith(self.domainname): 641 self.ports.append(f) 642 643 if len(self.ports) == 0: 644 return 645 self.fd.write(""" 646.SH PORT TYPES 647SELinux defines port types to represent TCP and UDP ports. 648.PP 649You can see the types associated with a port by using the following command: 650 651.B semanage port -l 652 653.PP 654Policy governs the access confined processes have to these ports. 655SELinux %(domainname)s policy is very flexible allowing users to setup their %(domainname)s processes in as secure a method as possible. 656.PP 657The following port types are defined for %(domainname)s:""" % {'domainname': self.domainname}) 658 659 for p in self.ports: 660 self.fd.write(""" 661 662.EX 663.TP 5 664.B %s 665.TP 10 666.EE 667""" % p) 668 once = True 669 for prot in ("tcp", "udp"): 670 if (p, prot) in self.portrecs: 671 if once: 672 self.fd.write(""" 673 674Default Defined Ports:""") 675 once = False 676 self.fd.write(r""" 677%s %s 678.EE""" % (prot, ",".join(self.portrecs[(p, prot)]))) 679 680 def _file_context(self): 681 flist = [] 682 mpaths = [] 683 for f in self.all_file_types: 684 if f.startswith(self.domainname): 685 flist.append(f) 686 if f in self.fcdict: 687 mpaths = mpaths + self.fcdict[f]["regex"] 688 if len(mpaths) == 0: 689 return 690 mpaths.sort() 691 mdirs = {} 692 for mp in mpaths: 693 found = False 694 for md in mdirs: 695 if mp.startswith(md): 696 mdirs[md].append(mp) 697 found = True 698 break 699 if not found: 700 for e in equiv_dirs: 701 if mp.startswith(e) and mp.endswith('(/.*)?'): 702 mdirs[mp[:-6]] = [] 703 break 704 705 equiv = [] 706 for m in mdirs: 707 if len(mdirs[m]) > 0: 708 equiv.append(m) 709 710 self.fd.write(r""" 711.SH FILE CONTEXTS 712SELinux requires files to have an extended attribute to define the file type. 713.PP 714You can see the context of a file using the \fB\-Z\fP option to \fBls\bP 715.PP 716Policy governs the access confined processes have to these files. 717SELinux %(domainname)s policy is very flexible allowing users to setup their %(domainname)s processes in as secure a method as possible. 718.PP 719""" % {'domainname': self.domainname}) 720 721 if len(equiv) > 0: 722 self.fd.write(r""" 723.PP 724.B EQUIVALENCE DIRECTORIES 725""") 726 for e in equiv: 727 self.fd.write(r""" 728.PP 729%(domainname)s policy stores data with multiple different file context types under the %(equiv)s directory. If you would like to store the data in a different directory you can use the semanage command to create an equivalence mapping. If you wanted to store this data under the /srv directory you would execute the following command: 730.PP 731.B semanage fcontext -a -e %(equiv)s /srv/%(alt)s 732.br 733.B restorecon -R -v /srv/%(alt)s 734.PP 735""" % {'domainname': self.domainname, 'equiv': e, 'alt': e.split('/')[-1]}) 736 737 self.fd.write(r""" 738.PP 739.B STANDARD FILE CONTEXT 740 741SELinux defines the file context types for the %(domainname)s, if you wanted to 742store files with these types in a different paths, you need to execute the semanage command to specify alternate labeling and then use restorecon to put the labels on disk. 743 744.B semanage fcontext -a -t %(type)s '/srv/%(domainname)s/content(/.*)?' 745.br 746.B restorecon -R -v /srv/my%(domainname)s_content 747 748Note: SELinux often uses regular expressions to specify labels that match multiple files. 749""" % {'domainname': self.domainname, "type": flist[0]}) 750 751 self.fd.write(r""" 752.I The following file types are defined for %(domainname)s: 753""" % {'domainname': self.domainname}) 754 flist.sort() 755 for f in flist: 756 self.fd.write(""" 757 758.EX 759.PP 760.B %s 761.EE 762 763- %s 764""" % (f, sepolicy.get_description(f))) 765 766 if f in self.fcdict: 767 plural = "" 768 if len(self.fcdict[f]["regex"]) > 1: 769 plural = "s" 770 self.fd.write(""" 771.br 772.TP 5 773Path%s: 774%s""" % (plural, self.fcdict[f]["regex"][0])) 775 for x in self.fcdict[f]["regex"][1:]: 776 self.fd.write(", %s" % x) 777 778 self.fd.write(""" 779 780.PP 781Note: File context can be temporarily modified with the chcon command. If you want to permanently change the file context you need to use the 782.B semanage fcontext 783command. This will modify the SELinux labeling database. You will need to use 784.B restorecon 785to apply the labels. 786""") 787 788 def _see_also(self): 789 ret = "" 790 for d in self.domains: 791 if d == self.domainname: 792 continue 793 if d.startswith(self.short_name): 794 ret += ", %s_selinux(8)" % d 795 if d.startswith(self.domainname + "_"): 796 ret += ", %s_selinux(8)" % d 797 self.fd.write(ret) 798 799 def _public_content(self): 800 if len(self.anon_list) > 0: 801 self.fd.write(""" 802.SH SHARING FILES 803If you want to share files with multiple domains (Apache, FTP, rsync, Samba), you can set a file context of public_content_t and public_content_rw_t. These context allow any of the above domains to read the content. If you want a particular domain to write to the public_content_rw_t domain, you must set the appropriate boolean. 804.TP 805Allow %(domainname)s servers to read the /var/%(domainname)s directory by adding the public_content_t file type to the directory and by restoring the file type. 806.PP 807.B 808semanage fcontext -a -t public_content_t "/var/%(domainname)s(/.*)?" 809.br 810.B restorecon -F -R -v /var/%(domainname)s 811.pp 812.TP 813Allow %(domainname)s servers to read and write /var/%(domainname)s/incoming by adding the public_content_rw_t type to the directory and by restoring the file type. You also need to turn on the %(domainname)s_anon_write boolean. 814.PP 815.B 816semanage fcontext -a -t public_content_rw_t "/var/%(domainname)s/incoming(/.*)?" 817.br 818.B restorecon -F -R -v /var/%(domainname)s/incoming 819.br 820.B setsebool -P %(domainname)s_anon_write 1 821""" % {'domainname': self.domainname}) 822 for b in self.anon_list: 823 desc = self.booleans_dict[b][2][0].lower() + self.booleans_dict[b][2][1:] 824 self.fd.write(""" 825.PP 826If you want to %s, you must turn on the %s boolean. 827 828.EX 829.B setsebool -P %s 1 830.EE 831""" % (desc, b, b)) 832 833 def _footer(self): 834 self.fd.write(""" 835.SH "COMMANDS" 836.B semanage fcontext 837can also be used to manipulate default file context mappings. 838.PP 839.B semanage permissive 840can also be used to manipulate whether or not a process type is permissive. 841.PP 842.B semanage module 843can also be used to enable/disable/install/remove policy modules. 844""") 845 846 if len(self.ports) > 0: 847 self.fd.write(""" 848.B semanage port 849can also be used to manipulate the port definitions 850""") 851 852 if self.booltext != "": 853 self.fd.write(""" 854.B semanage boolean 855can also be used to manipulate the booleans 856""") 857 858 self.fd.write(""" 859.PP 860.B system-config-selinux 861is a GUI tool available to customize SELinux policy settings. 862 863.SH AUTHOR 864This manual page was auto-generated using 865.B "sepolicy manpage". 866 867.SH "SEE ALSO" 868selinux(8), %s(8), semanage(8), restorecon(8), chcon(1), sepolicy(8)""" % (self.domainname)) 869 870 if self.booltext != "": 871 self.fd.write(", setsebool(8)") 872 873 self._see_also() 874 875 def _valid_write(self, check, attributes): 876 if check in [self.type, "domain"]: 877 return False 878 if check.endswith("_t"): 879 for a in attributes: 880 if a in self.types[check]: 881 return False 882 return True 883 884 def _entrypoints(self): 885 entrypoints = [x['target'] for x in filter(lambda y: 886 y['source'] == self.type and y['class'] == 'file' and 'entrypoint' in y['permlist'], 887 sepolicy.get_all_allow_rules() 888 )] 889 890 if len(entrypoints) == 0: 891 return 892 893 self.fd.write(""" 894.SH "ENTRYPOINTS" 895""") 896 if len(entrypoints) > 1: 897 entrypoints_str = "\\fB%s\\fP file types" % ", ".join(entrypoints) 898 else: 899 entrypoints_str = "\\fB%s\\fP file type" % entrypoints[0] 900 901 self.fd.write(""" 902The %s_t SELinux type can be entered via the %s. 903 904The default entrypoint paths for the %s_t domain are the following: 905""" % (self.domainname, entrypoints_str, self.domainname)) 906 if "bin_t" in entrypoints: 907 entrypoints.remove("bin_t") 908 self.fd.write(""" 909All executables with the default executable label, usually stored in /usr/bin and /usr/sbin.""") 910 911 paths = [] 912 for entrypoint in entrypoints: 913 if entrypoint in self.fcdict: 914 paths += self.fcdict[entrypoint]["regex"] 915 916 self.fd.write(""" 917%s""" % ", ".join(paths)) 918 919 def _mcs_types(self): 920 try: 921 mcs_constrained_type = next(sepolicy.info(sepolicy.ATTRIBUTE, "mcs_constrained_type")) 922 except StopIteration: 923 return 924 if self.type not in mcs_constrained_type['types']: 925 return 926 self.fd.write (""" 927.SH "MCS Constrained" 928The SELinux process type %(type)s_t is an MCS (Multi Category Security) constrained type. Sometimes this separation is referred to as sVirt. These types are usually used for securing multi-tenant environments, such as virtualization, containers or separation of users. The tools used to launch MCS types, pick out a different MCS label for each process group. 929 930For example one process might be launched with %(type)s_t:s0:c1,c2, and another process launched with %(type)s_t:s0:c3,c4. The SELinux kernel only allows these processes can only write to content with a matching MCS label, or a MCS Label of s0. A process running with the MCS level of s0:c1,c2 is not allowed to write to content with the MCS label of s0:c3,c4 931""" % {'type': self.domainname}) 932 933 def _writes(self): 934 # add assigned attributes 935 src_list = [self.type] 936 try: 937 src_list += list(filter(lambda x: x['name'] == self.type, sepolicy.get_all_types_info()))[0]['attributes'] 938 except: 939 pass 940 941 permlist = list(filter(lambda x: 942 x['source'] in src_list and 943 set(['open', 'write']).issubset(x['permlist']) and 944 x['class'] == 'file', 945 sepolicy.get_all_allow_rules())) 946 if permlist is None or len(permlist) == 0: 947 return 948 949 all_writes = [] 950 attributes = ["proc_type", "sysctl_type"] 951 952 for i in permlist: 953 if self._valid_write(i['target'], attributes): 954 if i['target'] not in all_writes: 955 all_writes.append(i['target']) 956 957 if len(all_writes) == 0: 958 return 959 self.fd.write(""" 960.SH "MANAGED FILES" 961""") 962 self.fd.write(""" 963The SELinux process type %s_t can manage files labeled with the following file types. The paths listed are the default paths for these file types. Note the processes UID still need to have DAC permissions. 964""" % self.domainname) 965 966 all_writes.sort() 967 if "file_type" in all_writes: 968 all_writes = ["file_type"] 969 for f in all_writes: 970 self.fd.write(""" 971.br 972.B %s 973 974""" % f) 975 if f in self.fcdict: 976 for path in self.fcdict[f]["regex"]: 977 self.fd.write("""\t%s 978.br 979""" % path) 980 981 def _get_users_range(self): 982 if self.domainname in self.all_users_range: 983 return self.all_users_range[self.domainname] 984 return "s0" 985 986 def _user_header(self): 987 self.fd.write('.TH "%(type)s_selinux" "8" "%(type)s" "[email protected]" "%(type)s SELinux Policy documentation"' 988 % {'type': self.domainname}) 989 990 self.fd.write(r""" 991.SH "NAME" 992%(user)s_u \- \fB%(desc)s\fP - Security Enhanced Linux Policy 993 994.SH DESCRIPTION 995 996\fB%(user)s_u\fP is an SELinux User defined in the SELinux 997policy. SELinux users have default roles, \fB%(user)s_r\fP. The 998default role has a default type, \fB%(user)s_t\fP, associated with it. 999 1000The SELinux user will usually login to a system with a context that looks like: 1001 1002.B %(user)s_u:%(user)s_r:%(user)s_t:%(range)s 1003 1004Linux users are automatically assigned an SELinux users at login. 1005Login programs use the SELinux User to assign initial context to the user's shell. 1006 1007SELinux policy uses the context to control the user's access. 1008 1009By default all users are assigned to the SELinux user via the \fB__default__\fP flag 1010 1011On Targeted policy systems the \fB__default__\fP user is assigned to the \fBunconfined_u\fP SELinux user. 1012 1013You can list all Linux User to SELinux user mapping using: 1014 1015.B semanage login -l 1016 1017If you wanted to change the default user mapping to use the %(user)s_u user, you would execute: 1018 1019.B semanage login -m -s %(user)s_u __default__ 1020 1021""" % {'desc': self.desc, 'user': self.domainname, 'range': self._get_users_range()}) 1022 1023 if "login_userdomain" in self.attributes and "login_userdomain" in self.all_attributes: 1024 self.fd.write(""" 1025If you want to map the one Linux user (joe) to the SELinux user %(user)s, you would execute: 1026 1027.B $ semanage login -a -s %(user)s_u joe 1028 1029""" % {'user': self.domainname}) 1030 1031 def _can_sudo(self): 1032 sudotype = "%s_sudo_t" % self.domainname 1033 self.fd.write(""" 1034.SH SUDO 1035""") 1036 if sudotype in self.types: 1037 role = self.domainname + "_r" 1038 self.fd.write(""" 1039The SELinux user %(user)s can execute sudo. 1040 1041You can set up sudo to allow %(user)s to transition to an administrative domain: 1042 1043Add one or more of the following record to sudoers using visudo. 1044 1045""" % {'user': self.domainname}) 1046 for adminrole in self.role_allows[role]: 1047 self.fd.write(""" 1048USERNAME ALL=(ALL) ROLE=%(admin)s_r TYPE=%(admin)s_t COMMAND 1049.br 1050sudo will run COMMAND as %(user)s_u:%(admin)s_r:%(admin)s_t:LEVEL 1051""" % {'admin': adminrole[:-2], 'user': self.domainname}) 1052 1053 self.fd.write(""" 1054You might also need to add one or more of these new roles to your SELinux user record. 1055 1056List the SELinux roles your SELinux user can reach by executing: 1057 1058.B $ semanage user -l |grep selinux_name 1059 1060Modify the roles list and add %(user)s_r to this list. 1061 1062.B $ semanage user -m -R '%(roles)s' %(user)s_u 1063 1064For more details you can see semanage man page. 1065 1066""" % {'user': self.domainname, "roles": " ".join([role] + self.role_allows[role])}) 1067 else: 1068 self.fd.write(""" 1069The SELinux type %s_t is not allowed to execute sudo. 1070""" % self.domainname) 1071 1072 def _user_attribute(self): 1073 self.fd.write(""" 1074.SH USER DESCRIPTION 1075""") 1076 if "unconfined_usertype" in self.attributes: 1077 self.fd.write(""" 1078The SELinux user %s_u is an unconfined user. It means that a mapped Linux user to this SELinux user is supposed to be allow all actions. 1079""" % self.domainname) 1080 1081 if "unpriv_userdomain" in self.attributes: 1082 self.fd.write(""" 1083The SELinux user %s_u is defined in policy as a unprivileged user. SELinux prevents unprivileged users from doing administration tasks without transitioning to a different role. 1084""" % self.domainname) 1085 1086 if "admindomain" in self.attributes: 1087 self.fd.write(""" 1088The SELinux user %s_u is an admin user. It means that a mapped Linux user to this SELinux user is intended for administrative actions. Usually this is assigned to a root Linux user. 1089""" % self.domainname) 1090 1091 def _xwindows_login(self): 1092 if "x_domain" in self.all_attributes: 1093 self.fd.write(""" 1094.SH X WINDOWS LOGIN 1095""") 1096 if "x_domain" in self.attributes: 1097 self.fd.write(""" 1098The SELinux user %s_u is able to X Windows login. 1099""" % self.domainname) 1100 else: 1101 self.fd.write(""" 1102The SELinux user %s_u is not able to X Windows login. 1103""" % self.domainname) 1104 1105 def _terminal_login(self): 1106 if "login_userdomain" in self.all_attributes: 1107 self.fd.write(""" 1108.SH TERMINAL LOGIN 1109""") 1110 if "login_userdomain" in self.attributes: 1111 self.fd.write(""" 1112The SELinux user %s_u is able to terminal login. 1113""" % self.domainname) 1114 else: 1115 self.fd.write(""" 1116The SELinux user %s_u is not able to terminal login. 1117""" % self.domainname) 1118 1119 def _network(self): 1120 from sepolicy import network 1121 self.fd.write(""" 1122.SH NETWORK 1123""") 1124 for net in ("tcp", "udp"): 1125 portdict = network.get_network_connect(self.type, net, "name_bind") 1126 if len(portdict) > 0: 1127 self.fd.write(""" 1128.TP 1129The SELinux user %s_u is able to listen on the following %s ports. 1130""" % (self.domainname, net)) 1131 for p in portdict: 1132 for t, ports in portdict[p]: 1133 self.fd.write(""" 1134.B %s 1135""" % ",".join(ports)) 1136 portdict = network.get_network_connect(self.type, "tcp", "name_connect") 1137 if len(portdict) > 0: 1138 self.fd.write(""" 1139.TP 1140The SELinux user %s_u is able to connect to the following tcp ports. 1141""" % (self.domainname)) 1142 for p in portdict: 1143 for t, ports in portdict[p]: 1144 self.fd.write(""" 1145.B %s 1146""" % ",".join(ports)) 1147 1148 def _home_exec(self): 1149 permlist = list(filter(lambda x: 1150 x['source'] == self.type and 1151 x['target'] == 'user_home_type' and 1152 x['class'] == 'file' and 1153 set(['ioctl', 'read', 'getattr', 'execute', 'execute_no_trans', 'open']).issubset(set(x['permlist'])), 1154 sepolicy.get_all_allow_rules())) 1155 self.fd.write(""" 1156.SH HOME_EXEC 1157""") 1158 if permlist is not None: 1159 self.fd.write(""" 1160The SELinux user %s_u is able execute home content files. 1161""" % self.domainname) 1162 1163 else: 1164 self.fd.write(""" 1165The SELinux user %s_u is not able execute home content files. 1166""" % self.domainname) 1167 1168 def _transitions(self): 1169 self.fd.write(r""" 1170.SH TRANSITIONS 1171 1172Three things can happen when %(type)s attempts to execute a program. 1173 1174\fB1.\fP SELinux Policy can deny %(type)s from executing the program. 1175 1176.TP 1177 1178\fB2.\fP SELinux Policy can allow %(type)s to execute the program in the current user type. 1179 1180Execute the following to see the types that the SELinux user %(type)s can execute without transitioning: 1181 1182.B sesearch -A -s %(type)s -c file -p execute_no_trans 1183 1184.TP 1185 1186\fB3.\fP SELinux can allow %(type)s to execute the program and transition to a new type. 1187 1188Execute the following to see the types that the SELinux user %(type)s can execute and transition: 1189 1190.B $ sesearch -A -s %(type)s -c process -p transition 1191 1192""" % {'type': self.type}) 1193 1194 def _role_header(self): 1195 self.fd.write('.TH "%(user)s_selinux" "8" "%(user)s" "[email protected]" "%(user)s SELinux Policy documentation"' 1196 % {'user': self.domainname}) 1197 1198 self.fd.write(r""" 1199.SH "NAME" 1200%(user)s_r \- \fB%(desc)s\fP - Security Enhanced Linux Policy 1201 1202.SH DESCRIPTION 1203 1204SELinux supports Roles Based Access Control (RBAC), some Linux roles are login roles, while other roles need to be transition into. 1205 1206.I Note: 1207Examples in this man page will use the 1208.B staff_u 1209SELinux user. 1210 1211Non login roles are usually used for administrative tasks. For example, tasks that require root privileges. Roles control which types a user can run processes with. Roles often have default types assigned to them. 1212 1213The default type for the %(user)s_r role is %(user)s_t. 1214 1215The 1216.B newrole 1217program to transition directly to this role. 1218 1219.B newrole -r %(user)s_r -t %(user)s_t 1220 1221.B sudo 1222is the preferred method to do transition from one role to another. You setup sudo to transition to %(user)s_r by adding a similar line to the /etc/sudoers file. 1223 1224USERNAME ALL=(ALL) ROLE=%(user)s_r TYPE=%(user)s_t COMMAND 1225 1226.br 1227sudo will run COMMAND as staff_u:%(user)s_r:%(user)s_t:LEVEL 1228 1229When using a non login role, you need to setup SELinux so that your SELinux user can reach %(user)s_r role. 1230 1231Execute the following to see all of the assigned SELinux roles: 1232 1233.B semanage user -l 1234 1235You need to add %(user)s_r to the staff_u user. You could setup the staff_u user to be able to use the %(user)s_r role with a command like: 1236 1237.B $ semanage user -m -R 'staff_r system_r %(user)s_r' staff_u 1238 1239""" % {'desc': self.desc, 'user': self.domainname}) 1240 troles = [] 1241 for i in self.role_allows: 1242 if self.domainname + "_r" in self.role_allows[i]: 1243 troles.append(i) 1244 if len(troles) > 0: 1245 plural = "" 1246 if len(troles) > 1: 1247 plural = "s" 1248 1249 self.fd.write(""" 1250 1251SELinux policy also controls which roles can transition to a different role. 1252You can list these rules using the following command. 1253 1254.B search --role_allow 1255 1256SELinux policy allows the %s role%s can transition to the %s_r role. 1257 1258""" % (", ".join(troles), plural, self.domainname)) 1259