Skip to content

Commit cef64d9

Browse files
committed
providers/base: add fscrypt test (New)
1 parent a65231d commit cef64d9

File tree

10 files changed

+295
-25
lines changed

10 files changed

+295
-25
lines changed

.github/workflows/checkbox-promote-beta-to-candidate/job.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ test_data:
4545
else
4646
echo "Installing checkbox runtime on device (from deb package)"
4747
_run sudo add-apt-repository -y ppa:checkbox-dev/$CHANNEL
48-
_run install_packages checkbox-ng python3-checkbox-ng checkbox-provider-base checkbox-provider-resource checkbox-provider-sru fswebcam obexftp wmctrl iperf mesa-utils vim pastebinit fwts xorg-dev gir1.2-clutter-1.0
48+
_run install_packages checkbox-ng python3-checkbox-ng checkbox-provider-base checkbox-provider-resource checkbox-provider-sru fscrypt fswebcam obexftp wmctrl iperf mesa-utils vim pastebinit fwts xorg-dev gir1.2-clutter-1.0
4949
# list installed checkbox-related packages to facilitate debugging
5050
_run "apt list --installed | grep checkbox"
5151
CHECKBOX_CLI_CMD="checkbox-cli"

checkbox-core-snap/series24/snap/snapcraft.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ parts:
290290
- efibootmgr
291291
- ethtool
292292
- freeipmi-tools
293+
- fscrypt
293294
- fswebcam
294295
- gir1.2-cheese-3.0
295296
- gir1.2-clutter-1.0

providers/base/README.rst

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,28 @@ under the units folder.
1212
Base Provider Units
1313
###################
1414

15-
+------------+-------------+-------------+------------------+-------------+----------------+
16-
| 6lowpan | eeprom | i2c | miscellanea | serial | ubuntucore |
17-
+------------+-------------+-------------+------------------+-------------+----------------+
18-
| acpi | esata | image | mobilebroadband | smoke | usb |
19-
+------------+-------------+-------------+------------------+-------------+----------------+
20-
| audio | ethernet | info | monitor | snapd | virtualization |
21-
+------------+-------------+-------------+------------------+-------------+----------------+
22-
| benchmarks | expresscard | input | networking | socketcan | watchdog |
23-
+------------+-------------+-------------+------------------+-------------+----------------+
24-
| bluetooth | fingerprint | install | nvdimm | stress | wireless |
25-
+------------+-------------+-------------+------------------+-------------+----------------+
26-
| camera_ | firewire | kernel-snap | oob-management | submission | wwan |
27-
+------------+-------------+-------------+------------------+-------------+----------------+
28-
| canary | firmware | keys | optical | suspend | |
29-
+------------+-------------+-------------+------------------+-------------+----------------+
30-
| codecs | gadget | led | power-management | thunderbolt | |
31-
+------------+-------------+-------------+------------------+-------------+----------------+
32-
| cpu | gpio | location | rtc | touchpad | |
33-
+------------+-------------+-------------+------------------+-------------+----------------+
34-
| disk | graphics | mediacard | security | touchscreen | |
35-
+------------+-------------+-------------+------------------+-------------+----------------+
36-
| dock | hibernate | memory | self | tpm | |
37-
+------------+-------------+-------------+------------------+-------------+----------------+
15+
+-----------+-------------+-------------+-------------------+-------------+----------------+
16+
| 6lowpan | eeprom | hibernate | memory | self | tmp |
17+
+-----------+-------------+-------------+-------------------+-------------+----------------+
18+
| acpi | esata | i2c | miscellanea | serial | ubuntucore |
19+
+-----------+-------------+-------------+-------------------+-------------+----------------+
20+
| audio | ethernet | image | mobilebroadband | smoke | usb |
21+
+-----------+-------------+-------------+-------------------+-------------+----------------+
22+
| benchmarks| expresscard | info | monitor | snapd | virtualization |
23+
+-----------+-------------+-------------+-------------------+-------------+----------------+
24+
| bluetooth | fingerprint | input | networking | socketcan | watchdog |
25+
+-----------+-------------+-------------+-------------------+-------------+----------------+
26+
| camera_ | firewire | install | nvdimm | submission | wireless |
27+
+-----------+-------------+-------------+-------------------+-------------+----------------+
28+
| canary | firmware | kernel-snap | oob-management | suspend | wwan |
29+
+-----------+-------------+-------------+-------------------+-------------+----------------+
30+
| codecs | fscrypt | keys | optical | tpm | |
31+
+-----------+-------------+-------------+-------------------+-------------+----------------+
32+
| cpu | gadget | led | power-management | thunderbolt | |
33+
+-----------+-------------+-------------+-------------------+-------------+----------------+
34+
| disk | gpio | location | rtc | touchpad | |
35+
+-----------+-------------+-------------+-------------------+-------------+----------------+
36+
| dock | graphics | mediacard | security | touchscreen | |
37+
+-----------+-------------+-------------+-------------------+-------------+----------------+
3838

