diff options
| author | Loek Le Blansch <loek@pipeframe.xyz> | 2025-10-14 18:55:31 +0200 |
|---|---|---|
| committer | Loek Le Blansch <loek@pipeframe.xyz> | 2025-10-14 18:55:31 +0200 |
| commit | e8f200337975e9f8b8d57a5a0a3a2b25395cee88 (patch) | |
| tree | 07cfdfa9983c11f8454e0428475b95b3a5b1b02b | |
initial commit (wip)
| -rw-r--r-- | .gitignore | 5 | ||||
| -rw-r--r-- | license | 21 | ||||
| -rw-r--r-- | nmpass/__init__.py | 0 | ||||
| -rw-r--r-- | nmpass/main.py | 61 | ||||
| -rw-r--r-- | nmpass/store.py | 38 | ||||
| -rw-r--r-- | pyproject.toml | 23 | ||||
| -rw-r--r-- | readme.md | 0 | ||||
| -rw-r--r-- | setup.py | 2 |
8 files changed, 150 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c93d10 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.venv +venv +*.egg-info +build +__pycache__ @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Loek Le Blansch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/nmpass/__init__.py b/nmpass/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/nmpass/__init__.py diff --git a/nmpass/main.py b/nmpass/main.py new file mode 100644 index 0000000..e969e13 --- /dev/null +++ b/nmpass/main.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from asyncio import new_event_loop +from sdbus_async.networkmanager import NetworkManagerSecretAgentInterfaceAsync, NetworkManagerAgentManager, NetworkManagerConnectionProperties +from sdbus_async.networkmanager.settings import ConnectionProfile +from typing import Any +import sdbus + +from .store import PasswordStore + +class NetworkManagerPasswordStoreAgent(NetworkManagerSecretAgentInterfaceAsync): + store = PasswordStore() + + @sdbus.dbus_method_async_override() + async def get_secrets( + self, + connection: NetworkManagerConnectionProperties, + _connection_path: str, + setting_name: str, + _hints: list[str], + _flags: int, + ) -> dict[str, dict[str, tuple[str, Any]]]: + profile = ConnectionProfile.from_dbus(connection) + + printf(setting_name) + if setting_name == '802-11-wireless-security': + ssid = profile.wireless.ssid.decode() + pass_name = f"net/{ssid}/passwd" + password = self.store.retrieve(pass_name) + printf(f"SSID: {ssid} PASSWORD: {password}") + if password != None: + profile.wireless_security.psk = password + return profile.wireless_security.psk.to_dbus() + + return {} + + @sdbus.dbus_method_async_override() + async def save_secrets( + self, + connection: NetworkManagerConnectionProperties, + _connection_path: str, + ) -> None: + profile = ConnectionProfile.from_dbus(connection) + print(profile) + raise NotImplementedError + +def main(): + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + agent = NetworkManagerPasswordStoreAgent() + loop = new_event_loop() + agent.export_to_dbus('/org/freedesktop/NetworkManager/SecretAgent') + agent_manager = NetworkManagerAgentManager() + try: + loop.run_until_complete(agent_manager.register('org.nmpassd')) + loop.run_forever() + except KeyboardInterrupt: + loop.stop() + +if __name__ == "__main__": + main() + diff --git a/nmpass/store.py b/nmpass/store.py new file mode 100644 index 0000000..08080ba --- /dev/null +++ b/nmpass/store.py @@ -0,0 +1,38 @@ +from subprocess import run, PIPE, DEVNULL +from os import environ, path + +NEWLINE = "\n" + +class PasswordStore(): + PASSWORD_STORE = "pass" + PASSWORD_STORE_DIR = path.join(environ["HOME"], ".password-store") + + def __init__(self): + if "PASSWORD_STORE_DIR" in environ: + self.PASSWORD_STORE_DIR = environ["PASSWORD_STORE_DIR"] + + # There's no way to check using the pass CLI whether a password name passed + # to `pass show` is a password or a directory, so this checks manually + def exists(self, name: str) -> bool: + return path.isfile(path.join(self.PASSWORD_STORE_DIR, f"{name}.gpg")) + + def retrieve(self, name: str, truncate: bool = True) -> str | None: + if not self.exists(name): + return None + cmd = (self.PASSWORD_STORE, 'show', name,) + proc = run(cmd, stdout=PIPE) + output = proc.stdout.decode() + if not truncate: + return output + return output.split(NEWLINE)[0] + + def store(self, name: str, password: str) -> None: + current = self.retrieve(name, False) + if current == None: + current = NEWLINE + to = current.find(NEWLINE) + current = f"{password}{current[to:]}" + + cmd = (self.PASSWORD_STORE, 'insert', '--multiline', '--force', name,) + run(cmd, input=current.encode(), stdout=DEVNULL, stderr=DEVNULL) + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..dc3486a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "nm-pass" +description = "NetworkManager secret agent that reads/writes secrets to password-store" +version = "0.0.1" +authors = [ + { name="Loek Le Blansch", email="loek@pipeframe.xyz" }, +] +readme = "readme.md" +requires-python = ">=3.9" +license = "MIT" +license-files = ["license"] +dependencies = [ + "sdbus", + "sdbus-networkmanager", +] + +[project.scripts] +nmpassd = "nmpass.main:main" + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/readme.md diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8bf1ba9 --- /dev/null +++ b/setup.py @@ -0,0 +1,2 @@ +from setuptools import setup +setup() |