How to use AppArmor on Linux and detect script changes

Last update: 17/12/2025
Author Isaac
  • AppArmor implements mandatory access control based on path-based profiles, which is easier to manage than SELinux and very useful for confining services and scripts.
  • Profiles are stored in /etc/apparmor.d, managed with utilities such as aa-status, aa-enforce, aa-complain or apparmor_parser, and can be created in a guided manner with aa-genprof and aa-logprof.
  • By combining AppArmor with hash checking scripts (stat, find, du, sha1sum), changes in critical code and directories can be detected, even excluding specific paths.
  • In cloud environments, proper configuration of kernel, cloud-init, and AppArmor prevents many provisioning problems and makes it easier to audit anomalous process behavior.

Security with AppArmor on Linux

If you work with GNU/Linux and are concerned about the security of your scripts and services, sooner or later you will encounter AppArmor as a key component for controlling what each process can doIt's not just for hardening the system: when used properly, it also helps you find out when something changes where it shouldn't, for example in the code of a script delicate.

In the following lines we will see, quite calmly but to the point, What is AppArmor, and how does it compare to other systems like SELinux?, how to activate and manage it in the most common distributions (especially Debian and Ubuntu), how to create and adjust your own profiles, and finally, how to take advantage of all this along with some simple shell techniques to detect changes in script code or directories in Linux without the need to set up real-time surveillance.

What is AppArmor and why does it matter for your scripts?

AppArmor (Application Armor) is a mandatory access control mechanism, or MACintegrated into the Linux kernel through the Linux Security Modules (LSM) interface. Instead of relying solely on classic user/group permissions (the traditional DAC model), it adds an extra layer that strictly defines what each program can do.

In practice, the kernel queries AppArmor before executing certain system calls (opening files, creating sockets, executing other binaries, using certain capabilities, etc.) to determine if the requesting process is authorized according to its security profile. If the rule allows it, it is executed; if not, it is blocked or logged, depending on the mode.

The basic unit in AppArmor is the profile: a set of rules that is associated with an executable identified by its absolute path, for example /usr/sbin/sshd o /bin/pingUnlike in SELinux, The rules of a profile apply equally to all users that execute that binary; the permissions Unix Traditional ones still exist, but AppArmor goes above and beyond, further defining the scope.

Profiles are stored as plain text files in /etc/apparmor.d/ and are typically loaded into the kernel during the BootEach profile can be run in mode strict (enforce)where violations are blocked and logged, or in mode relaxed (complain)where they are allowed but are recorded in the logs to review them later.

Historically, AppArmor was launched by Novell in 2005 and was primarily used in SUSE and openSUSEHowever, Canonical brought it into the kernel mainline starting with version 2.6.36. Today, it is included by default in many distributions such as Ubuntu and its derivatives or Debian (enabled from Debian 10 onwards), while others like Fedora or RHEL do not. They continue to rely on SELinux as their primary MAC solution.

DAC, MAC, AppArmor and SELinux: how do they differ?

To understand what AppArmor brings to the table, it's helpful to separate two permission models that coexist in Linux: DAC (Discretionary Access Control) and MAC (Mandatory Access Control)The DAC is the one you've always known: owner, group, others, bits rwxACLs, etc. Each user can manage their file permissions within certain limits.

The MAC model, for its part, It is not discretionaryAccess decisions are based on global policies defined by the administrator and enforced by the system, regardless of who owns a file. This is where AppArmor and SELinux come in, both acting as two distinct MAC implementations under the LSM interfacebut with very different philosophies of use.

SELinux uses security labels (contexts) associated with processes, files, sockets, etc., and makes decisions based on combinations of labels plus roles, types, and other jargon. This gives it extremely granular control, but also high complexity both administration and auditing. AppArmor, on the other hand, is based on file pathsA profile is associated with a specific path of the executable and describes what resources that binary can use.

This makes AppArmor profiles usually be easier to read, write, and maintain for the average administrator. In return, some of the flexibility of SELinux is sacrificed, which allows for control over more types of operations and scenarios. In many distributions, AppArmor has become a very practical option for harden regular services without going crazy with syntax.

