Merge pull request #2619 from balena-io/alexgg/sb

os configure, config generate: Add '--secureBoot' option to opt-in secure boot
This commit is contained in:
flowzone-app[bot] 2023-05-19 18:11:02 +00:00 committed by GitHub
commit 9a8b0b4a0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 160 additions and 23 deletions

View File

@ -23,7 +23,7 @@ jobs:
)
secrets: inherit
with:
custom_runs_on: '[["self-hosted","Linux","distro:focal","X64"],["self-hosted","Linux","distro:focal","ARM64"],["macos-11"],["windows-2019"]]'
custom_runs_on: '[["self-hosted","Linux","distro:focal","X64"],["self-hosted","Linux","distro:focal","ARM64"],["macos-12"],["windows-2019"]]'
repo_config: true
repo_description: "The official balena CLI tool."
github_prerelease: true

View File

@ -2160,6 +2160,9 @@ confuse the balenaOS "development mode" with a device's "local mode", the latter
being a supervisor feature that allows the "balena push" command to push a user's
application directly to a device in the local network.
The '--secureBoot' option is used to configure a balenaOS installer image to opt-in
secure boot and disk encryption.
The --system-connection (-c) option is used to inject NetworkManager connection
profiles for additional network interfaces, such as cellular/GSM or additional
WiFi or ethernet connections. This option may be passed multiple times in case there
@ -2230,6 +2233,10 @@ WiFi SSID (network name) (non-interactive configuration)
Configure balenaOS to operate in development mode
#### --secureBoot
Configure balenaOS installer to opt-in secure boot and disk encryption
#### -d, --device DEVICE
device UUID
@ -2314,6 +2321,9 @@ confuse the balenaOS "development mode" with a device's "local mode", the latter
being a supervisor feature that allows the "balena push" command to push a user's
application directly to a device in the local network.
The '--secureBoot' option is used to configure a balenaOS installer image to opt-in
secure boot and disk encryption.
To configure an image for a fleet of mixed device types, use the --fleet option
alongside the --deviceType option to specify the target device type.
@ -2337,6 +2347,7 @@ Examples:
$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>
$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json
$ balena config generate --fleet myorg/fleet --version 2.12.7 --dev
$ balena config generate --fleet myorg/fleet --version 2.12.7 --secureBoot
$ balena config generate --fleet myorg/fleet --version 2.12.7 --deviceType fincm3
$ balena config generate --fleet myorg/fleet --version 2.12.7 --output config.json
$ balena config generate --fleet myorg/fleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15
@ -2355,6 +2366,10 @@ fleet name or slug (preferred)
Configure balenaOS to operate in development mode
#### --secureBoot
Configure balenaOS installer to opt-in secure boot and disk encryption
#### -d, --device DEVICE
device UUID

View File

