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