In the context of scripts and automations, AppArmor allows you to limit what a given script can do (for example, which directories it can read or modify, or whether it can open network connections), and thanks to its complain mode, it also detect unexpected access before even blocking them.

AppArmor statuses, profiles, and monitoring

Before using AppArmor to protect anything, you should check if it's actually working on your system. In modern distributions like Ubuntu or Debian, it's usually integrated, but Not all profiles are always active, nor are they all in enforce mode.so it's worth taking a look.

To quickly check if AppArmor is enabled, you can use:

sudo aa-enabled

This command basically responds with a "Yes" if the module is active or a "No" otherwise (for example, if the kernel was not booted with AppArmor support or the service is not loaded).

If you want to see the details of all uploaded profiles and their status, the key command is

  What Is Mozilla Firefox? Uses, Features, Reviews, Prices

sudo aa-status

The output of aa-status It shows, among other things, the number of loaded profiles, how many are in enforce mode, how many are in complain mode, and the list of routes associated with each one. This information is very useful for having a Quick snapshot of the actual level of protection that AppArmor provides on your machine.

For example, you might see something similar to:

apparmor module is loaded.
46 profiles are loaded.
44 profiles are in enforce mode.
/snap/bin/evince
/snap/bin/evince/previewer
...
2 profiles are in complain mode.
/snap.code.code
...

The more profiles that are in mode enforceThe stricter the global policy, the more effective it will be. Profiles in mode complain They don't block transactions, but they log every violation, which is very valuable for Adjust profiles without breaking services or to audit what an application is trying to do.

Installing and activating AppArmor on Debian and Ubuntu

In current Debian and Ubuntu models, AppArmor support is already included in the standard kernel, so Activating the system is a matter of installing the necessary packages and ensuring that the module starts up in the system boot.

apt -y install apparmor apparmor-profiles apparmor-utils

The package apparmor-utils It provides the online tools of commands to check states (aa-status), change profile modes (aa-enforce, aa-complain), create new profiles (aa-genprof), review logs (aa-logprof), Etc.

In some environments, it may also be necessary force the kernel to boot with AppArmor as an active LSM system. A typical way in Ubuntu is to add the parameters to GRUB with:

perl -pi -e 's,GRUB_CMDLINE_LINUX="(.*)"$,GRUB_CMDLINE_LINUX="$1 apparmor=1 security=apparmor",' /etc/default/grub
update-grub
reboot

After the restart, AppArmor should be loaded, which you can confirm with aa-status o apparmor_status (the latter is a very common alternative name).

If you need to expand the collection of predefined profiles maintained by the community, you can also install:

apt install apparmor-profiles-extra

These additional profiles cover a good number of services and applications, many of them exposed to the network and especially interesting to confine.

Basic profile management: enforce, complain, disable, and reload

Once you have AppArmor up and running, the key is managing profiles properly. Each file within /etc/apparmor.d/ It defines the rules of a profile, and its name usually corresponds to the executable path, replacing the slashes with periods. For example, /etc/apparmor.d/bin.ping is the profile of /bin/ping.

Normally, you shouldn't have to edit these files manually to change the mode; that's what other tools are for. specific utilitiesFor example, to force a specific profile into strict mode (enforce) you can use:

sudo aa-enforce /usr/sbin/cupsd

If, on the other hand, you want to create a profile in relaxed mode (complain) Because you suspect it's causing problems or you want to fine-tune it without blocking traffic, you would do the following:

sudo aa-complain /usr/sbin/privoxy

These commands accept both the executable path and the path to profile fileIn fact, if you want to change the global mode of all profiles defined in /etc/apparmor.d/You could run:

