Run One Command Under an AppArmor Profile with aa-exec
You will finish with a repeatable way to launch one program under an existing AppArmor profile, confirm that the launch succeeded, and keep the profile transition separate from policy management. The examples use aa-exec from AppArmor 4.0.1, installed here as package version 4.0.1really4.0.1-0ubuntu0.24.04.7.
Allow about fifteen minutes. You need a shell, the AppArmor utilities package, and the name of a profile already loaded on your machine. This guide does not create, load, unload or edit a profile. Those are security-sensitive changes and are deliberately outside the workflow.
1. Check the command and its contract
Start by asking the installed command for its help. This is an ordinary, read-only command and does not need elevated privileges:
$ aa-exec --help
USAGE: aa-exec [OPTIONS] <prog> <args>
Confine <prog> with the specified PROFILE.
The important shape is aa-exec -p PROFILE PROGRAM ARGUMENTS. The profile is selected with -p or --profile; the program and its arguments follow. Keep the profile name and program as separate shell words. Do not paste an untrusted string into a command line without quoting it.
Checkpoint: confirm which binary you are about to use:
$ command -v aa-exec
/usr/bin/aa-exec
$ dpkg-query -W -f='${Package} ${Version}\n' apparmor
apparmor 4.0.1really4.0.1-0ubuntu0.24.04.7
2. Find a profile name without changing policy
aa-exec cannot invent a profile. It asks AppArmor to apply a profile that already exists. On a host with the AppArmor utilities installed, ask aa-status for profiles that are currently loaded:
$ aa-status --profiled
# profile names printed here are host-specific
Your list will differ, and an empty result is useful information: there may be no loaded profile name that you can test. Replace PROFILE_NAME below with an exact name from your own output. Do not guess a profile from a file name.
Checkpoint: record the exact name and inspect the command you intend to run. For a harmless first test, /usr/bin/true exits immediately and produces no output:
$ PROFILE_NAME='unconfined'
$ command -v true
/usr/bin/true
The special unconfined profile is useful for verifying aa-exec's argument handling on this machine, but it is not a security boundary. For a real restriction test, use a loaded, named profile that was designed for the program.
3. Run a harmless command under the profile
Use -- to make the boundary between aa-exec options and the program command obvious:
$ aa-exec --profile "$PROFILE_NAME" -- /usr/bin/true
$ printf 'exit status: %s\n' "$?"
exit status: 0
Exit status 0 means that aa-exec successfully started the requested program and that the program returned 0. It does not prove that a profile imposed the restrictions you expect. For that, the profile must be a real named policy, and the program must exercise the accesses that policy controls.
There is no undo command for this example because the profile is applied to the child process only. When true exits, the transition is over and no service or persistent configuration has changed.
4. Verify the profile transition from inside the child
A better smoke test prints the process security context from inside the child. The exact output depends on the profile and kernel, so verify the command's exit status first:
$ aa-exec --profile "$PROFILE_NAME" -- /bin/sh -c 'cat /proc/self/attr/current; printf "child status: %s\n" "$?"'
unconfined
child status: 0
With the unconfined test profile used above, unconfined is the expected context. With a named profile, expect that profile's context instead. If the command prints a different profile name, stop and investigate before treating the result as a successful policy test.
This check reads the child process's context. It does not prove that every file, capability or network operation will be allowed. Those outcomes belong to the profile's rules and the kernel's enforcement mode.
5. Pass options to the child without losing the boundary
Arguments after the program belong to the child. For example, this runs printf and keeps its format string and value after the program path:
$ aa-exec -p "$PROFILE_NAME" /usr/bin/printf '%s\n' 'aa-exec-child-ok'
aa-exec-child-ok
The short option -p and long option --profile are equivalent. Quote a profile name, path or argument whenever it can contain whitespace or shell metacharacters. Do not put a command substitution or a user-supplied option string into the aa-exec options area.
6. Use diagnostics when a transition fails
A missing profile is a configuration error, not evidence that AppArmor is disabled. Reproduce it safely with a deliberately invalid name:
$ aa-exec --profile definitely-not-a-profile -- /usr/bin/true
[PID] aa-exec: ERROR: profile 'definitely-not-a-profile' does not exist
$ printf 'exit status: %s\n' "$?"
exit status: 1
The PID in the error is variable. The useful parts are the profile name and the non-zero status. Check aa-status --profiled again, spelling and namespace before trying sudo. Elevated privileges do not create a missing profile.
Use --verbose when you need to see the requested transition:
$ aa-exec --profile "$PROFILE_NAME" --verbose -- /usr/bin/true
[PID] aa_change_onexec ("unconfined")
[PID] exec /usr/bin/true
The PID and exact diagnostic formatting vary. If you need to see more detail, --debug is also available. These options report what aa-exec is doing; they do not repair policy.
7. Know when the other options apply
--immediate changes the profile immediately instead of at exec time. That is a more specialised transition and can alter the security context of the aa-exec process before the child starts. Use it only when the profile and the program's transition model require it; the normal command example above leaves it out.
--namespace NAMESPACE selects an AppArmor namespace. A profile name that exists in one namespace may not exist in another. Do not add this option merely because a profile lookup failed; first establish that your deployment actually uses namespaces.
All of these operations can affect the security context of the launched process. Before using aa-exec for a service, test the exact command as the service account in a maintenance window. Do not replace a service's existing confinement command in production until you have a rollback path, such as restoring the previous unit or wrapper command.
Done means
- You confirmed the installed aa-exec version and option syntax.
- You selected an exact profile name from the host instead of guessing.
- A harmless child command returned status 0 under that profile.
- You checked the child context when a transition result mattered.
- You can distinguish a missing profile from a disabled AppArmor module.
- You have not changed a profile, service, mount, boot setting or persistent configuration.