Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix file download error in Ubuntu Core agent #675

Merged
merged 8 commits into from
Dec 11, 2024
74 changes: 47 additions & 27 deletions snap/local/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ Visit [Ubuntu Core Official Page](https://ubuntu.com/core) for more information.

## Inside Ubuntu Core

The Ubuntu Core architecture overview diagram below depicts the delivery of the kernel, boot assets, runtime environment, applications, and device enablement capabilities as snaps. These snaps are managed by the snap daemon (snapd) and the daemon itself is packaged as a snap.
The Ubuntu Core architecture overview diagram below depicts the delivery of the kernel, boot assets, runtime environment, applications, and device enablement capabilities as snaps. These snaps are managed by the snap daemon (snapd) and the daemon itself is packaged as a snap.
Visit [Ubuntu Core](https://ubuntu.com/core/docs/uc20/inside) page for more details.

![Inside Ubuntu Core - Diagram](./assets/du-agent-snap-inside-ubuntu-core.svg)

> Credit: the diagram above is adapted from [Inside Ubuntu Core](https://ubuntu.com/core/docs/uc20/inside) diagram.
## Device Update Agent Snap for Ubuntu Core

The Device Update Agent, also known as the DU Agent, is a crucial `application` snap specifically designed for Ubuntu Core. Its primary purpose is to manage updates and ensure the security and reliability of devices that run on this operating system.
The Device Update Agent, also known as the DU Agent, is a crucial `application` snap specifically designed for Ubuntu Core. Its primary purpose is to manage updates and ensure the security and reliability of devices that run on this operating system.

### Device Update Agent and Dependencies

Expand All @@ -48,14 +48,14 @@ In a nutshell, `snaps` are isolated from other snaps and the rest of the system

Overall, the snap user and group security model in Ubuntu Core provides a high degree of isolation and security for individual applications, while still allowing them to access the resources they need to function properly.

For more information, see [Security Policy and Sandboxing of Snapcraft](https://snapcraft.io/docs/security-policy-and-sandboxing), a snap is run inside a isolated sandbox, so the user accessing Device Update Agent will always be `root`.
For more information, see [Security Policy and Sandboxing of Snapcraft](https://snapcraft.io/docs/security-policy-and-sandboxing), a snap is run inside a isolated sandbox, so the user accessing Device Update Agent will always be `root`.

#### DU Agent Snap User and Group
Key differences between Device Update Agent and Device Update Agent Snap:

| | Device Upate Agent | Device Update Agent Snap |
|---|---|---|
| user id| adu | snap_aziotdu |
| user id| adu | snap_aziotdu |
| group id| adu | snap_aziotdu |

### How DU Agent Acquire the IoT Hub Connection Information
Expand All @@ -80,7 +80,7 @@ This specifies the principal information for the DU Agent Module, including its
When connecting snaps with interfaces, the snaps are typically connected with the default user or "system" user. However, it is possible to connect snaps with a specific user ID by using the `--classic` and `--username` options with the snap connect command.

For example, to connect to Azure Identity Service with a specific user ID called "snap_aziotdu", you would use the following command:
`snap connect --classic --username=snap_aziotdu deviceupdate-agent:AIS-interface azureIdentityService-snap:AIS-interface`
`snap connect --classic --username=snap_aziotdu deviceupdate-agent:AIS-interface azureIdentityService-snap:AIS-interface`
This will connect the two snaps using the "snap_aziotdu" user ID, allowing the snaps to communicate with each other as that user.

It is important to note that using the `--classic` and `--username` options with the snap connect command can have security implications, as it allows the connected snaps to access each other's data and resources as the specified user. Therefore, it should only be used if necessary and with caution.
Expand Down Expand Up @@ -190,50 +190,50 @@ layout:

```

> Note that above DU's `_plug_name_:deviceupdate-agent-downloads` identifier must match DO's `_slot_name_:deviceupdate-agent-downloads` identifier
> Note that above DU's `_plug_name_:deviceupdate-agent-downloads` identifier must match DO's `_slot_name_:deviceupdate-agent-downloads` identifier

To connect, use following commands:

```shell
# Required connections for content downloading using Delivery Optimization snap.
sudo snap connect deviceupdate-agent:do-port-numbers deliveryoptimization-client:do-port-numbers
sudo snap connect deviceupdate-agent:do-port-numbers deliveryoptimization-agent:do-port-numbers

sudo snap connect deviceupdate-agent:do-configs deliveryoptimization-client:do-configs
sudo snap connect deviceupdate-agent:do-configs deliveryoptimization-agent:do-configs

sudo snap connect deliveryoptimization-client:deviceupdate-agent-downloads deviceupdate-agent:deviceupdate-agent-downloads
sudo snap connect deliveryoptimization-agent:deviceupdate-agent-downloads deviceupdate-agent:deviceupdate-agent-downloads

# Required interface for Snapd RestFul requests.
# Required interface for Snapd RestFul requests.
sudo snap connect deviceupdate-agent:snapd-control

```

### Azure Identity Service Integration
To connect to Azure Identity Service, install AIS with this command `sudo snap install --edge azure-iot-identity`, wiring it up with the following command,
```shell
sudo snap connect azure-iot-identity:log-observe
sudo snap connect azure-iot-identity:log-observe

sudo snap connect azure-iot-identity:system-observe
sudo snap connect azure-iot-identity:system-observe

sudo snap connect azure-iot-identity:tpm
sudo snap connect azure-iot-identity:tpm
```
Here is the process of configuring AIS using a `config.toml` file and creating a `testing.toml` file for cloud identity creation.
Here is the process of configuring AIS using a `config.toml` file and creating a `testing.toml` file for cloud identity creation.

Use the following commands:

```shell
sudo snap set azure-iot-identity raw-config="$(cat /path/to/config.toml)"
```
```shell
$ sudo cat testing.toml
[[principal]]
uid = <REPLACE WITH uid>
name = "testing"
idtype = ["module"]
$ sudo cat testing.toml
[[principal]]
uid = <REPLACE WITH uid>
name = "testing"
idtype = ["module"]

sudo chown root:root testing.toml
sudo chmod 0600 testing.toml
sudo chown root:root testing.toml
sudo chmod 0600 testing.toml

sudo mv testing.toml /var/snap/azure-iot-identity/current/shared/config/aziot/identityd/config.d/testing.toml
sudo mv testing.toml /var/snap/azure-iot-identity/current/shared/config/aziot/identityd/config.d/testing.toml
```
then use the following command to connect Device Update and AIS:

Expand All @@ -244,7 +244,7 @@ sudo snap connect deviceupdate-agent:identity-service azure-iot-identity:identit
Verify that connections are ok:

```shell
$ snap connections deviceupdate-agent
$ snap connections deviceupdate-agent

Interface Plug Slot Notes
content[deviceupdate-agent-downloads] deliveryoptimization-client:deviceupdate-agent-downloads deviceupdate-agent:deviceupdate-agent-downloads manual
Expand Down Expand Up @@ -392,12 +392,32 @@ $ sudo systemctl restart snap.deviceupdate-agent.deviceupdate-agent

To configure the DU Agent, you can use `deviceupdate-agent.set-config` command.

This command decodes and saves base64 encoded configuration data to a file with the given name in the `\$SNAP_DATA/config` directory.
This command decodes and saves base64 encoded configuration data to a file with the given name in the `\$SNAP_DATA/config` directory.

The script takes two required options - the name of the config file and the base64 encoded data - and saves the data to the specified file.
The script takes two required options - the name of the config file and the base64 encoded data - and saves the data to the specified file.

> NOTE | Only two config file names are allowed: `du-config.json` and `du-diagnostics-config.json`.

> IMPORTANT | In order to communicate with deliveryoptimization-agent snap, the downloadsFolder must point to '/var/lib/deviceupdate-agent-downloads'. This can be accomplished by setting "downloadsFolder" value in du-config.json to "/var/lib/deviceupdate-agent-downloads". See example below...
```json
{
"schemaVersion": "1.1",
"aduShellTrustedUsers": [
"snap_aziotdu",
"snap_aziotdo"
],
"iotHubProtocol": "mqtt",
"manufacturer": "Contoso",
"model": "Virtual-Vacuum",
"downloadsFolder": "/var/lib/deviceupdate-agent-downloads",
"agents": [
{
...
}
]

```

| Options | Arguments | Description |
|---|---|---|
|-c, --config-file | \<config file name\> | Specifies the name of the configuration file to be created or updated.<br/><br/>The only valid values for the config file name are \"du-config.json\" and \"du-diagnostics-config.json\".|
Expand Down Expand Up @@ -426,8 +446,8 @@ Key differences between Device Update Agent and Device Update Agent Snap:

| | Device Upate Agent | Device Update Agent Snap |
|---|---|---|
| user id| adu | snap_aziotdu |
| user id| adu | snap_aziotdu |
| group id| adu | snap_aziotdu |
| downloads folder | /var/lib/adu/downloads | $SNAP_DATA/data/downloads |
| configs file | /etc/adu/du-config.json | $SNAP_DATA/configs/du-config.json|
| logs folder | /var/log/adu | $SNAP_DATA/log
| logs folder | /var/log/adu | $SNAP_DATA/log
6 changes: 1 addition & 5 deletions src/agent/adu_core_interface/src/adu_core_interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -454,9 +454,7 @@ void OrchestratorUpdateCallback(

// Ensure update to latest rootkey pkg, which is required for validating the update metadata.
workFolder = workflow_get_root_sandbox_dir(workflowData->WorkflowHandle);
#ifdef ADUC_BUILD_SNAP
workFolder = ADUC_DOWNLOADS_FOLDER;
#endif

if (workFolder == NULL)
{
Log_Error("workflow_get_root_sandbox_dir failed");
Expand Down Expand Up @@ -513,9 +511,7 @@ void OrchestratorUpdateCallback(
STRING_delete(rootKeyPackageFilePath);
workflow_free_string(rootKeyPkgUrl);
workflow_free_string(workflowId);
#ifndef ADUC_BUILD_SNAP
workflow_free_string(workFolder);
#endif
STRING_delete(jsonToSend);
free(jsonString);

Expand Down
29 changes: 19 additions & 10 deletions src/utils/config_utils/src/config_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -445,24 +445,33 @@ bool ADUC_ConfigInfo_Init(ADUC_ConfigInfo* config, const char* configFolder)
config->dataFolder = ADUC_JSON_GetStringFieldPtr(config->rootJsonValue, CONFIG_ADU_DATA_FOLDER);
}

if (!EnsureDataSubFolderSpecifiedOrSetDefaultValue(
config->rootJsonValue,
CONFIG_ADU_DOWNLOADS_FOLDER,
&config->downloadsFolder,
config->dataFolder,
DOWNLOADS_PATH_SEGMENT))
config->downloadsFolder = ADUC_JSON_GetStringFieldPtr(config->rootJsonValue, CONFIG_ADU_DOWNLOADS_FOLDER);

if (config->downloadsFolder == NULL)
{
goto done;
if (!EnsureDataSubFolderSpecifiedOrSetDefaultValue(
config->rootJsonValue,
CONFIG_ADU_DOWNLOADS_FOLDER,
&config->downloadsFolder,
config->dataFolder,
DOWNLOADS_PATH_SEGMENT))
{
goto done;
}
}

if (!EnsureDataSubFolderSpecifiedOrSetDefaultValue(
config->extensionsFolder = ADUC_JSON_GetStringFieldPtr(config->rootJsonValue, CONFIG_ADU_EXTENSIONS_FOLDER);
if (config->extensionsFolder == NULL)
{
if (!EnsureDataSubFolderSpecifiedOrSetDefaultValue(
config->rootJsonValue,
CONFIG_ADU_EXTENSIONS_FOLDER,
&config->extensionsFolder,
config->dataFolder,
EXTENSIONS_PATH_SEGMENT))
{
goto done;
{
goto done;
}
}

// Since we're only allow overriding of 'extensions' folder, let's populate all extensions sub-folders.
Expand Down
66 changes: 65 additions & 1 deletion src/utils/config_utils/tests/config_utils_ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,42 @@ static const char* validConfigWithOverrideFolder =
R"(])"
R"(})";

static const char* validConfigWithUbuntuCoreDownloadsFolder =
R"({)"
R"("schemaVersion": "1.1",)"
R"("aduShellTrustedUsers": ["adu","do"],)"
R"("manufacturer": "device_info_manufacturer",)"
R"("model": "device_info_model",)"
R"("downloadTimeoutInMinutes": 1440,)"
R"("aduShellFolder": "/usr/mybin",)"
R"("dataFolder": "/var/lib/adu/mydata",)"
R"("downloadsFolder": "/var/lib/deviceupdate-agent-downloads",)"
R"("extensionsFolder": "/var/lib/adu/myextensions",)"
R"("compatPropertyNames": "manufacturer,model",)"
R"("agents": [)"
R"({ )"
R"("name": "host-update",)"
R"("runas": "adu",)"
R"("connectionSource": {)"
R"("connectionType": "AIS",)"
R"("connectionData": "iotHubDeviceUpdate")"
R"(},)"
R"("manufacturer": "Contoso",)"
R"("model": "Smart-Box")"
R"(},)"
R"({)"
R"("name": "leaf-update",)"
R"("runas": "adu",)"
R"("connectionSource": {)"
R"("connectionType": "string",)"
R"("connectionData": "HOSTNAME=...")"
R"(},)"
R"("manufacturer": "Fabrikam",)"
R"("model": "Camera")"
R"(})"
R"(])"
R"(})";

static const char* invalidConfigContentDownloadTimeout =
R"({)"
R"("schemaVersion": "1.1",)"
Expand Down Expand Up @@ -618,7 +654,7 @@ TEST_CASE_METHOD(GlobalMockHookTestCaseFixture, "ADUC_ConfigInfo_Init Functional
CHECK(config->refCount == 0);
}

SECTION("User folders from du-config file")
SECTION("Uses folders from du-config file")
{
REQUIRE(mallocAndStrcpy_s(&g_configContentString, validConfigWithOverrideFolder) == 0);
ADUC::StringUtils::cstr_wrapper configStr{ g_configContentString };
Expand All @@ -641,4 +677,32 @@ TEST_CASE_METHOD(GlobalMockHookTestCaseFixture, "ADUC_ConfigInfo_Init Functional
ADUC_ConfigInfo_ReleaseInstance(config);
CHECK(config->refCount == 0);
}


// NOTE: Ensure that the downloadsFolder is set correctly, if specified in du-configjson.
// (Instead of a 'downloads' sub-folder of the dataFolder )
SECTION("Ubuntu Core DO download")
{
REQUIRE(mallocAndStrcpy_s(&g_configContentString, validConfigWithUbuntuCoreDownloadsFolder) == 0);
ADUC::StringUtils::cstr_wrapper configStr{ g_configContentString };
const ADUC_ConfigInfo* config = ADUC_ConfigInfo_GetInstance();
CHECK_THAT(config->aduShellFolder, Equals("/usr/mybin"));

#if defined(WIN32)
CHECK_THAT(config->aduShellFilePath, Equals("/usr/mybin/adu-shell.exe"));
#else
CHECK_THAT(config->aduShellFilePath, Equals("/usr/mybin/adu-shell"));
#endif

CHECK_THAT(config->dataFolder, Equals("/var/lib/adu/mydata"));
CHECK_THAT(config->extensionsFolder, Equals("/var/lib/adu/myextensions"));
CHECK_THAT(config->extensionsComponentEnumeratorFolder, Equals("/var/lib/adu/myextensions/component_enumerator"));
CHECK_THAT(config->extensionsContentDownloaderFolder, Equals("/var/lib/adu/myextensions/content_downloader"));
CHECK_THAT(config->extensionsStepHandlerFolder, Equals("/var/lib/adu/myextensions/update_content_handlers"));
CHECK_THAT(config->extensionsDownloadHandlerFolder, Equals("/var/lib/adu/myextensions/download_handlers"));
CHECK_THAT(config->downloadsFolder, Equals("/var/lib/deviceupdate-agent-downloads"));
ADUC_ConfigInfo_ReleaseInstance(config);
CHECK(config->refCount == 0);
}
}