Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,15 @@ jobs:
run: |
./hack/authentik/wait.sh
- run: make containers/e2e/ci-build
# AppArmor policy is host-global: and the container cannot load policy of
# its own (no securityfs during docker build)
- name: Load authentik AppArmor drop-in
run: |
sudo install -m 0644 -D \
vpkg/linux/nss/_deb/apparmor/abstractions/nameservice.d/authentik \
/etc/apparmor.d/abstractions/nameservice.d/authentik
sudo systemctl reload apparmor.service
sudo aa-status | grep -q unix-chkpwd
- env:
AK_PASSWORD: ${{ steps.setup.outputs.admin_password }}
AK_TOKEN: ${{ steps.setup.outputs.admin_token }}
Expand Down
20 changes: 18 additions & 2 deletions ak-platform-e2e/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ pub async fn exec_command(

let exit_code = result.exit_code().await.unwrap().unwrap();
if is_ci() {
println!("::group::{cmd} (Exit code {exit_code}");
eprintln!("::group::{cmd} (Exit code {exit_code})");
} else {
tracing::info!("[exec] {} exit={}", cmd, exit_code);
}
Expand All @@ -292,7 +292,7 @@ pub async fn exec_command(
.for_each(|l| tracing::warn!("[stderr] {}", l));

if is_ci() {
println!("::endgroup::");
eprintln!("::endgroup::");
}
let output = format!("{}{}", stdout_str, stderr_str);

Expand All @@ -318,6 +318,22 @@ pub async fn must_exec(
Ok(output)
}

/// Fails if the kernel logged an AppArmor denial for an authentik path
pub async fn assert_no_apparmor_denials(container: &ContainerAsync<GenericImage>) -> Result<()> {
// Scoped to authentik paths so unrelated host denials don't fail the test.
let (exit_code, output) = exec_command(
container,
r#"journalctl --no-pager | grep 'apparmor="DENIED"' \
| grep -E 'name="(/att/[^"]*)?/(etc|run)/authentik/'"#,
&[],
)
.await?;
if exit_code == 0 {
bail!("AppArmor denied access to authentik paths:\n{}", output);
}
Ok(())
}

/// A single parameterized command test case.
pub struct CmdTestCase {
pub name: String,
Expand Down
4 changes: 1 addition & 3 deletions ak-platform-e2e/src/test_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,7 @@ impl Drop for TestMachine {
tracing::info!("Test machine cleanup");
tokio::runtime::Handle::current().block_on(async {
for cmd in [
"journalctl -u ak-sysd",
"journalctl -u ak-agent",
"journalctl -u ssh",
"journalctl",
"systemctl stop ak-sysd",
"systemctl stop ak-agent",
] {
Expand Down
12 changes: 10 additions & 2 deletions ak-platform-e2e/tests/auth.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use ak_platform_e2e::{
CmdTestCase, TestMachine, agent_setup, cleanup_hosts, cmd_test, join_domain, must_exec,
test_init,
CmdTestCase, TestMachine, agent_setup, assert_no_apparmor_denials, cleanup_hosts, cmd_test,
join_domain, must_exec, test_init,
};

#[tokio::test(flavor = "multi_thread")]
Expand Down Expand Up @@ -32,6 +32,10 @@ async fn test_auth_identity_agent() {
.await
.expect("cmd test");

assert_no_apparmor_denials(&tm.container)
.await
.expect("no apparmor denials");

cleanup_hosts().await.expect("cleanup");
}

Expand Down Expand Up @@ -101,5 +105,9 @@ async fn test_auth_local_only_user() {
.await
.expect("cmd test");

assert_no_apparmor_denials(&tm.container)
.await
.expect("no apparmor denials");

cleanup_hosts().await.expect("cleanup");
}
7 changes: 6 additions & 1 deletion ak-platform-e2e/tests/login.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use ak_platform_e2e::{
TestMachine, authentik_creds, cleanup_hosts, exec_command, join_domain, must_exec, test_init,
TestMachine, assert_no_apparmor_denials, authentik_creds, cleanup_hosts, exec_command,
join_domain, must_exec, test_init,
};

/// Verifies that a real local (non-SSH) login via the `login` PAM service is
Expand All @@ -25,6 +26,10 @@ async fn test_local_login_success() {
"expected successful local login, got: {output}"
);

assert_no_apparmor_denials(&tm.container)
.await
.expect("no apparmor denials");

cleanup_hosts().await.expect("cleanup");
}

Expand Down
13 changes: 13 additions & 0 deletions vpkg/linux/nss/_deb/apparmor/abstractions/nameservice.d/authentik
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Drop-in for <abstractions/nameservice>, which is included by every profile that
# performs user/group lookups

# Config file, read to discover the socket path and debug config.
/etc/authentik/config.json r,

# The ak-sysd control socket.
@{run}/authentik/sys.sock rw,

# Disconnected-path variants: apparmor >= 4.1 declares unix-chkpwd with
# flags=(attach_disconnected.path=/att/unix-chkpwd/)
/att/unix-chkpwd/etc/authentik/config.json r,
/att/unix-chkpwd/run/authentik/sys.sock rw,
26 changes: 26 additions & 0 deletions vpkg/linux/nss/_deb/postinst.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,34 @@ insert_nss_entry() {
' /etc/nsswitch.conf
}

# Reload AppArmor so it picks up the drop-in we ship under /etc/apparmor.d.
# The apparmor package registers no dpkg trigger on that directory, so this only
# shortcuts the wait until the next apparmor reload or reboot.
reload_apparmor() {
log "Checking AppArmor setup..."
if ! command -v apparmor_parser >/dev/null 2>&1; then
log "apparmor_parser not found, skipping."
return
fi
if ! aa-enabled --quiet 2>/dev/null; then
log "AppArmor is not enabled, skipping."
return
fi
if systemctl reload apparmor.service >/dev/null 2>&1; then
return
fi
log "Could not reload apparmor.service, falling back to unix-chkpwd only."
if ! [ -f /etc/apparmor.d/unix-chkpwd ]; then
log "Could not find /etc/apparmor.d/unix-chkpwd."
return
fi
apparmor_parser -r -T -W /etc/apparmor.d/unix-chkpwd \
|| log "Failed to reload the unix-chkpwd profile, changes apply after reboot."
}

action="$1"

if [ configure = "$action" ]; then
insert_nss_entry
reload_apparmor
fi
9 changes: 9 additions & 0 deletions vpkg/linux/nss/nfpm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ contents:
file_info:
mode: 0644
packager: rpm
# AppArmor. Every confined profile that does user/group lookups includes
# <abstractions/nameservice>, and glibc loads our NSS module into it, so this
# single drop-in covers all of them (unix-chkpwd, sshd, cron, ...).
- src: ./nss/_deb/apparmor/abstractions/nameservice.d/authentik
dst: /etc/apparmor.d/abstractions/nameservice.d/authentik
file_info:
mode: 0644
packager: deb
type: config
overrides:
deb:
depends:
Expand Down
Loading