@ -19,13 +19,18 @@ import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
import { applicationIdInfo, devModeInfo } from '../../utils/messages';
import {
applicationIdInfo,
devModeInfo,
secureBootInfo,
} from '../../utils/messages';
import type { PineDeferred } from 'balena-sdk';
interface FlagsDef {
version: string; // OS version
fleet?: string;
dev?: boolean; // balenaOS development variant
secureBoot?: boolean;
device?: string;
deviceApiKey?: string;
deviceType?: string;
@ -51,6 +56,8 @@ export default class ConfigGenerateCmd extends Command {
${devModeInfo.split('\n').join('\n\t\t')}
${secureBootInfo.split('\n').join('\n\t\t')}
To configure an image for a fleet of mixed device types, use the --fleet option
alongside the --deviceType option to specify the target device type.
@ -66,6 +73,7 @@ export default class ConfigGenerateCmd extends Command {
'$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>',
'$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --dev',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --secureBoot',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --deviceType fincm3',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --output config.json',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15',
@ -80,6 +88,7 @@ export default class ConfigGenerateCmd extends Command {
}),
fleet: { ...cf.fleet, exclusive: ['device'] },
dev: cf.dev,
secureBoot: cf.secureBoot,
device: {
...cf.device,
exclusive: [
@ -195,6 +204,15 @@ export default class ConfigGenerateCmd extends Command {
deviceType,
);
const { validateSecureBootOptionAndWarn } = await import(
'../../utils/config'
);
await validateSecureBootOptionAndWarn(
options.secureBoot,
deviceType,
options.version,
);
// Prompt for values
// Pass params as an override: if there is any param with exactly the same name as a
// required option, that value is used (and the corresponding question is not asked)
@ -203,6 +221,7 @@ export default class ConfigGenerateCmd extends Command {
});
answers.version = options.version;
answers.developmentMode = options.dev;
answers.secureBoot = options.secureBoot;
answers.provisioningKeyName = options['provisioning-key-name'];
answers.provisioningKeyExpiryDate = options['provisioning-key-expiry-date'];

View File

@ -23,7 +23,11 @@ import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { applicationIdInfo, devModeInfo } from '../../utils/messages';
import {
applicationIdInfo,
devModeInfo,
secureBootInfo,
} from '../../utils/messages';
const CONNECTIONS_FOLDER = '/system-connections';
@ -36,6 +40,7 @@ interface FlagsDef {
'config-wifi-key'?: string;
'config-wifi-ssid'?: string;
dev?: boolean; // balenaOS development variant
secureBoot?: boolean;
device?: string; // device UUID
'device-type'?: string;
help?: void;
@ -53,6 +58,7 @@ interface ArgsDef {
interface Answers {
appUpdatePollInterval: number; // in minutes
developmentMode?: boolean; // balenaOS development variant
secureBoot?: boolean;
deviceType: string; // e.g. "raspberrypi3"
network: 'ethernet' | 'wifi';
version: string; // e.g. "2.32.0+rev1"
@ -80,6 +86,8 @@ export default class OsConfigureCmd extends Command {
${devModeInfo.split('\n').join('\n\t\t')}
${secureBootInfo.split('\n').join('\n\t\t')}
The --system-connection (-c) option is used to inject NetworkManager connection
profiles for additional network interfaces, such as cellular/GSM or additional
WiFi or ethernet connections. This option may be passed multiple times in case there
@ -140,6 +148,7 @@ export default class OsConfigureCmd extends Command {
description: 'WiFi SSID (network name) (non-interactive configuration)',
}),
dev: cf.dev,
secureBoot: cf.secureBoot,
device: {
...cf.device,
exclusive: [
@ -238,6 +247,15 @@ export default class OsConfigureCmd extends Command {
const { validateDevOptionAndWarn } = await import('../../utils/config');
await validateDevOptionAndWarn(options.dev, osVersion);
const { validateSecureBootOptionAndWarn } = await import(
'../../utils/config'
);
await validateSecureBootOptionAndWarn(
options.secureBoot,
deviceTypeSlug,
osVersion,
);
const answers: Answers = await askQuestionsForDeviceType(
deviceTypeManifest,
options,
@ -248,6 +266,7 @@ export default class OsConfigureCmd extends Command {
}
answers.version = osVersion;
answers.developmentMode = options.dev;
answers.secureBoot = options.secureBoot;
answers.provisioningKeyName = options['provisioning-key-name'];
answers.provisioningKeyExpiryDate = options['provisioning-key-expiry-date'];

View File

@ -74,6 +74,12 @@ export const dev: IBooleanFlag<boolean> = flags.boolean({
default: false,
});
export const secureBoot: IBooleanFlag<boolean> = flags.boolean({
description:
'Configure balenaOS installer to opt-in secure boot and disk encryption',
default: false,
});
export const drive = flags.string({
char: 'd',
description: stripIndent`

View File

@ -52,6 +52,10 @@ export interface ImgConfig {
os?: {
sshKeys?: string[];
};
installer?: {
secureboot?: boolean;
};
}
export async function generateApplicationConfig(
@ -63,6 +67,7 @@ export async function generateApplicationConfig(
os?: {
sshKeys?: string[];
};
secureBoot?: boolean;
},
): Promise<ImgConfig> {
options = {
@ -84,6 +89,12 @@ export async function generateApplicationConfig(
: options.os.sshKeys;
}
// configure installer secure boot opt-in if specified
if (options.secureBoot) {
config.installer ??= {};
config.installer.secureboot = options.secureBoot;
}
return config;
}
@ -165,3 +176,62 @@ export async function validateDevOptionAndWarn(
and exposes network ports such as 2375 that allows unencrypted access to balenaEngine.
Therefore, development mode should only be used in private, trusted local networks.`);
}
/**
* Chech whether the `--secureBoot` option of commands related to OS configuration
* such as `os configure` and `config generate` is compatible with a given
* OS release, and print a warning regarding the consequences of using that
* option.
*/
export async function validateSecureBootOptionAndWarn(
secureBoot?: boolean,
slug?: string,
version?: string,
logger?: import('./logger'),
) {
if (!secureBoot) {
return;
}
const { ExpectedError } = await import('../errors');
if (!version) {
throw new ExpectedError(`Error: No version provided`);
}
if (!slug) {
throw new ExpectedError(`Error: No device type provided`);
}
const sdk = getBalenaSdk();
const releasePineOpts = {
$select: 'contract',
$filter: { raw_version: `${version.replace(/^v/, '')}` },
} satisfies BalenaSdk.PineOptions<BalenaSdk.Release>;
// TODO: Remove the added type casting when the SDK returns the fully typed result
const [osRelease] = (await sdk.models.os.getAllOsVersions(
slug,
releasePineOpts,
)) as Array<
BalenaSdk.OsVersion &
BalenaSdk.PineTypedResult<BalenaSdk.Release, typeof releasePineOpts>
>;
if (!osRelease) {
throw new ExpectedError(`Error: No ${version} release for ${slug}`);
}
const contract = osRelease.contract ? JSON.parse(osRelease.contract) : null;
if (
contract?.provides.some((entry: Dictionary<string>) => {
return entry.type === 'sw.feature' && entry.slug === 'secureboot';
})
) {
if (!logger) {
const Logger = await import('./logger');
logger = Logger.getLogger();
}
logger.logInfo(stripIndent`
The '--secureBoot' option is being used to configure a balenaOS installer image
into secure boot and full disk encryption.`);
} else {
throw new ExpectedError(
`Error: The '--secureBoot' option is not supported for ${slug} in ${version}`,
);
}
}

View File

@ -169,6 +169,10 @@ confuse the balenaOS "development mode" with a device's "local mode", the latter
being a supervisor feature that allows the "balena push" command to push a user's
application directly to a device in the local network.`;
export const secureBootInfo = `\
The '--secureBoot' option is used to configure a balenaOS installer image to opt-in
secure boot and disk encryption.`;
export const jsonInfo = `\
The --json option is recommended when scripting the output of this command,
because field names are less likely to change in JSON format and because it

42
npm-shrinkwrap.json generated
View File

@ -24,7 +24,7 @@
"balena-image-fs": "^7.0.6",
"balena-image-manager": "^8.0.0",
"balena-preload": "^13.0.0",
"balena-sdk": "^16.40.0",
"balena-sdk": "^16.44.2",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.7",
"balena-settings-storage": "^7.0.0",
@ -3048,9 +3048,9 @@
}
},
"node_modules/@types/node": {
"version": "16.18.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.30.tgz",
"integrity": "sha512-Kmp/wBZk19Dn7uRiol8kF8agnf8m0+TU9qIwyfPmXglVxMlmiIz0VQSMw5oFgwhmD2aKTlfBIO5FtsVj3y7hKQ=="
"version": "16.18.31",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.31.tgz",
"integrity": "sha512-KPXltf4z4g517OlVJO9XQ2357CYw7fvuJ3ZuBynjXC5Jos9i+K7LvFb7bUIwtJXSZj0vTp9Q6NJBSQpkwwO8Zw=="
},
"node_modules/@types/node-cleanup": {
"version": "2.1.2",
@ -4148,9 +4148,9 @@
}
},
"node_modules/balena-sdk": {
"version": "16.40.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-16.40.0.tgz",
"integrity": "sha512-w7oP/oESZnymWtngieQrR3YJAR+CLSnys5RllPZBtZ5vYZsSHdrdRRMamlemy5ydCQN4q66Uw95U20bBXigT5g==",
"version": "16.45.1",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-16.45.1.tgz",
"integrity": "sha512-pY3anTkmEcOp8GEgjkeJl8aj7SZBWifh4DSp8sUnryE0U7IGA3UcKggsfMjYsP1ZKvPlAvweNHQuUujfBOHbXg==",
"dependencies": {
"@balena/es-version": "^1.0.0",
"@types/json-schema": "^7.0.9",
@ -4527,6 +4527,7 @@
"node_modules/bonjour": {
"version": "3.5.0",
"resolved": "git+ssh://git@github.com/balena-io-modules/bonjour.git#e018851dc823b4b3f670f658f71d0c1c7f3e637c",
"integrity": "sha512-JCw9ygNWGhAngIABMPiBlBlyPYVDFwllFa0Xuory9yatGday0pmSBRJhYMXqIJ4vmqDkPVqTD5JV+YM8ypLRKg==",
"license": "MIT",
"dependencies": {
"array-flatten": "^2.1.0",
@ -13488,6 +13489,7 @@
"node_modules/multicast-dns": {
"version": "6.1.1",
"resolved": "git+ssh://git@github.com/resin-io-modules/multicast-dns.git#db98d68b79bbefc936b6799de9de1038ba49f85d",
"integrity": "sha512-AYHxmNN71KOfxHElYREhx8LqjIpfc3WFhq+Oht9IOEk82jXXF9qRavowyUvc9JLkPjUzLZmjy14/a1NtBduQoQ==",
"license": "MIT",
"dependencies": {
"dns-packet": "^1.0.1",
@ -19435,9 +19437,9 @@
}
},
"node_modules/tslib": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
"integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA=="
},
"node_modules/tslint": {
"version": "6.1.3",
@ -24890,9 +24892,9 @@
}
},
"@types/node": {
"version": "16.18.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.30.tgz",
"integrity": "sha512-Kmp/wBZk19Dn7uRiol8kF8agnf8m0+TU9qIwyfPmXglVxMlmiIz0VQSMw5oFgwhmD2aKTlfBIO5FtsVj3y7hKQ=="
"version": "16.18.31",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.31.tgz",
"integrity": "sha512-KPXltf4z4g517OlVJO9XQ2357CYw7fvuJ3ZuBynjXC5Jos9i+K7LvFb7bUIwtJXSZj0vTp9Q6NJBSQpkwwO8Zw=="
},
"@types/node-cleanup": {
"version": "2.1.2",
@ -25814,9 +25816,9 @@
}
},
"balena-sdk": {
"version": "16.40.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-16.40.0.tgz",
"integrity": "sha512-w7oP/oESZnymWtngieQrR3YJAR+CLSnys5RllPZBtZ5vYZsSHdrdRRMamlemy5ydCQN4q66Uw95U20bBXigT5g==",
"version": "16.45.1",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-16.45.1.tgz",
"integrity": "sha512-pY3anTkmEcOp8GEgjkeJl8aj7SZBWifh4DSp8sUnryE0U7IGA3UcKggsfMjYsP1ZKvPlAvweNHQuUujfBOHbXg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/json-schema": "^7.0.9",
@ -26149,6 +26151,7 @@
},
"bonjour": {
"version": "git+ssh://git@github.com/balena-io-modules/bonjour.git#e018851dc823b4b3f670f658f71d0c1c7f3e637c",
"integrity": "sha512-JCw9ygNWGhAngIABMPiBlBlyPYVDFwllFa0Xuory9yatGday0pmSBRJhYMXqIJ4vmqDkPVqTD5JV+YM8ypLRKg==",
"from": "bonjour@git+https://github.com/balena-io-modules/bonjour.git#fixed-mdns",
"requires": {
"array-flatten": "^2.1.0",
@ -33190,6 +33193,7 @@
},
"multicast-dns": {
"version": "git+ssh://git@github.com/resin-io-modules/multicast-dns.git#db98d68b79bbefc936b6799de9de1038ba49f85d",
"integrity": "sha512-AYHxmNN71KOfxHElYREhx8LqjIpfc3WFhq+Oht9IOEk82jXXF9qRavowyUvc9JLkPjUzLZmjy14/a1NtBduQoQ==",
"from": "multicast-dns@git+https://github.com/resin-io-modules/multicast-dns#listen-on-all-interfaces",
"requires": {
"dns-packet": "^1.0.1",
@ -37863,9 +37867,9 @@
}
},
"tslib": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
"integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA=="
},
"tslint": {
"version": "6.1.3",

View File

@ -207,7 +207,7 @@
"balena-image-fs": "^7.0.6",
"balena-image-manager": "^8.0.0",
"balena-preload": "^13.0.0",
"balena-sdk": "^16.40.0",
"balena-sdk": "^16.44.2",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.7",
"balena-settings-storage": "^7.0.0",