crypt_reboot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|