Configure snaps with confdb¶
Confdb is a configuration system that allows snaps to be configured in fine-grained ways using a confdb-schema assertion.
This guide will detail the steps required to use confdb, as well as explore some of its features. For implementation details, see the Confdb configuration mechanism.
We’ll use an example where one snap creates a Wi-Fi configuration that another snap can view.
Caution
Confdb is currently considered an Experimental feature and implementation details may change as development progresses.
Prerequisites¶
We first need to ensure the latest version of snapd is installed. This is important as confdb is still being actively developed:
snap refresh
We can then enable confdb with its experimental feature flag:
sudo snap set system experimental.confdb=true
Creating a confdb-schema assertion¶
To use confdb, we must first create a confdb-schema assertion.
There are two main parts to a confdb-schema assertion:
View rules
Storage schema
The view rules determine the “schema” of the configuration namespace exposed to the snap, i.e., what paths can be accessed and how they’re mapped onto the storage schema.
The storage schema determines what the actual stored data looks like and what constraints apply.
We’ll start by defining a storage schema.
Storage schema¶
When creating the storage schema, we can constrain the format and values that the stored data can take, such as the characters you can include in a password.
For example:
{
"storage": {
"aliases": {
"password": {
"type": "string",
"pattern": "^[\w~!@#\$%\^&-\+=\;:,\.\/\?]{8,63}$"
}
},
"schema": {
"v1": {
"schema": {
"wifi": {
"values": {
"schema": {
"psk": "${password}",
"ssid": "string",
"state": {
"type": "string",
"choices": [
"up",
"down"
]
}
}
}
}
}
}
}
}
}
The above schema describes three fields within each Wi-Fi entry: an SSID, a password and a connection state.
Snapd expects the storage schema to conform to a very precise format, using 2 spaces for indentation and sorting map entries. Both Python 3’s json.dump and Golang’s json.MarshalIndent can be used to produce this format. jq -S can also be used to sort the schema. It’s useful to keep the storage schema in its own file so we can modify it over time.
See confdb-schema types for a detailed description of types and constraints.
View rules¶
Now let’s create some view rules to access confdb.
In our example, we’ll use one snap to configure the network and another to access it, which means we need two views: wifi-admin and wifi-state.
wifi-adminexposes parameters and allows them to be set.wifi-stateallows the snap to list Wi-Fi connections and read SSID and state information (e.g., up, down).
Both of these can be defined as follows:
type: confdb-schema
authority-id: <account-id>
account-id: <account-id>
name: network
summary: Configure network parameters
timestamp: 2025-04-02T19:31:32Z
views:
wifi-admin:
summary: Configure Wi-Fi networks
rules:
-
request: "{name}.ssid"
storage: "v1.wifi.{name}.ssid"
-
request: "{name}.password"
storage: "v1.wifi.{name}.psk"
-
request: "{name}.state"
storage: "v1.wifi.{name}.state"
wifi-state:
summary: List and read Wi-Fi SSIDs
rules:
-
request: "{name}"
storage: "v1.wifi.{name}"
access: read
content:
-
storage: ssid
-
storage: state
body-length: 552
sign-key-sha3-384: 74KHeq1foV…
See Access Ubuntu One for details on retrieving your account-id.
Putting it together¶
Now we’re ready to create the assertion. Run the following command, filling in the account ID and key name:
snapcraft edit-confdb-schema <account-id> network --key-name=<key-name>
Next, edit the template assertion, replacing the views and storage schema with the ones we defined above.
The end result should look something like the following:
account-id: <account-id>
name: network
views:
wifi-admin:
rules:
-
request: "{name}.ssid"
storage: "v1.wifi.{name}.ssid"
-
request: "{name}.password"
storage: "v1.wifi.{name}.psk"
-
request: "{name}.state"
storage: "v1.wifi.{name}.state"
wifi-state:
rules:
-
request: "{name}"
storage: "v1.wifi.{name}"
access: read
content:
-
storage: ssid
-
storage: state
body: |-
{
"storage": {
"aliases": {
"password": {
"pattern": "^[a-zA-Z0-9~!@#\\$%&-=:,\\.]{8,63}$",
"type": "string"
}
},
"schema": {
"v1": {
"schema": {
"wifi": {
"values": {
"schema": {
"psk": "${password}",
"ssid": "string",
"state": {
"choices": ["up", "down"],
"type": "string"
}
}
}
}
}
}
}
}
}
Then exit the editor and sign the assertion when prompted.
Creating the snaps¶
See Create a new snap for a general overview of the snap creation process.
There are two things a snap needs to be able to use confdb:
a plug to declare its intent to use a confdb namespace and
an optional set of hooks that snapd invokes when the namespace is accessed
Custodian snap¶
The interface plug specifies an account and a view which together identify the view through which the configuration is being accessed. It can also optionally declare a role, with a value of custodian.
Custodian snaps have a special role when accessing ephemeral data. They’re responsible for saving or loading this data to an external source outside of snapd.
The data that snapd stores for ephemeral configuration is only a cached, non-authoritative version of the external data. At least one custodian snap must be installed in the system for a particular confdb-schema.
The first snap will configure the Wi-Fi network to be used, so let’s configure it accordingly:
plugs:
network-wifi-admin:
interface: confdb
account: <account-id>
view: network/wifi-admin
role: custodian
The other component that must be defined in the snap are the hooks.
Snaps interact with confdb through snapctl and, in turn, snapd invokes various hooks when confdb is read from or written to.
In this case, the custodian snap will only define the change-view-<plug>, which provides an opportunity for the snap to modify values being set.
As an example, we’ll say that the SSID must be prefixed with our company’s name: “Acme”. The configuration can be set by the administrator using snap set or by any other snap with access to that view, so it’s the custodian snap’s responsibility to enforce the prefix.
Our change-view-network-wifi-admin hook looks like this:
#!/bin/bash -xe
# prefix the SSID with "acme", if not already present
value=$(snapctl get --view :network-wifi-admin acme.ssid)
if [[ "$?" -eq 0 && "$value" != acme-* ]]; then
snapctl set --view :network-wifi-admin acme.ssid="acme-$value"
fi
That’s it for the custodian snap.
Reader snap¶
Now we’ll create a snap that will read the configuration. Its plug will reference the read-only view and it will omit the role:
plugs:
network-wifi-state:
interface: confdb
account: <account-id>
view: network/wifi-state
Non-custodian snaps can only define an observe-view-<plug> hook, which allows them to be notified when the confdb-schema referenced by that plug has been used to modify data.
#!/bin/sh -xe
# read the new SSID and store it somewhere
new_ssid=$(snapctl get --view :network-wifi-state acme.ssid)
echo "$new_ssid" >> "$SNAP_COMMON"/ssid
Install and connect the snaps¶
We can mediate snap access to confdb-schema views by connecting their confdb plugs.
Note that when a snap is published by the same account ID as the assertion, the interface plug will be auto-connected.
snap install custodian-snap reader-snap
snap connect custodian-snap:network-wifi-admin
snap connect reader-snap:network-wifi-state
Setting data¶
Now we’re ready to start setting data, we’ll use snap run --shell to mimic the snaps interacting with confdb:
$ sudo snap run --shell custodian-snap.sh
# snapctl set --view :network-wifi-admin acme.ssid=some-ssid acme.password=super-secret
# exit
$ sudo snap run --shell reader-snap.sh
# snapctl get --view :network-wifi-state acme.ssid
acme-some-ssid
# exit
$ snap get <account-id>/network/wifi-state acme.ssid
acme-some-ssid
As expected, the change-view-network-wifi-admin hook was invoked when custodian-snap modified the SSID and prefixed the value with “acme”.
We were also able to read the same value from both another connected snap and through the snapd API using snap get.
We can also check that the reader-snap’s observe-view-network-wifi-state hook was invoked when the SSID was changed and that it saved the new value in SNAP_COMMON:
$ snap changes
ID Status Spawn Ready Summary
123 Done today at ... today at ... Set confdb through "<account-id>/network/wifi-admin"
...
$ snap change 123
Status Spawn Ready Summary
Done ... ... Clears the ongoing confdb transaction from state (on error)
Done ... ... Run hook change-view-network-wifi-admin of snap "custodian-snap"
Done ... ... Run hook observe-view-network-wifi-state of snap "reader-snap"
Done ... ... Commit changes to confdb (<account-id>/network/wifi-admin)
Done ... ... Clears the ongoing confdb transaction from state
$ cat /snap/reader-snap/common/ssid
acme-some-ssid
Note that concurrent accesses may block if they would result in writes running concurrently with another access. If any access is blocked, subsequent accesses will also be queued to ensure that they are run in order.
For example, if there is a read ongoing and a write is requested, the write will be blocked. A new read would then also be queued and only run after the write, even though reads are normally run concurrently. This ensures that a stream of reads cannot starve a write access.
Getting secret data¶
From snapd version 2.76+
If we wanted to ensure that only admins were able to access certain data in confdb storage, we could add a visibility field to storage entries. For example, say we wanted only admins to read the psk field defined earlier. To limit access to psk, in the storage schema definition, we would change
"psk": "${password}",
to
"psk": {
"type": "${password}",
"visibility": "secret"
},
Assuming we have then recreated and signed the assertion, we can see that read access to that field is now limited.
$ sudo snap run --shell custodian-snap.sh
# snapctl get --view :network-wifi-admin acme.password
super-secret
# exit
$ snap run --shell custodian-snap.sh
# snapctl get --view :network-wifi-admin acme.password
cannot get "acme.password" through <account-id>/network/wifi-admin: unauthorized access
# exit
$ sudo snap get <account-id>/network/wifi-admin acme.password
super-secret
$ snap get <account-id>/network/wifi-admin acme.password
access denied (try with sudo)