This repository was archived by the owner on Aug 31, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsemodule_replace_hosif
More file actions
executable file
·227 lines (214 loc) · 12.1 KB
/
semodule_replace_hosif
File metadata and controls
executable file
·227 lines (214 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#!/usr/bin/env python
from pathlib import Path
from re import compile,search
from sys import argv
#############
### USAGE ###
#############
def usage():
name = Path(argv[0]).name
print(f"NAME\n\t{name} - python script that replaces the default interfaces with their Hard Hat OS equivalents for the specified SELinux /path/to/filename.te\n")
print(f"SYNOPSIS\n\t{name} /path/to/hos.if /path/to/filename.te [OPTIONS]\n")
print(f"DESCRIPTION\n\t{name} is a Python script provided by the Hard Hat OS (HOS) project that replaces the default SELinux interfaces with their HOS equivalents to ensure no unnecessary permissions are granted. By default, the new contents of the /path/to/filename.te (argument 2) that contain the HOS replacements will be shown to stdout. Argument 3 can optionally be used to specify an output file, see the OPTIONS section below.\n")
print(f"OPTIONS\n\t/path/to/outfile.te\tThe output file to write the new contents of /path/to/filename.te specified in argument 2\n")
print(f"EXAMPLE\n\t1. {name} /usr/local/src/devel-selinux-interfaces/hos-dev.if ~/cat.te\n\n\t2. {name} ~/git/devel-selinux-interfaces/hos-manage.if ~/cat.te /tmp/cat.new.te\n\n\t3. {name} /home/user/devel-selinux-interfaces/hos-auth.if ./cat.te ./cat.te\n")
print("\n")
#################
### FUNCTIONS ###
#################
def args():
# Check if the user has specified the help flag (any argument)
if any([True for arg in argv if arg == '-h']):
# If so, then display the usage information
usage()
# Exit
exit(0)
try:
# Argument 1: Hard Hat OS interface file
hos_if = Path(argv[1]).resolve()
except IndexError:
# Show the usage information
usage()
# If the argument was not specified, then display an error message to stdout
print('ERROR: Argument 1: Specify the path to the Hard Hat OS interface file.')
# Exit with an error
exit(1)
try:
# Argument 2: SELinux .te file whose default interfaces will be replaced with those within the Hard Hat OS interface file
te = Path(argv[2]).resolve()
except IndexError:
# Show the usage information
usage()
# If argument 2 was not specified, then display an error message to stdout
print('ERROR: Argument 2: Specify the SELinux filename.te file whose default interfaces will be replaced with Hard Hat OS ones.')
# Exit with an error
exit(1)
try:
# Argument 3 (OPTIONAL): New SELinux .te file to write once the default interfaces are replaced
outfile = Path(argv[3]).resolve()
except IndexError:
outfile = False
# Return the files
return(hos_if, te, outfile)
def is_file(filename):
# Check if $filename is a valid file, and if so, then return True
if Path(filename).is_file(): return(True)
# Otherwise, display an error message to stdout
print(f"ERROR: Invalid file: '{filename}'")
# Exit with an error
exit(1)
def hos_interfaces(hos_if):
# Open the Hard Hat OS interface file and obtain its full contents
with open(hos_if, 'r') as f: contents = f.readlines()
# Only keep the lines that define the interface names
contents = [line.strip() for line in contents if line.strip().startswith('interface(`')]
# Define the output set containing all Hard Hat OS interface names
interfaces = set()
# Define the regular expression that will be used to only obtain the name of each interface
exp = compile('hos_[\w]+')
# Iterate through each entry within the $contents list
for entry in contents:
try:
# Obtain the Hard Hat OS interface name
name = search(exp, entry).group()
except AttributeError:
# If the regular expression $exp did not match anything for the current $entry, then display the $entry to stdout
print(f"WARN: The regular expression '{exp}' did not match anything in the current entry: '{entry}'")
# Continue to the next $entry within $contents
continue
# If the $name was defined, then add it to the output $interfaces set. Here, we remove the prefix so $interfaces will be a list of normal interfaces. This makes it easier to search later on
interfaces.add(name.replace('hos_', '').strip())
# Return the set of all Hard Hat OS interface names
return(interfaces)
def leading_whitespace(line):
try:
# Define all leading whitespace for $line
ws = search(r'^[\s]+', line).group()
except AttributeError:
# If there is no whitespace at the beginning of the $line then use nothing
ws = ''
# Return $ws
return(ws)
class replacement:
def __init__(self, te, interfaces):
# Obtain the full contents of the $te file
with open(te, 'r') as f: contents = f.readlines()
# Replace the existing interfaces with HOS ones
contents = self.default_interface(contents, interfaces)
# Replace permissions like 'chr_file_perms' for example with their corresponding HOS interfaces. This is a simple change for lines that are in the format of: 'allow something self:fifo_file fifo_file_perms;'
contents = self.permissions_simple(contents, interfaces)
# Another function to replace the permissions but this one handles permissions within curly braces
contents = self.permissions_complex(contents, interfaces)
# Define $contents as a global variable within this class so it can be returned
self.contents = contents
def __call__(self):
# Return $contents
return(self.contents)
def default_interface(self, contents, interfaces):
# Define the list that will hold the new $contents entries
new_contents = []
# Iterate through each line in the $contents list
for line in contents:
# Define $curr as the current line minus any extraneous whitespace
curr = line.strip()
# If $curr starts with one of the interfaces within the $interfaces set, then replace the default interface with the custom Hard Hat OS one
new_line = [line.replace(interface, f"hos_{interface}") for interface in interfaces if curr.startswith(f"{interface}(")]
# Check if the $replacement list is non-empty
if new_line:
# If so, then add the contents of $new_line to the $new_contents output list
new_contents = new_contents + new_line
else:
# Otherwise add the $line as-is
new_contents.append(line)
# Return the $new_contents list
return(new_contents)
def permissions_simple(self, contents, interfaces):
# For every line in $contents that has applicable HOS permissions, define a tuple that contains the index of the line within $contnets and the corresponding $interface
all_replacements = [(i, interface) for i in range(len(contents)) for interface in interfaces if contents[i].strip().endswith(f"{interface};")]
# Iterate through each tuple within $all_replacements
for tple in all_replacements:
# The index of the line within $contents
index = tple[0]
# The name of the perm interface that will be used
interface = tple[1]
# Define the original line within $contents to replace
line = contents[index]
# If the current $line does not start with 'allow' then continue to the next $tple. We only need to replace lines that will be actively used, nothing else
if not line.strip().startswith('allow'): continue
# Define the whitespace at the beginning of $line
whitespace = leading_whitespace(line)
# First split the $line via spaces and keep every entry except the first, which is the 'allow' string, and the last, which is the name of the permission that's already defined by $interface. Next, split each of these entries by the semicolon delimiter and define a list where all of the entries aren't nested
line = [entry_s for entry_l in line.split()[1:-1] for entry_s in entry_l.split(':')]
# Replace the $line within $contents by adding back the $whitespace and replacing the line with the Hard Hat OS interface for the current permission. We skup
contents[index] = f"{whitespace}hos_{interface}({', '.join(line)})\n"
# Return $contents
return(contents)
def permissions_complex(self, contents, interfaces):
# Define the output list of new $contents
new_contents = []
# Iterate through each line within $contents
for line in contents:
# Check if the current $line is not an 'allow' rule
if not line.strip().startswith('allow'):
# If so, then add the $line to the $new_contents list
new_contents.append(line)
# Continue to the next $line since there's nothing left to do
continue
# Define the whitespace at the beginning of $line
whitespace = leading_whitespace(line)
# Find all $interfaces that are located in the current $line within curly braces
matches = [interface for interface in interfaces if search(r"\{.*%s.*\}" % (interface), line)]
# Check if there are $matches, which is a list of each $interface found in the current line
if matches:
# If so, then iterate through each interface within $matches
for inf in matches:
# For the current $line, remove the current $inf from between the curl braces and then join the line back together
current_line = ' '.join([entry.strip() for entry in line.split(inf)])
# Check if the curly braces are now empty
if search('\{ \}', current_line):
# If so, then set the $current_line to an empty string
current_line = ''
else:
# Otherwise, add the $whitespace to the $current_line to reconstruct it fully
current_line = f"{whitespace}{current_line}\n"
# Break the $line into parts but ignore everything starting from the right curly brace
line_s = [entry_s_s.strip() for entry_l in line.split('{')[0:-1] for entry_s in entry_l.split(':') for entry_s_s in entry_s.split()]
# Reconstruct a completely new $line by adding the $whitespace and using the Hard Hat OS interface for the parts of the $line_s list, ignoring the first index which is just 'allow'
new_line = f"{whitespace}hos_{inf}({', '.join(line_s[1:])})\n"
# Add both the reconstructed $current_line and the $new_line to the output list
new_contents.append(f"{current_line}{whitespace}{new_line}")
else:
# If there were no $matches then add the $line as-is to $new_contents
new_contents.append(line)
# Return $new_contents
return(new_contents)
def write_contents(outfile, new_contents):
# Open the $outfile and write the $new_contents to it
with open(outfile, 'w') as f: [f.write(line) for line in new_contents]
############
### MAIN ###
############
def main(hos_if, te, outfile):
# Verify the Hard Hat OS interface file is valid
is_file(hos_if)
# Verify the SELinux policy file is valid
is_file(te)
# Obtain a set of all interface names defined within the $hos_if file
interfaces = hos_interfaces(hos_if)
# Replace all of the default interfaces within $te with their Hard Hat OS replacements whenever possible
new_contents = replacement(te, interfaces)()
# Check if an $outfile has been specified
if outfile:
# If so, then write the $new_contents to the $outfile
write_contents(outfile, new_contents)
else:
# Otherwise, display the $new_contents to stdout
print(''.join(new_contents))
#############
### START ###
#############
if __name__ == '__main__':
# Obtain user-defined arguments
[hos_if, te, outfile] = args()
# Start the main function
main(hos_if, te, outfile)