diff options
| author | sommerfeld <sommerfeld@sommerfeld.dev> | 2026-09-17 15:05:37 +0100 |
|---|---|---|
| committer | sommerfeld <sommerfeld@sommerfeld.dev> | 2026-09-17 15:05:37 +0100 |
| commit | f9e25d7e30205756088e7dd330b8070fb5195ae9 (patch) | |
| tree | f5da3f7975e90a50c437e4c00748ee2ddf2867c8 | |
| parent | 2f97417d16224ca1f79b2fe41e70b8d7ddc0ab7d (diff) | |
| download | dotfiles-f9e25d7e30205756088e7dd330b8070fb5195ae9.tar.gz dotfiles-f9e25d7e30205756088e7dd330b8070fb5195ae9.tar.bz2 dotfiles-f9e25d7e30205756088e7dd330b8070fb5195ae9.zip | |
Permit the Mattermost Snap to use GNOME Keyring
| -rw-r--r-- | canonical/systemd/dotfiles-mattermost-keyring.path | 8 | ||||
| -rw-r--r-- | canonical/systemd/dotfiles-mattermost-keyring.service | 12 | ||||
| -rw-r--r-- | scripts/canonical-system.sh | 4 | ||||
| -rw-r--r-- | scripts/mattermost_keyring.py | 61 | ||||
| -rw-r--r-- | tests/test_mattermost_keyring.py | 59 |
5 files changed, 144 insertions, 0 deletions
diff --git a/canonical/systemd/dotfiles-mattermost-keyring.path b/canonical/systemd/dotfiles-mattermost-keyring.path new file mode 100644 index 0000000..6d3f852 --- /dev/null +++ b/canonical/systemd/dotfiles-mattermost-keyring.path @@ -0,0 +1,8 @@ +[Unit] +Description=Watch the Mattermost Snap AppArmor profile + +[Path] +PathChanged=/var/lib/snapd/apparmor/profiles/snap.mattermost-desktop.mattermost-desktop + +[Install] +WantedBy=multi-user.target diff --git a/canonical/systemd/dotfiles-mattermost-keyring.service b/canonical/systemd/dotfiles-mattermost-keyring.service new file mode 100644 index 0000000..fd75f86 --- /dev/null +++ b/canonical/systemd/dotfiles-mattermost-keyring.service @@ -0,0 +1,12 @@ +[Unit] +Description=Allow Mattermost Snap to use GNOME Keyring +After=snapd.apparmor.service snapd.service apparmor.service +Before=display-manager.service +ConditionPathExists=/var/lib/snapd/apparmor/profiles/snap.mattermost-desktop.mattermost-desktop + +[Service] +Type=oneshot +ExecStart=/usr/bin/python3 /usr/local/lib/dotfiles/mattermost_keyring.py + +[Install] +WantedBy=multi-user.target diff --git a/scripts/canonical-system.sh b/scripts/canonical-system.sh index e6f0825..cd85879 100644 --- a/scripts/canonical-system.sh +++ b/scripts/canonical-system.sh @@ -13,6 +13,10 @@ sudo apparmor_parser --skip-kernel-load --skip-cache canonical/apparmor/dotfiles sudo install -m 644 canonical/apparmor/dotfiles-nix /etc/apparmor.d/dotfiles-nix sudo apparmor_parser --replace /etc/apparmor.d/dotfiles-nix sudo snap connect thunderbird:gpg-keys +sudo install -D -m 644 scripts/mattermost_keyring.py /usr/local/lib/dotfiles/mattermost_keyring.py +sudo install -m 644 canonical/systemd/dotfiles-mattermost-keyring.{service,path} /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now dotfiles-mattermost-keyring.path dotfiles-mattermost-keyring.service systemctl --user daemon-reload systemctl --user enable --now gpg-agent.socket gpg-agent-ssh.socket podman.socket echo 'Existing GPG agent processes keep their executable until the next login.' diff --git a/scripts/mattermost_keyring.py b/scripts/mattermost_keyring.py new file mode 100644 index 0000000..a54ed6d --- /dev/null +++ b/scripts/mattermost_keyring.py @@ -0,0 +1,61 @@ +"""Permit the Mattermost Snap to use the desktop Secret Service.""" + +import os +import re +import subprocess +import tempfile +from pathlib import Path + +PROFILE = Path( + "/var/lib/snapd/apparmor/profiles/snap.mattermost-desktop.mattermost-desktop" +) +MARKER = "# dotfiles: Mattermost Secret Service access" +RULES = """ +dbus (receive, send) + bus=session + path=/org/freedesktop/secrets{,/**} + interface=org.freedesktop.DBus.* + peer=(label=unconfined), +dbus (receive, send) + bus=session + path=/org/freedesktop/secrets{,/**} + interface=org.freedesktop.Secret.{Collection,Item,Prompt,Service,Session} + peer=(label=unconfined), +""" + + +def patch_profile(text: str) -> str: + profiles = re.findall(r'^profile "([^"]+)"', text, re.MULTILINE) + if profiles != [PROFILE.name] or not text.rstrip().endswith("}"): + raise ValueError("Unexpected Mattermost AppArmor profile format.") + if MARKER in text: + return text + return text.rstrip()[:-1] + MARKER + "\n" + RULES + "}\n" + + +def install_profile(text: str) -> None: + with tempfile.TemporaryDirectory(dir=PROFILE.parent) as directory: + target = Path(directory) / PROFILE.name + target.write_text(text) + subprocess.run( + ["apparmor_parser", "--skip-kernel-load", "--skip-cache", str(target)], + check=True, + ) + target.chmod(PROFILE.stat().st_mode & 0o777) + os.replace(target, PROFILE) + + +def main() -> None: + if os.geteuid() != 0: + raise SystemExit("Run this command as root.") + original = PROFILE.read_text() + patched = patch_profile(original) + if patched != original: + install_profile(patched) + subprocess.run( + ["apparmor_parser", "--replace", "--skip-cache", str(PROFILE)], check=True + ) + + +if __name__ == "__main__": + main() diff --git a/tests/test_mattermost_keyring.py b/tests/test_mattermost_keyring.py new file mode 100644 index 0000000..93cc96d --- /dev/null +++ b/tests/test_mattermost_keyring.py @@ -0,0 +1,59 @@ +import subprocess +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from scripts import mattermost_keyring + + +class MattermostKeyringTests(unittest.TestCase): + def test_adds_rules_only_once(self): + original = 'profile "snap.mattermost-desktop.mattermost-desktop" {\n}\n' + patched = mattermost_keyring.patch_profile(original) + self.assertIn("interface=org.freedesktop.Secret.", patched) + self.assertEqual(mattermost_keyring.patch_profile(patched), patched) + + def test_rejects_other_profiles(self): + with self.assertRaises(ValueError): + mattermost_keyring.patch_profile('profile "snap.other" {\n}\n') + + def test_rejects_incomplete_profile(self): + with self.assertRaises(ValueError): + mattermost_keyring.patch_profile( + 'profile "snap.mattermost-desktop.mattermost-desktop" {\n' + ) + + def test_rejects_multiple_profiles(self): + with self.assertRaises(ValueError): + mattermost_keyring.patch_profile( + 'profile "snap.mattermost-desktop.mattermost-desktop" {\n}\n' + 'profile "snap.other" {\n}\n' + ) + + def test_preserves_snap_permissions(self): + original = ( + 'profile "snap.mattermost-desktop.mattermost-desktop" ' + "flags=(attach_disconnected,mediate_deleted) {\n /example r,\n}\n" + ) + patched = mattermost_keyring.patch_profile(original) + self.assertTrue(patched.startswith(original[:-2])) + self.assertNotIn("kwallet", patched) + self.assertNotIn("complain", patched) + + def test_parser_failure_leaves_installed_profile_unchanged(self): + with tempfile.TemporaryDirectory() as directory: + profile = Path(directory) / mattermost_keyring.PROFILE.name + profile.write_text("original") + with ( + patch.object(mattermost_keyring, "PROFILE", profile), + patch.object( + mattermost_keyring.subprocess, + "run", + side_effect=subprocess.CalledProcessError(1, "apparmor_parser"), + ), + self.assertRaises(subprocess.CalledProcessError), + ): + mattermost_keyring.install_profile("invalid") + self.assertEqual(profile.read_text(), "original") + self.assertEqual(list(Path(directory).iterdir()), [profile]) |
