mirror of
https://github.com/kiviktnm/decman.git
synced 2026-09-19 12:08:28 +00:00
Add user and pgp key management modules (fixes #38)
This commit is contained in:
+42
-1
@@ -14,7 +14,7 @@ Building of foreign packages happens in a chroot. This creates some overhead, bu
|
||||
|
||||
Build packages are by default stored in a cache `/var/cache/decman/aur`. This plugin keeps 3 most recent versions of all packages.
|
||||
|
||||
When installing packages from other version control systems than git, you'll need to install the package for that VCS. There is an [issue and a workaround](source) related to fossil packages. Note that the issue's workaround is for an old version of decman. With this version, set the `makepkg_user` with `decman.aur.makepkg_user`.
|
||||
When installing packages from other version control systems than git, you'll need to install the package for that VCS.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -79,6 +79,47 @@ class MyModule(decman.Module):
|
||||
|
||||
If these sets change, this plugin will flag the module as changed. The module's `on_change` method will be executed.
|
||||
|
||||
## Recommended setup
|
||||
|
||||
I recommend setting up a build user for AUR packages. Then you can import PGP keys to that user's keyring that will be used for verifying AUR packages. The build user setup might help with some version control systems such as fossil packages.
|
||||
|
||||
```python
|
||||
import decman
|
||||
import os
|
||||
from decman.extras.gpg import GPGReceiver
|
||||
from decman.extras.users import User, UserManager
|
||||
|
||||
um = UserManager()
|
||||
gpg = GPGReceiver()
|
||||
|
||||
# Create builduser
|
||||
um.add_user(User(
|
||||
username="builduser",
|
||||
home="/var/lib/builduser",
|
||||
system=True,
|
||||
))
|
||||
|
||||
# Receive desired PGP keys to that account (Spotify as an example)
|
||||
gpg.fetch_key(
|
||||
user="builduser",
|
||||
gpg_home="/var/lib/builduser/gnupg",
|
||||
fingerprint="E1096BCBFF6D418796DE78515384CE82BA52C83A",
|
||||
uri="https://download.spotify.com/debian/pubkey_5384CE82BA52C83A.gpg",
|
||||
)
|
||||
|
||||
# Configure aur to use builduser and the GNUPGHOME.
|
||||
os.environ["GNUPGHOME"] = "/var/lib/builduser/gnupg"
|
||||
decman.aur.makepkg_user = "builduser"
|
||||
|
||||
# Add version control systems required by the packages
|
||||
decman.pacman.packages |= {"fossil"}
|
||||
|
||||
# Add AUR packages that require PGP keys or builduser setup
|
||||
decman.aur.packages |= {"spotify", "pikchr-fossil"}
|
||||
|
||||
decman.modules += [um, gpg]
|
||||
```
|
||||
|
||||
## Keys used in the decman store
|
||||
|
||||
- `aur_packages_for_module`
|
||||
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
# Extras
|
||||
|
||||
Decman ships with some built in modules. They implement functionality that is probably useful for declarative management, but for one reason or another don't make sense as plugins.
|
||||
|
||||
## User and group management module
|
||||
|
||||
```python
|
||||
import decman.extras.users
|
||||
```
|
||||
|
||||
A decman module for managing system users, groups, and supplementary group membership and subordinate UID/GID ranges for existing users.
|
||||
|
||||
The module is **additive**: it only manages users/groups you explicitly register, and it only manages additional groups/subids you explicitly define. Anything created manually and not tracked by this module is left alone.
|
||||
|
||||
### Provided types
|
||||
|
||||
#### `Group`
|
||||
|
||||
Represents a managed group.
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class Group:
|
||||
groupname: str
|
||||
gid: Optional[int] = None
|
||||
system: bool = False
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
- `groupname`: Group name.
|
||||
- `gid`: Desired numeric GID. If omitted, system assigns one.
|
||||
- `system`: Only affects _creation_ (`groupadd --system`). Changing this after creation does nothing.
|
||||
|
||||
#### `User`
|
||||
|
||||
Represents a managed user.
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class User:
|
||||
username: str
|
||||
uid: Optional[int] = None
|
||||
group: Optional[str] = None
|
||||
home: Optional[str] = None
|
||||
shell: Optional[str] = None
|
||||
groups: tuple[str, ...] = ()
|
||||
system: bool = False
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
- `username`: Login name.
|
||||
- `uid`: Desired numeric UID. If omitted, system assigns one.
|
||||
- `group`: Primary group name.
|
||||
- `home`: Home directory.
|
||||
- `shell`: Login shell.
|
||||
- `groups`: Supplementary groups set.
|
||||
- `system`: Only affects _creation_ (`useradd --system`). Changing this after creation does nothing.
|
||||
|
||||
### `UserManager` module
|
||||
|
||||
```python
|
||||
class UserManager(Module):
|
||||
```
|
||||
|
||||
#### Lifecycle
|
||||
|
||||
- Before update
|
||||
- Create/modify managed groups.
|
||||
- Create/modify managed users.
|
||||
- Delete previously-managed users/groups that are no longer listed.
|
||||
- After update
|
||||
- Apply **additional** supplementary group membership and **subuid/subgid** ranges (including removals).
|
||||
|
||||
#### Store keys
|
||||
|
||||
The module persists state in decman store under these keys:
|
||||
|
||||
- `usermanager_users`
|
||||
- `usermanager_groups`
|
||||
- `usermanager_user_additional_groups`
|
||||
- `usermanager_user_subuids`
|
||||
- `usermanager_user_subgids`
|
||||
|
||||
The module does **not** parse `/etc/subuid` or `/etc/subgid`; it relies on these store keys to compute additions/removals.
|
||||
|
||||
#### Methods
|
||||
|
||||
##### `add_user(user: User)`
|
||||
|
||||
Ensure a user exists with the configured attributes.
|
||||
|
||||
Notes:
|
||||
|
||||
- If `uid` is provided and an existing user matches by UID but has a different name, the module will rename the user (`usermod --login`) and apply other changes.
|
||||
|
||||
##### `add_group(group: Group)`
|
||||
|
||||
Ensure a group exists with the configured attributes.
|
||||
|
||||
##### `add_user_to_group(user: str, group: str)`
|
||||
|
||||
Ensure `user` is a member of `group`.
|
||||
|
||||
- This is applied in `after_update`.
|
||||
- Both `user` and `group` are expected to exist
|
||||
|
||||
You should not use this method for users added with `add_user`.
|
||||
|
||||
##### `add_subuids(user: str, first: int, last: int)`
|
||||
|
||||
Ensure subordinate UID range `first-last` is present for `user`.
|
||||
|
||||
##### `add_subgids(user: str, first: int, last: int)`
|
||||
|
||||
Ensure subordinate GID range `first-last` is present for `user`.
|
||||
|
||||
### Example usage
|
||||
|
||||
```python
|
||||
from decman.extras.users import UserManager, User, Group
|
||||
|
||||
um = UserManager()
|
||||
|
||||
um.add_group(Group("containers", system=True))
|
||||
um.add_user(User(
|
||||
username="alice",
|
||||
uid=1001,
|
||||
group="users",
|
||||
home="/home/alice",
|
||||
groups=(),
|
||||
shell="/bin/zsh",
|
||||
))
|
||||
|
||||
um.add_user_to_group("bob", "containers")
|
||||
|
||||
um.add_subuids("alice", 100000, 165535)
|
||||
um.add_subgids("alice", 100000, 165535)
|
||||
|
||||
import decman
|
||||
decman.modules += [um]
|
||||
```
|
||||
|
||||
## GPG receiver module
|
||||
|
||||
```python
|
||||
import decman.extras.gpg
|
||||
```
|
||||
|
||||
Manages importing OpenPGP public keys into per-user GnuPG homes. Tracks imported keys in the decman store and removes keys that were previously managed but are no longer configured.
|
||||
|
||||
This module is intentionally limited since it's main usage is for AUR build users. You probably shouldn't manage your primary user’s keyring with it.
|
||||
|
||||
### Types
|
||||
|
||||
#### `OwnerTrust`
|
||||
|
||||
Valid ownertrust levels:
|
||||
|
||||
- `never`
|
||||
- `marginal`
|
||||
- `full`
|
||||
- `ultimate`
|
||||
|
||||
These map to GnuPG `--import-ownertrust` numeric levels `1..4`.
|
||||
|
||||
#### `SourceKind`
|
||||
|
||||
How a key is imported:
|
||||
|
||||
- `fingerprint`: fetch from keyserver via `--recv-keys`
|
||||
- `uri`: fetch from URI via `--fetch-key`
|
||||
- `file`: import from local file via `--import`
|
||||
|
||||
#### `Key`
|
||||
|
||||
Represents one managed key entry.
|
||||
|
||||
Fields:
|
||||
|
||||
- `fingerprint`: OpenPGP fingerprint, validated to be exactly 40 hex chars (spaces allowed in input; normalized by removing spaces and uppercasing).
|
||||
- `source_kind`: one of `fingerprint | uri | file`.
|
||||
- `source`: keyserver (for `fingerprint`), URI (for `uri`), or filepath (for `file`).
|
||||
- `trust`: optional `OwnerTrust` to set via ownertrust import.
|
||||
|
||||
Validation behavior:
|
||||
|
||||
- Fingerprint is normalized: `replace(" ", "").upper()`.
|
||||
- Fingerprint must match `^[0-9A-F]{40}$`; otherwise `ValueError`.
|
||||
|
||||
### `GPGReceiver` module
|
||||
|
||||
```python
|
||||
class GPGReceiver(module.Module):
|
||||
```
|
||||
|
||||
#### Store keys
|
||||
|
||||
The module persists state in decman store under these keys:
|
||||
|
||||
- `gpgreceiver_userhome_keys`
|
||||
|
||||
It relies on the store to keep track which keys were added by it.
|
||||
|
||||
#### Public API
|
||||
|
||||
##### `receive_key(user: str, gpg_home: str, fingerprint: str, keyserver: str, trust: OwnerTrust | None = None)`
|
||||
|
||||
Receives a key with a `fingerprint` from a `keyserver` to a `gpg_home` owned by `user`.
|
||||
|
||||
If `trust` is provided, ownertrust is set after import.
|
||||
|
||||
##### `fetch_key(user: str, gpg_home: str, fingerprint: str, uri: str, trust: OwnerTrust | None=None)`
|
||||
|
||||
Receives a key with a `fingerprint` from a `uri` to a `gpg_home` owned by `user`.
|
||||
|
||||
If `trust` is provided, ownertrust is set after import.
|
||||
|
||||
##### `import_key(user: str, gpg_home: str, fingerprint: str, file: str, trust: OwnerTrust | None =None)`
|
||||
|
||||
Receives a key with a `fingerprint` from a local `file` to a `gpg_home` owned by `user`.
|
||||
|
||||
If `trust` is provided, ownertrust is set after import.
|
||||
|
||||
### Example usage
|
||||
|
||||
```python
|
||||
from decman.modules.gpg import GPGReceiver
|
||||
import decman
|
||||
|
||||
gpg = GPGReceiver()
|
||||
|
||||
# Receive a key from a keyserver
|
||||
gpg.receive_key(
|
||||
user="builduser",
|
||||
gpg_home="/var/lib/builduser/gnupg",
|
||||
fingerprint="AAAA AAAA AAAA AAAA AAAA AAAA AAAA AAAA AAAA AAAA",
|
||||
keyserver="hkps://keyserver.ubuntu.com",
|
||||
trust="marginal",
|
||||
)
|
||||
|
||||
# Fetch a key from a URI
|
||||
gpg.fetch_key(
|
||||
user="alice",
|
||||
gpg_home="/home/alice/.gnupg",
|
||||
fingerprint="BBBB BBBB BBBB BBBB BBBB BBBB BBBB BBBB BBBB BBBB",
|
||||
uri="https://example.org/signing-key.asc",
|
||||
)
|
||||
|
||||
# Import a key from a local file
|
||||
gpg.import_key(
|
||||
user="bob",
|
||||
gpg_home="/home/bob/.gnupg",
|
||||
fingerprint="CCCC CCCC CCCC CCCC CCCC CCCC CCCC CCCC CCCC CCCC",
|
||||
file="/etc/decman/keys/custom.asc",
|
||||
)
|
||||
|
||||
decman.modules += [gpg]
|
||||
```
|
||||
Reference in New Issue
Block a user