File generate_spec.rb of Package smar-apparmor-profiles (Revision 11f37346f3ff47bd3255954f60851c7f)
Currently displaying revision 11f37346f3ff47bd3255954f60851c7f , Show latest
444
1
#!/usr/bin/env ruby
2
3
# Generates spec so manual repetition is not necessary
4
5
require "fileutils"
6
require "pathname"
7
require "yaml"
8
9
require_relative "systemd_override"
10
11
def set_log_level(&block)
12
# Set to true to enable trace messages.
13
trace = false
14
#trace = true
15
16
# Set to true to enable debug messages.
17
debug = false
18
debug = true if trace
19
#debug = true
20
21
block.call trace, debug
22
end
23
24
set_log_level do |trace, debug|
25
TRACE = trace
26
DEBUG = debug
27
end
28
29
def verb(message)
30
puts "[INFO] #{message}"
31
end
32
33
def debug(message)
34
puts "[DEBUG] #{message}" if DEBUG
35
end
36
37
def trace(message)
38
puts "[TRACE] #{message}" if TRACE
39
end
40
41
class GenerateSpec
42
INSTALL_RULES_TARGET_LINE = "___INSTALL_RULES_HERE___"
43
PACKAGES_TARGET_LINE = "___PACKAGES_HERE___"
44
INPUT_FILE = "smar-apparmor-profiles.spec.in"
45
OUTPUT_FILE = "smar-apparmor-profiles.spec"
46
47
def initialize
48
@packages = load_packages
49
@systemd_override = SystemdOverride.new
50
end
51
52
def generate
53
install_line_rows = generate_install_lines
54
package_rows = generate_package_rows
55
56
lines = File.readlines INPUT_FILE
57
58
lines.each_with_index do |line, line_index|
59
line.strip!
60
61
if line == INSTALL_RULES_TARGET_LINE
62
# Replace target first to avoid infinite loop.
63
lines[line_index] = "## Generated install commands starts."
64
lines[line_index+=1] = ""
65
66
install_line_rows.each_with_index do |row, row_index|
67
lines.insert((line_index + row_index), row)
68
end
69
end
70
end
71
72
# Loop twice so that line indices are correct.
73
lines.each_with_index do |line, line_index|
74
line.strip!
75
76
if line == PACKAGES_TARGET_LINE
77
# Replace target first to avoid infinite loop.
78
lines[line_index] = "## Generated packages starts."
79
lines[line_index+=1] = ""
80
81
package_rows.each_with_index do |package, index|
82
trace "index: #{index}, package: “#{package}”"
83
lines.insert((line_index + index), package)
84
85
# Append newline after package rows.
86
line_index += 1
87
lines.insert((line_index + index), "\n")
88
end
89
end
90
end
91
92
File.open OUTPUT_FILE, "w" do |file|
93
file.puts lines
94
end
95
end
96
97
def finalize
98
@systemd_override.finalize
99
end
100
101
private def generate_install_lines
102
out = []
103
104
@packages.each do |package|
105
definition = generate_install_line package
106
107
out << definition.join("\n")
108
end
109
110
out
111
end
112
113
private def generate_install_line package
114
trace "Generating install line for #{package}"
115
116
definition = []
117
package["files"]&.each do |file|
118
definition << "mv profiles/#{file} %{buildroot}%{_sysconfdir}/apparmor.d"
119
end
120
package["local"]&.each do |file|
121
definition << "mv profiles/local/#{file} %{buildroot}%{_sysconfdir}/apparmor.d/local"
122
end
123
package["namespaces"]&.each do |file|
124
unless File.symlink? file
125
definition << "mv namespaces/#{file} %{buildroot}%{_sysconfdir}/apparmor.d/namespaces.d"
126
else
127
# If a file is a symlink, it should be added manually
128
# by the package where it is a symlink to.
129
warn "File #{file} needs to be owned manually (1)."
130
end
131
end
132
package["namespace_directories"]&.each do |directory|
133
definition << "mv namespaces/#{directory}/ %{buildroot}%{_sysconfdir}/apparmor.d/namespaces.d"
134
end
135
package["included_abstractions"]&.each do |file|
136
definition << "mv abstractions/#{file} %{buildroot}%{_sysconfdir}/apparmor.d/abstractions"
137
end
138
package["included_tunables"]&.each do |file|
139
definition << "mv tunables/#{file} %{buildroot}%{_sysconfdir}/apparmor.d/tunables"
140
end
141
package["extra_files"]&.each do |directory, files|
142
extra_files_line definition, directory, files
143
end
144
package["load_profile_by_systemd"]&.each do |service_name, profile_name|
145
@systemd_override.install_lines_for_load_profile_by_systemd definition, package, service_name, profile_name
146
end
147
148
package["in_directory"]&.each do |target, data|
149
in_directory_lines definition, target, data, parent_package: package
150
end
151
152
definition
153
end
154
155
private def in_directory_lines definition, target, package, parent_package:
156
unless package.respond_to? :each_pair
157
STDERR.puts "Syntax error in a YAML config."
158
STDERR.puts "Package “#{parent_package["name"]}” has invalid content in block “in_directory”:"
159
STDERR.puts
160
STDERR.puts package.inspect
161
STDERR.puts
162
STDERR.puts "Maybe you’re missing “files:” keyword to denote a hash?"
163
exit 20
164
end
165
package["files"]&.each do |file|
166
definition << "mv profiles/#{target}/#{file} %{buildroot}%{_sysconfdir}/apparmor.d"
167
end
168
package["local"]&.each do |file|
169
definition << "mv profiles/#{target}/local/#{file} %{buildroot}%{_sysconfdir}/apparmor.d/local"
170
end
171
package["included_abstractions"]&.each do |file|
172
definition << "mv profiles/#{target}/abstractions/#{file} %{buildroot}%{_sysconfdir}/apparmor.d/abstractions"
173
end
174
package["included_tunables"]&.each do |file|
175
definition << "mv profiles/#{target}/tunables/#{file} %{buildroot}%{_sysconfdir}/apparmor.d/tunables"
176
end
177
if package["extra_files"]
178
extra_files_line definition, "profiles/#{target}", package["extra_files"]
179
end
180
if package["rpm_scriptlets_symlinks"]
181
rpm_scriptlets_symlinks definition, package["rpm_scriptlets_symlinks"]
182
end
183
end
184
185
private def extra_files_line definition, directory, files
186
case directory
187
when "ssh"
188
directory = "profiles/security/ssh"
189
files.each do |file|
190
destination_dir = File.dirname file
191
definition << "mkdir -p '%{buildroot}%{_sysconfdir}/apparmor.d/#{destination_dir}'"
192
definition << "mv #{directory}/#{file} '%{buildroot}%{_sysconfdir}/apparmor.d/#{destination_dir}'"
193
end
194
when "gcc"
195
directory = "profiles/compilation"
196
files.each do |file|
197
destination_dir = File.dirname file
198
definition << "mkdir -p '%{buildroot}%{_sysconfdir}/apparmor.d/#{destination_dir}'"
199
definition << "mv #{directory}/#{file} '%{buildroot}%{_sysconfdir}/apparmor.d/#{destination_dir}'"
200
end
201
when "shells"
202
directory = "profiles/system/shells"
203
files.each do |file|
204
destination_dir = File.dirname file
205
definition << "mkdir -p '%{buildroot}%{_sysconfdir}/apparmor.d/#{destination_dir}'"
206
definition << "mv #{directory}/#{file} '%{buildroot}%{_sysconfdir}/apparmor.d/#{destination_dir}'"
207
end
208
else
209
add_lines = lambda do |file|
210
destination_dir = File.dirname file
211
definition << "mkdir -p '%{buildroot}%{_sysconfdir}/apparmor.d/#{destination_dir}'"
212
definition << "mv #{directory}/#{file} '%{buildroot}%{_sysconfdir}/apparmor.d/#{destination_dir}'"
213
end
214
215
# This check allows similar style to what in_directory
216
# block’s extra_files allows.
217
if files.nil?
218
file = directory
219
directory = "." # Root of the repository.
220
221
add_lines.call file
222
return
223
end
224
225
files.each do |file|
226
add_lines.call file
227
end
228
end
229
end
230
231
private def rpm_scriptlets_symlinks definition, files
232
files.each do |file|
233
rpm_scriptlets_d = "%{buildroot}%{_sysconfdir}/apparmor.d/namespaces.d/rpm-scriptlets.d"
234
profile_name = File.basename file
235
236
definition << %{ln -t '#{rpm_scriptlets_d}' -s '../../#{profile_name}'}
237
end
238
end
239
240
# Generates the package rows that are inserted to template spec.
241
private def generate_package_rows
242
out = []
243
244
@packages.each do |package|
245
definition = <<~EOF
246
# #{package["name"]}
247
%package -n #{package["name"]}-profiles
248
Summary: AppArmor profiles for #{package["name"]}
249
Supplements: #{package["name"]}
250
BuildArch: noarch
251
EOF
252
253
package["provides"]&.each do |provided|
254
definition += "Provides: #{provided}-profiles\n"
255
end
256
257
package["abstractions"]&.each do |abstraction|
258
definition += "Requires: smar-apparmor-profiles-#{abstraction}-abstractions\n"
259
end
260
261
package["supplements"]&.each do |supplement|
262
definition += "Supplements: #{supplement}\n"
263
end
264
265
package["requires"]&.each do |required|
266
name = "#{required}-profiles"
267
name = "smar-apparmor-profiles-common" if required == "common"
268
definition += "Requires: #{name}\n"
269
end
270
271
package["recommends"]&.each do |recommended|
272
name = "#{recommended}-profiles"
273
name = "smar-apparmor-profiles-common" if recommended == "common"
274
definition += "Recommends: #{name}\n"
275
end
276
277
package["suggests"]&.each do |suggested|
278
name = "#{suggested}-profiles"
279
name = "smar-apparmor-profiles-common" if suggested == "common"
280
definition += "Suggests: #{name}\n"
281
end
282
283
file_list_clause = %|%files -n #{package["name"]}-profiles|
284
if package["namespace_directories"] && !package["namespace_directories"].empty?
285
raise "This is for now only hacked for rpm-scriptlets. I think." unless package["name"] == "rpm"
286
file_list_clause = %|#{file_list_clause} -f namespace_files.#{package["name"]}|
287
end
288
289
definition += <<~EOF
290
%description -n #{package["name"]}-profiles
291
AppArmor profiles for #{package["name"]} from project smar-apparmor-profiles.
292
293
#{file_list_clause}
294
EOF
295
296
297
definition = generate_file_rows definition, package
298
299
out << definition
300
end
301
302
out
303
end
304
305
private def generate_file_rows definition, package
306
package["files"]&.each do |file|
307
basename = File.basename file
308
definition += "%config %{_sysconfdir}/apparmor.d/#{basename}\n"
309
end
310
311
package["local"]&.each do |file|
312
basename = File.basename file
313
definition += "%config(noreplace) %{_sysconfdir}/apparmor.d/local/#{basename}\n"
314
end
315
316
package["namespaces"]&.each do |file|
317
unless File.symlink? file
318
basename = File.basename file
319
definition += "%config %{_sysconfdir}/apparmor.d/namespaces.d/#{basename}\n"
320
else
321
# If a file is a symlink, it should be added manually
322
# by the package where it is a symlink to.
323
warn "File #{file} needs to be owned manually (2)."
324
end
325
end
326
327
package["namespace_directories"]&.each do |directory|
328
basename = File.basename directory
329
definition += "%dir %{_sysconfdir}/apparmor.d/namespaces.d/#{basename}\n"
330
#definition += "%config %{_sysconfdir}/apparmor.d/namespaces.d/#{basename}\n"
331
end
332
333
package["included_abstractions"]&.each do |file|
334
basename = File.basename file
335
definition += "%config(noreplace) %{_sysconfdir}/apparmor.d/abstractions/#{basename}\n"
336
end
337
338
package["included_tunables"]&.each do |file|
339
basename = File.basename file
340
definition += "%config(noreplace) %{_sysconfdir}/apparmor.d/tunables/#{basename}\n"
341
end
342
343
package["extra_directories"]&.each do |directory|
344
definition += "%dir %{_sysconfdir}/apparmor.d/#{directory}\n"
345
end
346
347
package["extra_files"]&.each do |directory, files|
348
config_tag = lambda do |filename|
349
if filename[0..5] == "local/"
350
"%config(noreplace)"
351
else
352
"%config"
353
end
354
end
355
356
if files.nil?
357
file = directory
358
definition += "#{config_tag.call(file)} %{_sysconfdir}/apparmor.d/#{file}\n"
359
else
360
files.each do |file|
361
definition += "#{config_tag.call(file)} %{_sysconfdir}/apparmor.d/#{file}\n"
362
end
363
end
364
end
365
366
package["load_profile_by_systemd"]&.each do |service_name, profile_name|
367
definition += load_profile_by_systemd_for_profile service_name, profile_name
368
end
369
370
package["rpm_scriptlets_symlinks"]&.each do |file|
371
basename = File.basename file
372
definition += "%config %{_sysconfdir}/apparmor.d/namespaces.d/rpm-scriptlets.d/#{basename}\n"
373
end
374
375
package["in_directory"]&.each do |directory, hashes|
376
hashes.each do |name, values|
377
hash = { name => values }
378
definition = generate_file_rows definition, hash
379
end
380
end
381
382
definition
383
end
384
385
private def load_profile_by_systemd_for_profile service_name, profile_name
386
definition = ""
387
388
if [ "system", "system_conditional" ].include? service_name
389
profile_name.each do |service_name, profile_name|
390
if profile_name.respond_to? :each_pair
391
profile_name = profile_name["profile"]
392
end
393
394
definition += "%dir %{_unitdir}/#{service_name}.service.d\n"
395
definition += "%{_unitdir}/#{service_name}.service.d/#{profile_name}.conf\n"
396
end
397
398
return definition
399
end
400
401
#if [ "user", "user_conditional" ].include? service_name
402
if service_name == "user"
403
profile_name.each do |service_name, profile_name|
404
if profile_name.respond_to? :each_pair
405
profile_name = profile_name["profile"]
406
end
407
408
definition += "%dir %{_userunitdir}/#{service_name}.service.d\n"
409
definition += "%{_userunitdir}/#{service_name}.service.d/#{profile_name}.conf\n"
410
end
411
412
return definition
413
end
414
415
definition += "%dir %{_unitdir}/#{service_name}.service.d\n"
416
definition += "%{_unitdir}/#{service_name}.service.d/#{profile_name}.conf\n"
417
418
definition
419
end
420
421
# WARNING: This is not too safe to use, so trust the yaml files.
422
private def load_packages
423
main = YAML.load_file "main.yaml"
424
425
packages = []
426
427
main["includes"].each do |file|
428
packages += YAML.load_file(file)
429
end
430
431
packages.sort do |a, b|
432
# rpm needs to be first as some other profiles requires
433
# namespaces.d/rpm-scriptlets.d/ dir it creates.
434
next -1 if a["name"] == "rpm"
435
next 1 if b["name"] == "rpm"
436
437
a["name"] <=> b["name"]
438
end
439
end
440
end
441
442
generate_spec = GenerateSpec.new
443
generate_spec.generate
444
generate_spec.finalize