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