summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRicardo Salveti <ricardo@opensourcefoundries.com>2018-03-29 15:47:26 -0300
committerRicardo Salveti <ricardo@opensourcefoundries.com>2018-03-29 15:47:26 -0300
commit82a9c20ffb045011b53f3188d04d79f440ee8b06 (patch)
tree8309cc593f59861ed4848dea09b09b3f04e18416
parent2619e3f3312713f4077d83b2f2e5f9c7de66d12b (diff)
parentb1a114da280a05cfc2b7b099c97101bd20cc6b8f (diff)
downloadmeta-updater-82a9c20ffb045011b53f3188d04d79f440ee8b06.tar.gz
Merge remote-tracking branch 'origin/rocko' into rocko-merge
Signed-off-by: Ricardo Salveti <ricardo@opensourcefoundries.com>
-rw-r--r--README.adoc46
-rw-r--r--classes/image_types_ostree.bbclass26
-rw-r--r--classes/sota_raspberrypi.bbclass17
-rw-r--r--lib/oeqa/selftest/cases/updater.py605
-rw-r--r--recipes-sota/aktualizr/aktualizr-auto-prov.bb4
-rw-r--r--recipes-sota/aktualizr/aktualizr-ca-implicit-prov.bb72
-rw-r--r--recipes-sota/aktualizr/aktualizr_git.bb65
-rw-r--r--recipes-sota/aktualizr/environment.inc2
-rw-r--r--recipes-sota/aktualizr/files/aktualizr-secondary.service9
-rw-r--r--recipes-sota/aktualizr/files/aktualizr-secondary.socket6
-rw-r--r--recipes-sota/aktualizr/files/aktualizr.service1
-rw-r--r--recipes-sota/aktualizr/files/ca.cnf10
-rw-r--r--recipes-support/libp11/files/0001-Workaround-for-a-buggy-version-of-openssl-1.0.2m.patch2
-rw-r--r--recipes-support/libp11/libp11_0.4.7.bb1
-rw-r--r--recipes-test/demo-network-config/files/25-dhcp-server.network12
-rw-r--r--recipes-test/demo-network-config/files/26-dhcp-client.network6
-rw-r--r--recipes-test/demo-network-config/files/27-dhcp-client-external.network6
-rw-r--r--recipes-test/demo-network-config/primary-network-config.bb16
-rw-r--r--recipes-test/demo-network-config/secondary-network-config.bb20
-rw-r--r--recipes-test/images/primary-image.bb14
-rw-r--r--recipes-test/images/secondary-image.bb25
-rw-r--r--scripts/qemucommand.py6
-rwxr-xr-xscripts/run-qemu-ota3
23 files changed, 846 insertions, 128 deletions
diff --git a/README.adoc b/README.adoc
index 403e0f8..980fa81 100644
--- a/README.adoc
+++ b/README.adoc
@@ -17,10 +17,10 @@ If you don't already have a Yocto project that you want to add OTA to, you can u
17If you already have a Yocto-based project and you want to add atomic filesystem updates to it, you just need to do three things: 17If you already have a Yocto-based project and you want to add atomic filesystem updates to it, you just need to do three things:
18 18
191. Clone the `meta-updater` layer and add it to your https://www.yoctoproject.org/docs/2.1/ref-manual/ref-manual.html#structure-build-conf-bblayers.conf[bblayers.conf]. 191. Clone the `meta-updater` layer and add it to your https://www.yoctoproject.org/docs/2.1/ref-manual/ref-manual.html#structure-build-conf-bblayers.conf[bblayers.conf].
202. Clone BSP integration layer (meta-updater-$\{PLATFORM}, e.g. https://github.com/advancedtelematic/meta-updater-raspberrypi[meta-updater-raspberrypi]) and add it to your conf/bblayers.conf. If your board isn't supported yet, you could write a BSP integration for it yourself. See the <<Adding support for your board>> section for the details. 202. Clone BSP integration layer (`meta-updater-$\{PLATFORM}`, e.g. https://github.com/advancedtelematic/meta-updater-raspberrypi[meta-updater-raspberrypi]) and add it to your `conf/bblayers.conf`. If your board isn't supported yet, you could write a BSP integration for it yourself. See the <<Adding support for your board>> section for the details.
213. Set up your https://www.yoctoproject.org/docs/2.1/ref-manual/ref-manual.html#var-DISTRO[distro]. If you are using "poky", the default distro in Yocto, you can change it in your conf/local.conf to "poky-sota". Alternatively, if you are using your own or third party distro configuration, you can add 'INHERIT += " sota"' to it, thus combining capabilities of your distro with meta-updater features. 213. Set up your https://www.yoctoproject.org/docs/2.1/ref-manual/ref-manual.html#var-DISTRO[distro]. If you are using "poky", the default distro in Yocto, you can change it in your `conf/local.conf` to "poky-sota". Alternatively, if you are using your own or third party distro configuration, you can add `INHERIT += " sota"` to it, thus combining capabilities of your distro with meta-updater features.
22 22
23You can then build your image as usual, with bitbake. After building the root file system, bitbake will then create an https://ostree.readthedocs.io/en/latest/manual/adapting-existing/[OSTree-enabled version] of it, commit it to your local OSTree repo and (optionally) push it to a remote server. Additionally, a live disk image will be created (normally named $\{IMAGE_NAME}.-sdimg-ota e.g. core-image-raspberrypi3.rpi-sdimg-ota). You can control this behaviour through <<variables in your local.conf,OSTree-related variables in your local.conf>>. 23You can then build your image as usual, with bitbake. After building the root file system, bitbake will then create an https://ostree.readthedocs.io/en/latest/manual/adapting-existing/[OSTree-enabled version] of it, commit it to your local OSTree repo and (optionally) push it to a remote server. Additionally, a live disk image will be created (normally named `$\{IMAGE_NAME}.-sdimg-ota` e.g. `core-image-raspberrypi3.rpi-sdimg-ota`). You can control this behaviour through <<sota-related-variables-in-localconf,variables in your local.conf>>.
24 24
25=== Build in AGL 25=== Build in AGL
26 26
@@ -30,19 +30,19 @@ With AGL you can just add agl-sota feature while configuring your build environm
30source meta-agl/scripts/aglsetup.sh -m porter agl-demo agl-appfw-smack agl-devel agl-sota 30source meta-agl/scripts/aglsetup.sh -m porter agl-demo agl-appfw-smack agl-devel agl-sota
31.... 31....
32 32
33you can then run 33You can then run:
34 34
35.... 35....
36bitbake agl-demo-platform 36bitbake agl-demo-platform
37.... 37....
38 38
39and get as a result an "ostree_repo" folder in your images directory (tmp/deploy/images/$\{MACHINE}/ostree_repo). It will contain 39and get as a result an `ostree_repo` folder in your images directory (`tmp/deploy/images/$\{MACHINE}/ostree_repo`). It will contain:
40 40
41* your OSTree repository, with the rootfs committed as an OSTree deployment, 41* your OSTree repository, with the rootfs committed as an OSTree deployment,
42* an 'otaimg' bootstrap image, which is an OSTree physical sysroot as a burnable filesystem image, and optionally 42* an `otaimg` bootstrap image, which is an OSTree physical sysroot as a burnable filesystem image, and optionally
43* some machine-dependent live images (e.g. '_.rpi-sdimg-ota' for Raspberry Pi or '_.porter-sdimg-ota' Renesas Porter board). 43* some machine-dependent live images (e.g. `.rpi-sdimg-ota` for Raspberry Pi or `.porter-sdimg-ota` Renesas Porter board).
44 44
45Although aglsetup.sh hooks provide reasonable defaults for SOTA-related variables, you may want to tune some of them. 45Although `aglsetup.sh` hooks provide reasonable defaults for SOTA-related variables, you may want to tune some of them.
46 46
47=== Build problems 47=== Build problems
48 48
@@ -80,8 +80,8 @@ Although we have used U-Boot so far, other boot loaders can be configured work w
80* `OSTREE_INITRAMFS_IMAGE` - initramfs/initrd image that is used as a proxy while booting into OSTree deployment. Do not change this setting unless you are sure that your initramfs can serve as such a proxy. 80* `OSTREE_INITRAMFS_IMAGE` - initramfs/initrd image that is used as a proxy while booting into OSTree deployment. Do not change this setting unless you are sure that your initramfs can serve as such a proxy.
81* `SOTA_PACKED_CREDENTIALS` - when set, your ostree commit will be pushed to a remote repo as a bitbake step. This should be the path to a zipped credentials file in https://github.com/advancedtelematic/aktualizr/blob/master/docs/credentials.adoc[the format accepted by garage-push]. 81* `SOTA_PACKED_CREDENTIALS` - when set, your ostree commit will be pushed to a remote repo as a bitbake step. This should be the path to a zipped credentials file in https://github.com/advancedtelematic/aktualizr/blob/master/docs/credentials.adoc[the format accepted by garage-push].
82* `SOTA_CLIENT_PROV` - which provisioning method to use. Valid options are https://github.com/advancedtelematic/aktualizr/blob/master/docs/automatic-provisioning.adoc[`aktualizr-auto-prov`], https://github.com/advancedtelematic/aktualizr/blob/master/docs/implicit-provisioning.adoc[`aktualizr-implicit-prov`], and `aktualizr-hsm-prov`. The default is `aktualizr-auto-prov`. This can also be set to an empty string to avoid using a provisioning recipe. 82* `SOTA_CLIENT_PROV` - which provisioning method to use. Valid options are https://github.com/advancedtelematic/aktualizr/blob/master/docs/automatic-provisioning.adoc[`aktualizr-auto-prov`], https://github.com/advancedtelematic/aktualizr/blob/master/docs/implicit-provisioning.adoc[`aktualizr-implicit-prov`], and `aktualizr-hsm-prov`. The default is `aktualizr-auto-prov`. This can also be set to an empty string to avoid using a provisioning recipe.
83* `SOTA_CLIENT_FEATURES` - extensions to aktualizr. Multiple can be specified if separated by spaces. Valid options are `hsm` (to build with HSM support) and `secondary-example` (to install an example https://github.com/advancedtelematic/aktualizr/blob/master/docs/legacysecondary.adoc[legacy secondary interface] in the image). 83* `SOTA_CLIENT_FEATURES` - extensions to aktualizr. The only valid option is `hsm` (to build with HSM support)
84* `SOTA_LEGACY_SECONDARY_INTERFACE` - path to a legacy secondary interface installed on the device. To use the example interface from the Aktualizr repo, use `/usr/bin/example-interface` and make sure `SOTA_CLIENT_FEATURES = "secondary-example"`. 84* `SOTA_LEGACY_SECONDARY_INTERFACE` - path to a https://github.com/advancedtelematic/aktualizr/blob/master/docs/legacysecondary.adoc[legacy secondary interface] installed on the device. To use the example interface from the Aktualizr repo, use `/usr/bin/example-interface` and make sure `IMAGE_INSTALL_append` includes `aktualizr-examples`.
85* `SOTA_SECONDARY_ECUS` - a list of paths separated by spaces of JSON configuration files for virtual secondaries on the host. These will be installed into `/var/sota/ecus` on the device. 85* `SOTA_SECONDARY_ECUS` - a list of paths separated by spaces of JSON configuration files for virtual secondaries on the host. These will be installed into `/var/sota/ecus` on the device.
86* `SOTA_VIRTUAL_SECONDARIES` - a list of paths separated by spaces of JSON configuration files for virtual secondaries installed on the device. If `SOTA_SECONDARY_ECUS` is used to install them, then you can expect them to be installed in `/var/sota/ecus`. 86* `SOTA_VIRTUAL_SECONDARIES` - a list of paths separated by spaces of JSON configuration files for virtual secondaries installed on the device. If `SOTA_SECONDARY_ECUS` is used to install them, then you can expect them to be installed in `/var/sota/ecus`.
87 87
@@ -133,20 +133,36 @@ The https://github.com/advancedtelematic/aktualizr[aktualizr repo] contains a to
133garage-push --repo=/path/to/ostree-repo --ref=mybranch --credentials=/path/to/credentials.zip 133garage-push --repo=/path/to/ostree-repo --ref=mybranch --credentials=/path/to/credentials.zip
134.... 134....
135 135
136You can set SOTA_PACKED_CREDENTIALS in your local.conf to make your build results be automatically synchronized with a remote server. Credentials are stored in the JSON format described in the https://github.com/advancedtelematic/aktualizr/blob/master/README.sotatools.adoc[garage-push README]. This JSON file can be optionally stored inside a zip file, although if it is stored this way, the JSON file must be named treehub.json. 136You can set `SOTA_PACKED_CREDENTIALS` in your `local.conf` to automatically synchronize your build results with a remote server. Credentials are stored in an archive as described in the https://github.com/advancedtelematic/aktualizr/blob/master/docs/credentials.adoc[aktualizr documentation].
137 137
138=== QA 138== QA with `oe-selftest`
139 139
140This layer relies on the test framework oe-selftest for quality assurance. Follow the steps below to run the tests: 140This layer relies on the test framework oe-selftest for quality assurance. Follow the steps below to run the tests:
141 141
142* Append the line below to conf/local.conf 1421. Append the line below to `conf/local.conf` to disable the warning about supported operating systems:
143+
144```
145SANITY_TESTED_DISTROS = ""
146```
143 147
1482. If your image does not already include an ssh daemon such as dropbear or openssh, add this line to `conf/local.conf` as well:
149+
144``` 150```
145SANITY_TESTED_DISTROS="" 151IMAGE_INSTALL_append = " dropbear "
146``` 152```
147 153
148* Run oe-selftest: 1543. Some tests require that `SOTA_PACKED_CREDENTIALS` is set in your `conf/local.conf`. See the <<sota-related-variables-in-localconf,SOTA-related variables in local.conf>> section.
155
1564. To be able to build an image for the grub tests, you will need to install https://github.com/tianocore/tianocore.github.io/wiki/OVMF[TianoCore's ovmf] package on your host system. On Debian-like systems, you can do so with this command:
157+
158```
159sudo apt install ovmf
160```
149 161
1625. Run oe-selftest:
163+
150``` 164```
151oe-selftest --run-tests updater 165oe-selftest --run-tests updater
152``` 166```
167
168For more information about oe-selftest, including details about how to run individual test modules or classes, please refer to the https://wiki.yoctoproject.org/wiki/Oe-selftest[Yocto Project wiki].
diff --git a/classes/image_types_ostree.bbclass b/classes/image_types_ostree.bbclass
index 5893e8d..e6d6fe0 100644
--- a/classes/image_types_ostree.bbclass
+++ b/classes/image_types_ostree.bbclass
@@ -9,9 +9,9 @@ do_image_ostree[depends] += "ostree-native:do_populate_sysroot \
9 9
10export OSTREE_REPO 10export OSTREE_REPO
11export OSTREE_BRANCHNAME 11export OSTREE_BRANCHNAME
12export GARAGE_TARGET_NAME
12 13
13RAMDISK_EXT ?= ".${OSTREE_INITRAMFS_FSTYPES}" 14RAMDISK_EXT ?= ".${OSTREE_INITRAMFS_FSTYPES}"
14export GARAGE_TARGET_NAME
15 15
16OSTREE_KERNEL ??= "${KERNEL_IMAGETYPE}" 16OSTREE_KERNEL ??= "${KERNEL_IMAGETYPE}"
17 17
@@ -194,7 +194,9 @@ IMAGE_CMD_garagesign () {
194 fi 194 fi
195 195
196 rm -rf ${GARAGE_SIGN_REPO} 196 rm -rf ${GARAGE_SIGN_REPO}
197 garage-sign init --repo tufrepo --home-dir ${GARAGE_SIGN_REPO} --credentials ${SOTA_PACKED_CREDENTIALS} 197 garage-sign init --repo tufrepo \
198 --home-dir ${GARAGE_SIGN_REPO} \
199 --credentials ${SOTA_PACKED_CREDENTIALS}
198 200
199 ostree_target_hash=$(cat ${OSTREE_REPO}/refs/heads/${OSTREE_BRANCHNAME}) 201 ostree_target_hash=$(cat ${OSTREE_REPO}/refs/heads/${OSTREE_BRANCHNAME})
200 202
@@ -202,11 +204,23 @@ IMAGE_CMD_garagesign () {
202 # in which case targets.json should be pulled again and the whole procedure repeated 204 # in which case targets.json should be pulled again and the whole procedure repeated
203 push_success=0 205 push_success=0
204 for push_retries in $( seq 3 ); do 206 for push_retries in $( seq 3 ); do
205 garage-sign targets pull --repo tufrepo --home-dir ${GARAGE_SIGN_REPO} 207 garage-sign targets pull --repo tufrepo \
206 garage-sign targets add --repo tufrepo --home-dir ${GARAGE_SIGN_REPO} --name ${GARAGE_TARGET_NAME} --format OSTREE --version ${ostree_target_hash} --length 0 --url "https://example.com/" --sha256 ${ostree_target_hash} --hardwareids ${MACHINE} 208 --home-dir ${GARAGE_SIGN_REPO}
207 garage-sign targets sign --repo tufrepo --home-dir ${GARAGE_SIGN_REPO} --key-name=targets 209 garage-sign targets add --repo tufrepo \
210 --home-dir ${GARAGE_SIGN_REPO} \
211 --name ${GARAGE_TARGET_NAME} \
212 --format OSTREE \
213 --version ${ostree_target_hash} \
214 --length 0 \
215 --url "https://example.com/" \
216 --sha256 ${ostree_target_hash} \
217 --hardwareids ${MACHINE}
218 garage-sign targets sign --repo tufrepo \
219 --home-dir ${GARAGE_SIGN_REPO} \
220 --key-name=targets
208 errcode=0 221 errcode=0
209 garage-sign targets push --repo tufrepo --home-dir ${GARAGE_SIGN_REPO} || errcode=$? 222 garage-sign targets push --repo tufrepo \
223 --home-dir ${GARAGE_SIGN_REPO} || errcode=$?
210 if [ "$errcode" -eq "0" ]; then 224 if [ "$errcode" -eq "0" ]; then
211 push_success=1 225 push_success=1
212 break 226 break
diff --git a/classes/sota_raspberrypi.bbclass b/classes/sota_raspberrypi.bbclass
index 2c69ea0..a5558b4 100644
--- a/classes/sota_raspberrypi.bbclass
+++ b/classes/sota_raspberrypi.bbclass
@@ -1,11 +1,20 @@
1RPI_USE_U_BOOT_sota = "1" 1RPI_USE_U_BOOT_sota = "1"
2KERNEL_IMAGETYPE_sota = "uImage" 2
3KERNEL_CLASSES_append_sota = " kernel-fitimage"
4KERNEL_IMAGETYPE_sota = "fitImage"
5
3PREFERRED_PROVIDER_virtual/bootloader_sota ?= "u-boot" 6PREFERRED_PROVIDER_virtual/bootloader_sota ?= "u-boot"
4UBOOT_MACHINE_raspberrypi2_sota ?= "rpi_2_defconfig" 7UBOOT_ENTRYPOINT_sota ?= "0x00008000"
5UBOOT_MACHINE_raspberrypi3_sota ?= "rpi_3_32b_defconfig"
6 8
7IMAGE_FSTYPES_remove_sota = "rpi-sdimg" 9IMAGE_FSTYPES_remove_sota = "rpi-sdimg"
8OSTREE_BOOTLOADER ?= "u-boot" 10OSTREE_BOOTLOADER ?= "u-boot"
9 11
10# OSTree puts its own boot.scr to bcm2835-bootfiles 12# OSTree puts its own boot.scr to bcm2835-bootfiles
11IMAGE_BOOT_FILES_remove_sota += "boot.scr" 13IMAGE_BOOT_FILES_sota = "bcm2835-bootfiles/* u-boot.bin;${SDIMG_KERNELIMAGE}"
14
15# Just the overlays that will be used should be listed
16KERNEL_DEVICETREE_raspberrypi2_sota ?= " bcm2709-rpi-2-b.dtb "
17KERNEL_DEVICETREE_raspberrypi3_sota ?= " bcm2710-rpi-3-b.dtb overlays/vc4-kms-v3d.dtbo overlays/rpi-ft5406.dtbo"
18
19# Kernel args normally provided by RPi's internal bootloader. Non-updateable
20OSTREE_KERNEL_ARGS_sota ?= " 8250.nr_uarts=1 bcm2708_fb.fbwidth=720 bcm2708_fb.fbheight=480 bcm2708_fb.fbswap=1 vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000 dwc_otg.lpm_enable=0 console=ttyS0,115200 usbhid.mousepoll=0 "
diff --git a/lib/oeqa/selftest/cases/updater.py b/lib/oeqa/selftest/cases/updater.py
index 91ac9fc..e459ffb 100644
--- a/lib/oeqa/selftest/cases/updater.py
+++ b/lib/oeqa/selftest/cases/updater.py
@@ -1,6 +1,7 @@
1# pylint: disable=C0111,C0325 1# pylint: disable=C0111,C0325
2import os 2import os
3import logging 3import logging
4import re
4import subprocess 5import subprocess
5import unittest 6import unittest
6from time import sleep 7from time import sleep
@@ -20,32 +21,13 @@ class SotaToolsTests(OESelftestTestCase):
20 bitbake('aktualizr-native') 21 bitbake('aktualizr-native')
21 22
22 def test_push_help(self): 23 def test_push_help(self):
23 bb_vars = get_bb_vars(['SYSROOT_DESTDIR', 'bindir'], 'aktualizr-native') 24 akt_native_run(self, 'garage-push --help')
24 p = bb_vars['SYSROOT_DESTDIR'] + bb_vars['bindir'] + "/" + "garage-push"
25 self.assertTrue(os.path.isfile(p), msg = "No garage-push found (%s)" % p)
26 result = runCmd('%s --help' % p, ignore_status=True)
27 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
28 25
29 def test_deploy_help(self): 26 def test_deploy_help(self):
30 bb_vars = get_bb_vars(['SYSROOT_DESTDIR', 'bindir'], 'aktualizr-native') 27 akt_native_run(self, 'garage-deploy --help')
31 p = bb_vars['SYSROOT_DESTDIR'] + bb_vars['bindir'] + "/" + "garage-deploy"
32 self.assertTrue(os.path.isfile(p), msg = "No garage-deploy found (%s)" % p)
33 result = runCmd('%s --help' % p, ignore_status=True)
34 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
35 28
36 def test_garagesign_help(self): 29 def test_garagesign_help(self):
37 bb_vars = get_bb_vars(['SYSROOT_DESTDIR', 'bindir'], 'aktualizr-native') 30 akt_native_run(self, 'garage-sign --help')
38 p = bb_vars['SYSROOT_DESTDIR'] + bb_vars['bindir'] + "/" + "garage-sign"
39 self.assertTrue(os.path.isfile(p), msg = "No garage-sign found (%s)" % p)
40 result = runCmd('%s --help' % p, ignore_status=True)
41 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
42
43
44class HsmTests(OESelftestTestCase):
45
46 def test_hsm(self):
47 self.write_config('SOTA_CLIENT_FEATURES="hsm"')
48 bitbake('core-image-minimal')
49 31
50 32
51class GeneralTests(OESelftestTestCase): 33class GeneralTests(OESelftestTestCase):
@@ -59,6 +41,9 @@ class GeneralTests(OESelftestTestCase):
59 self.assertNotEqual(result, -1, 'Feature "systemd" not set at DISTRO_FEATURES') 41 self.assertNotEqual(result, -1, 'Feature "systemd" not set at DISTRO_FEATURES')
60 42
61 def test_credentials(self): 43 def test_credentials(self):
44 logger = logging.getLogger("selftest")
45 logger.info('Running bitbake to build core-image-minimal')
46 self.append_config('SOTA_CLIENT_PROV = "aktualizr-auto-prov"')
62 bitbake('core-image-minimal') 47 bitbake('core-image-minimal')
63 credentials = get_bb_var('SOTA_PACKED_CREDENTIALS') 48 credentials = get_bb_var('SOTA_PACKED_CREDENTIALS')
64 # skip the test if the variable SOTA_PACKED_CREDENTIALS is not set 49 # skip the test if the variable SOTA_PACKED_CREDENTIALS is not set
@@ -75,17 +60,17 @@ class GeneralTests(OESelftestTestCase):
75 60
76 def test_java(self): 61 def test_java(self):
77 result = runCmd('which java', ignore_status=True) 62 result = runCmd('which java', ignore_status=True)
78 self.assertEqual(result.status, 0, "Java not found.") 63 self.assertEqual(result.status, 0,
64 "Java not found. Do you have a JDK installed on your host machine?")
79 65
80 def test_add_package(self): 66 def test_add_package(self):
81 print('')
82 deploydir = get_bb_var('DEPLOY_DIR_IMAGE') 67 deploydir = get_bb_var('DEPLOY_DIR_IMAGE')
83 imagename = get_bb_var('IMAGE_LINK_NAME', 'core-image-minimal') 68 imagename = get_bb_var('IMAGE_LINK_NAME', 'core-image-minimal')
84 image_path = deploydir + '/' + imagename + '.otaimg' 69 image_path = deploydir + '/' + imagename + '.otaimg'
85 logger = logging.getLogger("selftest") 70 logger = logging.getLogger("selftest")
86 71
87 logger.info('Running bitbake with man in the image package list') 72 logger.info('Running bitbake with man in the image package list')
88 self.write_config('IMAGE_INSTALL_append = " man "') 73 self.append_config('IMAGE_INSTALL_append = " man "')
89 bitbake('-c cleanall man') 74 bitbake('-c cleanall man')
90 bitbake('core-image-minimal') 75 bitbake('core-image-minimal')
91 result = runCmd('oe-pkgdata-util find-path /usr/bin/man') 76 result = runCmd('oe-pkgdata-util find-path /usr/bin/man')
@@ -95,7 +80,7 @@ class GeneralTests(OESelftestTestCase):
95 logger.info('First image %s has size %i' % (path1, size1)) 80 logger.info('First image %s has size %i' % (path1, size1))
96 81
97 logger.info('Running bitbake without man in the image package list') 82 logger.info('Running bitbake without man in the image package list')
98 self.write_config('IMAGE_INSTALL_remove = " man "') 83 self.append_config('IMAGE_INSTALL_remove = " man "')
99 bitbake('-c cleanall man') 84 bitbake('-c cleanall man')
100 bitbake('core-image-minimal') 85 bitbake('core-image-minimal')
101 result = runCmd('oe-pkgdata-util find-path /usr/bin/man', ignore_status=True) 86 result = runCmd('oe-pkgdata-util find-path /usr/bin/man', ignore_status=True)
@@ -108,98 +93,512 @@ class GeneralTests(OESelftestTestCase):
108 self.assertNotEqual(size1, size2, "Image sizes are identical; image was not rebuilt.") 93 self.assertNotEqual(size1, size2, "Image sizes are identical; image was not rebuilt.")
109 94
110 95
111class QemuTests(OESelftestTestCase): 96class AktualizrToolsTests(OESelftestTestCase):
112 97
113 @classmethod 98 @classmethod
114 def setUpClass(cls): 99 def setUpClass(cls):
115 super(QemuTests, cls).setUpClass() 100 super(AktualizrToolsTests, cls).setUpClass()
116 cls.qemu, cls.s = qemu_launch(machine='qemux86-64') 101 logger = logging.getLogger("selftest")
102 logger.info('Running bitbake to build aktualizr-native tools')
103 bitbake('aktualizr-native')
117 104
118 @classmethod 105 def test_implicit_writer_help(self):
119 def tearDownClass(cls): 106 akt_native_run(self, 'aktualizr_implicit_writer --help')
120 qemu_terminate(cls.s) 107
108 def test_cert_provider_help(self):
109 akt_native_run(self, 'aktualizr_cert_provider --help')
110
111 def test_cert_provider_local_output(self):
112 logger = logging.getLogger("selftest")
113 logger.info('Running bitbake to build aktualizr-implicit-prov')
114 bitbake('aktualizr-implicit-prov')
115 bb_vars = get_bb_vars(['SOTA_PACKED_CREDENTIALS', 'T'], 'aktualizr-native')
116 creds = bb_vars['SOTA_PACKED_CREDENTIALS']
117 temp_dir = bb_vars['T']
118 bb_vars_prov = get_bb_vars(['STAGING_DIR_NATIVE', 'libdir'], 'aktualizr-implicit-prov')
119 config = bb_vars_prov['STAGING_DIR_NATIVE'] + bb_vars_prov['libdir'] + '/sota/sota_implicit_prov.toml'
120
121 akt_native_run(self, 'aktualizr_cert_provider -c {creds} -r -l {temp} -g {config}'
122 .format(creds=creds, temp=temp_dir, config=config))
123
124 # Might be nice if these names weren't hardcoded.
125 cert_path = temp_dir + '/client.pem'
126 self.assertTrue(os.path.isfile(cert_path), "Client certificate not found at %s." % cert_path)
127 self.assertTrue(os.path.getsize(cert_path) > 0, "Client certificate at %s is empty." % cert_path)
128 pkey_path = temp_dir + '/pkey.pem'
129 self.assertTrue(os.path.isfile(pkey_path), "Private key not found at %s." % pkey_path)
130 self.assertTrue(os.path.getsize(pkey_path) > 0, "Private key at %s is empty." % pkey_path)
131 ca_path = temp_dir + '/root.crt'
132 self.assertTrue(os.path.isfile(ca_path), "Client certificate not found at %s." % ca_path)
133 self.assertTrue(os.path.getsize(ca_path) > 0, "Client certificate at %s is empty." % ca_path)
134
135
136class AutoProvTests(OESelftestTestCase):
137
138 def setUpLocal(self):
139 layer = "meta-updater-qemux86-64"
140 result = runCmd('bitbake-layers show-layers')
141 if re.search(layer, result.output) is None:
142 # Assume the directory layout for finding other layers. We could also
143 # make assumptions by using 'show-layers', but either way, if the
144 # layers we need aren't where we expect them, we are out of like.
145 path = os.path.abspath(os.path.dirname(__file__))
146 metadir = path + "/../../../../../"
147 self.meta_qemu = metadir + layer
148 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
149 else:
150 self.meta_qemu = None
151 self.append_config('MACHINE = "qemux86-64"')
152 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
153 self.qemu, self.s = qemu_launch(machine='qemux86-64')
154
155 def tearDownLocal(self):
156 qemu_terminate(self.s)
157 if self.meta_qemu:
158 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
121 159
122 def run_command(self, command): 160 def qemu_command(self, command):
123 return qemu_send_command(self.qemu.ssh_port, command) 161 return qemu_send_command(self.qemu.ssh_port, command)
124 162
125 def test_hostname(self): 163 def test_provisioning(self):
126 print('')
127 print('Checking machine name (hostname) of device:') 164 print('Checking machine name (hostname) of device:')
128 stdout, stderr, retcode = self.run_command('hostname') 165 stdout, stderr, retcode = self.qemu_command('hostname')
166 self.assertEqual(retcode, 0, "Unable to check hostname. " +
167 "Is an ssh daemon (such as dropbear or openssh) installed on the device?")
129 machine = get_bb_var('MACHINE', 'core-image-minimal') 168 machine = get_bb_var('MACHINE', 'core-image-minimal')
130 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode()) 169 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
131 # Strip off line ending. 170 # Strip off line ending.
132 value_str = stdout.decode()[:-1] 171 value = stdout.decode()[:-1]
133 self.assertEqual(value_str, machine, 172 self.assertEqual(value, machine,
134 'MACHINE does not match hostname: ' + machine + ', ' + value_str) 173 'MACHINE does not match hostname: ' + machine + ', ' + value)
135 print(value_str) 174 print(value)
136
137 def test_var_sota(self):
138 print('')
139 print('Checking contents of /var/sota:')
140 stdout, stderr, retcode = self.run_command('ls /var/sota')
141 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
142 self.assertEqual(retcode, 0)
143 print(stdout.decode())
144
145 def test_aktualizr_info(self):
146 print('Checking output of aktualizr-info:') 175 print('Checking output of aktualizr-info:')
147 ran_ok = False 176 ran_ok = False
148 for delay in [0, 1, 2, 5, 10, 15]: 177 for delay in [0, 1, 2, 5, 10, 15]:
149 sleep(delay) 178 sleep(delay)
150 try: 179 stdout, stderr, retcode = self.qemu_command('aktualizr-info')
151 stdout, stderr, retcode = self.run_command('aktualizr-info') 180 if retcode == 0 and stderr == b'':
152 if retcode == 0 and stderr == b'': 181 ran_ok = True
153 ran_ok = True 182 break
154 break 183 self.assertTrue(ran_ok, 'aktualizr-info failed: ' + stderr.decode() + stdout.decode())
155 except IOError as e: 184
156 print(e) 185 verifyProvisioned(self, machine)
157 if not ran_ok: 186
158 print(stdout.decode()) 187
159 print(stderr.decode()) 188class RpiTests(OESelftestTestCase):
189
190 def setUpLocal(self):
191 # Add layers before changing the machine type, otherwise the sanity
192 # checker complains loudly.
193 layer_python = "meta-openembedded/meta-python"
194 layer_rpi = "meta-raspberrypi"
195 layer_upd_rpi = "meta-updater-raspberrypi"
196 result = runCmd('bitbake-layers show-layers')
197 # Assume the directory layout for finding other layers. We could also
198 # make assumptions by using 'show-layers', but either way, if the
199 # layers we need aren't where we expect them, we are out of like.
200 path = os.path.abspath(os.path.dirname(__file__))
201 metadir = path + "/../../../../../"
202 if re.search(layer_python, result.output) is None:
203 self.meta_python = metadir + layer_python
204 runCmd('bitbake-layers add-layer "%s"' % self.meta_python)
205 else:
206 self.meta_python = None
207 if re.search(layer_rpi, result.output) is None:
208 self.meta_rpi = metadir + layer_rpi
209 runCmd('bitbake-layers add-layer "%s"' % self.meta_rpi)
210 else:
211 self.meta_rpi = None
212 if re.search(layer_upd_rpi, result.output) is None:
213 self.meta_upd_rpi = metadir + layer_upd_rpi
214 runCmd('bitbake-layers add-layer "%s"' % self.meta_upd_rpi)
215 else:
216 self.meta_upd_rpi = None
217
218 # This is trickier that I would've thought. The fundamental problem is
219 # that the qemu layer changes the u-boot file extension to .rom, but
220 # raspberrypi still expects .bin. To prevent this, the qemu layer must
221 # be temporarily removed if it is present. It has to be removed by name
222 # without the complete path, but to add it back when we are done, we
223 # need the full path.
224 p = re.compile(r'meta-updater-qemux86-64\s*(\S*meta-updater-qemux86-64)\s')
225 m = p.search(result.output)
226 if m and m.lastindex > 0:
227 self.meta_qemu = m.group(1)
228 runCmd('bitbake-layers remove-layer meta-updater-qemux86-64')
229 else:
230 self.meta_qemu = None
231
232 self.append_config('MACHINE = "raspberrypi3"')
233 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
234
235 def tearDownLocal(self):
236 if self.meta_qemu:
237 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu, ignore_status=True)
238 if self.meta_upd_rpi:
239 runCmd('bitbake-layers remove-layer "%s"' % self.meta_upd_rpi, ignore_status=True)
240 if self.meta_rpi:
241 runCmd('bitbake-layers remove-layer "%s"' % self.meta_rpi, ignore_status=True)
242 if self.meta_python:
243 runCmd('bitbake-layers remove-layer "%s"' % self.meta_python, ignore_status=True)
244
245 def test_rpi(self):
246 logger = logging.getLogger("selftest")
247 logger.info('Running bitbake to build rpi-basic-image')
248 self.append_config('SOTA_CLIENT_PROV = "aktualizr-auto-prov"')
249 bitbake('rpi-basic-image')
250 credentials = get_bb_var('SOTA_PACKED_CREDENTIALS')
251 # Skip the test if the variable SOTA_PACKED_CREDENTIALS is not set.
252 if credentials is None:
253 raise unittest.SkipTest("Variable 'SOTA_PACKED_CREDENTIALS' not set.")
254 # Check if the file exists.
255 self.assertTrue(os.path.isfile(credentials), "File %s does not exist" % credentials)
256 deploydir = get_bb_var('DEPLOY_DIR_IMAGE')
257 imagename = get_bb_var('IMAGE_LINK_NAME', 'rpi-basic-image')
258 # Check if the credentials are included in the output image.
259 result = runCmd('tar -jtvf %s/%s.tar.bz2 | grep sota_provisioning_credentials.zip' %
260 (deploydir, imagename), ignore_status=True)
261 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
160 262
161 263
162class GrubTests(OESelftestTestCase): 264class GrubTests(OESelftestTestCase):
163 265
164 def setUpLocal(self): 266 def setUpLocal(self):
165 # This is a bit of a hack but I can't see a better option. 267 layer_intel = "meta-intel"
268 layer_minnow = "meta-updater-minnowboard"
269 result = runCmd('bitbake-layers show-layers')
270 # Assume the directory layout for finding other layers. We could also
271 # make assumptions by using 'show-layers', but either way, if the
272 # layers we need aren't where we expect them, we are out of like.
166 path = os.path.abspath(os.path.dirname(__file__)) 273 path = os.path.abspath(os.path.dirname(__file__))
167 metadir = path + "/../../../../../" 274 metadir = path + "/../../../../../"
168 grub_config = 'OSTREE_BOOTLOADER = "grub"\nMACHINE = "intel-corei7-64"' 275 if re.search(layer_intel, result.output) is None:
169 self.append_config(grub_config) 276 self.meta_intel = metadir + layer_intel
170 self.meta_intel = metadir + "meta-intel" 277 runCmd('bitbake-layers add-layer "%s"' % self.meta_intel)
171 self.meta_minnow = metadir + "meta-updater-minnowboard" 278 else:
172 runCmd('bitbake-layers add-layer "%s"' % self.meta_intel) 279 self.meta_intel = None
173 runCmd('bitbake-layers add-layer "%s"' % self.meta_minnow) 280 if re.search(layer_minnow, result.output) is None:
281 self.meta_minnow = metadir + layer_minnow
282 runCmd('bitbake-layers add-layer "%s"' % self.meta_minnow)
283 else:
284 self.meta_minnow = None
285 self.append_config('MACHINE = "intel-corei7-64"')
286 self.append_config('OSTREE_BOOTLOADER = "grub"')
287 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
174 self.qemu, self.s = qemu_launch(efi=True, machine='intel-corei7-64') 288 self.qemu, self.s = qemu_launch(efi=True, machine='intel-corei7-64')
175 289
176 def tearDownLocal(self): 290 def tearDownLocal(self):
177 qemu_terminate(self.s) 291 qemu_terminate(self.s)
178 runCmd('bitbake-layers remove-layer "%s"' % self.meta_intel, ignore_status=True) 292 if self.meta_intel:
179 runCmd('bitbake-layers remove-layer "%s"' % self.meta_minnow, ignore_status=True) 293 runCmd('bitbake-layers remove-layer "%s"' % self.meta_intel, ignore_status=True)
294 if self.meta_minnow:
295 runCmd('bitbake-layers remove-layer "%s"' % self.meta_minnow, ignore_status=True)
296
297 def qemu_command(self, command):
298 return qemu_send_command(self.qemu.ssh_port, command)
180 299
181 def test_grub(self): 300 def test_grub(self):
182 print('')
183 print('Checking machine name (hostname) of device:') 301 print('Checking machine name (hostname) of device:')
184 value, err, retcode = qemu_send_command(self.qemu.ssh_port, 'hostname') 302 stdout, stderr, retcode = self.qemu_command('hostname')
303 self.assertEqual(retcode, 0, "Unable to check hostname. " +
304 "Is an ssh daemon (such as dropbear or openssh) installed on the device?")
305 machine = get_bb_var('MACHINE', 'core-image-minimal')
306 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
307 # Strip off line ending.
308 value = stdout.decode()[:-1]
309 self.assertEqual(value, machine,
310 'MACHINE does not match hostname: ' + machine + ', ' + value +
311 '\nIs TianoCore ovmf installed on your host machine?')
312 print(value)
313 print('Checking output of aktualizr-info:')
314 ran_ok = False
315 for delay in [0, 1, 2, 5, 10, 15]:
316 sleep(delay)
317 stdout, stderr, retcode = self.qemu_command('aktualizr-info')
318 if retcode == 0 and stderr == b'':
319 ran_ok = True
320 break
321 self.assertTrue(ran_ok, 'aktualizr-info failed: ' + stderr.decode() + stdout.decode())
322
323 verifyProvisioned(self, machine)
324
325
326class ImplProvTests(OESelftestTestCase):
327
328 def setUpLocal(self):
329 layer = "meta-updater-qemux86-64"
330 result = runCmd('bitbake-layers show-layers')
331 if re.search(layer, result.output) is None:
332 # Assume the directory layout for finding other layers. We could also
333 # make assumptions by using 'show-layers', but either way, if the
334 # layers we need aren't where we expect them, we are out of like.
335 path = os.path.abspath(os.path.dirname(__file__))
336 metadir = path + "/../../../../../"
337 self.meta_qemu = metadir + layer
338 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
339 else:
340 self.meta_qemu = None
341 self.append_config('MACHINE = "qemux86-64"')
342 self.append_config('SOTA_CLIENT_PROV = " aktualizr-implicit-prov "')
343 self.qemu, self.s = qemu_launch(machine='qemux86-64')
344
345 def tearDownLocal(self):
346 qemu_terminate(self.s)
347 if self.meta_qemu:
348 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
349
350 def qemu_command(self, command):
351 return qemu_send_command(self.qemu.ssh_port, command)
352
353 def test_provisioning(self):
354 print('Checking machine name (hostname) of device:')
355 stdout, stderr, retcode = self.qemu_command('hostname')
356 self.assertEqual(retcode, 0, "Unable to check hostname. " +
357 "Is an ssh daemon (such as dropbear or openssh) installed on the device?")
358 machine = get_bb_var('MACHINE', 'core-image-minimal')
359 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
360 # Strip off line ending.
361 value = stdout.decode()[:-1]
362 self.assertEqual(value, machine,
363 'MACHINE does not match hostname: ' + machine + ', ' + value)
364 print(value)
365 print('Checking output of aktualizr-info:')
366 ran_ok = False
367 for delay in [0, 1, 2, 5, 10, 15]:
368 stdout, stderr, retcode = self.qemu_command('aktualizr-info')
369 if retcode == 0 and stderr == b'':
370 ran_ok = True
371 break
372 self.assertTrue(ran_ok, 'aktualizr-info failed: ' + stderr.decode() + stdout.decode())
373 # Verify that device has NOT yet provisioned.
374 self.assertIn(b'Couldn\'t load device ID', stdout,
375 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
376 self.assertIn(b'Couldn\'t load ECU serials', stdout,
377 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
378 self.assertIn(b'Provisioned on server: no', stdout,
379 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
380 self.assertIn(b'Fetched metadata: no', stdout,
381 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
382
383 # Run cert_provider.
384 bb_vars = get_bb_vars(['SOTA_PACKED_CREDENTIALS'], 'aktualizr-native')
385 creds = bb_vars['SOTA_PACKED_CREDENTIALS']
386 bb_vars_prov = get_bb_vars(['STAGING_DIR_NATIVE', 'libdir'], 'aktualizr-implicit-prov')
387 config = bb_vars_prov['STAGING_DIR_NATIVE'] + bb_vars_prov['libdir'] + '/sota/sota_implicit_prov.toml'
388
389 akt_native_run(self, 'aktualizr_cert_provider -c {creds} -t root@localhost -p {port} -s -g {config}'
390 .format(creds=creds, port=self.qemu.ssh_port, config=config))
391
392 verifyProvisioned(self, machine)
393
394
395class HsmTests(OESelftestTestCase):
396
397 def setUpLocal(self):
398 layer = "meta-updater-qemux86-64"
399 result = runCmd('bitbake-layers show-layers')
400 if re.search(layer, result.output) is None:
401 # Assume the directory layout for finding other layers. We could also
402 # make assumptions by using 'show-layers', but either way, if the
403 # layers we need aren't where we expect them, we are out of like.
404 path = os.path.abspath(os.path.dirname(__file__))
405 metadir = path + "/../../../../../"
406 self.meta_qemu = metadir + layer
407 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
408 else:
409 self.meta_qemu = None
410 self.append_config('MACHINE = "qemux86-64"')
411 self.append_config('SOTA_CLIENT_PROV = "aktualizr-hsm-prov"')
412 self.append_config('SOTA_CLIENT_FEATURES = "hsm"')
413 self.qemu, self.s = qemu_launch(machine='qemux86-64')
414
415 def tearDownLocal(self):
416 qemu_terminate(self.s)
417 if self.meta_qemu:
418 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
419
420 def qemu_command(self, command):
421 return qemu_send_command(self.qemu.ssh_port, command)
422
423 def test_provisioning(self):
424 print('Checking machine name (hostname) of device:')
425 stdout, stderr, retcode = self.qemu_command('hostname')
426 self.assertEqual(retcode, 0, "Unable to check hostname. " +
427 "Is an ssh daemon (such as dropbear or openssh) installed on the device?")
185 machine = get_bb_var('MACHINE', 'core-image-minimal') 428 machine = get_bb_var('MACHINE', 'core-image-minimal')
186 self.assertEqual(err, b'', 'Error: ' + err.decode()) 429 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
187 self.assertEqual(retcode, 0)
188 # Strip off line ending. 430 # Strip off line ending.
189 value_str = value.decode()[:-1] 431 value = stdout.decode()[:-1]
190 self.assertEqual(value_str, machine, 432 self.assertEqual(value, machine,
191 'MACHINE does not match hostname: ' + machine + ', ' + value_str + 433 'MACHINE does not match hostname: ' + machine + ', ' + value +
192 '\nIs tianocore ovmf installed?') 434 '\nIs tianocore ovmf installed?')
193 print(value_str) 435 print(value)
436 print('Checking output of aktualizr-info:')
437 ran_ok = False
438 for delay in [0, 1, 2, 5, 10, 15]:
439 stdout, stderr, retcode = self.qemu_command('aktualizr-info')
440 if retcode == 0 and stderr == b'':
441 ran_ok = True
442 break
443 self.assertTrue(ran_ok, 'aktualizr-info failed: ' + stderr.decode() + stdout.decode())
444 # Verify that device has NOT yet provisioned.
445 self.assertIn(b'Couldn\'t load device ID', stdout,
446 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
447 self.assertIn(b'Couldn\'t load ECU serials', stdout,
448 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
449 self.assertIn(b'Provisioned on server: no', stdout,
450 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
451 self.assertIn(b'Fetched metadata: no', stdout,
452 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
453
454 # Verify that HSM is not yet initialized.
455 pkcs11_command = 'pkcs11-tool --module=/usr/lib/softhsm/libsofthsm2.so -O'
456 stdout, stderr, retcode = self.qemu_command(pkcs11_command)
457 self.assertNotEqual(retcode, 0, 'pkcs11-tool succeeded before initialization: ' +
458 stdout.decode() + stderr.decode())
459 softhsm2_command = 'softhsm2-util --show-slots'
460 stdout, stderr, retcode = self.qemu_command(softhsm2_command)
461 self.assertNotEqual(retcode, 0, 'softhsm2-tool succeeded before initialization: ' +
462 stdout.decode() + stderr.decode())
463
464 # Run cert_provider.
465 bb_vars = get_bb_vars(['SOTA_PACKED_CREDENTIALS'], 'aktualizr-native')
466 creds = bb_vars['SOTA_PACKED_CREDENTIALS']
467 bb_vars_prov = get_bb_vars(['STAGING_DIR_NATIVE', 'libdir'], 'aktualizr-hsm-prov')
468 config = bb_vars_prov['STAGING_DIR_NATIVE'] + bb_vars_prov['libdir'] + '/sota/sota_hsm_prov.toml'
469
470 akt_native_run(self, 'aktualizr_cert_provider -c {creds} -t root@localhost -p {port} -r -s -g {config}'
471 .format(creds=creds, port=self.qemu.ssh_port, config=config))
472
473 # Verify that HSM is able to initialize.
474 ran_ok = False
475 for delay in [5, 5, 5, 5, 10]:
476 sleep(delay)
477 p11_out, p11_err, p11_ret = self.qemu_command(pkcs11_command)
478 hsm_out, hsm_err, hsm_ret = self.qemu_command(softhsm2_command)
479 if p11_ret == 0 and hsm_ret == 0 and hsm_err == b'':
480 ran_ok = True
481 break
482 self.assertTrue(ran_ok, 'pkcs11-tool or softhsm2-tool failed: ' + p11_err.decode() +
483 p11_out.decode() + hsm_err.decode() + hsm_out.decode())
484 self.assertIn(b'present token', p11_err, 'pkcs11-tool failed: ' + p11_err.decode() + p11_out.decode())
485 self.assertIn(b'X.509 cert', p11_out, 'pkcs11-tool failed: ' + p11_err.decode() + p11_out.decode())
486 self.assertIn(b'Initialized: yes', hsm_out, 'softhsm2-tool failed: ' +
487 hsm_err.decode() + hsm_out.decode())
488 self.assertIn(b'User PIN init.: yes', hsm_out, 'softhsm2-tool failed: ' +
489 hsm_err.decode() + hsm_out.decode())
490
491 # Check that pkcs11 output matches sofhsm output.
492 p11_p = re.compile(r'Using slot [0-9] with a present token \((0x[0-9a-f]*)\)\s')
493 p11_m = p11_p.search(p11_err.decode())
494 self.assertTrue(p11_m, 'Slot number not found with pkcs11-tool: ' + p11_err.decode() + p11_out.decode())
495 self.assertGreater(p11_m.lastindex, 0, 'Slot number not found with pkcs11-tool: ' +
496 p11_err.decode() + p11_out.decode())
497 hsm_p = re.compile(r'Description:\s*SoftHSM slot ID (0x[0-9a-f]*)\s')
498 hsm_m = hsm_p.search(hsm_out.decode())
499 self.assertTrue(hsm_m, 'Slot number not found with softhsm2-tool: ' + hsm_err.decode() + hsm_out.decode())
500 self.assertGreater(hsm_m.lastindex, 0, 'Slot number not found with softhsm2-tool: ' +
501 hsm_err.decode() + hsm_out.decode())
502 self.assertEqual(p11_m.group(1), hsm_m.group(1), 'Slot number does not match: ' +
503 p11_err.decode() + p11_out.decode() + hsm_err.decode() + hsm_out.decode())
504
505 verifyProvisioned(self, machine)
506
507class SecondaryTests(OESelftestTestCase):
508 @classmethod
509 def setUpClass(cls):
510 super(SecondaryTests, cls).setUpClass()
511 logger = logging.getLogger("selftest")
512 logger.info('Running bitbake to build secondary-image')
513 bitbake('secondary-image')
194 514
515 def setUpLocal(self):
516 layer = "meta-updater-qemux86-64"
517 result = runCmd('bitbake-layers show-layers')
518 if re.search(layer, result.output) is None:
519 # Assume the directory layout for finding other layers. We could also
520 # make assumptions by using 'show-layers', but either way, if the
521 # layers we need aren't where we expect them, we are out of like.
522 path = os.path.abspath(os.path.dirname(__file__))
523 metadir = path + "/../../../../../"
524 self.meta_qemu = metadir + layer
525 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
526 else:
527 self.meta_qemu = None
528 self.append_config('MACHINE = "qemux86-64"')
529 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
530 self.qemu, self.s = qemu_launch(machine='qemux86-64', imagename='secondary-image')
531
532 def tearDownLocal(self):
533 qemu_terminate(self.s)
534 if self.meta_qemu:
535 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
536
537 def qemu_command(self, command):
538 return qemu_send_command(self.qemu.ssh_port, command)
539
540 def test_secondary_present(self):
541 print('Checking aktualizr-secondary is present')
542 stdout, stderr, retcode = self.qemu_command('aktualizr-secondary --help')
543 self.assertEqual(retcode, 0, "Unable to run aktualizr-secondary --help")
544 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
195 545
196def qemu_launch(efi=False, machine=None): 546 def test_secondary_listening(self):
547 print('Checking aktualizr-secondary service is listening')
548 stdout, stderr, retcode = self.qemu_command('echo test | nc localhost 9030')
549 self.assertEqual(retcode, 0, "Unable to connect to secondary")
550
551
552class PrimaryTests(OESelftestTestCase):
553 @classmethod
554 def setUpClass(cls):
555 super(PrimaryTests, cls).setUpClass()
556 logger = logging.getLogger("selftest")
557 logger.info('Running bitbake to build primary-image')
558 bitbake('primary-image')
559
560 def setUpLocal(self):
561 layer = "meta-updater-qemux86-64"
562 result = runCmd('bitbake-layers show-layers')
563 if re.search(layer, result.output) is None:
564 # Assume the directory layout for finding other layers. We could also
565 # make assumptions by using 'show-layers', but either way, if the
566 # layers we need aren't where we expect them, we are out of like.
567 path = os.path.abspath(os.path.dirname(__file__))
568 metadir = path + "/../../../../../"
569 self.meta_qemu = metadir + layer
570 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
571 else:
572 self.meta_qemu = None
573 self.append_config('MACHINE = "qemux86-64"')
574 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
575 self.append_config('SOTA_CLIENT_FEATURES = "secondary-network"')
576 self.qemu, self.s = qemu_launch(machine='qemux86-64', imagename='primary-image')
577
578 def tearDownLocal(self):
579 qemu_terminate(self.s)
580 if self.meta_qemu:
581 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
582
583 def qemu_command(self, command):
584 return qemu_send_command(self.qemu.ssh_port, command)
585
586 def test_aktualizr_present(self):
587 print('Checking aktualizr is present')
588 stdout, stderr, retcode = self.qemu_command('aktualizr --help')
589 self.assertEqual(retcode, 0, "Unable to run aktualizr --help")
590 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
591
592def qemu_launch(efi=False, machine=None, imagename=None):
197 logger = logging.getLogger("selftest") 593 logger = logging.getLogger("selftest")
198 logger.info('Running bitbake to build core-image-minimal') 594 logger.info('Running bitbake to build core-image-minimal')
199 bitbake('core-image-minimal') 595 bitbake('core-image-minimal')
200 # Create empty object. 596 # Create empty object.
201 args = type('', (), {})() 597 args = type('', (), {})()
202 args.imagename = 'core-image-minimal' 598 if imagename:
599 args.imagename = imagename
600 else:
601 args.imagename = 'core-image-minimal'
203 args.mac = None 602 args.mac = None
204 # Could use DEPLOY_DIR_IMAGE here but it's already in the machine 603 # Could use DEPLOY_DIR_IMAGE here but it's already in the machine
205 # subdirectory. 604 # subdirectory.
@@ -212,6 +611,7 @@ def qemu_launch(efi=False, machine=None):
212 args.pcap = None 611 args.pcap = None
213 args.overlay = None 612 args.overlay = None
214 args.dry_run = False 613 args.dry_run = False
614 args.secondary_network = False
215 615
216 qemu = QemuCommand(args) 616 qemu = QemuCommand(args)
217 cmdline = qemu.command_line() 617 cmdline = qemu.command_line()
@@ -220,17 +620,62 @@ def qemu_launch(efi=False, machine=None):
220 sleep(10) 620 sleep(10)
221 return qemu, s 621 return qemu, s
222 622
623
223def qemu_terminate(s): 624def qemu_terminate(s):
224 try: 625 try:
225 s.terminate() 626 s.terminate()
226 except KeyboardInterrupt: 627 except KeyboardInterrupt:
227 pass 628 pass
228 629
630
229def qemu_send_command(port, command): 631def qemu_send_command(port, command):
230 command = ['ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@localhost -p ' + 632 command = ['ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@localhost -p ' +
231 str(port) + ' "' + command + '"'] 633 str(port) + ' "' + command + '"']
232 s2 = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 634 s2 = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
233 stdout, stderr = s2.communicate() 635 stdout, stderr = s2.communicate(timeout=60)
234 return stdout, stderr, s2.returncode 636 return stdout, stderr, s2.returncode
235 637
638
639def akt_native_run(testInst, cmd, **kwargs):
640 # run a command supplied by aktualizr-native and checks that:
641 # - the executable exists
642 # - the command runs without error
643 # NOTE: the base test class must have built aktualizr-native (in
644 # setUpClass, for example)
645 bb_vars = get_bb_vars(['SYSROOT_DESTDIR', 'base_prefix', 'libdir', 'bindir'],
646 'aktualizr-native')
647 sysroot = bb_vars['SYSROOT_DESTDIR'] + bb_vars['base_prefix']
648 sysrootbin = bb_vars['SYSROOT_DESTDIR'] + bb_vars['bindir']
649 libdir = bb_vars['libdir']
650
651 program, *_ = cmd.split(' ')
652 p = '{}/{}'.format(sysrootbin, program)
653 testInst.assertTrue(os.path.isfile(p), msg="No {} found ({})".format(program, p))
654 env = dict(os.environ)
655 env['LD_LIBRARY_PATH'] = libdir
656 result = runCmd(cmd, env=env, native_sysroot=sysroot, ignore_status=True, **kwargs)
657 testInst.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
658
659
660def verifyProvisioned(testInst, machine):
661 # Verify that device HAS provisioned.
662 ran_ok = False
663 for delay in [5, 5, 5, 5, 10]:
664 sleep(delay)
665 stdout, stderr, retcode = testInst.qemu_command('aktualizr-info')
666 if retcode == 0 and stderr == b'' and stdout.decode().find('Fetched metadata: yes') >= 0:
667 ran_ok = True
668 break
669 testInst.assertIn(b'Device ID: ', stdout, 'Provisioning failed: ' + stderr.decode() + stdout.decode())
670 testInst.assertIn(b'Primary ecu hardware ID: ' + machine.encode(), stdout,
671 'Provisioning failed: ' + stderr.decode() + stdout.decode())
672 testInst.assertIn(b'Fetched metadata: yes', stdout, 'Provisioning failed: ' + stderr.decode() + stdout.decode())
673 p = re.compile(r'Device ID: ([a-z0-9-]*)\n')
674 m = p.search(stdout.decode())
675 testInst.assertTrue(m, 'Device ID could not be read: ' + stderr.decode() + stdout.decode())
676 testInst.assertGreater(m.lastindex, 0, 'Device ID could not be read: ' + stderr.decode() + stdout.decode())
677 logger = logging.getLogger("selftest")
678 logger.info('Device successfully provisioned with ID: ' + m.group(1))
679
680
236# vim:set ts=4 sw=4 sts=4 expandtab: 681# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/recipes-sota/aktualizr/aktualizr-auto-prov.bb b/recipes-sota/aktualizr/aktualizr-auto-prov.bb
index 2190512..07e5bb8 100644
--- a/recipes-sota/aktualizr/aktualizr-auto-prov.bb
+++ b/recipes-sota/aktualizr/aktualizr-auto-prov.bb
@@ -35,7 +35,9 @@ do_install() {
35 install -d ${D}${libdir}/sota 35 install -d ${D}${libdir}/sota
36 install -d ${D}${localstatedir}/sota 36 install -d ${D}${localstatedir}/sota
37 if [ -n "${SOTA_PACKED_CREDENTIALS}" ]; then 37 if [ -n "${SOTA_PACKED_CREDENTIALS}" ]; then
38 install -m 0644 ${STAGING_DIR_NATIVE}${libdir}/sota/sota_autoprov.toml ${D}${libdir}/sota/sota.toml 38 aktualizr_toml=${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'secondary-network', 'sota_autoprov_primary.toml', 'sota_autoprov.toml', d)}
39
40 install -m 0644 ${STAGING_DIR_NATIVE}${libdir}/sota/${aktualizr_toml} ${D}${libdir}/sota/sota.toml
39 41
40 # deploy SOTA credentials 42 # deploy SOTA credentials
41 if [ -e ${SOTA_PACKED_CREDENTIALS} ]; then 43 if [ -e ${SOTA_PACKED_CREDENTIALS} ]; then
diff --git a/recipes-sota/aktualizr/aktualizr-ca-implicit-prov.bb b/recipes-sota/aktualizr/aktualizr-ca-implicit-prov.bb
new file mode 100644
index 0000000..51e313d
--- /dev/null
+++ b/recipes-sota/aktualizr/aktualizr-ca-implicit-prov.bb
@@ -0,0 +1,72 @@
1SUMMARY = "Aktualizr configuration for implicit provisioning with CA"
2DESCRIPTION = "Systemd service and configurations for implicitly provisioning Aktualizr using externally provided or generated CA"
3
4# WARNING: it is NOT a production solution. The secure way to provision devices is to create certificate request directly on the device
5# (either with HSM/TPM or with software) and then sign it with a CA stored on a disconnected machine
6
7HOMEPAGE = "https://github.com/advancedtelematic/aktualizr"
8SECTION = "base"
9LICENSE = "MPL-2.0"
10LIC_FILES_CHKSUM = "file://${WORKDIR}/LICENSE;md5=9741c346eef56131163e13b9db1241b3"
11
12DEPENDS = "aktualizr-native openssl-native"
13RDEPENDS_${PN} = "aktualizr"
14
15SRC_URI = " \
16 file://LICENSE \
17 file://ca.cnf \
18 "
19PV = "1.0"
20PR = "1"
21
22require environment.inc
23require credentials.inc
24
25export SOTA_CACERT_PATH
26export SOTA_CAKEY_PATH
27
28do_install() {
29 install -d ${D}${libdir}/sota
30
31 if [ -z "${SOTA_PACKED_CREDENTIALS}" ]; then
32 bberror "SOTA_PACKED_CREDENTIALS are required for implicit provisioning"
33 fi
34
35 if [ -z ${SOTA_CACERT_PATH} ]; then
36 SOTA_CACERT_PATH=${DEPLOY_DIR_IMAGE}/CA/cacert.pem
37 SOTA_CAKEY_PATH=${DEPLOY_DIR_IMAGE}/CA/ca.private.pem
38 mkdir -p ${DEPLOY_DIR_IMAGE}/CA
39 bbwarn "SOTA_CACERT_PATH is not specified, use default one at $SOTA_CACERT_PATH"
40
41 if [ ! -f ${SOTA_CACERT_PATH} ]; then
42 bbwarn "${SOTA_CACERT_PATH} does not exist, generate a new CA"
43 SOTA_CACERT_DIR_PATH="$(dirname "$SOTA_CACERT_PATH")"
44 openssl genrsa -out ${SOTA_CACERT_DIR_PATH}/ca.private.pem 4096
45 openssl req -key ${SOTA_CACERT_DIR_PATH}/ca.private.pem -new -x509 -days 7300 -out ${SOTA_CACERT_PATH} -subj "/C=DE/ST=Berlin/O=Reis und Kichererbsen e.V/commonName=meta-updater" -batch -config ${WORKDIR}/ca.cnf -extensions cacert
46 bbwarn "${SOTA_CACERT_PATH} has been created, you'll need to upload it to the server"
47 fi
48 fi
49
50 if [ -z ${SOTA_CAKEY_PATH} ]; then
51 bberror "SOTA_CAKEY_PATH should be set when using implicit provisioning"
52 fi
53
54 install -d ${D}${libdir}/sota
55 install -d ${D}${localstatedir}/sota
56 install -m 0644 ${STAGING_DIR_NATIVE}${libdir}/sota/sota_implicit_prov_ca.toml ${D}${libdir}/sota/sota.toml
57 aktualizr_cert_provider --credentials ${SOTA_PACKED_CREDENTIALS} \
58 --device-ca ${SOTA_CACERT_PATH} \
59 --device-ca-key ${SOTA_CAKEY_PATH} \
60 --root-ca \
61 --server-url \
62 --local ${D}${localstatedir}/sota \
63 --config ${D}${libdir}/sota/sota.toml
64}
65
66FILES_${PN} = " \
67 ${localstatedir}/sota/* \
68 ${libdir}/sota/sota.toml \
69 ${libdir}/sota/root.crt \
70 "
71
72# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/recipes-sota/aktualizr/aktualizr_git.bb b/recipes-sota/aktualizr/aktualizr_git.bb
index 768ec3d..2a803a8 100644
--- a/recipes-sota/aktualizr/aktualizr_git.bb
+++ b/recipes-sota/aktualizr/aktualizr_git.bb
@@ -6,11 +6,10 @@ LICENSE = "MPL-2.0"
6LIC_FILES_CHKSUM = "file://${S}/LICENSE;md5=9741c346eef56131163e13b9db1241b3" 6LIC_FILES_CHKSUM = "file://${S}/LICENSE;md5=9741c346eef56131163e13b9db1241b3"
7 7
8DEPENDS = "boost curl openssl libarchive libsodium asn1c-native " 8DEPENDS = "boost curl openssl libarchive libsodium asn1c-native "
9DEPENDS_append_class-target = "jansson ostree ${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'hsm', ' libp11', '', d)} " 9DEPENDS_append_class-target = "ostree ${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'hsm', ' libp11', '', d)} "
10DEPENDS_append_class-native = "glib-2.0-native " 10DEPENDS_append_class-native = "glib-2.0-native "
11 11
12RDEPENDS_${PN}_class-target = "lshw " 12RDEPENDS_${PN}_class-target = "lshw "
13RDEPENDS_${PN}_append_class-target = "${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'hsm', ' engine-pkcs11', '', d)} "
14RDEPENDS_${PN}_append_class-target = " ${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'serialcan', ' slcand-start', '', d)} " 13RDEPENDS_${PN}_append_class-target = " ${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'serialcan', ' slcand-start', '', d)} "
15 14
16PV = "1.0+git${SRCPV}" 15PV = "1.0+git${SRCPV}"
@@ -19,9 +18,11 @@ PR = "7"
19SRC_URI = " \ 18SRC_URI = " \
20 gitsm://github.com/advancedtelematic/aktualizr;branch=${BRANCH} \ 19 gitsm://github.com/advancedtelematic/aktualizr;branch=${BRANCH} \
21 file://aktualizr.service \ 20 file://aktualizr.service \
21 file://aktualizr-secondary.service \
22 file://aktualizr-secondary.socket \
22 file://aktualizr-serialcan.service \ 23 file://aktualizr-serialcan.service \
23 " 24 "
24SRCREV = "d861896e7467e3e0cafdd7384ff87c62fe724640" 25SRCREV = "930d8eef6eb584686654601c056d7c9c6fca3048"
25BRANCH ?= "master" 26BRANCH ?= "master"
26 27
27S = "${WORKDIR}/git" 28S = "${WORKDIR}/git"
@@ -29,56 +30,80 @@ S = "${WORKDIR}/git"
29inherit cmake 30inherit cmake
30 31
31inherit systemd 32inherit systemd
33
34SYSTEMD_PACKAGES = "${PN} ${PN}-secondary"
32SYSTEMD_SERVICE_${PN} = "aktualizr.service" 35SYSTEMD_SERVICE_${PN} = "aktualizr.service"
36SYSTEMD_SERVICE_${PN}-secondary = "aktualizr-secondary.socket"
33 37
34BBCLASSEXTEND =+ "native" 38BBCLASSEXTEND =+ "native"
35 39
36EXTRA_OECMAKE = "-DWARNING_AS_ERROR=OFF -DCMAKE_BUILD_TYPE=Release -DAKTUALIZR_VERSION=${PV} " 40EXTRA_OECMAKE = "-DWARNING_AS_ERROR=OFF -DCMAKE_BUILD_TYPE=Release -DAKTUALIZR_VERSION=${PV} "
37EXTRA_OECMAKE_append_class-target = " -DBUILD_OSTREE=ON -DBUILD_ISOTP=ON ${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'hsm', '-DBUILD_P11=ON', '', d)} " 41EXTRA_OECMAKE_append_class-target = " -DBUILD_OSTREE=ON -DBUILD_ISOTP=ON ${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'hsm', '-DBUILD_P11=ON', '', d)} "
38EXTRA_OECMAKE_append_class-native = " -DBUILD_SOTA_TOOLS=ON -DBUILD_OSTREE=OFF " 42EXTRA_OECMAKE_append_class-native = " -DBUILD_SOTA_TOOLS=ON -DBUILD_OSTREE=OFF -DBUILD_SYSTEMD=OFF "
39 43
40do_install_append () { 44do_install_append () {
41 rm -f ${D}${bindir}/aktualizr_cert_provider 45 rm -fr ${D}${libdir}/systemd
46 rm -f ${D}${libdir}/sota/sota.toml # Only needed for the Debian package
47 install -d ${D}${libdir}/sota
48 install -m 0644 ${S}/config/sota_secondary.toml ${D}/${libdir}/sota/sota_secondary.toml
49 install -d ${D}${systemd_unitdir}/system
50 install -m 0644 ${WORKDIR}/aktualizr-secondary.socket ${D}${systemd_unitdir}/system/aktualizr-secondary.socket
51 install -m 0644 ${WORKDIR}/aktualizr-secondary.service ${D}${systemd_unitdir}/system/aktualizr-secondary.service
42} 52}
43do_install_append_class-target () {
44 rm -f ${D}${bindir}/aktualizr_implicit_writer
45 rm -f ${D}${libdir}/sota/sota.toml
46 ${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'secondary-example', '', 'rm -f ${D}${bindir}/example-interface', d)}
47 ${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'secondary-isotp-example', '', 'rm -f ${D}${bindir}/isotp-test-interface', d)}
48 53
54do_install_append_class-target () {
49 install -d ${D}${systemd_unitdir}/system 55 install -d ${D}${systemd_unitdir}/system
50 aktualizr_service=${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'serialcan', '${WORKDIR}/aktualizr-serialcan.service', '${WORKDIR}/aktualizr.service', d)} 56 aktualizr_service=${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'serialcan', '${WORKDIR}/aktualizr-serialcan.service', '${WORKDIR}/aktualizr.service', d)}
51 install -m 0644 ${aktualizr_service} ${D}${systemd_unitdir}/system/aktualizr.service 57 install -m 0644 ${aktualizr_service} ${D}${systemd_unitdir}/system/aktualizr.service
52} 58}
59
53do_install_append_class-native () { 60do_install_append_class-native () {
54 rm -f ${D}${bindir}/aktualizr
55 rm -f ${D}${bindir}/aktualizr-info
56 rm -f ${D}${bindir}/example-interface
57 install -d ${D}${libdir}/sota 61 install -d ${D}${libdir}/sota
58 install -m 0644 ${S}/config/sota_autoprov.toml ${D}/${libdir}/sota/sota_autoprov.toml 62 install -m 0644 ${S}/config/sota_autoprov.toml ${D}/${libdir}/sota/sota_autoprov.toml
63 install -m 0644 ${S}/config/sota_autoprov_primary.toml ${D}/${libdir}/sota/sota_autoprov_primary.toml
59 install -m 0644 ${S}/config/sota_hsm_prov.toml ${D}/${libdir}/sota/sota_hsm_prov.toml 64 install -m 0644 ${S}/config/sota_hsm_prov.toml ${D}/${libdir}/sota/sota_hsm_prov.toml
60 install -m 0644 ${S}/config/sota_implicit_prov.toml ${D}/${libdir}/sota/sota_implicit_prov.toml 65 install -m 0644 ${S}/config/sota_implicit_prov.toml ${D}/${libdir}/sota/sota_implicit_prov.toml
66 install -m 0644 ${S}/config/sota_implicit_prov_ca.toml ${D}/${libdir}/sota/sota_implicit_prov_ca.toml
61 67
62 install -m 0755 ${B}/src/sota_tools/garage-sign-prefix/src/garage-sign/bin/* ${D}${bindir} 68 install -m 0755 ${B}/src/sota_tools/garage-sign-prefix/src/garage-sign/bin/* ${D}${bindir}
63 install -m 0644 ${B}/src/sota_tools/garage-sign-prefix/src/garage-sign/lib/* ${D}${libdir} 69 install -m 0644 ${B}/src/sota_tools/garage-sign-prefix/src/garage-sign/lib/* ${D}${libdir}
64} 70}
65 71
66FILES_${PN}_append = " \ 72PACKAGES =+ " ${PN}-common ${PN}-examples ${PN}-host-tools ${PN}-secondary "
67 ${libdir}/sota \
68 "
69 73
70FILES_${PN}_class-target = " \ 74FILES_${PN} = " \
71 ${bindir}/aktualizr \ 75 ${bindir}/aktualizr \
72 ${bindir}/aktualizr-info \ 76 ${bindir}/aktualizr-info \
77 ${bindir}/aktualizr-check-discovery \
73 ${systemd_unitdir}/system/aktualizr.service \ 78 ${systemd_unitdir}/system/aktualizr.service \
74 " 79 "
75 80
76FILES_${PN}_append_class-target = " ${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'secondary-example', ' ${bindir}/example-interface', '', d)} " 81FILES_${PN}-common = " \
77FILES_${PN}_append_class-target = " ${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'secondary-isotp-example', ' ${bindir}/isotp-test-interface', '', d)} " 82 ${libdir}/sota/schemas \
78FILES_${PN}_class-native = " \ 83 "
84
85FILES_${PN}-examples = " \
86 ${libdir}/sota/demo_secondary.json \
87 ${bindir}/example-interface \
88 ${bindir}/isotp-test-interface \
89 "
90
91FILES_${PN}-host-tools = " \
92 ${bindir}/aktualizr_cert_provider \
79 ${bindir}/aktualizr_implicit_writer \ 93 ${bindir}/aktualizr_implicit_writer \
80 ${bindir}/garage-deploy \ 94 ${bindir}/garage-deploy \
81 ${bindir}/garage-push \ 95 ${bindir}/garage-push \
82 " 96 "
83 97
98FILES_${PN}-secondary = " \
99 ${bindir}/aktualizr-secondary \
100 ${libdir}/sota/sota_secondary.toml \
101 ${systemd_unitdir}/system/aktualizr-secondary.socket \
102 ${systemd_unitdir}/system/aktualizr-secondary.service \
103 "
104
105# Both primary and secondary need the SQL Schemas
106RDEPENDS_${PN}_class-target =+ "${PN}-common"
107RDEPENDS_${PN}-secondary_class-target =+ "${PN}-common"
108
84# vim:set ts=4 sw=4 sts=4 expandtab: 109# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/recipes-sota/aktualizr/environment.inc b/recipes-sota/aktualizr/environment.inc
index cba77e7..09da6b7 100644
--- a/recipes-sota/aktualizr/environment.inc
+++ b/recipes-sota/aktualizr/environment.inc
@@ -3,7 +3,7 @@ export SOTA_VIRTUAL_SECONDARIES
3 3
4do_install_append() { 4do_install_append() {
5 if [ -n "${SOTA_LEGACY_SECONDARY_INTERFACE}" ]; then 5 if [ -n "${SOTA_LEGACY_SECONDARY_INTERFACE}" ]; then
6 AKTUALIZR_PARAMETERS_LEGACYSEC="--legacy-interface ${SOTA_LEGACY_SECONDARY_INTERFACE}"; 6 AKTUALIZR_PARAMETERS_LEGACYSEC="--legacy-interface ${SOTA_LEGACY_SECONDARY_INTERFACE}"
7 fi 7 fi
8 8
9 AKTUALIZR_PARAMETERS_CONFIGFILE="--config /usr/lib/sota/sota.toml" 9 AKTUALIZR_PARAMETERS_CONFIGFILE="--config /usr/lib/sota/sota.toml"
diff --git a/recipes-sota/aktualizr/files/aktualizr-secondary.service b/recipes-sota/aktualizr/files/aktualizr-secondary.service
new file mode 100644
index 0000000..a1e0e1b
--- /dev/null
+++ b/recipes-sota/aktualizr/files/aktualizr-secondary.service
@@ -0,0 +1,9 @@
1[Unit]
2Description=Aktualizr SOTA Client (UPTANE Secondary)
3
4[Service]
5RestartSec=10
6Restart=always
7EnvironmentFile=-/etc/sota/sota.env
8ExecStart=/usr/bin/aktualizr-secondary --config /usr/lib/sota/sota_secondary.toml
9
diff --git a/recipes-sota/aktualizr/files/aktualizr-secondary.socket b/recipes-sota/aktualizr/files/aktualizr-secondary.socket
new file mode 100644
index 0000000..da0ee44
--- /dev/null
+++ b/recipes-sota/aktualizr/files/aktualizr-secondary.socket
@@ -0,0 +1,6 @@
1[Socket]
2ListenStream=9030
3ListenDatagram=9031
4
5[Install]
6WantedBy=sockets.target \ No newline at end of file
diff --git a/recipes-sota/aktualizr/files/aktualizr.service b/recipes-sota/aktualizr/files/aktualizr.service
index b6df9d7..1c2e1df 100644
--- a/recipes-sota/aktualizr/files/aktualizr.service
+++ b/recipes-sota/aktualizr/files/aktualizr.service
@@ -8,6 +8,7 @@ Requires=network-online.target
8RestartSec=10 8RestartSec=10
9Restart=always 9Restart=always
10EnvironmentFile=/usr/lib/sota/sota.env 10EnvironmentFile=/usr/lib/sota/sota.env
11EnvironmentFile=-/etc/sota/sota.env
11ExecStart=/usr/bin/aktualizr $AKTUALIZR_CMDLINE_PARAMETERS 12ExecStart=/usr/bin/aktualizr $AKTUALIZR_CMDLINE_PARAMETERS
12 13
13[Install] 14[Install]
diff --git a/recipes-sota/aktualizr/files/ca.cnf b/recipes-sota/aktualizr/files/ca.cnf
new file mode 100644
index 0000000..352ec38
--- /dev/null
+++ b/recipes-sota/aktualizr/files/ca.cnf
@@ -0,0 +1,10 @@
1[req]
2req_extensions = cacert
3distinguished_name = req_distinguished_name
4
5[req_distinguished_name]
6
7[cacert]
8basicConstraints = critical,CA:true
9keyUsage = keyCertSign
10
diff --git a/recipes-support/libp11/files/0001-Workaround-for-a-buggy-version-of-openssl-1.0.2m.patch b/recipes-support/libp11/files/0001-Workaround-for-a-buggy-version-of-openssl-1.0.2m.patch
index 0538eff..bd233ee 100644
--- a/recipes-support/libp11/files/0001-Workaround-for-a-buggy-version-of-openssl-1.0.2m.patch
+++ b/recipes-support/libp11/files/0001-Workaround-for-a-buggy-version-of-openssl-1.0.2m.patch
@@ -17,7 +17,7 @@ index 45d5ad3..75625e6 100644
17 17
18-#if OPENSSL_VERSION_NUMBER < 0x100020d0L || defined(LIBRESSL_VERSION_NUMBER) 18-#if OPENSSL_VERSION_NUMBER < 0x100020d0L || defined(LIBRESSL_VERSION_NUMBER)
19-static void EVP_PKEY_meth_get_sign(EVP_PKEY_METHOD *pmeth, 19-static void EVP_PKEY_meth_get_sign(EVP_PKEY_METHOD *pmeth,
20+#if OPENSSL_VERSION_NUMBER <= 0x100020e0L || defined(LIBRESSL_VERSION_NUMBER) 20+#if OPENSSL_VERSION_NUMBER < 0x100020f0L || defined(LIBRESSL_VERSION_NUMBER)
21+ 21+
22+# if (OPENSSL_VERSION_NUMBER & 0xFFFFFFF0) == 0x100020d0L 22+# if (OPENSSL_VERSION_NUMBER & 0xFFFFFFF0) == 0x100020d0L
23+# undef EVP_PKEY_meth_get_sign 23+# undef EVP_PKEY_meth_get_sign
diff --git a/recipes-support/libp11/libp11_0.4.7.bb b/recipes-support/libp11/libp11_0.4.7.bb
index 7a93102..02d9e50 100644
--- a/recipes-support/libp11/libp11_0.4.7.bb
+++ b/recipes-support/libp11/libp11_0.4.7.bb
@@ -7,6 +7,7 @@ SECTION = "Development/Libraries"
7LICENSE = "LGPLv2+" 7LICENSE = "LGPLv2+"
8LIC_FILES_CHKSUM = "file://COPYING;md5=fad9b3332be894bab9bc501572864b29" 8LIC_FILES_CHKSUM = "file://COPYING;md5=fad9b3332be894bab9bc501572864b29"
9DEPENDS = "libtool openssl" 9DEPENDS = "libtool openssl"
10RDEPENDS_${PN} += " opensc"
10 11
11SRC_URI = "git://github.com/OpenSC/libp11.git \ 12SRC_URI = "git://github.com/OpenSC/libp11.git \
12 file://0001-Workaround-for-a-buggy-version-of-openssl-1.0.2m.patch" 13 file://0001-Workaround-for-a-buggy-version-of-openssl-1.0.2m.patch"
diff --git a/recipes-test/demo-network-config/files/25-dhcp-server.network b/recipes-test/demo-network-config/files/25-dhcp-server.network
new file mode 100644
index 0000000..4766f9a
--- /dev/null
+++ b/recipes-test/demo-network-config/files/25-dhcp-server.network
@@ -0,0 +1,12 @@
1[Match]
2Name=enp0s4
3
4[Network]
5Description=Private internal network between aktualizr Primary and Secondary nodes
6DHCPServer=yes
7Address=10.0.3.1/24
8IPForward=yes
9IPMasquerade=yes
10
11[DHCPServer]
12PoolOffset=10 \ No newline at end of file
diff --git a/recipes-test/demo-network-config/files/26-dhcp-client.network b/recipes-test/demo-network-config/files/26-dhcp-client.network
new file mode 100644
index 0000000..319664f
--- /dev/null
+++ b/recipes-test/demo-network-config/files/26-dhcp-client.network
@@ -0,0 +1,6 @@
1[Match]
2Name=enp0s4
3
4[Network]
5Description=Private internal network between aktualizr Primary and Secondary nodes
6DHCP=yes
diff --git a/recipes-test/demo-network-config/files/27-dhcp-client-external.network b/recipes-test/demo-network-config/files/27-dhcp-client-external.network
new file mode 100644
index 0000000..ba49593
--- /dev/null
+++ b/recipes-test/demo-network-config/files/27-dhcp-client-external.network
@@ -0,0 +1,6 @@
1[Match]
2Name=enp0s3
3
4[Network]
5Description=External network for secondary
6DHCP=yes
diff --git a/recipes-test/demo-network-config/primary-network-config.bb b/recipes-test/demo-network-config/primary-network-config.bb
new file mode 100644
index 0000000..78678a2
--- /dev/null
+++ b/recipes-test/demo-network-config/primary-network-config.bb
@@ -0,0 +1,16 @@
1DESCRIPTION = "Sample network configuration for an Uptane Primary"
2LICENSE = "CLOSED"
3
4inherit allarch
5
6SRC_URI = "file://25-dhcp-server.network"
7
8
9FILES_${PN} = "/usr/lib/systemd/network"
10
11PR = "1"
12
13do_install() {
14 install -d ${D}/usr/lib/systemd/network
15 install -m 0644 ${WORKDIR}/25-dhcp-server.network ${D}/usr/lib/systemd/network/
16}
diff --git a/recipes-test/demo-network-config/secondary-network-config.bb b/recipes-test/demo-network-config/secondary-network-config.bb
new file mode 100644
index 0000000..9091c65
--- /dev/null
+++ b/recipes-test/demo-network-config/secondary-network-config.bb
@@ -0,0 +1,20 @@
1DESCRIPTION = "Sample network configuration for an Uptane Secondary"
2LICENSE = "CLOSED"
3
4inherit allarch
5
6SRC_URI = "\
7 file://26-dhcp-client.network \
8 file://27-dhcp-client-external.network \
9 "
10
11
12FILES_${PN} = "/usr/lib/systemd/network"
13
14PR = "1"
15
16do_install() {
17 install -d ${D}/usr/lib/systemd/network
18 install -m 0644 ${WORKDIR}/26-dhcp-client.network ${D}/usr/lib/systemd/network/
19 install -m 0644 ${WORKDIR}/27-dhcp-client-external.network ${D}/usr/lib/systemd/network/
20}
diff --git a/recipes-test/images/primary-image.bb b/recipes-test/images/primary-image.bb
new file mode 100644
index 0000000..6d2df94
--- /dev/null
+++ b/recipes-test/images/primary-image.bb
@@ -0,0 +1,14 @@
1include recipes-core/images/core-image-minimal.bb
2
3SUMMARY = "A minimal Uptane Primary image running aktualizr, for testing with a Linux secondary"
4
5LICENSE = "MIT"
6
7IMAGE_INSTALL_remove = " \
8 "
9
10IMAGE_INSTALL_append = " \
11 primary-network-config \
12 "
13
14# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/recipes-test/images/secondary-image.bb b/recipes-test/images/secondary-image.bb
new file mode 100644
index 0000000..9adbdc5
--- /dev/null
+++ b/recipes-test/images/secondary-image.bb
@@ -0,0 +1,25 @@
1include recipes-core/images/core-image-minimal.bb
2
3SUMMARY = "A minimal Uptane Secondary image running aktualizr-secondary"
4
5LICENSE = "MIT"
6
7
8# Remove default aktualizr primary, and the provisioning configuration (which
9# RDEPENDS on aktualizr)
10IMAGE_INSTALL_remove = " \
11 aktualizr \
12 aktualizr-auto-prov \
13 aktualizr-ca-implicit-prov \
14 aktualizr-hsm-prov \
15 aktualizr-implicit-prov \
16 connman \
17 connman-client \
18 "
19
20IMAGE_INSTALL_append = " \
21 aktualizr-secondary \
22 secondary-network-config \
23 "
24
25# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/scripts/qemucommand.py b/scripts/qemucommand.py
index 6b1106d..4918314 100644
--- a/scripts/qemucommand.py
+++ b/scripts/qemucommand.py
@@ -81,6 +81,7 @@ class QemuCommand(object):
81 self.gdb = args.gdb 81 self.gdb = args.gdb
82 self.pcap = args.pcap 82 self.pcap = args.pcap
83 self.overlay = args.overlay 83 self.overlay = args.overlay
84 self.secondary_network = args.secondary_network
84 85
85 def command_line(self): 86 def command_line(self):
86 netuser = 'user,hostfwd=tcp:0.0.0.0:%d-:22,restrict=off' % self.ssh_port 87 netuser = 'user,hostfwd=tcp:0.0.0.0:%d-:22,restrict=off' % self.ssh_port
@@ -104,6 +105,11 @@ class QemuCommand(object):
104 ] 105 ]
105 if self.pcap: 106 if self.pcap:
106 cmdline += ['-net', 'dump,file=' + self.pcap] 107 cmdline += ['-net', 'dump,file=' + self.pcap]
108 if self.secondary_network:
109 cmdline += [
110 '-net', 'nic,vlan=1,macaddr='+random_mac(),
111 '-net', 'socket,vlan=1,mcast=230.0.0.1:1234,localaddr=127.0.0.1',
112 ]
107 if self.gui: 113 if self.gui:
108 cmdline += ["-serial", "stdio"] 114 cmdline += ["-serial", "stdio"]
109 else: 115 else:
diff --git a/scripts/run-qemu-ota b/scripts/run-qemu-ota
index 56e4fbc..b2f55e9 100755
--- a/scripts/run-qemu-ota
+++ b/scripts/run-qemu-ota
@@ -33,6 +33,9 @@ def main():
33 help='Use an overlay storage image file. Will be created if it does not exist. ' + 33 help='Use an overlay storage image file. Will be created if it does not exist. ' +
34 'This option lets you have a persistent image without modifying the underlying image ' + 34 'This option lets you have a persistent image without modifying the underlying image ' +
35 'file, permitting multiple different persistent machines.') 35 'file, permitting multiple different persistent machines.')
36 parser.add_argument('--secondary-network', action='store_true', dest='secondary_network',
37 help='Give the image a second network card connected to a virtual network. ' +
38 'This can be used to test Uptane Primary/Secondary communication.')
36 parser.add_argument('-n', '--dry-run', help='Print qemu command line rather then run it', action='store_true') 39 parser.add_argument('-n', '--dry-run', help='Print qemu command line rather then run it', action='store_true')
37 args = parser.parse_args() 40 args = parser.parse_args()
38 try: 41 try: