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