Skip to content

Commit 88cc0b0

Browse files
authored
feat: add AppArmor profile to FPM targets to pair with afterInstall and afterRemove template scripts (#8636)
1 parent fb26f6a commit 88cc0b0

File tree

9 files changed

+135
-11
lines changed

9 files changed

+135
-11
lines changed

.changeset/mighty-forks-pump.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"app-builder-lib": minor
3+
---
4+
5+
feat: add support for AppArmor with template profile and configuration property

packages/app-builder-lib/scheme.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,12 +519,21 @@
519519
"additionalProperties": false,
520520
"properties": {
521521
"afterInstall": {
522+
"description": "File path to script to be passed to FPM for `--after-install` arg.",
522523
"type": [
523524
"null",
524525
"string"
525526
]
526527
},
527528
"afterRemove": {
529+
"description": "File path to script to be passed to FPM for `--after-remove` arg.",
530+
"type": [
531+
"null",
532+
"string"
533+
]
534+
},
535+
"appArmorProfile": {
536+
"description": "File path to custom AppArmor profile (Ubuntu 24+)",
528537
"type": [
529538
"null",
530539
"string"
@@ -2050,12 +2059,21 @@
20502059
"additionalProperties": false,
20512060
"properties": {
20522061
"afterInstall": {
2062+
"description": "File path to script to be passed to FPM for `--after-install` arg.",
20532063
"type": [
20542064
"null",
20552065
"string"
20562066
]
20572067
},
20582068
"afterRemove": {
2069+
"description": "File path to script to be passed to FPM for `--after-remove` arg.",
2070+
"type": [
2071+
"null",
2072+
"string"
2073+
]
2074+
},
2075+
"appArmorProfile": {
2076+
"description": "File path to custom AppArmor profile (Ubuntu 24+)",
20592077
"type": [
20602078
"null",
20612079
"string"

packages/app-builder-lib/src/options/linuxOptions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,18 @@ export interface LinuxTargetSpecificOptions extends CommonLinuxOptions, TargetSp
122122
readonly vendor?: string | null
123123
readonly maintainer?: string | null
124124

125+
/**
126+
* File path to script to be passed to FPM for `--after-install` arg.
127+
*/
125128
readonly afterInstall?: string | null
129+
/**
130+
* File path to script to be passed to FPM for `--after-remove` arg.
131+
*/
126132
readonly afterRemove?: string | null
133+
/**
134+
* File path to custom AppArmor profile (Ubuntu 24+)
135+
*/
136+
readonly appArmorProfile?: string | null
127137

128138
/**
129139
* *Advanced only* The [fpm](https://fpm.readthedocs.io/en/latest/cli-reference.html) options.

packages/app-builder-lib/src/targets/FpmTarget.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Arch, executeAppBuilder, getArchSuffix, log, TmpDir, toLinuxArchString, use, serializeToYaml, asArray } from "builder-util"
22
import { unlinkIfExists } from "builder-util"
3-
import { outputFile, stat } from "fs-extra"
3+
import { copyFile, outputFile, stat } from "fs-extra"
44
import { mkdir, readFile } from "fs/promises"
55
import * as path from "path"
66
import { smarten } from "../appInfo"
@@ -26,10 +26,16 @@ interface FpmOptions {
2626
url: string
2727
}
2828

29+
interface ScriptFiles {
30+
afterRemove: string
31+
afterInstall: string
32+
appArmor: string
33+
}
34+
2935
export default class FpmTarget extends Target {
3036
readonly options: LinuxTargetSpecificOptions = { ...this.packager.platformSpecificBuildOptions, ...(this.packager.config as any)[this.name] }
3137

32-
private readonly scriptFiles: Promise<Array<string>>
38+
private readonly scriptFiles: Promise<ScriptFiles>
3339

3440
constructor(
3541
name: string,
@@ -42,7 +48,7 @@ export default class FpmTarget extends Target {
4248
this.scriptFiles = this.createScripts()
4349
}
4450

45-
private async createScripts(): Promise<Array<string>> {
51+
private async createScripts(): Promise<ScriptFiles> {
4652
const defaultTemplatesDir = getTemplatePath("linux")
4753

4854
const packager = this.packager
@@ -61,10 +67,11 @@ export default class FpmTarget extends Target {
6167
return path.resolve(packager.projectDir, value)
6268
}
6369

64-
return await Promise.all<string>([
65-
writeConfigFile(packager.info.tempDirManager, getResource(this.options.afterInstall, "after-install.tpl"), templateOptions),
66-
writeConfigFile(packager.info.tempDirManager, getResource(this.options.afterRemove, "after-remove.tpl"), templateOptions),
67-
])
70+
return {
71+
afterInstall: await writeConfigFile(packager.info.tempDirManager, getResource(this.options.afterInstall, "after-install.tpl"), templateOptions),
72+
afterRemove: await writeConfigFile(packager.info.tempDirManager, getResource(this.options.afterRemove, "after-remove.tpl"), templateOptions),
73+
appArmor: await writeConfigFile(packager.info.tempDirManager, getResource(this.options.appArmorProfile, "apparmor-profile.tpl"), templateOptions),
74+
}
6875
}
6976

7077
checkOptions(): Promise<any> {
@@ -130,30 +137,35 @@ export default class FpmTarget extends Target {
130137
if (packager.packagerOptions.prepackaged != null) {
131138
await mkdir(this.outDir, { recursive: true })
132139
}
140+
const linuxDistType = packager.packagerOptions.prepackaged || path.join(this.outDir, `linux${getArchSuffix(arch)}-unpacked`)
141+
const resourceDir = packager.getResourcesDir(linuxDistType)
133142

134143
const publishConfig = this.supportsAutoUpdate(target)
135144
? await getAppUpdatePublishConfiguration(packager, arch, false /* in any case validation will be done on publish */)
136145
: null
137146
if (publishConfig != null) {
138-
const linuxDistType = this.packager.packagerOptions.prepackaged || path.join(this.outDir, `linux${getArchSuffix(arch)}-unpacked`)
139-
const resourceDir = packager.getResourcesDir(linuxDistType)
140147
log.info({ resourceDir: log.filePath(resourceDir) }, `adding autoupdate files for: ${target}. (Beta feature)`)
141148
await outputFile(path.join(resourceDir, "app-update.yml"), serializeToYaml(publishConfig))
142149
// Extra file needed for auto-updater to detect installation method
143150
await outputFile(path.join(resourceDir, "package-type"), target)
144151
}
145152

146153
const scripts = await this.scriptFiles
154+
155+
// Install AppArmor support for ubuntu 24+
156+
// https://github.com/electron-userland/electron-builder/issues/8635
157+
await copyFile(scripts.appArmor, path.join(resourceDir, "apparmor-profile"))
158+
147159
const appInfo = packager.appInfo
148160
const options = this.options
149161
const synopsis = options.synopsis
150162
const args = [
151163
"--architecture",
152164
toLinuxArchString(arch, target),
153165
"--after-install",
154-
scripts[0],
166+
scripts.afterInstall,
155167
"--after-remove",
156-
scripts[1],
168+
scripts.afterRemove,
157169
"--description",
158170
smarten(target === "rpm" ? this.helper.getDescription(options) : `${synopsis || ""}\n ${this.helper.getDescription(options)}`),
159171
"--version",

packages/app-builder-lib/templates/linux/after-install.tpl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,30 @@ fi
2525
if hash update-desktop-database 2>/dev/null; then
2626
update-desktop-database /usr/share/applications || true
2727
fi
28+
29+
# Install apparmor profile. (Ubuntu 24+)
30+
# First check if the version of AppArmor running on the device supports our profile.
31+
# This is in order to keep backwards compatibility with Ubuntu 22.04 which does not support abi/4.0.
32+
# In that case, we just skip installing the profile since the app runs fine without it on 22.04.
33+
#
34+
# Those apparmor_parser flags are akin to performing a dry run of loading a profile.
35+
# https://wiki.debian.org/AppArmor/HowToUse#Dumping_profiles
36+
#
37+
# Unfortunately, at the moment AppArmor doesn't have a good story for backwards compatibility.
38+
# https://askubuntu.com/questions/1517272/writing-a-backwards-compatible-apparmor-profile
39+
APPARMOR_PROFILE_SOURCE='/opt/${sanitizedProductName}/resources/apparmor-profile'
40+
APPARMOR_PROFILE_TARGET='/etc/apparmor.d/${executable}'
41+
if test -d "/etc/apparmor.d"; then
42+
if apparmor_parser --skip-kernel-load --debug "$APPARMOR_PROFILE_SOURCE" > /dev/null 2>&1; then
43+
cp -f "$APPARMOR_PROFILE_SOURCE" "$APPARMOR_PROFILE_TARGET"
44+
45+
if hash apparmor_parser 2>/dev/null; then
46+
# Extra flags taken from dh_apparmor:
47+
# > By using '-W -T' we ensure that any abstraction updates are also pulled in.
48+
# https://wiki.debian.org/AppArmor/Contribute/FirstTimeProfileImport
49+
apparmor_parser --replace --write-cache --skip-read-cache "$APPARMOR_PROFILE_TARGET"
50+
fi
51+
else
52+
echo "Skipping the installation of the AppArmor profile as this version of AppArmor does not seem to support the bundled profile"
53+
fi
54+
fi

packages/app-builder-lib/templates/linux/after-remove.tpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@ if type update-alternatives >/dev/null 2>&1; then
66
else
77
rm -f '/usr/bin/${executable}'
88
fi
9+
10+
APPARMOR_PROFILE_DEST='/etc/apparmor.d/${executable}'
11+
12+
# Remove apparmor profile.
13+
if [ -f "$APPARMOR_PROFILE_DEST" ]; then
14+
rm -f "$APPARMOR_PROFILE_DEST"
15+
fi
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
abi <abi/4.0>,
2+
include <tunables/global>
3+
4+
profile ${executable} "/opt/${sanitizedProductName}/${executable}" flags=(unconfined) {
5+
userns,
6+
7+
# Site-specific additions and overrides. See local/README for details.
8+
include if exists <local/${executable}>
9+
}

test/snapshots/PublishManagerTest.js.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ exports[`custom provider 2`] = `
5858
"/opt/Test App ßW/resources/",
5959
"/opt/Test App ßW/resources/app-update.yml",
6060
"/opt/Test App ßW/resources/app.asar",
61+
"/opt/Test App ßW/resources/apparmor-profile",
6162
"/opt/Test App ßW/resources/package-type",
6263
"/usr/share/applications/",
6364
"/usr/share/applications/testapp.desktop",

test/snapshots/linux/debTest.js.snap

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ exports[`arm 2`] = `
4141
"/usr/share/",
4242
"/opt/Test App ßW/resources/",
4343
"/opt/Test App ßW/resources/app.asar",
44+
"/opt/Test App ßW/resources/apparmor-profile",
4445
"/usr/share/applications/",
4546
"/usr/share/applications/testapp.desktop",
4647
"/usr/share/doc/",
@@ -115,6 +116,7 @@ exports[`arm 5`] = `
115116
"/usr/share/",
116117
"/opt/Test App ßW/resources/",
117118
"/opt/Test App ßW/resources/app.asar",
119+
"/opt/Test App ßW/resources/apparmor-profile",
118120
"/usr/share/applications/",
119121
"/usr/share/applications/testapp.desktop",
120122
"/usr/share/doc/",
@@ -200,6 +202,7 @@ exports[`custom depends 2`] = `
200202
"/usr/share/",
201203
"/opt/Test App ßW/resources/",
202204
"/opt/Test App ßW/resources/app.asar",
205+
"/opt/Test App ßW/resources/apparmor-profile",
203206
"/usr/share/applications/",
204207
"/usr/share/applications/Boo.desktop",
205208
"/usr/share/doc/",
@@ -285,6 +288,7 @@ exports[`deb 2`] = `
285288
"/usr/share/",
286289
"/opt/Test App ßW/resources/",
287290
"/opt/Test App ßW/resources/app.asar",
291+
"/opt/Test App ßW/resources/apparmor-profile",
288292
"/usr/share/applications/",
289293
"/usr/share/applications/testapp.desktop",
290294
"/usr/share/doc/",
@@ -370,6 +374,7 @@ exports[`deb file associations 2`] = `
370374
"/usr/share/",
371375
"/opt/Test App ßW/resources/",
372376
"/opt/Test App ßW/resources/app.asar",
377+
"/opt/Test App ßW/resources/apparmor-profile",
373378
"/usr/share/applications/",
374379
"/usr/share/applications/testapp.desktop",
375380
"/usr/share/doc/",
@@ -469,6 +474,7 @@ exports[`executable path in postinst script 2`] = `
469474
"/usr/share/",
470475
"/opt/foo/resources/",
471476
"/opt/foo/resources/app.asar",
477+
"/opt/foo/resources/apparmor-profile",
472478
"/usr/share/applications/",
473479
"/usr/share/applications/Boo.desktop",
474480
"/usr/share/doc/",
@@ -544,6 +550,33 @@ fi
544550
545551
if hash update-desktop-database 2>/dev/null; then
546552
update-desktop-database /usr/share/applications || true
553+
fi
554+
555+
# Install apparmor profile. (Ubuntu 24+)
556+
# First check if the version of AppArmor running on the device supports our profile.
557+
# This is in order to keep backwards compatibility with Ubuntu 22.04 which does not support abi/4.0.
558+
# In that case, we just skip installing the profile since the app runs fine without it on 22.04.
559+
#
560+
# Those apparmor_parser flags are akin to performing a dry run of loading a profile.
561+
# https://wiki.debian.org/AppArmor/HowToUse#Dumping_profiles
562+
#
563+
# Unfortunately, at the moment AppArmor doesn't have a good story for backwards compatibility.
564+
# https://askubuntu.com/questions/1517272/writing-a-backwards-compatible-apparmor-profile
565+
APPARMOR_PROFILE_SOURCE='/opt/foo/resources/apparmor-profile'
566+
APPARMOR_PROFILE_TARGET='/etc/apparmor.d/Boo'
567+
if test -d "/etc/apparmor.d"; then
568+
if apparmor_parser --skip-kernel-load --debug "$APPARMOR_PROFILE_SOURCE" > /dev/null 2>&1; then
569+
cp -f "$APPARMOR_PROFILE_SOURCE" "$APPARMOR_PROFILE_TARGET"
570+
571+
if hash apparmor_parser 2>/dev/null; then
572+
# Extra flags taken from dh_apparmor:
573+
# > By using '-W -T' we ensure that any abstraction updates are also pulled in.
574+
# https://wiki.debian.org/AppArmor/Contribute/FirstTimeProfileImport
575+
apparmor_parser --replace --write-cache --skip-read-cache "$APPARMOR_PROFILE_TARGET"
576+
fi
577+
else
578+
echo "Skipping the installation of the AppArmor profile as this version of AppArmor does not seem to support the bundled profile"
579+
fi
547580
fi"
548581
`;
549582

@@ -597,6 +630,7 @@ exports[`no quotes for safe exec name 3`] = `
597630
"/usr/share/",
598631
"/opt/foo/resources/",
599632
"/opt/foo/resources/app.asar",
633+
"/opt/foo/resources/apparmor-profile",
600634
"/usr/share/applications/",
601635
"/usr/share/applications/Boo.desktop",
602636
"/usr/share/doc/",
@@ -682,6 +716,7 @@ exports[`top-level exec name 2`] = `
682716
"/usr/share/",
683717
"/opt/foo/resources/",
684718
"/opt/foo/resources/app.asar",
719+
"/opt/foo/resources/apparmor-profile",
685720
"/usr/share/applications/",
686721
"/usr/share/applications/Boo.desktop",
687722
"/usr/share/doc/",

0 commit comments

Comments
 (0)