sudo aa-complain /etc/apparmor.d/*

or in the opposite direction:

sudo aa-enforce /etc/apparmor.d/*

In more extreme situations, you might want to temporarily disable a specific profileFor this purpose there is aa-disable:

sudo aa-disable /etc/apparmor.d/usr.sbin.cupsd

And if what you need is to scrutinize everything an application does, even what is normally allowed, you have audit mode with aa-audit, which makes all monitored calls are recorded without blocking them.

When you manually modify a profile or want to reload it from its file, you can use apparmor_parser to load or reload specific profiles:

cat /etc/apparmor.d/profile.name | sudo apparmor_parser -a # cargar
cat /etc/apparmor.d/profile.name | sudo apparmor_parser -r # recargar

And if you're interested in reloading all active profiles on the system, you can use the init script (or its equivalent in systemd on modern distributions):

/etc/init.d/apparmor reload

Explore and understand existing profiles

The profiles loaded into the kernel are a reflection of the files residing in /etc/apparmor.d/To list the contents of the directory you can do:

sudo ls /etc/apparmor.d

You'll see names like usr.sbin.sshd, usr.bin.manetc. Many profiles of the distribution itself are grouped into subdirectories such as /etc/apparmor.d/localdesigned for local variations without altering the main profile.

If you're curious and want to see the rules for a specific profile, simply:

sudo cat /etc/apparmor.d/usr.sbin.sshd

At first glance, the syntax may seem somewhat dry, but many lines are explained in simple terms which help to understand the purpose of each block. In any case, if you are not comfortable with the syntax, it is best not to edit these files manually and instead use the interactive generation and adjustment tools.

Detect processes without a profile and prioritize what to confine

Not all programs on your system will have an associated AppArmor profile. In fact, most won't. The ones you're likely to lock down first are usually... those exposed to the networkeither because they open ports or because they handle potentially dangerous data.

AppArmor includes the tool aa-unconfined To list processes that are listening on the network but do not have a profile:

aa-unconfined

If you add the parameter --paranoidThe utility will show all running processes that maintain at least one active network connection and are not confined:

aa-unconfined --paranoid

This list serves as a guide to help you decide where to begin. For example, you might discover that a web service, a proxy, or a database daemon... They are operating without any AppArmor policywhich is an invitation to create a dedicated profile for them.

Creating new profiles with aa-genprof and aa-logprof

Creating a profile from scratch can be intimidating, but AppArmor offers "learning mode" tools that greatly simplify the task. The idea is simple: You run the program in complain mode, let it do its job, and then build the profile from the logs. generated by AppArmor.

  The Complete Guide to Post-Quantum Cryptography: What It Is, How It Works, and Why You Need It

The main use for this is aa-genprof. Its basic syntax is:

sudo aa-genprof nombre_ejecutable

For example, to generate a profile for dhclient (DHCP client), you could launch:

sudo aa-genprof dhclient

The typical work process is something like this: aa-genprof creates an initial empty or very open profileIt loads it in complain mode and asks you to, in another terminal, use the application you want to profile (in this case, launch or force the acquisition of a new IP with dhclient).

While the program is running, AppArmor logs all actions that would have been blocked in enforce mode. Then, when you return to aa-genprof and choose the option to scan events, the tool presents you with an interactive list of detected violations along with rule suggestions.

An example of a first event could be the execution of an auxiliary script:

Perfil: /usr/sbin/dhclient
Ejecutar: /usr/sbin/dhclient-script
Severity: desconocido
(I)nherit / (C)hild / (P)rofile / (N)amed / (U)nconfined / (X) ix On / (D)eny / Abo(r)t / (F)inalizar

From here, you decide what to do with the child processes: launch the script with the same profile (inherit), with a subprofile (Child), with a dedicated profile (Profile/Named), without confining (Unconfined) or prevent its execution (Deny).

AppArmor also manages capabilitieswhich are special kernel permissions originally reserved for root (for example, using raw sockets, changing the system time, etc.). During profiling, you'll see things like:

Perfil: /usr/sbin/dhclient
Capability: net_raw
Severity: 8
(A)llow / (D)eny / (I)gnorar / Audi(t) / Abo(r)t / (F)inalizar

In each case, you decide whether to allow, deny, ignore (not create a rule), audit, or abort. The same applies to file accesswhere you will be shown specific routes, access modes (read, write, etc.) and possible reusable abstractions.

The abstractions They are sets of common rules packaged into reusable files (for example, <abstractions/nameservice> for everything related to name resolution). Instead of adding a loose rule for /etc/nsswitch.confYou can include abstraction and simplify the profile.

At the end of the process, aa-genprof offers to save the modified profiles and automatically reloads them in enforce mode, so that the program is confined with the rules you just approvedIf you detect new behaviors later, you can always run it again. aa-genprof or, more directly, aa-logprof about the recorded logs.

In fact, aa-genprof is simply a small script that uses aa-logprof underneath.It creates an empty profile, sets it to complain mode, launches the program, and then calls `aa-logprof` to build the profile from the logged violations. You can use aa-logprof separately whenever you want to refine existing profiles based on new events.

Detecting changes in code and directories using shell and hash

In addition to access control capabilities, in many cases it is of interest detect if the code of a script or the contents of a directory have changed between executions, for example to fire a full backup only when there are modifications, instead of making continuous incremental changes.

We're not talking about real-time monitoring here (for that you would need inotify and derived tools), but rather a one-off check: you run a script that checks the directory status and, if it detects changes compared to the last time, it takes action.

A very simple approach for a complete directory, without exceptions, involves using statThe following skeleton illustrates the idea:

DIR_TO_CHECK='/dir/to/check'
OLD_STAT_FILE='/home/johndoe/old_stat.txt'

if ; then
OLD_STAT=$(cat «$OLD_STAT_FILE»)
else
OLD_STAT="nothing"
fi

NEW_STAT=$(stat -t «$DIR_TO_CHECK»)

if ; then
echo 'Directory has changed. Do something!'
# Here you do backups, validations, etc.
echo «$NEW_STAT» > «$OLD_STAT_FILE»
fi

The logic is very simple: You store the result of `stat` in a file, and on the next run you compare it. with the new result. If it differs, you assume something has changed (size, timings, etc.) in the directory. This technique is fast but somewhat clunky, and doesn't allow you to easily exclude subpaths.

If you want to check a directory but excluding certain files or subdirectories (for example a tmp/ local or log files), you can put together something more elaborate by combining find, du, sort y sha1sum to obtain a "footprint" of the state of the tree that interests you.

An example of a script could be:

DIR_TO_CHECK='/dir/to/check'
PATH_TO_EXCLUDE="/dir/to/check/tmp*"
OLD_SUM_FILE='/home/johndoe/old_sum.txt'

if ; then
OLD_SUM=$(cat «$OLD_SUM_FILE»)
else
OLD_SUM="nothing"
fi

NEW_SUM=$(find «$DIR_TO_CHECK»/* \! -path «$PATH_TO_EXCLUDE» -print0 \
| xargs -0 du -b –time –exclude=»$PATH_TO_EXCLUDE» \
| sort -k4,4 \
| sha1sum \
| awk '{print $1}')

if ; then
echo «Directory has changed. "Do something!"
echo «$NEW_SUM» > «$OLD_SUM_FILE»
fi

The key here is in the line where the calculation is done. NEW_SUM:

  • find ... \! -path "$PATH_TO_EXCLUDE" -print0All files under the directory are listed except those that match the pattern to be excluded. -print0 It makes the names separate with a null character to avoid messes with spaces and strange characters.
  • xargs -0 du -b --time --exclude=...For each file obtained, the size in bytes and timestamp information (depending on options) are calculated, building a list in which each line represents a file system object and from which Unwanted routes are excluded again.
  • sort -k4,4The list is ordered according to the column containing the path (usually the fourth), so that the order is deterministic and any change is reflected in the sequence.
  • sha1sum | awk '{print $1}': the is calculated SHA1 sum of the entire list, which generates a short footprint that “represents” the complete state of the directory (excluding those excluded). A single change in size, path, or timestamp will cause the footprint to change.

This way, each time you run the script you'll know if, between one run and the next, Something relevant in the tree has been modified, added, removed, or renamedAnd that allows you to hook into other actions: regenerate full backups, run integrity checks, reload services, etc.

  Amazon is removing a key privacy feature from Alexa, and here are the reasons behind the change.

How does AppArmor fit into script change detection?

The techniques described above, on their own, already allow you detect modifications in the code of a script or in its associated resourcesBut if you combine this with AppArmor, you can go a step further and have the security system itself warn you when a script starts doing things it wasn't intended to do.

Imagine you have a script that should only read a set of files and write to a specific directory. If you define an AppArmor profile that only allows... reading to /opt/misdatos/ and writing to /var/log/miscript/Any attempt to access other sites will generate violation events in the logs (in complain mode) or will be blocked outright (in enforce mode).

Thus, if someone modifies the script (or replaces it with a malicious one) to delete files in /etc/ or sends data over the network, AppArmor will act as a safety net. Additionally, reviewing the logs with aa-logprof or directly in /var/log/syslog/journalctl, you can detect anomalous behaviors that reveal changes in the code or execution logic.

A practical strategy is to combine both approaches:

  • On one hand, using a small hash script (based on stat or in the sum of contents) for check if the script code and its resources have changed since the last review.
  • On the other hand, placing the script under a well-tuned AppArmor profile that limits its behavior and Record any attempt to deviate from the script.

If both systems give signals (different hash and new violations in AppArmor), you have a pretty clear indication that something has changed and it's worth inspecting thoroughly.

Common problems related to AppArmor and Linux VM provisioning

In cloud environments like Azure, AppArmor typically coexists with other critical system components, such as cloud-init and the Azure Linux agentWhen they are created Virtual machines Starting with custom images, it is not uncommon to encounter provisioning errors that, although not directly due to AppArmor, do affect services that may be confined by profiles.

A typical case: when deploying a VM from an image, the state remains in creating for a good while and then it moves on to failed with messages like:

Provisioning failed. OS Provisioning for VM 'sentilo' did not finish in the allotted time...

O well:

OSProvisioningInternalError: The VM encountered an error during deployment...

To diagnose these problems, one resorts to serial console log Enabling Azure boot diagnostics allows you to see everything from kernel command-line options to systemd target sequences, cloud-init and Azure agent execution, network configuration, and key generation. SSH, etc.

These logs check, for example, whether the goal has been achieved. "Network is Online", if cloud-init has completed its phases (init-local, init, config and final modules), if the provisioning ISO disks have been mounted correctly (they need the UDF driver) or if the Azure agent marks Finished provisioning.

Common mistakes include:

  • UDF module blocked or missingThe provisioning disk is not mounted and cloud-init cannot read the metadata, resulting in "No Azure metadata found" messages.
  • Unicode problems in VM labels or passwords, especially with older versions of cloud-init that don't handle non-ASCII characters well.
  • Assembly /var/tmp with noexecwhich prevents cloud-init from running dhclient when it is copied to that path in versions prior to 20.3.

All of this can be resolved by updating to recent versions of cloud-init, correcting the mount parameters, and ensuring that necessary kernel modules (such as UDFs) are not locked in /etc/modprobe.d/If you also use AppArmor on these VMs, it's worth checking that avoid overly restrictive profiles affecting components such as cloud-init, dhclient, systemd-networkd or the Azure agent, as they could interfere with provisioning.

Completely disable AppArmor (when there is no other option)

Although it's recommended to adjust profiles and modes rather than taking the easy way out, in very specific cases it may be necessary completely disable AppArmorFor example, to rule out that it is causing an esoteric problem in a test environment.

sudo /etc/init.d/apparmor stop
sudo update-rc.d -f apparmor remove

And, if you want to reactivate it later, simply start it and register it again at the default boot levels:

sudo /etc/init.d/apparmor start
sudo update-rc.d apparmor defaults

Keep in mind that disabling AppArmor removes the entire layer of MAC protection it provides, leaving processes vulnerable once again. limited only by traditional DAC permitsThe most sensible thing to do is to use this measure only for specific diagnostic purposes and, once the problem has been identified, re-enable it and adjust the conflicting profiles.

With all that said, it's possible to combine the use of AppArmor to limit the behavior of scripts and services with simple hashing and comparison techniques without too much complication. detect changes in the code and key directories of a Linux systemThis provides an extra layer of peace of mind when managing machines in production or in the cloud and you don't want anything to move without your knowledge.

schedule tasks in linux with cron and at
Related article:
Scheduling tasks in Linux with cron and at: a practical and complete guide