-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathkcrypt_uki.go
186 lines (169 loc) · 6.33 KB
/
kcrypt_uki.go
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package hook
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
internalutils "github.com/kairos-io/kairos-agent/v2/pkg/utils"
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
)
type KcryptUKI struct{}
func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
// pre-check for systemd version, we need something higher or equal to 252
run, err := utils.SH("systemctl --version | head -1 | awk '{ print $2}'")
systemdVersion := strings.TrimSpace(string(run))
if err != nil {
c.Logger.Errorf("could not get systemd version: %s", err)
c.Logger.Errorf("could not get systemd version: %s", run)
return err
}
if systemdVersion == "" {
c.Logger.Errorf("could not get systemd version: %s", err)
return err
}
// Extract the numeric portion of the version string using a regular expression
re := regexp.MustCompile(`\d+`)
matches := re.FindString(systemdVersion)
if matches == "" {
return fmt.Errorf("could not extract numeric part from systemd version: %s", systemdVersion)
}
// Change systemdVersion to int value
systemdVersionInt, err := strconv.Atoi(systemdVersion)
if err != nil {
c.Logger.Errorf("could not convert systemd version to int: %s", err)
return err
}
// If systemd version is less than 252 return
if systemdVersionInt < 252 {
c.Logger.Infof("systemd version is %s, we need 252 or higher for encrypting partitions", systemdVersion)
return nil
}
// Check for a TPM 2.0 device as its needed to encrypt
// Exposed by the kernel to userspace as /dev/tpmrm0 since kernel 4.12
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fdc915f7f71939ad5a3dda3389b8d2d7a7c5ee66
_, err = os.Stat("/dev/tpmrm0")
if err != nil {
c.Logger.Warnf("Skipping partition encryption, could not find TPM 2.0 device at /dev/tpmrm0")
return nil
}
c.Logger.Logger.Debug().Msg("Running KcryptUKI hook")
// We always encrypt OEM and PERSISTENT under UKI
// If mounted, unmount it
_ = machine.Umount(constants.OEMDir) //nolint:errcheck
_ = machine.Umount(constants.PersistentDir) //nolint:errcheck
// Backup oem as we already copied files on there and on luksify it will be wiped
err = machine.Mount(constants.OEMLabel, constants.OEMDir)
if err != nil {
return err
}
tmpDir, err := fsutils.TempDir(c.Fs, "", "oem-backup-xxxx")
if err != nil {
return err
}
// Remove everything when we finish
defer c.Fs.RemoveAll(tmpDir) //nolint:errcheck
err = internalutils.SyncData(c.Logger, c.Runner, c.Fs, constants.OEMDir, tmpDir, []string{}...)
if err != nil {
return err
}
err = machine.Umount(constants.OEMDir) //nolint:errcheck
if err != nil {
return err
}
for _, p := range append([]string{constants.OEMLabel, constants.PersistentLabel}, c.Install.Encrypt...) {
c.Logger.Infof("Encrypting %s", p)
_ = os.Setenv("SYSTEMD_LOG_LEVEL", "debug")
err := kcrypt.LuksifyMeasurements(p, c.BindPublicPCRs, c.BindPCRs, c.Logger.Logger)
_ = os.Unsetenv("SYSTEMD_LOG_LEVEL")
if err != nil {
c.Logger.Errorf("could not encrypt partition: %s", err)
if c.FailOnBundleErrors {
return err
}
// Give time to show the error
time.Sleep(10 * time.Second)
return nil // do not error out
}
c.Logger.Infof("Done encrypting %s", p)
}
_, _ = utils.SH("sync")
_ = os.Setenv("SYSTEMD_LOG_LEVEL", "debug")
err = kcrypt.UnlockAllWithLogger(true, c.Logger.Logger)
_ = os.Unsetenv("SYSTEMD_LOG_LEVEL")
if err != nil {
c.Logger.Errorf("could not unlock partitions: %s", err)
return err
}
// Close the unlocked partitions after dealing with them, otherwise we leave them open and they can be mounted by anyone
defer func() {
for _, p := range append([]string{constants.OEMLabel, constants.PersistentLabel}, c.Install.Encrypt...) {
c.Logger.Debugf("Closing unencrypted /dev/disk/by-label/%s", p)
out, err := utils.SH(fmt.Sprintf("cryptsetup close /dev/disk/by-label/%s", p))
// There is a known error with cryptsetup that it can't close the device because of a semaphore
// doesnt seem to affect anything as the device is closed as expected so we ignore it if it matches the
// output of the error
if err != nil && !strings.Contains(out, "incorrect semaphore state") {
c.Logger.Errorf("could not close /dev/disk/by-label/%s: %s", p, out)
}
}
}()
// Here it can take the oem partition a bit of time to appear after unlocking so we need to retry a couple of time with some waiting
// retry + backoff
// Check all encrypted partitions are unlocked
for _, p := range append([]string{constants.OEMLabel, constants.PersistentLabel}) {
for i := 0; i < 10; i++ {
c.Logger.Infof("Waiting for unlocked partition %s to appear", p)
_, _ = utils.SH("sync")
part, _ := utils.SH(fmt.Sprintf("blkid -L %s", p))
if part == "" {
c.Logger.Infof("Partition %s not found, waiting %d seconds before retrying", p, i)
time.Sleep(time.Duration(i) * time.Second)
// Retry the unlock as well, because maybe the partition was not refreshed on time for unlock to unlock it
// So no matter how many tries we do, it will still be locked and will never appear
err := kcrypt.UnlockAllWithLogger(true, c.Logger.Logger)
if err != nil {
c.Logger.Debugf("UnlockAll returned: %s", err)
}
if i == 9 {
c.Logger.Errorf("Partition %s not unlocked/found after 10 retries", p)
return fmt.Errorf("partition %s not unlocked/found after 10 retries", p)
}
continue
}
c.Logger.Infof("Partition found, continuing")
break
}
}
err = machine.Mount(constants.OEMLabel, constants.OEMDir)
if err != nil {
return err
}
err = internalutils.SyncData(c.Logger, c.Runner, c.Fs, tmpDir, constants.OEMDir, []string{}...)
if err != nil {
return err
}
err = machine.Umount(constants.OEMDir)
if err != nil {
return err
}
c.Logger.Logger.Debug().Msg("Finish KcryptUKI hook")
// We now have the partitions unlocked and ready, lets call the other hooks here instead of closing and reopening them each time
err = BundlePostInstall{}.Run(c, spec)
if err != nil {
return err
}
_ = CustomMounts{}.Run(c, spec)
err = CopyLogs{}.Run(c, spec)
if err != nil {
return err
}
return nil
}