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,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Patch initramfs and load it along with kernel using kexec,
|
5
|
+
# so it is ready to be executed.
|
6
|
+
class KexecPatchingLoader
|
7
|
+
def call(boot_config = BootConfig.new(
|
8
|
+
kernel: Config.kernel,
|
9
|
+
initramfs: Config.initramfs,
|
10
|
+
cmdline: Config.cmdline
|
11
|
+
))
|
12
|
+
generator.call(boot_config.initramfs) do |patched_initramfs|
|
13
|
+
patched_boot_config = boot_config.with_initramfs(patched_initramfs)
|
14
|
+
loader.call(patched_boot_config)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :generator, :loader
|
21
|
+
|
22
|
+
def initialize(generator: PatchedInitramfsGenerator.new,
|
23
|
+
loader: Kexec::Loader.new)
|
24
|
+
@generator = generator
|
25
|
+
@loader = loader
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Return getter lambdas instead of configuration settings directly
|
5
|
+
class LazyConfig
|
6
|
+
class << self
|
7
|
+
def method_missing(method_name, *args, **kwargs, &block)
|
8
|
+
return super unless instance.respond_to?(method_name)
|
9
|
+
|
10
|
+
-> { instance.send(method_name, *args, **kwargs, &block) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to_missing?(name, *_, **_)
|
14
|
+
instance.respond_to?(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def instance
|
18
|
+
Config.instance
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Return true in case given device is LUKS (of given version is provided), false otherwise
|
6
|
+
class Checker
|
7
|
+
def call(headevice, version = :any)
|
8
|
+
args = version == :any ? [] : ['--type', version]
|
9
|
+
runner.call(binary, 'isLuks', 'none', '--header', headevice, *args)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def binary
|
15
|
+
lazy_binary.call
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :lazy_binary, :runner
|
19
|
+
|
20
|
+
def initialize(lazy_binary: LazyConfig.cryptsetup_path,
|
21
|
+
runner: Runner::Boolean.new)
|
22
|
+
@lazy_binary = lazy_binary
|
23
|
+
@runner = runner
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Value-object with encryption parameters
|
6
|
+
class Data
|
7
|
+
attr_reader :cipher, :offset, :sector_size, :key
|
8
|
+
|
9
|
+
def ==(other)
|
10
|
+
cipher == other.cipher && offset == other.offset &&
|
11
|
+
sector_size == other.sector_size && key == other.key
|
12
|
+
end
|
13
|
+
|
14
|
+
def key_bits
|
15
|
+
key.bytesize * 8
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_key(new_key)
|
19
|
+
self.class.new(
|
20
|
+
cipher: cipher,
|
21
|
+
offset: offset,
|
22
|
+
sector_size: sector_size,
|
23
|
+
key: new_key
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def initialize(cipher:, offset:, sector_size:, key: '')
|
30
|
+
@cipher = cipher
|
31
|
+
@offset = offset
|
32
|
+
@sector_size = sector_size
|
33
|
+
@key = key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Fetch LUKS data including key (user will be asked for passphrase)
|
6
|
+
class DataFetcher
|
7
|
+
def call(headevice)
|
8
|
+
version = detector.call(headevice)
|
9
|
+
data = dumper.call(headevice, version)
|
10
|
+
pass = asker.call("Enter passphrase to unlock #{headevice}: ")
|
11
|
+
key = key_fetcher.call(headevice, pass)
|
12
|
+
data.with_key(key)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :detector, :dumper, :asker, :key_fetcher
|
18
|
+
|
19
|
+
def initialize(detector: VersionDetector.new,
|
20
|
+
dumper: Dumper.new,
|
21
|
+
asker: PassphraseAsker.new,
|
22
|
+
key_fetcher: KeyFetcher.new)
|
23
|
+
@detector = detector
|
24
|
+
@dumper = dumper
|
25
|
+
@asker = asker
|
26
|
+
@key_fetcher = key_fetcher
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
class Dumper
|
6
|
+
# Parse LUKS1
|
7
|
+
class LuksV1Parser
|
8
|
+
ParsingError = Class.new StandardError
|
9
|
+
|
10
|
+
def call(lines)
|
11
|
+
data = parse_lines(lines)
|
12
|
+
instantiate(data.to_h)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
SECTOR_SIZE = 512 # LUKS1 support only this sector size
|
18
|
+
private_constant :SECTOR_SIZE
|
19
|
+
|
20
|
+
def instantiate(raw)
|
21
|
+
data_class.new(
|
22
|
+
cipher: [raw.fetch(:cipher_name), raw.fetch(:cipher_mode)].join('-'),
|
23
|
+
offset: raw.fetch(:offset),
|
24
|
+
sector_size: SECTOR_SIZE
|
25
|
+
)
|
26
|
+
rescue KeyError => e
|
27
|
+
raise ParsingError, 'Parsing failed because of missing data', cause: e
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_lines(lines)
|
31
|
+
map_generator.call.tap do |result|
|
32
|
+
lines.each do |line|
|
33
|
+
update_result!(result, line: line)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_result!(result, line:)
|
39
|
+
case line
|
40
|
+
when /^Cipher name:\s+([\w-]+)$/
|
41
|
+
result[:cipher_name] = Regexp.last_match(1)
|
42
|
+
when /^Cipher mode:\s+([\w-]+)$/
|
43
|
+
result[:cipher_mode] = Regexp.last_match(1)
|
44
|
+
when /^Payload offset:\s+(\d+)$/
|
45
|
+
# LUKS1 provides offset in sectors
|
46
|
+
result[:offset] = Regexp.last_match(1).to_i * SECTOR_SIZE
|
47
|
+
end
|
48
|
+
rescue duplicate_exception => e
|
49
|
+
raise ParsingError, "Parsing failed on: `#{line}`", cause: e
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :data_class, :map_generator, :duplicate_exception
|
53
|
+
|
54
|
+
def initialize(data_class: Data,
|
55
|
+
map_generator: -> { SingleAssignRestrictedMap.new },
|
56
|
+
duplicate_exception: SingleAssignRestrictedMap::AlreadyAssigned)
|
57
|
+
@data_class = data_class
|
58
|
+
@map_generator = map_generator
|
59
|
+
@duplicate_exception = duplicate_exception
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
class Dumper
|
6
|
+
# Parse LUKS2
|
7
|
+
class LuksV2Parser
|
8
|
+
ParsingError = Class.new StandardError
|
9
|
+
|
10
|
+
def call(lines)
|
11
|
+
data = parse_lines(lines)
|
12
|
+
instantiate(data.to_h)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def instantiate(args)
|
18
|
+
data_class.new(**args)
|
19
|
+
rescue ArgumentError => e
|
20
|
+
raise ParsingError, 'Parsing failed because of missing data', cause: e
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_lines(lines)
|
24
|
+
map_generator.call.tap do |result|
|
25
|
+
section_found = false
|
26
|
+
lines.each do |line|
|
27
|
+
if section_found
|
28
|
+
break if line =~ /^\w/
|
29
|
+
|
30
|
+
update_result!(result, line: line)
|
31
|
+
end
|
32
|
+
line =~ /^Data segments:/ && section_found = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_result!(result, line:)
|
38
|
+
case line
|
39
|
+
when /offset:\s+(\d+) \[bytes\]/
|
40
|
+
result[:offset] = Regexp.last_match(1).to_i
|
41
|
+
when /cipher:\s+([\w-]+)$/
|
42
|
+
result[:cipher] = Regexp.last_match(1)
|
43
|
+
when /sector:\s+(\d+) \[bytes\]/
|
44
|
+
result[:sector_size] = Regexp.last_match(1).to_i
|
45
|
+
end
|
46
|
+
rescue duplicate_exception => e
|
47
|
+
raise ParsingError, "Parsing failed on: `#{line}`", cause: e
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :data_class, :map_generator, :duplicate_exception
|
51
|
+
|
52
|
+
def initialize(data_class: Data,
|
53
|
+
map_generator: -> { SingleAssignRestrictedMap.new },
|
54
|
+
duplicate_exception: SingleAssignRestrictedMap::AlreadyAssigned)
|
55
|
+
@data_class = data_class
|
56
|
+
@map_generator = map_generator
|
57
|
+
@duplicate_exception = duplicate_exception
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Depending on LUKS version, delegates parsing to different parser
|
6
|
+
class Dumper
|
7
|
+
def call(headevice, version)
|
8
|
+
dump = runner.call(binary, 'luksDump', 'none', '--header', headevice)
|
9
|
+
parser = parsers.fetch(version)
|
10
|
+
parser.call(dump)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def binary
|
16
|
+
lazy_binary.call
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :lazy_binary, :runner, :parsers
|
20
|
+
|
21
|
+
def initialize(lazy_binary: LazyConfig.cryptsetup_path,
|
22
|
+
runner: Runner::Lines.new,
|
23
|
+
parsers: { 'LUKS2' => LuksV2Parser.new, 'LUKS1' => LuksV1Parser.new })
|
24
|
+
@lazy_binary = lazy_binary
|
25
|
+
@runner = runner
|
26
|
+
@parsers = parsers
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Fetch LUKS key
|
6
|
+
class KeyFetcher
|
7
|
+
InvalidPassphrase = Class.new StandardError
|
8
|
+
|
9
|
+
def call(headevice, passphrase)
|
10
|
+
temp_provider.call do |key_file|
|
11
|
+
luks_dump(headevice, key_file, passphrase)
|
12
|
+
file_reader.call(key_file)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def luks_dump(headevice, master_key_file, passphrase)
|
19
|
+
runner.call(
|
20
|
+
binary, 'luksDump', 'none', '--header', headevice,
|
21
|
+
'--dump-master-key', '--master-key-file', master_key_file,
|
22
|
+
'--key-file', '-',
|
23
|
+
input: passphrase
|
24
|
+
)
|
25
|
+
rescue run_exception
|
26
|
+
# For simplicity sake let's assume it's invalid passphrase.
|
27
|
+
# Other errors such as invalid device/header were handled by previous validation.
|
28
|
+
raise InvalidPassphrase
|
29
|
+
end
|
30
|
+
|
31
|
+
def binary
|
32
|
+
lazy_binary.call
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :lazy_binary, :runner, :run_exception, :file_reader, :temp_provider
|
36
|
+
|
37
|
+
def initialize(lazy_binary: LazyConfig.cryptsetup_path,
|
38
|
+
runner: Runner::Lines.new,
|
39
|
+
run_exception: Runner::ExitError,
|
40
|
+
file_reader: File.method(:read),
|
41
|
+
temp_provider: SafeTemp::FileName.new)
|
42
|
+
@lazy_binary = lazy_binary
|
43
|
+
@runner = runner
|
44
|
+
@run_exception = run_exception
|
45
|
+
@file_reader = file_reader
|
46
|
+
@temp_provider = temp_provider
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Return LUKS version or raise the exception if given file doesn't represent a valid LUKS device
|
6
|
+
class VersionDetector
|
7
|
+
Error = Class.new StandardError
|
8
|
+
NotLuks = Class.new Error
|
9
|
+
UnsupportedVersion = Class.new Error
|
10
|
+
|
11
|
+
def call(headevice)
|
12
|
+
version = supported_versions.find do |tested_version|
|
13
|
+
checker.call(headevice, tested_version)
|
14
|
+
end
|
15
|
+
return version if version
|
16
|
+
raise UnsupportedVersion if checker.call(headevice)
|
17
|
+
|
18
|
+
raise NotLuks
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :checker, :supported_versions
|
24
|
+
|
25
|
+
def initialize(checker: Checker.new,
|
26
|
+
supported_versions: %w[LUKS2 LUKS1])
|
27
|
+
@checker = checker
|
28
|
+
@supported_versions = supported_versions
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Yield path to patched version of provided initramfs
|
5
|
+
class PatchedInitramfsGenerator
|
6
|
+
def call(initramfs_path, &block)
|
7
|
+
patch = squeezer.call(initramfs_path)
|
8
|
+
patcher.call(initramfs_path, patch, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_reader :squeezer, :patcher
|
14
|
+
|
15
|
+
def initialize(squeezer: InitramfsPatchSqueezer.new,
|
16
|
+
patcher: Initramfs::Patcher.new)
|
17
|
+
@squeezer = squeezer
|
18
|
+
@patcher = patcher
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Perform the reboot or exit (doesn't return)
|
5
|
+
class Rebooter
|
6
|
+
def call(act = !Config.prepare_only)
|
7
|
+
act ? runner.call : exiter.call
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
attr_reader :runner, :exiter
|
13
|
+
|
14
|
+
def initialize(runner: -> { Process.exec Config.reboot_path },
|
15
|
+
exiter: -> { exit 0 })
|
16
|
+
@runner = runner
|
17
|
+
@exiter = exiter
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Runner
|
5
|
+
# Return true or false, depending if command succeeded or failed
|
6
|
+
class Boolean < Generic
|
7
|
+
def call(...)
|
8
|
+
super(...).success?
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def initialize(**opts)
|
14
|
+
super(**opts.merge(run_method: :run!))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tty-command'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
module Runner
|
7
|
+
# Run an external process. Abstract class, use descendants.
|
8
|
+
class Generic
|
9
|
+
private
|
10
|
+
|
11
|
+
def call(*args, input: nil, output_file: nil, binary: false)
|
12
|
+
options = build_options(input, output_file, binary)
|
13
|
+
cmd.send(run_method, *args, **options)
|
14
|
+
rescue exceptions[:exit] => e
|
15
|
+
raise ExitError, cause: e
|
16
|
+
rescue exceptions[:not_found] => e
|
17
|
+
raise CommandNotFound, cause: e
|
18
|
+
end
|
19
|
+
|
20
|
+
def build_options(input, output_file, binary)
|
21
|
+
{}.tap do |options|
|
22
|
+
options[:input] = input if input
|
23
|
+
options[:out] = output_file if output_file
|
24
|
+
options[:binmode] = true if binary
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def cmd
|
29
|
+
lazy_cmd.call
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :lazy_cmd, :run_method, :exceptions
|
33
|
+
|
34
|
+
def initialize(lazy_cmd: -> { TTY::Command.new(uuid: false, printer: Config.debug ? :pretty : :null) },
|
35
|
+
run_method: :run,
|
36
|
+
exceptions: {
|
37
|
+
exit: TTY::Command::ExitError,
|
38
|
+
not_found: Errno::ENOENT
|
39
|
+
})
|
40
|
+
@lazy_cmd = lazy_cmd
|
41
|
+
@run_method = run_method
|
42
|
+
@exceptions = exceptions
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
module SafeTemp
|
7
|
+
# Create temporary directory, mounts tmpfs and yields tmp dir location.
|
8
|
+
# Make sure to cleanup afterwards.
|
9
|
+
class Directory
|
10
|
+
def call
|
11
|
+
tmp_maker.call do |dir|
|
12
|
+
mounter.call(dir) do
|
13
|
+
yield dir
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :mounter, :tmp_maker
|
21
|
+
|
22
|
+
def initialize(mounter: Mounter.new, tmp_maker: Dir.method(:mktmpdir))
|
23
|
+
@mounter = mounter
|
24
|
+
@tmp_maker = tmp_maker
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module SafeTemp
|
5
|
+
# Yield non-existing temporary file name located in a safe dir.
|
6
|
+
# Afterwards the directory containing this file is deleted.
|
7
|
+
class FileName
|
8
|
+
def call(name = 'file')
|
9
|
+
dir_provider.call do |dir|
|
10
|
+
yield File.join(dir, name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :dir_provider
|
17
|
+
|
18
|
+
def initialize(dir_provider: Directory.new)
|
19
|
+
@dir_provider = dir_provider
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module SafeTemp
|
5
|
+
# Mount tmpfs at the given mount point, yield and unmount
|
6
|
+
class Mounter
|
7
|
+
def call(dir, &block)
|
8
|
+
mounter.call(dir)
|
9
|
+
run(dir, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def run(dir, &block)
|
15
|
+
block.call
|
16
|
+
ensure
|
17
|
+
umounter.call(dir)
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :runner, :mounter, :umounter
|
21
|
+
|
22
|
+
def initialize(runner: Runner::NoResult.new,
|
23
|
+
mounter: lambda { |dir|
|
24
|
+
runner.call(Config.mount_path, '-t', 'tmpfs', '-o', 'mode=700', 'none', dir)
|
25
|
+
},
|
26
|
+
umounter: ->(dir) { runner.call(Config.umount_path, dir) })
|
27
|
+
@runner = runner
|
28
|
+
@mounter = mounter
|
29
|
+
@umounter = umounter
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|