Skip to content

Commit

Permalink
feat: add AppArmor profile to FPM targets to pair with afterInstall
Browse files Browse the repository at this point in the history
… and `afterRemove` template scripts (electron-userland#8636)
  • Loading branch information
mmaietta authored Nov 5, 2024
1 parent fb26f6a commit 88cc0b0
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/mighty-forks-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"app-builder-lib": minor
---

feat: add support for AppArmor with template profile and configuration property
18 changes: 18 additions & 0 deletions packages/app-builder-lib/scheme.json
Original file line number Diff line number Diff line change
Expand Up @@ -519,12 +519,21 @@
"additionalProperties": false,
"properties": {
"afterInstall": {
"description": "File path to script to be passed to FPM for `--after-install` arg.",
"type": [
"null",
"string"
]
},
"afterRemove": {
"description": "File path to script to be passed to FPM for `--after-remove` arg.",
"type": [
"null",
"string"
]
},
"appArmorProfile": {
"description": "File path to custom AppArmor profile (Ubuntu 24+)",
"type": [
"null",
"string"
Expand Down Expand Up @@ -2050,12 +2059,21 @@
"additionalProperties": false,
"properties": {
"afterInstall": {
"description": "File path to script to be passed to FPM for `--after-install` arg.",
"type": [
"null",
"string"
]
},
"afterRemove": {
"description": "File path to script to be passed to FPM for `--after-remove` arg.",
"type": [
"null",
"string"
]
},
"appArmorProfile": {
"description": "File path to custom AppArmor profile (Ubuntu 24+)",
"type": [
"null",
"string"
Expand Down
10 changes: 10 additions & 0 deletions packages/app-builder-lib/src/options/linuxOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,18 @@ export interface LinuxTargetSpecificOptions extends CommonLinuxOptions, TargetSp
readonly vendor?: string | null
readonly maintainer?: string | null

/**
* File path to script to be passed to FPM for `--after-install` arg.
*/
readonly afterInstall?: string | null
/**
* File path to script to be passed to FPM for `--after-remove` arg.
*/
readonly afterRemove?: string | null
/**
* File path to custom AppArmor profile (Ubuntu 24+)
*/
readonly appArmorProfile?: string | null

/**
* *Advanced only* The [fpm](https://fpm.readthedocs.io/en/latest/cli-reference.html) options.
Expand Down
34 changes: 23 additions & 11 deletions packages/app-builder-lib/src/targets/FpmTarget.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Arch, executeAppBuilder, getArchSuffix, log, TmpDir, toLinuxArchString, use, serializeToYaml, asArray } from "builder-util"
import { unlinkIfExists } from "builder-util"
import { outputFile, stat } from "fs-extra"
import { copyFile, outputFile, stat } from "fs-extra"
import { mkdir, readFile } from "fs/promises"
import * as path from "path"
import { smarten } from "../appInfo"
Expand All @@ -26,10 +26,16 @@ interface FpmOptions {
url: string
}

interface ScriptFiles {
afterRemove: string
afterInstall: string
appArmor: string
}

export default class FpmTarget extends Target {
readonly options: LinuxTargetSpecificOptions = { ...this.packager.platformSpecificBuildOptions, ...(this.packager.config as any)[this.name] }

private readonly scriptFiles: Promise<Array<string>>
private readonly scriptFiles: Promise<ScriptFiles>

constructor(
name: string,
Expand All @@ -42,7 +48,7 @@ export default class FpmTarget extends Target {
this.scriptFiles = this.createScripts()
}

private async createScripts(): Promise<Array<string>> {
private async createScripts(): Promise<ScriptFiles> {
const defaultTemplatesDir = getTemplatePath("linux")

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

return await Promise.all<string>([
writeConfigFile(packager.info.tempDirManager, getResource(this.options.afterInstall, "after-install.tpl"), templateOptions),
writeConfigFile(packager.info.tempDirManager, getResource(this.options.afterRemove, "after-remove.tpl"), templateOptions),
])
return {
afterInstall: await writeConfigFile(packager.info.tempDirManager, getResource(this.options.afterInstall, "after-install.tpl"), templateOptions),
afterRemove: await writeConfigFile(packager.info.tempDirManager, getResource(this.options.afterRemove, "after-remove.tpl"), templateOptions),
appArmor: await writeConfigFile(packager.info.tempDirManager, getResource(this.options.appArmorProfile, "apparmor-profile.tpl"), templateOptions),
}
}

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

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

const scripts = await this.scriptFiles

// Install AppArmor support for ubuntu 24+
// https://github.com/electron-userland/electron-builder/issues/8635
await copyFile(scripts.appArmor, path.join(resourceDir, "apparmor-profile"))

const appInfo = packager.appInfo
const options = this.options
const synopsis = options.synopsis
const args = [
"--architecture",
toLinuxArchString(arch, target),
"--after-install",
scripts[0],
scripts.afterInstall,
"--after-remove",
scripts[1],
scripts.afterRemove,
"--description",
smarten(target === "rpm" ? this.helper.getDescription(options) : `${synopsis || ""}\n ${this.helper.getDescription(options)}`),
"--version",
Expand Down
27 changes: 27 additions & 0 deletions packages/app-builder-lib/templates/linux/after-install.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,30 @@ fi
if hash update-desktop-database 2>/dev/null; then
update-desktop-database /usr/share/applications || true
fi

# Install apparmor profile. (Ubuntu 24+)
# First check if the version of AppArmor running on the device supports our profile.
# This is in order to keep backwards compatibility with Ubuntu 22.04 which does not support abi/4.0.
# In that case, we just skip installing the profile since the app runs fine without it on 22.04.
#
# Those apparmor_parser flags are akin to performing a dry run of loading a profile.
# https://wiki.debian.org/AppArmor/HowToUse#Dumping_profiles
#
# Unfortunately, at the moment AppArmor doesn't have a good story for backwards compatibility.
# https://askubuntu.com/questions/1517272/writing-a-backwards-compatible-apparmor-profile
APPARMOR_PROFILE_SOURCE='/opt/${sanitizedProductName}/resources/apparmor-profile'
APPARMOR_PROFILE_TARGET='/etc/apparmor.d/${executable}'
if test -d "/etc/apparmor.d"; then
if apparmor_parser --skip-kernel-load --debug "$APPARMOR_PROFILE_SOURCE" > /dev/null 2>&1; then
cp -f "$APPARMOR_PROFILE_SOURCE" "$APPARMOR_PROFILE_TARGET"

if hash apparmor_parser 2>/dev/null; then
# Extra flags taken from dh_apparmor:
# > By using '-W -T' we ensure that any abstraction updates are also pulled in.
# https://wiki.debian.org/AppArmor/Contribute/FirstTimeProfileImport
apparmor_parser --replace --write-cache --skip-read-cache "$APPARMOR_PROFILE_TARGET"
fi
else
echo "Skipping the installation of the AppArmor profile as this version of AppArmor does not seem to support the bundled profile"
fi
fi
7 changes: 7 additions & 0 deletions packages/app-builder-lib/templates/linux/after-remove.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ if type update-alternatives >/dev/null 2>&1; then
else
rm -f '/usr/bin/${executable}'
fi

APPARMOR_PROFILE_DEST='/etc/apparmor.d/${executable}'

# Remove apparmor profile.
if [ -f "$APPARMOR_PROFILE_DEST" ]; then
rm -f "$APPARMOR_PROFILE_DEST"
fi
9 changes: 9 additions & 0 deletions packages/app-builder-lib/templates/linux/apparmor-profile.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
abi <abi/4.0>,
include <tunables/global>

profile ${executable} "/opt/${sanitizedProductName}/${executable}" flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/${executable}>
}
1 change: 1 addition & 0 deletions test/snapshots/PublishManagerTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ exports[`custom provider 2`] = `
"/opt/Test App ßW/resources/",
"/opt/Test App ßW/resources/app-update.yml",
"/opt/Test App ßW/resources/app.asar",
"/opt/Test App ßW/resources/apparmor-profile",
"/opt/Test App ßW/resources/package-type",
"/usr/share/applications/",
"/usr/share/applications/testapp.desktop",
Expand Down
35 changes: 35 additions & 0 deletions test/snapshots/linux/debTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ exports[`arm 2`] = `
"/usr/share/",
"/opt/Test App ßW/resources/",
"/opt/Test App ßW/resources/app.asar",
"/opt/Test App ßW/resources/apparmor-profile",
"/usr/share/applications/",
"/usr/share/applications/testapp.desktop",
"/usr/share/doc/",
Expand Down Expand Up @@ -115,6 +116,7 @@ exports[`arm 5`] = `
"/usr/share/",
"/opt/Test App ßW/resources/",
"/opt/Test App ßW/resources/app.asar",
"/opt/Test App ßW/resources/apparmor-profile",
"/usr/share/applications/",
"/usr/share/applications/testapp.desktop",
"/usr/share/doc/",
Expand Down Expand Up @@ -200,6 +202,7 @@ exports[`custom depends 2`] = `
"/usr/share/",
"/opt/Test App ßW/resources/",
"/opt/Test App ßW/resources/app.asar",
"/opt/Test App ßW/resources/apparmor-profile",
"/usr/share/applications/",
"/usr/share/applications/Boo.desktop",
"/usr/share/doc/",
Expand Down Expand Up @@ -285,6 +288,7 @@ exports[`deb 2`] = `
"/usr/share/",
"/opt/Test App ßW/resources/",
"/opt/Test App ßW/resources/app.asar",
"/opt/Test App ßW/resources/apparmor-profile",
"/usr/share/applications/",
"/usr/share/applications/testapp.desktop",
"/usr/share/doc/",
Expand Down Expand Up @@ -370,6 +374,7 @@ exports[`deb file associations 2`] = `
"/usr/share/",
"/opt/Test App ßW/resources/",
"/opt/Test App ßW/resources/app.asar",
"/opt/Test App ßW/resources/apparmor-profile",
"/usr/share/applications/",
"/usr/share/applications/testapp.desktop",
"/usr/share/doc/",
Expand Down Expand Up @@ -469,6 +474,7 @@ exports[`executable path in postinst script 2`] = `
"/usr/share/",
"/opt/foo/resources/",
"/opt/foo/resources/app.asar",
"/opt/foo/resources/apparmor-profile",
"/usr/share/applications/",
"/usr/share/applications/Boo.desktop",
"/usr/share/doc/",
Expand Down Expand Up @@ -544,6 +550,33 @@ fi
if hash update-desktop-database 2>/dev/null; then
update-desktop-database /usr/share/applications || true
fi
# Install apparmor profile. (Ubuntu 24+)
# First check if the version of AppArmor running on the device supports our profile.
# This is in order to keep backwards compatibility with Ubuntu 22.04 which does not support abi/4.0.
# In that case, we just skip installing the profile since the app runs fine without it on 22.04.
#
# Those apparmor_parser flags are akin to performing a dry run of loading a profile.
# https://wiki.debian.org/AppArmor/HowToUse#Dumping_profiles
#
# Unfortunately, at the moment AppArmor doesn't have a good story for backwards compatibility.
# https://askubuntu.com/questions/1517272/writing-a-backwards-compatible-apparmor-profile
APPARMOR_PROFILE_SOURCE='/opt/foo/resources/apparmor-profile'
APPARMOR_PROFILE_TARGET='/etc/apparmor.d/Boo'
if test -d "/etc/apparmor.d"; then
if apparmor_parser --skip-kernel-load --debug "$APPARMOR_PROFILE_SOURCE" > /dev/null 2>&1; then
cp -f "$APPARMOR_PROFILE_SOURCE" "$APPARMOR_PROFILE_TARGET"
if hash apparmor_parser 2>/dev/null; then
# Extra flags taken from dh_apparmor:
# > By using '-W -T' we ensure that any abstraction updates are also pulled in.
# https://wiki.debian.org/AppArmor/Contribute/FirstTimeProfileImport
apparmor_parser --replace --write-cache --skip-read-cache "$APPARMOR_PROFILE_TARGET"
fi
else
echo "Skipping the installation of the AppArmor profile as this version of AppArmor does not seem to support the bundled profile"
fi
fi"
`;

Expand Down Expand Up @@ -597,6 +630,7 @@ exports[`no quotes for safe exec name 3`] = `
"/usr/share/",
"/opt/foo/resources/",
"/opt/foo/resources/app.asar",
"/opt/foo/resources/apparmor-profile",
"/usr/share/applications/",
"/usr/share/applications/Boo.desktop",
"/usr/share/doc/",
Expand Down Expand Up @@ -682,6 +716,7 @@ exports[`top-level exec name 2`] = `
"/usr/share/",
"/opt/foo/resources/",
"/opt/foo/resources/app.asar",
"/opt/foo/resources/apparmor-profile",
"/usr/share/applications/",
"/usr/share/applications/Boo.desktop",
"/usr/share/doc/",
Expand Down

0 comments on commit 88cc0b0

Please sign in to comment.