-
Notifications
You must be signed in to change notification settings - Fork 22
/
grub2pwd.rb
146 lines (119 loc) · 4.05 KB
/
grub2pwd.rb
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
require "yast"
require "shellwords"
Yast.import "Stage"
module Bootloader
# class is responsible for detection, encryption and writing of grub2 password protection
class GRUB2Pwd
# @!attribute used
# flag is password protection is used at all
# @return [Boolean] specifies if password protection enabled
#
# @!attribute unrestricted
# if password protection is unrestricted or not
# @see https://www.gnu.org/software/grub/manual/grub.html#Security
# @return [Boolean] specifies if unrestricted password protection should
# be used (see fate#318574)
attr_accessor :used, :unrestricted
alias_method :used?, :used
alias_method :unrestricted?, :unrestricted
# Reads or proposes configuration depending on stage
def initialize
if Yast::Stage.initial
propose
else
read
end
end
# writes configuration to disk
def write
if used?
enable
else
disable
end
end
# Sets password in encrypted form
# @param [String] value plain text password
def password=(value)
@encrypted_password = encrypt(value)
end
# Gets if password is specified
# Rationale for this method is that in some cases it is possible
# to disable password configuration, but still keep old configuration in
# object, so after enabling it again it use old configuration
def password?
!@encrypted_password.nil?
end
private
YAST_BASH_PATH = Yast::Path.new(".local.bash_output")
PWD_ENCRYPTION_FILE = "/etc/grub.d/42_password".freeze
def propose
@used = false
@unrestricted = true
@encrypted_password = nil # not set by default
end
def read
if !used_on_target?
propose
return
end
@used = true
content = Yast::SCR.Read(
Yast::Path.new(".target.string"),
PWD_ENCRYPTION_FILE
)
unrestricted_lines = content.lines.grep(/unrestricted_menu\s*=\s*\"y\"\s*/)
@unrestricted = !unrestricted_lines.empty?
pwd_line = content.lines.grep(/password_pbkdf2 root/).first
if !pwd_line
raise "Cannot find encrypted password. " \
"YaST2 password generator in /etc/grub.d is probably modified."
end
@encrypted_password = pwd_line[/password_pbkdf2 root (\S+)/, 1]
end
def used_on_target?
Yast.import "FileUtils"
Yast::FileUtils.Exists PWD_ENCRYPTION_FILE
end
def enable
raise "Wrong code: password not written" unless @encrypted_password
# The files in /etc/grub.d are programs that write GRUB 2 programs on their stdout.
# So 'exec tail' is a way of saying "just echo the rest of this program as its output".
file_content = "#! /bin/sh\n" \
"exec tail -n +3 $0\n" \
"# File created by YaST and next YaST run probably overwrite it\n" \
"set superusers=\"root\"\n" \
"password_pbkdf2 root #{@encrypted_password}\n" \
"export superusers\n"
if @unrestricted
file_content << "set unrestricted_menu=\"y\"\n" \
"export unrestricted_menu\n"
end
Yast::SCR.Write(
Yast::Path.new(".target.string"),
[PWD_ENCRYPTION_FILE, 0o700],
file_content
)
end
def disable
return unless used_on_target?
# operate on target as we have to remove password during installation from target grub2
Yast::SCR.Execute(Yast::Path.new(".target.bash"), "rm '#{PWD_ENCRYPTION_FILE.shellescape}'")
end
def encrypt(password)
result = Yast::Execute.locally("/usr/bin/grub2-mkpasswd-pbkdf2",
env: { "LANG" => "C" },
stdin: "#{password}\n#{password}\n",
stdout: :capture)
pwd_line = result.split("\n").grep(/password is/).first
if !pwd_line
raise "grub2-mkpasswd output do not contain encrypted password. Output: #{result}"
end
ret = pwd_line[/^.*password is\s*(\S+)/, 1]
if !ret
raise "grub2-mkpasswd output do not contain encrypted password. Output: #{result}"
end
ret
end
end
end