Database
The Redis database runs as a Podman rootfull container, bound to TCP port 6379.
Redis is not used only as a database:
- it exchanges messages among agents and the API server
- it helps modules to discover information about the core and other modules
- it provides a signaling bus based on its PUB/SUB feature
- it stores (a copy of) the system and modules configuration
- it is an authentication backend for the management UI and the agents
Get the Redis service status:
systemctl status redis
If any change occurs in the Redis database, it is persisted on the disk within 5 seconds. The following command prints the database path:
podman volume inspect redis-data | jq -r .[].Mountpoint
Redis provides blocking pop operations on lists. The BRPOP command is
used by agents to wait on a queue for incoming tasks. Task queues keys
look like module/{module_id}/tasks
.
Redis provides PUB/SUB operations on channels. Channels are used to implement the observer pattern, for instance in the following situations:
-
An agent wants to notify the UI about the progress of a running task. The channel name in this case looks like
progress/module/{module_id}/task/{uuid}
. -
An agent wants to communicate to other agents that a particular event occurred (e.g. a TLS certificate was automatically renewed). The channel name in this case could be like
module/{module_id}/event/{event_name}
Access
Access Redis with one of the following commands:
redis-cli PING
-
The
redis-cli
command is faster when invoked by the root user, because it runs a command in the existingredis
rootfull container. For non-root users a temporary container is created and destroyed. -
The
redis-cli
command attempts to connect with the higher available Redis privileges, by readingagent.env
files.
A Python-based helper script, redis-exec
is available and it better
suits Bash action scripts as it does not start any container. This client
is synchronous as it waits for the server response:
runagent redis-exec PING
Note that the runagent
wrapper is needed because redis-exec
is
designed to run inside an “agent environment”.
An alternative, synchronous invocation relies on the nc
command,
provided by the nmap-ncat
RPM (good for development environments):
nc 127.0.0.1 6379 <<EOF
PING
EOF
For completeness here is a pure Bash invocation that does not wait the server response:
cat >/dev/tcp/127.0.0.1/6379 <<EOF
PING
EOF
Roles
In a NS8 cluster the leader node runs Redis with master role. All write operations must be executed on the leader instance. Worker nodes run Redis with replica role, so they allow only read operations.
Changes are propagated from the leader instance to the worker instances. The following command on the leader node describes the replication status:
redis-cli ROLE
Example of the command output:
1) "master"
2) (integer) 57693
3) 1) 1) "10.5.4.2"
2) "6379"
3) "57693"
The same command in a worker node has a different output. Note that the deprecated term slave may still used by the Redis protocol to describe the replica role:
1) "slave"
2) "10.5.4.1"
3) (integer) 6379
4) "connected"
5) (integer) 57721
Refer to the Redis official documentation for the exact meaning of the command output.
The above command identifies the Redis leader instance by IP address and port number. To know the leader node ID number run this command instead
redis-cli HGET cluster/environment NODE_ID
To get back the IP address of an arbitrary node, given its node ID, e.g. 3:
redis-cli HGET node/3/vpn ip_address
Schema
Redis is a key/value database. Some key naming rules are enforced because access rights are based on key name patterns. Both admin UI users and agents have their own Redis credentials for authentication and authorization.
Some key rule examples:
-
cluster/environment
,node/{node_id}/environment
The key type is HASH. It stores a copy of environment variables of respectively the cluster agent running on the leader node, and the agent environment of node_id. -
module/{module_id}/environment
The key type is HASH. It stores a copy of environment variables of the module module_id. -
node/{node_id}/vpn
The key type is HASH. It stores the cluster VPN configuration applied by the node agent node_id -
cluster/node_sequence
The key value is an integer. As the name suggests it is used to generate a node ID, when a new node is added to the cluster.
Next sections describe the value stored for each database key. Key names follow this notation:
/
logical key namespace separator{var}
(curly braces with name) placeholder for any string value- ` ` (single space) hash field separator
A similar pattern is used also for channel names of events.
cluster/
Key prefix for the cluster agent key namespace. Keys with this prefix are publicly accessible. As many keys has this prefix, see also the next subsections for more information.
key | type | description |
---|---|---|
cluster/network | CIDR | WireGuard VPN network, default: 10.5.4.0/24 |
cluster/default_instance/{image} | STRING | a module ID for the given image ID |
cluster/node_sequence | INTEGER | generate node IDs, default: 0 |
cluster/module_sequence/{image} | INTEGER | module sequence to generate instances of image ID, default: 0 |
cluster/module_node | HASH | The module-node association, used for roles assignment |
cluster/module_domains | HASH | Store relation of modules with user domains. Hash key is MODULE_ID, hash value is a list of domains separated by spaces, e.g. mydom1.tld mydom2.tld mydom3.tld |
cluster/authorizations/{agent_id} | SET | Authorization labels persistence, to enforce labels on future modules |
cluster/roles/{role} | SET | glob patterns matching the actions that {role} can run. {role} is one of “owner”, “reader”… |
cluster/environment | HASH | Cluster environment variables |
cluster/environment NODE_ID | INTEGER | The node ID of the leader node |
cluster/ui_name | STRING | UI label for the cluster |
cluster/uuid | STRING | Generated UUID that identifies the cluster |
cluster/override/modules | HASH | Maps (image name) => (image URL), overriding the standard image name/URL resolution function |
cluster/subscription | HASH | Subscription attributes in key/value pairs |
cluster/apply_updates | HASH | Scheduled updates attributes in key/value pairs |
cluster/tcp_ports_demand | HASH | Max TCP ports a module can request. Hash key is MODULE_ID, hash value is the max TCP ports. |
cluster/udp_ports_demand | HASH | Max UDP ports a module can request. Hash key is MODULE_ID, hash value is the max UDP ports. |
cluster/smarthost
This is not a key prefix, it is a key of type HASH by itself and contains setting storage for SMTP smarthost.
key | type | description |
---|---|---|
cluster/smarthost | HASH | Settings and credentials of a SMTP smarthost provider |
cluster/smarthost port | INTEGER | Public TCP port of the smtp server |
cluster/smarthost enabled | 0|1 | (0 disabled, 1 enabled) enable the smarthost provider |
cluster/smarthost tls | 0|1 | (0 disabled, 1 enabled) enable STARTTLS |
cluster/smarthost tls_verify | 0|1 | (0 disabled, 1 enabled) verify if the certificate is valid and if the hostname is associated to the certificate |
cluster/smarthost username | STRING | username for smtp credentials |
cluster/smarthost password | STRING | password for smtp credentials |
cluster/repository/
Setting storage for Software repositories.
key | type | description |
---|---|---|
cluster/repository/{repo} | HASH | A software {repo} |
cluster/repository/{repo} url | STRING | HTTPS URL of repository |
cluster/repository/{repo} status | 0|1 | (0 disabled, 1 enabled) |
cluster/repository/{repo} testing | 0|1 | (0 disabled, 1 enabled) |
cluster/repository_cache/{repo} | HASH | Cache of remote repo metadata, with a TTL |
cluster/repository_cache/{repo} data | STRING | It contains a JSON string from repodata.json |
cluster/repository_cache/{repo} updates | STRING | Most recent modification date and time of remote repodata.json |
cluster/backup_repository/
Each backup repository is identified with a UUID-based key of type HASH,
under the key prefix cluster/backup_repository/
.
key | type | description |
---|---|---|
cluster/backup_repository/{repo} | HASH | A backup repository UUID |
cluster/backup_repository/{repo} url | STRING | Restic URL of repository, eg. b2:<bucket> , s3:s3.amazonaws.com/<bucket> |
cluster/backup_repository/{repo} password | STRING | Restic encrypt password |
cluster/backup_repository/{repo} name | STRING | Custom repository name |
cluster/backup_repository/{repo} provider | STRING | Provider name, can be: backblaze , aws |
cluster/backup_repository/{repo} b2_account_id | STRING | Backblaze B2 account ID, present only if provider is backblaze |
cluster/backup_repository/{repo} b2_account_key | STRING | Backblaze B2 account KEY, present only if provider is backblaze |
cluster/backup_repository/{repo} aws_access_key_id | STRING | AWS S3 access key, present only if provider is aws |
cluster/backup_repository/{repo} aws_secret_access_key | STRING | AWS S3 secret key, present only if provider is aws |
cluster/backup_repository/{repo} aws_access_key_id | STRING | AWS S3 region, present only if provider is aws |
cluster/backup/
The attribute of a backup schedule are stored in an HASH key under the
cluster/backup/
key prefix.
key | type | description |
---|---|---|
cluster/backup/{id} | HASH | A backup repository numeric ID |
cluster/backup/{id} name | STRING | Backup name |
cluster/backup/{id} enabled | 0|1 | (0 disabled, 1 enabled) |
cluster/backup/{id} repository | STRING | UUID of backup_repository |
cluster/backup/{id} retention | INTEGER | Number of snapshots to keep |
cluster/backup/{id} schedule | STRING | Schedule time using onCalendar systemd syntax |
cluster/backup/{id} schedule_hint | STRING | Schedule in JSON format for the UI |
cluster/user_domain/
Settings storage for external user domains, LDAP databases not hosted by the cluster.
key | type | description |
---|---|---|
cluster/user_domain/ldap/{domain}/conf | HASH | An external LDAP domain |
cluster/user_domain/ldap/{domain}/conf schema | STRING | It can be ad or rfc2307 |
cluster/user_domain/ldap/{domain}/conf bind_dn | STRING | LDAP bind DN |
cluster/user_domain/ldap/{domain}/conf bind_password | STRING | LDAP bind password |
cluster/user_domain/ldap/{domain}/conf base_dn | STRING | LDAP base DN |
cluster/user_domain/ldap/{domain}/conf tls | STRING | Can be on or off |
cluster/user_domain/ldap/{domain}/conf tls_verify | STRING | Can be on or off |
cluster/user_domain/ldap/{domain}/providers | LIST | List of domain provider hosts |
cluster/user_domain/ldap/{domain}/ui_names | HASH | UI labels for domain providers. The key is the provider name, eg ldap.ns.test:389 => mylabel |
cluster/counters_cache/
Store the counters of users and groups for each user domain.
key | type | description |
---|---|---|
cluster/counters_cache/users/{domain} | INT | Cached number of users in the domain, updated by list APIs |
cluster/counters_cache/groups/{domain} | INT | Cached number of groups in the domain, updated by list APIs |
node/
Keys with this key prefix are publicly readable. Each node agent has write
access to its keys under node/{id}/
, where {id}
is the integer identifier
of the node (e.g. node/1/...
).
key | type | description |
---|---|---|
node/{id}/vpn | HASH | |
node/{id}/vpn ip_address | STRING | IP address in cluster/network |
node/{id}/vpn public_key | STRING | Public WireGuard VPN key |
node/{id}/vpn destinations | STRING | List of IP addresses routed through the VPN |
node/{id}/vpn endpoint | STRING | Public IP or host name of the VPN endpoint with :port suffix |
node/{id}/flags | SET | Flags to mark a node. Supported flags: nomodules , the node can’t run any module |
node/{id}/roles/{role} | SET | glob patterns matching the actions that {role} can run. {role} is one of “owner”, “reader”… |
node/{id}/tcp_ports_sequence | INTEGER | Lower boundary for the next TCP_PORTS_RANGE allocation, default: 20000 |
node/{id}/udp_ports_sequence | INTEGER | Lower boundary for the next UDP_PORTS_RANGE allocation, default: 20000 |
node/{id}/default_instance/{image} | STRING | A module ID for the given image ID |
node/{id}/environment | HASH | Node environment variables |
node/{id}/ui_name | STRING | UI label for the node |
module/
Keys with this key prefix are publicly readable. Each node agent has write
access to its keys under module/{id}/
, where {id}
is the module
instance identifier (e.g. module/openldap1/...
).
key | type | description |
---|---|---|
module/{id}/environment | HASH | |
module/{id}/roles/{role} | SET | glob patterns matching the actions that {role} can run. {role} is one of “owner”, “reader”… |
module/{id}/backups | SET | List of backup numeric IDs |
module/{id}/flags | SET | Images flags copied from org.nethserver.flags image label |
module/{id}/srv/{transport}/{service} | HASH | Service discovery information for other modules. See Service providers |
module/{id}/backup_status/{backup_id} | HASH | Backup status information with Restic output |
task/
Keys with this prefix hold sensitive information about a task execution.
The {agent_id}
identifies the agent that can read/write the keys. For
instance cluster
, node/2
, module/traefik1
…
key | type | description |
---|---|---|
task/{agent_id}/context | STRING | JSON representation of a task |
task/{agent_id}/error | STRING | stderr data generated by the action execution |
task/{agent_id}/output | STRING | task output, JSON-encoded |
task/{agent_id}/exit_code | INTEGER | action exit code |
user/
key | type | description |
---|---|---|
user/{username} | HASH | |
user/{username} display_name | STRING | Name and surname |
user/{username} email | STRING | Mail address |
roles/
Each user (or agent) can assume at most one role when it interacts with another agent. This relation is stored in Redis in a key of type HASH. A key of the hash is the agent identifier and the corresponding value is the name of the role.
key | type | description |
---|---|---|
roles/{username} | HASH | |
roles/{username} {agent_id} | STRING | Name of the role - {agent_id} is for instance module/mail1 , cluster … |
module/traefikX/
Each node runs a Traefik module instance. TLS certificates are stored under its module key prefix.
key | type | description |
---|---|---|
module/traefik{X}/certificate/{name} | HASH | |
module/traefik{X}/certificate/{name} key | STRING | Base64 key of a x509 certificate |
module/traefik{X}/certificate/{name} cert | STRING | Base64 x509 certificate |
AGENT_ID/tasks
Each agent runs a wait loop to run tasks
assigned to it. It waits on a task queue implemented with a Redis LIST key
with the form {AGENT_ID}/tasks
.
key | type | description |
---|---|---|
cluster/tasks | LIST | Task items are JSON encoded objects |
node/{id}/tasks | LIST | Task items are JSON encoded objects |
module/{id}/tasks | LIST | Task items are JSON encoded objects |
An element of the task queue is a JSON-encoded string representing an object with the following basic attributes:
attribute | type | description |
---|---|---|
id | string | UUID generated by the API server |
action | string | e.g. list-users , install , create-domain … |
data | any | any JSON type (object, list…) |
For instance
{
"id": "66b73f7a-8998-4262-a784-36639fc4b2c1",
"action": "list-actions",
"data": ["cancel-task", "list-actions"]
}