crypt_reboot 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +169 -0
- data/exe/cryptreboot +9 -0
- data/lib/basic_loader.rb +60 -0
- data/lib/crypt_reboot/boot_config.rb +24 -0
- data/lib/crypt_reboot/cli/exiter.rb +25 -0
- data/lib/crypt_reboot/cli/happy_exiter.rb +14 -0
- data/lib/crypt_reboot/cli/params/definition.rb +125 -0
- data/lib/crypt_reboot/cli/params/flattener.rb +24 -0
- data/lib/crypt_reboot/cli/params/help_generator.rb +22 -0
- data/lib/crypt_reboot/cli/params/parser.rb +29 -0
- data/lib/crypt_reboot/cli/params_parsing_executor.rb +73 -0
- data/lib/crypt_reboot/cli/sad_exiter.rb +14 -0
- data/lib/crypt_reboot/cli.rb +8 -0
- data/lib/crypt_reboot/concatenator.rb +24 -0
- data/lib/crypt_reboot/config.rb +20 -0
- data/lib/crypt_reboot/crypt_tab/deserializer.rb +28 -0
- data/lib/crypt_reboot/crypt_tab/entry.rb +39 -0
- data/lib/crypt_reboot/crypt_tab/entry_deserializer.rb +51 -0
- data/lib/crypt_reboot/crypt_tab/entry_serializer.rb +21 -0
- data/lib/crypt_reboot/crypt_tab/keyfile_locator.rb +21 -0
- data/lib/crypt_reboot/crypt_tab/luks_to_plain_converter.rb +39 -0
- data/lib/crypt_reboot/crypt_tab/serializer.rb +29 -0
- data/lib/crypt_reboot/files_generator.rb +44 -0
- data/lib/crypt_reboot/files_writer.rb +17 -0
- data/lib/crypt_reboot/gziper.rb +22 -0
- data/lib/crypt_reboot/initramfs/archiver.rb +35 -0
- data/lib/crypt_reboot/initramfs/decompressor/intolerant_decompressor.rb +65 -0
- data/lib/crypt_reboot/initramfs/decompressor/tolerant_decompressor.rb +28 -0
- data/lib/crypt_reboot/initramfs/decompressor.rb +12 -0
- data/lib/crypt_reboot/initramfs/extractor.rb +36 -0
- data/lib/crypt_reboot/initramfs/patcher.rb +47 -0
- data/lib/crypt_reboot/initramfs_patch_squeezer.rb +28 -0
- data/lib/crypt_reboot/instantiable_config.rb +52 -0
- data/lib/crypt_reboot/kexec/loader.rb +30 -0
- data/lib/crypt_reboot/kexec_patching_loader.rb +28 -0
- data/lib/crypt_reboot/lazy_config.rb +22 -0
- data/lib/crypt_reboot/luks/checker.rb +27 -0
- data/lib/crypt_reboot/luks/data.rb +37 -0
- data/lib/crypt_reboot/luks/data_fetcher.rb +30 -0
- data/lib/crypt_reboot/luks/dumper/luks_v1_parser.rb +64 -0
- data/lib/crypt_reboot/luks/dumper/luks_v2_parser.rb +62 -0
- data/lib/crypt_reboot/luks/dumper.rb +30 -0
- data/lib/crypt_reboot/luks/key_fetcher.rb +50 -0
- data/lib/crypt_reboot/luks/version_detector.rb +32 -0
- data/lib/crypt_reboot/passphrase_asker.rb +15 -0
- data/lib/crypt_reboot/patched_initramfs_generator.rb +21 -0
- data/lib/crypt_reboot/rebooter.rb +20 -0
- data/lib/crypt_reboot/runner/binary.rb +12 -0
- data/lib/crypt_reboot/runner/boolean.rb +18 -0
- data/lib/crypt_reboot/runner/generic.rb +46 -0
- data/lib/crypt_reboot/runner/lines.rb +13 -0
- data/lib/crypt_reboot/runner/no_result.rb +13 -0
- data/lib/crypt_reboot/runner/text.rb +12 -0
- data/lib/crypt_reboot/runner.rb +8 -0
- data/lib/crypt_reboot/safe_temp/directory.rb +28 -0
- data/lib/crypt_reboot/safe_temp/file_name.rb +23 -0
- data/lib/crypt_reboot/safe_temp/mounter.rb +33 -0
- data/lib/crypt_reboot/single_assign_restricted_map.rb +26 -0
- data/lib/crypt_reboot/version.rb +5 -0
- data/lib/crypt_reboot.rb +17 -0
- metadata +138 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
# Global configuration singleton
|
7
|
+
class Config < InstantiableConfig
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def method_missing(method_name, *args, **kwargs, &block)
|
12
|
+
instance.respond_to?(method_name) ? instance.send(method_name, *args, **kwargs, &block) : super
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_to_missing?(name, *_, **_)
|
16
|
+
instance.respond_to?(name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module CryptTab
|
5
|
+
# Load crypttab file and return array with deserialized entries
|
6
|
+
class Deserializer
|
7
|
+
def call(filename = nil, content: File.read(filename))
|
8
|
+
split_to_important_lines(content).map do |line|
|
9
|
+
entry_deserializer.call line
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def split_to_important_lines(content)
|
16
|
+
content.split(/\n+|\r+/)
|
17
|
+
.reject(&:empty?)
|
18
|
+
.reject { |line| line.start_with? '#' }
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :entry_deserializer
|
22
|
+
|
23
|
+
def initialize(entry_deserializer: EntryDeserializer.new)
|
24
|
+
@entry_deserializer = entry_deserializer
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module CryptTab
|
5
|
+
# Value-object describing entry in crypttab file
|
6
|
+
class Entry
|
7
|
+
attr_reader :target, :source, :key_file, :options, :flags
|
8
|
+
|
9
|
+
def ==(other)
|
10
|
+
target == other.target && source == other.source && key_file == other.key_file &&
|
11
|
+
options == other.options && flags.sort == other.flags.sort
|
12
|
+
end
|
13
|
+
|
14
|
+
def headevice(header_prefix: nil)
|
15
|
+
if header_prefix && header_path
|
16
|
+
File.join(header_prefix, header_path)
|
17
|
+
elsif header_path
|
18
|
+
header_path
|
19
|
+
else
|
20
|
+
source
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def header_path
|
27
|
+
options[:header]
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(target:, source:, key_file:, options:, flags:)
|
31
|
+
@target = target
|
32
|
+
@source = source
|
33
|
+
@key_file = key_file
|
34
|
+
@options = options
|
35
|
+
@flags = flags
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module CryptTab
|
5
|
+
# Deserialize crypttab line into value object
|
6
|
+
class EntryDeserializer
|
7
|
+
InvalidFormat = Class.new StandardError
|
8
|
+
|
9
|
+
def call(line)
|
10
|
+
target, source, key_file, raw_floptions = columns = line.split
|
11
|
+
raise InvalidFormat if columns.size < 3
|
12
|
+
|
13
|
+
floptions = raw_floptions.to_s.split(',')
|
14
|
+
flags = extract_flags(floptions)
|
15
|
+
options = extract_options(floptions)
|
16
|
+
entry_class.new(
|
17
|
+
target: target, source: source, key_file: key_file,
|
18
|
+
options: options, flags: flags
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def extract_flags(floptions)
|
25
|
+
floptions.reject do |floption|
|
26
|
+
floption.include?('=')
|
27
|
+
end.map(&:to_sym)
|
28
|
+
end
|
29
|
+
|
30
|
+
def extract_options(floptions)
|
31
|
+
options = floptions.select do |floption|
|
32
|
+
floption.include?('=')
|
33
|
+
end
|
34
|
+
options.to_h do |option|
|
35
|
+
parse_option(option)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_option(option)
|
40
|
+
name, value = option.split('=')
|
41
|
+
[name.to_sym, value.to_i.to_s == value ? value.to_i : value]
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :entry_class
|
45
|
+
|
46
|
+
def initialize(entry_class: Entry)
|
47
|
+
@entry_class = entry_class
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module CryptTab
|
5
|
+
# Serialize crypttab entry into one line of text
|
6
|
+
class EntrySerializer
|
7
|
+
def call(entry)
|
8
|
+
floptions = (entry.flags + serialize_options(entry.options)).join(',')
|
9
|
+
[entry.target, entry.source, entry.key_file, floptions].join(' ')
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def serialize_options(options)
|
15
|
+
options.map do |option, value|
|
16
|
+
[option, value].join('=')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module CryptTab
|
5
|
+
# Return path of keyfile for given target
|
6
|
+
class KeyfileLocator
|
7
|
+
def call(target)
|
8
|
+
File.join(base_dir, target + extension)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_reader :base_dir, :extension
|
14
|
+
|
15
|
+
def initialize(base_dir: '/cryptreboot', extension: '.key')
|
16
|
+
@base_dir = base_dir
|
17
|
+
@extension = extension
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module CryptTab
|
5
|
+
# Convert given crypttab entry from LUKS to plain mode
|
6
|
+
class LuksToPlainConverter
|
7
|
+
def call(entry, data, keyfile)
|
8
|
+
entry_class.new(
|
9
|
+
target: entry.target,
|
10
|
+
source: entry.source,
|
11
|
+
key_file: keyfile,
|
12
|
+
options: convert_options(entry.options, data),
|
13
|
+
flags: entry.flags - [:luks] + [:plain]
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# According to cryptsetup manual, offset is specified in 512-byte sectors.
|
20
|
+
# Therefore sector size of the actual device shouldn't be used.
|
21
|
+
OFFSET_SECTOR_SIZE = 512
|
22
|
+
private_constant :OFFSET_SECTOR_SIZE
|
23
|
+
|
24
|
+
def convert_options(options, data)
|
25
|
+
options = options.reject do |option, _|
|
26
|
+
%i[keyfile-size keyslot key-slot header keyscript].include? option
|
27
|
+
end
|
28
|
+
options[:'sector-size'] ||= data.sector_size # allow user to set it explicitly
|
29
|
+
options.merge({ cipher: data.cipher, size: data.key_bits, offset: data.offset / OFFSET_SECTOR_SIZE })
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :entry_class
|
33
|
+
|
34
|
+
def initialize(entry_class: Entry)
|
35
|
+
@entry_class = entry_class
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module CryptTab
|
5
|
+
# Serialize entries and return crypttab file content as a string
|
6
|
+
class Serializer
|
7
|
+
def call(entries)
|
8
|
+
body = serialize(entries).join("\n")
|
9
|
+
"#{header}\n#{body}\n"
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def serialize(entries)
|
15
|
+
entries.map do |entry|
|
16
|
+
entry_serializer.call(entry)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :entry_serializer, :header
|
21
|
+
|
22
|
+
def initialize(entry_serializer: EntrySerializer.new,
|
23
|
+
header: '# This file has been patched by cryptreboot')
|
24
|
+
@entry_serializer = entry_serializer
|
25
|
+
@header = header
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Generate a hash with file names as keys and file contents as values
|
5
|
+
class FilesGenerator
|
6
|
+
def call(entries, base_dir)
|
7
|
+
files = {}
|
8
|
+
modified_entries = entries.map do |entry|
|
9
|
+
headevice = entry.headevice(header_prefix: base_dir)
|
10
|
+
next entry unless luks?(headevice)
|
11
|
+
|
12
|
+
data = luks_data_fetcher.call(headevice)
|
13
|
+
keyfile = keyfile_locator.call(entry.target)
|
14
|
+
files[keyfile] = data.key
|
15
|
+
entry_converter.call(entry, data, keyfile)
|
16
|
+
end
|
17
|
+
files.merge(CRYPTAB_PATH => serializer.call(modified_entries))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
CRYPTAB_PATH = '/cryptroot/crypttab'
|
23
|
+
private_constant :CRYPTAB_PATH
|
24
|
+
|
25
|
+
def luks?(headevice)
|
26
|
+
luks_checker.call(headevice)
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :keyfile_locator, :entry_converter, :serializer,
|
30
|
+
:luks_data_fetcher, :luks_checker
|
31
|
+
|
32
|
+
def initialize(keyfile_locator: CryptTab::KeyfileLocator.new,
|
33
|
+
entry_converter: CryptTab::LuksToPlainConverter.new,
|
34
|
+
serializer: CryptTab::Serializer.new,
|
35
|
+
luks_data_fetcher: Luks::DataFetcher.new,
|
36
|
+
luks_checker: Luks::Checker.new)
|
37
|
+
@keyfile_locator = keyfile_locator
|
38
|
+
@entry_converter = entry_converter
|
39
|
+
@serializer = serializer
|
40
|
+
@luks_data_fetcher = luks_data_fetcher
|
41
|
+
@luks_checker = luks_checker
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
# Writes files from hash to specified directory
|
7
|
+
class FilesWriter
|
8
|
+
def call(files, target_dir)
|
9
|
+
files.each do |relative_path, content|
|
10
|
+
path = File.join(target_dir, relative_path)
|
11
|
+
dir = File.dirname(path)
|
12
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
13
|
+
File.binwrite(path, content)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
# Gzip data and save it to file
|
7
|
+
class Gziper
|
8
|
+
def call(archive_path, data)
|
9
|
+
writer.call(archive_path) do |gz|
|
10
|
+
gz.write data
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :writer
|
17
|
+
|
18
|
+
def initialize(writer: Zlib::GzipWriter.method(:open))
|
19
|
+
@writer = writer
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'find'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
module Initramfs
|
7
|
+
# Create compressed CPIO archive from files in a given directory
|
8
|
+
class Archiver
|
9
|
+
def call(dir, archive)
|
10
|
+
Dir.chdir(dir) do
|
11
|
+
uncompressed = runner.call(cpio, '-oH', 'newc', '--reproducible', input: finder.call)
|
12
|
+
gziper.call(archive, uncompressed)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def cpio
|
19
|
+
lazy_cpio.call
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :runner, :finder, :lazy_cpio, :gziper
|
23
|
+
|
24
|
+
def initialize(runner: Runner::Binary.new,
|
25
|
+
finder: -> { Find.find('.').to_a.join("\n") },
|
26
|
+
lazy_cpio: LazyConfig.cpio_path,
|
27
|
+
gziper: Gziper.new)
|
28
|
+
@runner = runner
|
29
|
+
@finder = finder
|
30
|
+
@lazy_cpio = lazy_cpio
|
31
|
+
@gziper = gziper
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
module Initramfs
|
7
|
+
# Extract initramfs in strace to check if compression is supported
|
8
|
+
class Decompressor
|
9
|
+
class IntolerantDecompressor
|
10
|
+
Lz4NotAllowed = Class.new StandardError
|
11
|
+
|
12
|
+
def call(filename, dir)
|
13
|
+
command_line = prepare_command_line(filename, dir)
|
14
|
+
lines = runner.call(command_line)
|
15
|
+
raise_exception if intolerable_tool_used?(lines)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def prepare_command_line(filename, dir)
|
21
|
+
options = '-f --trace=execve -z -qq --signal=\!all'
|
22
|
+
args = "#{filename.shellescape} #{dir.shellescape}"
|
23
|
+
strace_command_line = "#{strace.shellescape} #{options} #{unmkinitramfs.shellescape} #{args}"
|
24
|
+
grep_command_line = "#{grep.shellescape} --line-buffered lz4"
|
25
|
+
# guarantee at least 1 line of grep output, otherwise grep will return non-zero status
|
26
|
+
grep_fixer = 'echo lz4 \"-t\"'
|
27
|
+
"(#{grep_fixer}; #{strace_command_line}) 2>&1 | #{grep_command_line}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def intolerable_tool_used?(lines)
|
31
|
+
!!lines.find { |line| line !~ /"-t"/ && line !~ /"--test"/ }
|
32
|
+
end
|
33
|
+
|
34
|
+
def raise_exception
|
35
|
+
raise Lz4NotAllowed, 'LZ4 compression is not allowed, change the compression ' \
|
36
|
+
'algorithm in initramfs.conf and regenerate the initramfs image'
|
37
|
+
end
|
38
|
+
|
39
|
+
def unmkinitramfs
|
40
|
+
lazy_unmkinitramfs.call
|
41
|
+
end
|
42
|
+
|
43
|
+
def strace
|
44
|
+
lazy_strace.call
|
45
|
+
end
|
46
|
+
|
47
|
+
def grep
|
48
|
+
lazy_grep.call
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :lazy_unmkinitramfs, :lazy_strace, :lazy_grep, :runner
|
52
|
+
|
53
|
+
def initialize(lazy_unmkinitramfs: LazyConfig.unmkinitramfs_path,
|
54
|
+
lazy_strace: LazyConfig.strace_path,
|
55
|
+
lazy_grep: LazyConfig.grep_path,
|
56
|
+
runner: Runner::Lines.new)
|
57
|
+
@lazy_unmkinitramfs = lazy_unmkinitramfs
|
58
|
+
@lazy_strace = lazy_strace
|
59
|
+
@lazy_grep = lazy_grep
|
60
|
+
@runner = runner
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Initramfs
|
5
|
+
class Decompressor
|
6
|
+
# Simply extract initramfs
|
7
|
+
class TolerantDecompressor
|
8
|
+
def call(filename, dir)
|
9
|
+
runner.call(unmkinitramfs, filename, dir)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader :lazy_unmkinitramfs, :runner
|
15
|
+
|
16
|
+
def unmkinitramfs
|
17
|
+
lazy_unmkinitramfs.call
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(lazy_unmkinitramfs: LazyConfig.unmkinitramfs_path,
|
21
|
+
runner: Runner::NoResult.new)
|
22
|
+
@lazy_unmkinitramfs = lazy_unmkinitramfs
|
23
|
+
@runner = runner
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Initramfs
|
5
|
+
# Instantiates appropriate decompressor
|
6
|
+
class Decompressor
|
7
|
+
def call(skip_lz4_check: Config.skip_lz4_check)
|
8
|
+
skip_lz4_check ? TolerantDecompressor.new : IntolerantDecompressor.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
module Initramfs
|
7
|
+
# Create temporary directory, extract initramfs there and yield, cleaning afterwards
|
8
|
+
class Extractor
|
9
|
+
def call(filename)
|
10
|
+
tmp_maker.call do |dir|
|
11
|
+
logger.call message
|
12
|
+
decompressor.call(filename, dir)
|
13
|
+
yield dir
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def decompressor
|
20
|
+
decompressor_factory.call
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :tmp_maker, :decompressor_factory, :message, :logger
|
24
|
+
|
25
|
+
def initialize(tmp_maker: Dir.method(:mktmpdir),
|
26
|
+
decompressor_factory: Decompressor.new,
|
27
|
+
message: 'Extracting initramfs... To speed things up, future versions will employ cache.',
|
28
|
+
logger: ->(msg) { warn msg })
|
29
|
+
@tmp_maker = tmp_maker
|
30
|
+
@decompressor_factory = decompressor_factory
|
31
|
+
@message = message
|
32
|
+
@logger = logger
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
module Initramfs
|
7
|
+
# Yield path to initramfs patched with files_spec.
|
8
|
+
# Patched initramfs will be removed afterwards if user doesn't want to save it
|
9
|
+
class Patcher
|
10
|
+
def call(initramfs_path, files_spec)
|
11
|
+
temp_provider.call do |base_dir|
|
12
|
+
files_dir, patch_path, patched_path = prefix('files', 'patch', 'result', with: base_dir)
|
13
|
+
writer.call(files_spec, files_dir)
|
14
|
+
archiver.call(files_dir, patch_path)
|
15
|
+
saver.call(patch_path)
|
16
|
+
concatenator.call(initramfs_path, patch_path, to: patched_path)
|
17
|
+
yield patched_path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def prefix(*file_names, with:)
|
24
|
+
file_names.map do |file_name|
|
25
|
+
File.join(with, file_name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :temp_provider, :writer, :archiver, :concatenator, :saver
|
30
|
+
|
31
|
+
def initialize(temp_provider: SafeTemp::Directory.new,
|
32
|
+
writer: FilesWriter.new,
|
33
|
+
archiver: Archiver.new,
|
34
|
+
concatenator: Concatenator.new,
|
35
|
+
saver: lambda { |file|
|
36
|
+
dir = Config.patch_save_path
|
37
|
+
FileUtils.cp(file, dir) if dir
|
38
|
+
})
|
39
|
+
@temp_provider = temp_provider
|
40
|
+
@writer = writer
|
41
|
+
@archiver = archiver
|
42
|
+
@concatenator = concatenator
|
43
|
+
@saver = saver
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Transform initramfs image into a patch (files hash)
|
5
|
+
class InitramfsPatchSqueezer
|
6
|
+
def call(initramfs_path)
|
7
|
+
extractor.call(initramfs_path) do |tmp_dir|
|
8
|
+
crypttab_path = File.join(tmp_dir, crypttab_relative_path)
|
9
|
+
crypttab_entries = deserializer.call(crypttab_path)
|
10
|
+
files_generator.call(crypttab_entries, tmp_dir)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :crypttab_relative_path, :extractor, :deserializer, :files_generator
|
17
|
+
|
18
|
+
def initialize(crypttab_relative_path = 'main/cryptroot/crypttab',
|
19
|
+
extractor: Initramfs::Extractor.new,
|
20
|
+
deserializer: CryptTab::Deserializer.new,
|
21
|
+
files_generator: FilesGenerator.new)
|
22
|
+
@crypttab_relative_path = crypttab_relative_path
|
23
|
+
@extractor = extractor
|
24
|
+
@deserializer = deserializer
|
25
|
+
@files_generator = files_generator
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Configuration object
|
5
|
+
class InstantiableConfig
|
6
|
+
UnrecognizedSetting = Class.new StandardError
|
7
|
+
|
8
|
+
attr_reader :initramfs, :cmdline, :kernel, :patch_save_path, :cat_path, :cpio_path,
|
9
|
+
:unmkinitramfs_path, :kexec_path, :cryptsetup_path, :reboot_path,
|
10
|
+
:mount_path, :umount_path, :strace_path, :grep_path,
|
11
|
+
:debug, :prepare_only, :skip_lz4_check
|
12
|
+
|
13
|
+
def update!(**settings)
|
14
|
+
settings.each do |name, value|
|
15
|
+
set!(name, value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def set!(name, value)
|
22
|
+
raise UnrecognizedSetting, "Unrecognized setting `#{name}`" unless instance_variable_defined?(:"@#{name}")
|
23
|
+
|
24
|
+
instance_variable_set(:"@#{name}", value)
|
25
|
+
end
|
26
|
+
|
27
|
+
# rubocop:disable Metrics/MethodLength
|
28
|
+
def initialize
|
29
|
+
# Options
|
30
|
+
@initramfs = '/boot/initrd.img'
|
31
|
+
@cmdline = nil
|
32
|
+
@kernel = '/boot/vmlinuz'
|
33
|
+
@patch_save_path = nil
|
34
|
+
@cat_path = 'cat'
|
35
|
+
@cpio_path = 'cpio'
|
36
|
+
@unmkinitramfs_path = 'unmkinitramfs'
|
37
|
+
@kexec_path = 'kexec'
|
38
|
+
@cryptsetup_path = 'cryptsetup'
|
39
|
+
@reboot_path = 'reboot'
|
40
|
+
@mount_path = 'mount'
|
41
|
+
@umount_path = 'umount'
|
42
|
+
@strace_path = 'strace'
|
43
|
+
@grep_path = 'grep'
|
44
|
+
|
45
|
+
# Flags
|
46
|
+
@debug = false
|
47
|
+
@prepare_only = false
|
48
|
+
@skip_lz4_check = false
|
49
|
+
end
|
50
|
+
# rubocop:enable Metrics/MethodLength
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Kexec
|
5
|
+
# Load new kernel and initramfs into memory, making then ready for later execution
|
6
|
+
class Loader
|
7
|
+
def call(boot_config)
|
8
|
+
args = [tool, '-al', boot_config.kernel]
|
9
|
+
args += ['--initrd', boot_config.initramfs] if boot_config.initramfs
|
10
|
+
args += boot_config.cmdline ? ['--append', boot_config.cmdline] : ['--reuse-cmdline']
|
11
|
+
|
12
|
+
runner.call(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def tool
|
18
|
+
lazy_tool.call
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :lazy_tool, :runner
|
22
|
+
|
23
|
+
def initialize(lazy_tool: LazyConfig.kexec_path,
|
24
|
+
runner: Runner::NoResult.new)
|
25
|
+
@lazy_tool = lazy_tool
|
26
|
+
@runner = runner
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|