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