-
Notifications
You must be signed in to change notification settings - Fork 1
/
semerge.pl
executable file
·251 lines (204 loc) · 6.53 KB
/
semerge.pl
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#!/usr/bin/perl -w
# This script accepts SELinux rules via STDIN (e.g. the output of audit2allow)
# and also by referencing an existing policy file. It merges the two to produce
# an output file which contains the contents of both sources.
use Getopt::Long;
# Read in cmdline args
my $result = GetOptions (
"i|input=s" => \$inputfile,
"o|output=s" => \$outputfile,
"v|version=s" => \$version,
"n|name=s" => \$name,
"h|help" => \$help,
);
if ($help) {
print << "LABEL";
This script accepts SELinux rulesets via STDIN (e.g. the output of audit2allow) and by
reading an existing policy file. It merges, deduplicates and sorts the two inputs to
produce an output policy which contains the contents of both sources.
ARGUMENTS
=========
-i|--input Read an existing SELinux policy file.
-o|--output Write the resulting merged policy to a file. Defaults to STDOUT.
-v|--version Override the module number given to the resulting merged policy.
Defaults to incrementing whatever version number is fed in from file,
then stdin.
-n|--name Override the module name given to the resulting merged policy.
Defaults to whatever name is fed in from file, then stdin.
-h|--help Print this message
EXAMPLES
========
semerge -i existingpolicy.pp -o existingpolicy.pp
Deduplicates and alphabetises existingpolicy.pp
cat existingpolicy.pp | semerge > existingpolicy.pp
Equivalent to the above
cat /var/log/audit/audit.log | audit2allow | semerge -i existingpolicy.pp -o newpolicy.pp
Create newpolicy.pp which merges new rules from audit2allow into existingpolicy.pp
LABEL
exit;
}
# Validation of input files
# Set up some globals
my %output;
my @input_stdin;
my ($stdinname, $stdinver);
my @input_file;
my ($infilename, $infilever);
if (! -t STDIN ) {
# Read in new SELinux rules from stdin
while (<STDIN>) {
chomp;
push(@input_stdin, $_);
}
# Make sure STDIN is not blank
if ($#input_stdin >= 1) {
($stdinname, $stdinver) = get_header(@input_stdin);
# Filter the data into a nested hash for output
&sort_output(@input_stdin);
} else {
print "no input from stdin\n";
exit;
}
# Filter the data into a nested hash for output
&sort_output(@input_stdin);
}
# Read in existing policy file from -i switch
if ($inputfile) {
open(my $fh, "<", $inputfile) or die "Can't open $inputfile for reading: $!";
chomp(my @input_file = <$fh>);
close $fh;
if ($#input_file >= 1) {
($infilename, $infilever) = get_header(@input_file);
# Filter the data into a nested hash for output
&sort_output(@input_file);
} else {
print "empty file $inputfile\n";
exit;
}
}
# Figure out what module name and version to use
# Set by arg takes first precedence, then the infile, then stdin
# If we infer the version from file or stdin, we increment it
my ($modulename, $modulever);
if ($name) {
$modulename = $name;
} elsif ($infilename) {
$modulename = $infilename;
} elsif ($stdinname) {
$modulename = $stdinname;
} else {
print "Must set module name\n";
exit;
}
if ($version) {
$modulever = $version;
} elsif ($infilever) {
$modulever = &incrementver($infilever);
} elsif ($stdinver) {
$modulever = &incrementver($stdinver);
} else {
print "Must set module version\n";
exit;
}
# Collect final printable output into an array
my @finaloutput = &print_output;
# Finally output the data into file or stdout
if ($outputfile) {
# Write to file
open(my $fh, ">", $outputfile) or die "Can't open $outputfile for writing: $!";
foreach (@finaloutput) {
print $fh $_;
}
} else {
# Write to stdout
foreach (@finaloutput) {
print $_;
}
}
sub print_output {
# Format the contents of the output hash into an array, ready for printing to file or stdout
my @output;
push (@output, "module $modulename $modulever;\n\n");
push (@output, "require {\n");
# Print types
# $TYPE
# #type rpm_exec_t;
foreach my $type (sort keys %{$output{'type'}}) {
push (@output, "\ttype $type;\n");
}
push (@output, "\n");
# Print classes
# $CLASS $OBJECT
#class file { rename execute setattr read lock create ioctl execute_no_trans write getattr unlink open append };
foreach my $class (sort keys %{$output{'class'}}) {
push (@output, "\tclass $class { ");
foreach my $object (sort keys %{$output{'class'}{$class}}) {
push (@output, "$object ");
}
push (@output, "};\n");
}
push (@output, "}\n");
# Print allows
# $ALLOW $OBJECT $CLASS $PROPERTY
# #allow nagios_services_plugin_t dhcpd_state_t:file { read getattr open ioctl };
foreach my $allow (sort keys %{$output{'allow'}}) {
push (@output, "\n#============= $allow ==============\n");
foreach my $object (sort keys %{$output{'allow'}{$allow}}) {
foreach my $class (sort keys %{$output{'allow'}{$allow}{$object}}) {
push (@output, "allow $allow $object:$class { ");
foreach my $property (sort keys %{$output{'allow'}{$allow}{$object}{$class}}) {
push (@output, "$property ");
}
push (@output, "};\n");
}
}
}
return @output;
}
sub sort_output {
# Spin through an array of SELinux config and sort it into a hierarchical hash
my @input = @_;
foreach my $line (@input) {
#type rpm_exec_t;
if ($line =~ m/^\s*type (\w+)/) {
$output{'type'}{$1} = $1;
#class file rename;
} elsif ($line =~ m/^\s*class (\w+) (\w+);$/) {
$output{'class'}{$1}{$2} = $2;
#class file { rename execute setattr read lock create ioctl execute_no_trans write getattr unlink open append };
} elsif ($line =~ m/^\s*class (\w+) \{ ([\s\w]+) \};$/) {
my @arrayofclasses = split(/ /, $2);
foreach my $class (@arrayofclasses) {
$output{'class'}{$1}{$class} = $class;
}
#allow nagios_services_plugin_t dhcpd_state_t:file read;
} elsif ($line =~ m/^\s*allow (\w+) (\w+):(\w+) (\w+);$/) {
$output{'allow'}{$1}{$2}{$3}{$4} = $4;
#allow nagios_services_plugin_t dhcpd_state_t:file { read getattr open ioctl };
} elsif ($line =~ m/^\s*allow (\w+) (\w+):(\w+) \{ ([\s\w]+) \};$/) {
my @arrayofallows = split(/ /, $4);
foreach my $allow (@arrayofallows) {
$output{'allow'}{$1}{$2}{$3}{$allow} = $allow;
}
}
}
}
sub incrementver {
# Increment a decimal-separated version number
my $ver = shift;
my @verarray = split(/\./, $ver);
$verarray[$#verarray]++;
$ver = join('.', @verarray);
return $ver;
}
sub get_header {
# Look at an array containing an SELinux policy and return
# the name and version of the policy, if it exists
my @policy = @_;
my $header = shift(@policy);
chomp $header;
# module resnet-nrpe 1.45;
if ($header =~ m/^module ([a-z\-_]+) ([0-9\.]+);$/) {
return ($1, $2);
}
}