Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ libmodcore = { path = "./libmodcore" }
libcommon = { path = "./libcommon" }
libsysproto = { path = "./libsysproto" }
libsetup = { path = "./libsetup" }
libsensors = { path = "./libsensors" }
log = "0.4.29"
sysinfo = { version = "0.33.1", features = ["linux-tmpfs"] }
tokio = { version = "1.52.3", features = ["full"] }
Expand Down
13 changes: 12 additions & 1 deletion docs/apidoc/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Authentication And Requests
The Web API uses:

- HTTPS/TLS for transport protection
- bearer tokens for authentication
- bearer tokens for operator and external client authentication
- plain JSON request and response bodies

Typical flow:
Expand All @@ -40,6 +40,17 @@ Typical flow:
2. receive ``access_token``
3. call later endpoints with ``Authorization: Bearer <token>``

Datastore endpoints used by internal minion-side resource workflows are a small
exception to the operator flow above:

- a minion can bootstrap datastore access through ``POST /store/auth/minion``
- the bootstrap request is signed with the minion's registered RSA identity
- the API returns a short-lived datastore bearer token
- later datastore calls use that token as normal bearer auth

This keeps external API access operator-authenticated while letting internal
``cfg.resource`` model actions access the datastore transparently.

Swagger UI itself is served over the same HTTPS listener. Operators typically:

1. open ``https://<host>:4202/doc/``
Expand Down
9 changes: 8 additions & 1 deletion docs/global_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -497,11 +497,18 @@ Below are directives for the configuration of the File Server service:

Type: **string**

Authentication method to be used for the embedded Web API. This is a string and can be one of the following:
Authentication method to be used for the embedded Web API. This applies to
normal operator or external API clients authenticating through
``POST /api/v1/authenticate``. This is a string and can be one of the
following:

- ``pam``
- ``ldap`` `(planned, not implemented yet)`

Datastore endpoints used by internal minion-originated ``cfg.resource``
calls also support transparent minion-auth bootstrap and are not intended
to require operator PAM credentials inside model execution.

``api.devmode``
################

Expand Down
111 changes: 105 additions & 6 deletions docs/moddescr/cfg_resource.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,59 @@ It can:
The module is designed for resource workflows where models only describe
**what** to place and **where**.

At the moment, transport is resolved internally as:
Transport is resolved internally against ``master.ip`` from Minion
configuration and can be controlled explicitly with module args:

- ``http://<master.ip>:4202``
- ``tls: true`` => ``https://<master.ip>:<port>``
- ``tls: false`` => ``http://<master.ip>:<port>``

where ``master.ip`` comes from Minion configuration, and ``4202`` is the current default API port.
Authentication headers are not used yet in this module version.
Authentication Model
--------------------

There are two different authentication contexts around ``cfg.resource``:

- **Operator / external Web API access**
The embedded Web API uses normal HTTPS transport plus bearer-token
authentication for human or external API clients.

- **Minion-internal resource access**
``cfg.resource`` is intended to be used from minion-side model actions.
In that flow the minion is already trusted by the Master over the normal
Sysinspect transport, so datastore access should be transparent and must not
require PAM login prompts or operator bearer-token handling inside models.

The current design for ``cfg.resource`` is therefore:

- keep bearer-token auth for normal external Web API users
- allow datastore endpoints to accept **minion-auth** for minion-originated
requests
- keep the module itself stateless

Current minion-auth bootstrap shape:

- ``X-Sysinspect-Minion-Id``
- ``X-Sysinspect-Timestamp``
- ``X-Sysinspect-Signature``
- ``X-Sysinspect-Body-Sha256``

The bootstrap signature covers a small canonical request string:

- HTTP method
- request path
- query string
- timestamp
- body hash

The module first authenticates on:

- ``POST /store/auth/minion``

and receives a short-lived datastore bearer token. Subsequent datastore calls
for that module run then use normal ``Authorization: Bearer`` headers, but the
token was bootstrapped transparently from the minion's registered RSA identity.

This keeps datastore access transparent for internal model execution while
still preventing unauthenticated external callers from using the same API.

In simple words for day-to-day usage:

Expand All @@ -43,6 +90,10 @@ The following options are available:
``force``
Disable checksum skip logic. Always push/pull even if data already matches.

``sync-dir``
Resolve all datastore items whose logical id matches the prefix from ``src``
and materialise them into a local directory.


The following keyword arguments are available:

Expand All @@ -62,19 +113,38 @@ The following keyword arguments are available:
``dst`` (type: string, optional)
Alias for ``file``.

For ``sync-dir`` it should point to a destination directory.

``mode`` (type: string, optional)
File mode for pull result (octal, for example ``0644``).
If omitted, mode from datastore metadata is applied.

``tls`` (type: bool, optional)
Whether to use HTTPS for the datastore API transport.
Default: ``true``.

``tls-accept-insecure`` (type: bool, optional)
Allow self-signed or otherwise invalid TLS certificates.
Default: ``false``.

``port`` (type: integer, optional)
Web API port on the master.
Default: ``4202``.


Behavior Notes
--------------