3939
.. _camera: units/camera/README.rst

providers/base/bin/fscrypt_test.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/usr/bin/env python3
2+
"""
3+
script to test fscrypt support
4+
5+
Copyright (C) 2025 Canonical Ltd.
6+
7+
Authors
8+
Alexis Cellier <[email protected]>
9+
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU General Public License version 3,
12+
as published by the Free Software Foundation.
13+
14+
This program is distributed in the hope that it will be useful,
15+
but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
GNU General Public License for more details.
18+
19+
You should have received a copy of the GNU General Public License
20+
along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
22+
The purpose of this scrupt is to make a small fscrupt test on a generated
23+
ext4 disk image to validate the filesystem encryption support.
24+
"""
25+
26+
import os
27+
import subprocess
28+
import tempfile
29+
import re
30+
from pathlib import Path
31+
32+
33+
def main():
34+
"""
35+
Create an ext4 disk image, mount it, setup fscrypt and use it with a simple
36+
file
37+
"""
38+
with tempfile.TemporaryDirectory() as tmp_path:
39+
tmp = Path(tmp_path)
40+
mnt = tmp / "mnt"
41+
img = tmp / "fs.img"
42+
key_file = tmp / "key"
43+
test_dir = mnt / "test"
44+
test_file = test_dir / "test.txt"
45+
fscrypt_config = Path("/etc/fscrypt.conf")
46+
fscrypt_setup = fscrypt_config.exists()
47+
48+
# Create a 50MB file
49+
subprocess.check_call(["truncate", "-s", "50M", str(img)])
50+
51+
mnt.mkdir(parents=True, exist_ok=True)
52+
53+
# Make ext4 image with encryption support
54+
subprocess.check_call(
55+
["mkfs.ext4", "-F", "-O", "encrypt,stable_inodes", str(img)]
56+
)
57+
58+
# Mount it
59+
subprocess.check_call(["mount", str(img), str(mnt)])
60+
61+
try:
62+
# Setup fscrypt
63+
print("Setup fscrupt")
64+
if not fscrypt_setup:
65+
subprocess.run(
66+
["fscrypt", "setup", "--force"],
67+
input="n\n",
68+
text=True,
69+
check=True,
70+
)
71+
subprocess.run(
72+
["fscrypt", "setup", "--force", str(mnt)],
73+
input="n\n",
74+
text=True,
75+
check=True,
76+
)
77+
78+
# Confirm fscrypt is enabled
79+
output = subprocess.check_output(
80+
["fscrypt", "status"],
81+
text=True,
82+
)
83+
found = False
84+
pattern = re.compile(rf"^{re.escape(str(mnt))}.*supported *Yes$")
85+
for line in output.splitlines():
86+
if pattern.match(line):
87+
found = True
88+
break
89+
if not found:
90+
raise SystemExit("Failed to setup fscrypt")
91+
92+
# Write random key
93+
with key_file.open("wb") as f:
94+
f.write(os.urandom(32))
95+
96+
# Make test directory
97+
test_dir.mkdir(parents=True, exist_ok=True)
98+
99+
# Encrypt directory
100+
subprocess.check_call(
101+
[
102+
"fscrypt",
103+
"encrypt",
104+
"--quiet",
105+
"--source=raw_key",
106+
"--name=test_key",
107+
f"--key={str(key_file)}",
108+
str(test_dir),
109+
]
110+
)
111+
112+
# Write a file inside
113+
with test_file.open("w") as f:
114+
f.write("test\n")
115+
116+
# Lock the directory
117+
subprocess.check_call(["fscrypt", "lock", str(test_dir)])
118+
119+
# Should not be able to list the file
120+
if test_file.exists():
121+
raise SystemExit("File should not be accessible when locked")
122+
print("File correctly inaccessible when locked")
123+
124+
# Unlock the directory
125+
subprocess.check_call(
126+
["fscrypt", "unlock", f"--key={str(key_file)}", str(test_dir)]
127+
)
128+
129+
with test_file.open("r") as f:
130+
content = f.read().strip()
131+
if content != "test":
132+
raise SystemExit("File contents not correct after unlock")
133+
print("File is accessible and content is correct after unlock")
134+
finally:
135+
subprocess.check_call(["umount", str(mnt)])
136+
if not fscrypt_setup and fscrypt_config.exists():
137+
fscrypt_config.unlink()
138+
139+
140+
if __name__ == "__main__":
141+
main()

