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