- If ``file`` and ``dst`` are both absent, module uses ``src`` as local path.
- ``pull`` checks local checksum first and skips download when already up to date (unless ``force``).
- ``push`` checks datastore checksum first and skips upload when already up to date (unless ``force``).
- Module transport/auth details are not passed from model arguments.
They are taken from runtime Minion config injected automatically during module call.
- Only ``master.ip`` comes from runtime Minion config.
- Protocol and API port can be overridden explicitly through ``tls``,
``tls-accept-insecure``, and ``port``.
- Operator bearer-token auth is not a suitable fit for internal model calls.
- ``cfg.resource`` therefore bootstraps datastore access from the minion's
registered RSA identity and then reuses a short-lived datastore bearer token
automatically.


State Semantics
Expand Down Expand Up @@ -110,6 +180,9 @@ Pull resource to local file:
src: /somehost/etc/ssh/authorized_keys
file: /etc/ssh/authorized_keys
mode: "0600"
tls: true
tls-accept-insecure: true
port: 4202

Push local file to datastore:

Expand All @@ -125,6 +198,9 @@ Push local file to datastore:
args:
src: /somehost/etc/ssh/authorized_keys
file: /etc/ssh/authorized_keys
tls: true
tls-accept-insecure: true
port: 4202

Force pull even if checksum matches:

Expand All @@ -140,6 +216,28 @@ Force pull even if checksum matches:
args:
src: /somehost/etc/ssh/authorized_keys
dst: /etc/ssh/authorized_keys
tls: true
tls-accept-insecure: true
port: 4202

Sync all public keys from one logical prefix into a local directory:

.. code-block:: yaml

actions:
sync-pubring:
module: cfg.resource
bind: [shared-keyring]
state:
$:
opts: [sync-dir]
args:
src: /keyringdemo/
dst: /tmp/keyringdemo/pubring
mode: "0644"
tls: true
tls-accept-insecure: true
port: 4202


Returning Data
Expand All @@ -158,6 +256,7 @@ Typical extra fields in ``data``:
- ``sha256`` resolved artifact checksum
- ``size_bytes`` artifact size
- ``mode`` resulting local mode (for pull)
- ``synced`` number of materialised files (for ``sync-dir``)

The examples below are **module runtime protocol payloads** and are exchanged
between Sysinspect runtime and module process over STDIN/STDOUT, therefore
Expand Down
11 changes: 10 additions & 1 deletion examples/demos/keypair/model.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Keypair Demo
version: "0.1"
version: "0.1.1"
description: |
Demo model for self-healing host keypairs in /tmp/keypairdemo and syncing
per-host public keys to Master datastore.
Expand Down Expand Up @@ -56,13 +56,19 @@ actions:
args:
src: /keypairdemo/{{ traits['system']['hostname']['fqdn'] | default(value='unknown-host') }}.pub
file: /tmp/keypairdemo/{{ traits['system']['hostname']['fqdn'] | default(value='unknown-host') }}.pub
tls: true
tls-accept-insecure: true
port: 4202

regen-priv:
opts:
- push
args:
src: /keypairdemo/{{ traits['system']['hostname']['fqdn'] | default(value='unknown-host') }}.pub
file: /tmp/keypairdemo/{{ traits['system']['hostname']['fqdn'] | default(value='unknown-host') }}.pub
tls: true
tls-accept-insecure: true
port: 4202

keypair-repair-pub:
descr: Pull host pubkey from Master datastore when deleted
Expand All @@ -77,6 +83,9 @@ actions:
src: /keypairdemo/{{ traits['system']['hostname']['fqdn'] | default(value='unknown-host') }}.pub
file: /tmp/keypairdemo/{{ traits['system']['hostname']['fqdn'] | default(value='unknown-host') }}.pub
mode: "0644"
tls: true
tls-accept-insecure: true
port: 4202

keypair-regen-priv:
descr: Regenerate keypair when private key is missing
Expand Down
61 changes: 61 additions & 0 deletions examples/demos/keyring/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
To setup this demo, do the following:


Master
======

1. Copy model.cfg to the master's models root into subdirectory "keyring",
so you end up with "$MASTER/data/models/keyring/model.cfg"

2. Copy sensors.cfg to the master's sensors root into subdirectory "keyring",
so you end up with "$MASTER/data/sensors/keyring/sensors.cfg"

3. Export both scopes in master config:

config:
master:
fileserver.models:
- keyring
fileserver.sensors:
- keyring

4. Ensure these modules exist on the master repository:

sysinspect module -L

You should have "cfg.resource" and "sys.run" available.


Minion
======

Nothing special. Let it sync and start normally.


To demo, bootstrap the cluster first:

sysinspect keyring/keyring-files/bootstrap '*'


Expected result:

- each host creates its own local private/public pair under /tmp/keyringdemo
- each host publishes its own public key to the Master datastore
- each host syncs all cluster public keys into:

/tmp/keyringdemo/pubring/


Useful watchdogs:

watch -n 1 'find /tmp/keyringdemo -maxdepth 2 -type f | sort'


Try deleting:

- /tmp/keyringdemo/<host>.pub
- /tmp/keyringdemo/<host>.priv
- /tmp/keyringdemo/pubring/<other-host>.pub

and watch how the local host repairs its own key files and resyncs the shared
pubring.
Loading
Loading