providers/base/debian/control

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ Recommends: bonnie++,
4242
smartmontools,
4343
sysstat,
4444
${plainbox:Recommends}
45-
Suggests: fswebcam,
45+
Suggests: fscrypt,
46+
fswebcam,
4647
fwts,
4748
glmark2,
4849
glmark2-es2,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from unittest.mock import patch
2+
import fscrypt_test
3+
import unittest
4+
5+
6+
def _prepare_path_and_subprocess(mock_path, mock_subprocess):
7+
mock_path.exists.side_effect = [True, False, True]
8+
mock_path.mkdir.return_value = True
9+
mock_path.__truediv__.return_value = mock_path
10+
mock_path.__str__.return_value = "path"
11+
mock_path.return_value = mock_path
12+
mock_path.open = mock_path
13+
mock_path.__enter__ = mock_path
14+
mock_path.read.return_value = "test\n"
15+
mock_subprocess.run.side_effect = [0, Exception("Should be called once")]
16+
mock_subprocess.check_call.return_value = 0
17+
mock_subprocess.check_output.return_value = (
18+
"path this-is-ignore this-also supported Yes"
19+
)
20+
21+
22+
class FscryptTestCase(unittest.TestCase):
23+
@patch("fscrypt_test.Path")
24+
@patch("fscrypt_test.subprocess")
25+
def test_main_success(self, mock_subprocess, mock_path):
26+
_prepare_path_and_subprocess(
27+
mock_path,
28+
mock_subprocess,
29+
)
30+
fscrypt_test.main()
31+
32+
@patch("fscrypt_test.Path")
33+
@patch("fscrypt_test.subprocess")
34+
def test_main_failure_setup(self, mock_subprocess, mock_path):
35+
_prepare_path_and_subprocess(
36+
mock_path,
37+
mock_subprocess,
38+
)
39+
# Fake fscrypt setup not working.
40+
mock_subprocess.check_output.return_value = (
41+
"path this-is-ignore this-also supported No"
42+
)
43+
with self.assertRaises(SystemExit) as context:
44+
fscrypt_test.main()
45+
self.assertEqual(str(context.exception), "Failed to setup fscrypt")
46+
47+
@patch("fscrypt_test.Path")
48+
@patch("fscrypt_test.subprocess")
49+
def test_main_failure_lock(self, mock_subprocess, mock_path):
50+
_prepare_path_and_subprocess(
51+
mock_path,
52+
mock_subprocess,
53+
)
54+
# Fake fscrypt lock not working.
55+
mock_path.exists.side_effect = [True, True, True]
56+
with self.assertRaises(SystemExit) as context:
57+
fscrypt_test.main()
58+
self.assertEqual(
59+
str(context.exception), "File should not be accessible when locked"
60+
)
61+
62+
@patch("fscrypt_test.Path")
63+
@patch("fscrypt_test.subprocess")
64+
def test_main_failure_unlock(self, mock_subprocess, mock_path):
65+
_prepare_path_and_subprocess(
66+
mock_path,
67+
mock_subprocess,
68+
)
69+
# Fake bad unlink file content.
70+
mock_path.read.return_value = "invalid\n"
71+
with self.assertRaises(SystemExit) as context:
72+
fscrypt_test.main()
73+
self.assertEqual(
74+
str(context.exception), "File contents not correct after unlock"
75+
)
76+
77+
@patch("fscrypt_test.Path")
78+
@patch("fscrypt_test.subprocess")
79+
def test_main_failure_not_already_setup(self, mock_subprocess, mock_path):
80+
_prepare_path_and_subprocess(
81+
mock_path,
82+
mock_subprocess,
83+
)
84+
# Fake fscrypt.conf not already existing, so it has to be deleted.
85+
mock_path.exists.side_effect = [False, False, True]
86+
mock_subprocess.run.side_effect = [0, 0]
87+
fscrypt_test.main()
88+
self.assertTrue(mock_path.unlink.called)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
unit: category
2+
id: fscrypt
3+
_name: Ubuntu filesystem encryption feature tests
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
id: fscrypt/check-kernel-config
2+
plugin: shell
3+
category_id: fscrypt
4+
command: kernel_config.py --config-flag CONFIG_FS_ENCRYPTION
5+
estimated_duration: 0.005
6+
_purpose: Checks the value of the CONFIG_FS_ENCRYPTION flag in the kernel configuration
7+
_summary: Check if the kernel is compiled with filesystem encryption support
8+
9+
id: fscrypt/check-support
10+
plugin: shell
11+
category_id: fscrypt
12+
estimated_duration: 5s
13+
user: root
14+
depends: fscrypt/check-kernel-config
15+
requires: executable.name == "fscrypt"
16+
_summary: Check fscrypt support
17+
_purpose: Create an ext4 image and test the filesystem encryption feature
18+
command: fscrypt_test.py
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
unit: packaging meta-data
2+
os-id: ubuntu
3+
Depends: fscrypt
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
id: fscrypt-full
2+
unit: test plan
3+
_name: fscrypt tests
4+
_description: fscrypt tests
5+
include:
6+
nested_part:
7+
fscrypt-automated
8+
9+
id: fscrypt-automated
10+
unit: test plan
11+
_name: automated fscrypt tests
12+
_description: Automated fscrypt tests
13+
include:
14+
fscrypt/check-kernel-config
15+
fscrypt/check-support

0 commit comments

Comments
 (0)