Scenario questions that test whether you actually understand the kernel mechanics, and terminal labs where you write the command yourself before checking the answer.
Flashcards
76
Labs
12
Decks
4
Format
Self-check
Linux permissions & kernel credentials
Five production scenarios covering stale credentials, fsuid, SGID inheritance, the 16-bit i_mode bitmask, and root override order.
Linux Internals
Device-locked Microsoft 365 (Entra + Intune)
Seven scenarios covering report-only vs enforced Conditional Access, the Intune service-app exclusions, device-ID approval, compliance defaults, the Android work-profile boundary, paste semantics and the iOS enrollment chain.
Cloud & Identity
RHEL 10 storage, permissions & SELinux
Fifty-five cards covering LVM provisioning and growth, SUID/SGID/sticky semantics, POSIX ACLs, polkit, Pacemaker ACLs and the SELinux toolchain, aimed at RHCSA EX200 and RHCE EX294.
Red Hat Certification
AWS NACLs vs security groups
Nine scenarios on the VPC packet path: stateful SG versus stateless NACL, the ephemeral port trap, first-match-wins rule numbering, and diagnosing REJECTs with Flow Logs and Reachability Analyzer.
Cloud Networking
Hands-on labs
Each lab gives you a scenario and a set of requirements. Write the command first, then open the solution to check your reasoning.
introLinux Operations
~5 min
Starting and managing background processes
The server is stable again. Just as you are about to take a break, a developer messages you: they need a long-running script, data_processor.sh, started on the server. You cannot keep your terminal session open for hours just to babysit it, so it has to survive you logging out.
Tasks
Start data_processor.sh so it runs in the background and is immune to hangups.
Requirements
You must be in the ~/project directory.
Use nohup to run the script.
Use the & operator to send the process to the background.
Redirect both standard output and standard error to processor.log.
Solution
cd ~/project
nohup ./data_processor.sh > processor.log 2>&1 &
Why
nohup makes the process ignore SIGHUP, the signal the kernel sends to a session’s children when the controlling terminal goes away. Without it, logging out kills the script.
& detaches the job so the shell returns a prompt immediately instead of blocking.
The redirect order matters.> processor.log 2>&1 first points stdout at the file, then points stderr at *wherever stdout now goes*. Writing 2>&1 > processor.log instead sends stderr to the original terminal and only stdout to the file — a classic way to lose exactly the error output you wanted.
If you omit the redirect entirely, nohup quietly appends to nohup.out in the current directory, which is rarely what you want on a shared box.
Verify
jobs # shows the job while the shell lives
ps -ef | grep data_processor
tail -f processor.log
coreLinux Internals
~8 min
Build a shared directory that keeps its group
Two engineers keep creating files in a shared deploy directory that land under their own primary groups, so the other one cannot write to them. You need new files and new subdirectories to inherit the devops group automatically, without anyone remembering to run chgrp.
Tasks
Create /srv/devops owned by deploy:devops.
Make it group-writable and ensure new entries inherit the devops group.
Confirm the inheritance also applies to subdirectories created later.
Requirements
The directory must be owned by user deploy and group devops.
New files inside must get group devops, not the creator’s primary group.
Use a single numeric chmod that sets the special bit.
The leading 2 is the SGID bit. On a directory it changes the inheritance rule for anything created inside: new entries take their group from the parent directory instead of from the creating process’s primary group.
New *subdirectories* also inherit the SGID bit itself, so the behaviour propagates down the tree with no further chmod.
SGID only governs the group, not the permission bits — those still come from each user’s umask. Pair this with a 002 umask, or a group-writable default ACL, or you will still get rw-r--r-- files that the other engineer cannot edit.
Verify
ls -ld /srv/devops # expect drwxrwsr-x, note the s
sudo -u other touch /srv/devops/t && ls -l /srv/devops/t
advancedSecurity & Hardening
~10 min
Audit every setuid-root binary on the system
Security review week. You need a written inventory of every setuid-root executable on the box, because each one is a potential privilege-escalation path if it has a bug. Noise from unreadable directories must not pollute the report.
Tasks
Find every regular file under /usr owned by root with the setuid bit set.
Write the list to suid-audit.txt in your home directory.
Suppress permission-denied noise from the search itself.
Requirements
Match the setuid bit specifically, not any arbitrary permission.
Restrict results to regular files owned by root.
Send find’s own stderr to /dev/null, not the results.
-perm -4000 uses the leading dash, which means "at least these bits are set". Plain -perm 4000 would match only files whose mode is *exactly* 4000, which is essentially nothing, and /4000 means "any of these bits".
-type f excludes directories, where the 4000 bit means something entirely different, and -user root narrows it to the binaries that actually escalate to root.
2>/dev/null discards only find’s error stream, so unreadable directories stay quiet while the result list on stdout still reaches the file. Redirecting stdout instead would throw away the report.
Review the output against a known-good baseline. Anything unexpected — especially an interpreter, an editor or something under a user-writable path — is worth investigating immediately.
Create the missing Intune Enrollment service principal via Graph
Enrollment on a new phone dies with "You can’t get there from here" and the sign-in log shows error 53003 against `Microsoft Intune Enrollment`. In the Conditional Access resource picker the app cannot be selected at all, because it has no service principal in the tenant yet.
Tasks
Check whether a service principal already exists for app ID d4ebce55-015a-49b5-a083-c84d1797ae8c.
Create the service principal through Microsoft Graph if it is missing.
Read the service principal back and confirm its display name is Microsoft Intune Enrollment.
Requirements
Use az rest against Microsoft Graph, not the portal click-path.
Sign in with an account that has Application.ReadWrite.All consented.
GET before POST: creating a duplicate service principal fails with 409.
Solution
az rest --method get --url "https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appId eq 'd4ebce55-015a-49b5-a083-c84d1797ae8c'&$select=id,appId,displayName" -o json
az rest --method post --url "https://graph.microsoft.com/v1.0/servicePrincipals" --body "{\"appId\": \"d4ebce55-015a-49b5-a083-c84d1797ae8c\"}"
Why
The trap is creating an app registration (POST /applications) instead of a service principal (POST /servicePrincipals). The Conditional Access picker lists service principals; an app registration alone does not make Microsoft Intune Enrollment selectable. The two objects share the same appId but are different directory objects.
The app ID d4ebce55-015a-49b5-a083-c84d1797ae8c is Microsoft’s published first-party constant for Intune Enrollment, so the POST body needs no tenant-specific value. Microsoft Intune itself is 0000000a-0000-0000-c000-000000000000.
The GET-first pattern matters operationally: re-running the POST after the service principal exists returns 409 Conflict, so a repeatable script checks value | length first and only creates when zero.
Verify
az rest --method get --url "https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appId eq 'd4ebce55-015a-49b5-a083-c84d1797ae8c'&$select=appId,displayName" | jq -r '.value[0].displayName' # "Microsoft Intune Enrollment"
az rest --method get --url "https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appId eq 'd4ebce55-015a-49b5-a083-c84d1797ae8c'" | jq -e '.value | length == 1' # exactly one SP, no duplicates
advancedCloud & Identity
~15 min
Audit Conditional Access policies and their Intune exclusions
Before rollout you owe a written audit: which Conditional Access policies are still report-only, which grant "require device to be marked as compliant", and whether the mobile policy really excludes the Intune service apps. Doing this in the portal is five clicks per policy and impossible to repeat; do it against Graph.
Tasks
List every Conditional Access policy with its id, state and display name.
For the mobile require-compliant policy, confirm the grant control and that both Intune first-party app IDs are in the excluded applications.
Requirements
Read-only: GET requests only.
Use az rest piped to jq.
Treat state enabledForReportingButNotEnforced as report-only, not enforcement.
Graph state values are enabled, disabled and enabledForReportingButNotEnforced. The trap is reading `enabledForReportingButNotEnforced` as "on" — it is report-only: sign-ins are logged as would-be success/failure but nothing is blocked. Only enabled enforces.
Exclusions live in conditions.applications.excludeApplications as app IDs, not display names — that is why the missing service principal mattered: you cannot exclude an app the tenant cannot resolve. The mobile policy must list d4ebce55-015a-49b5-a083-c84d1797ae8c and 0000000a-0000-0000-c000-000000000000 at minimum.
The per-user laptop rule keeps its approved-device list in conditions.devices.deviceFilter as a rule expression over device.deviceId — worth auditing in the same pass, since a mistyped ID there silently blocks an approved laptop.
Verify
jq -r '.value[] | select(.state == "enabledForReportingButNotEnforced") | .displayName' /tmp/ca-policies.json # lists the report-only policies
jq -e '.value[] | select(.displayName == "Contoso - Mobile - Require compliant device") | .conditions.applications.excludeApplications | index("d4ebce55-015a-49b5-a083-c84d1797ae8c") and index("0000000a-0000-0000-c000-000000000000")' /tmp/ca-policies.json # exit 0 only if both Intune app IDs are excluded
jq -r '.value[] | select(.displayName == "Contoso - Mobile - Require compliant device") | .grantControls.builtInControls[]' /tmp/ca-policies.json # includes "compliant"
advancedCloud & Identity
~20 min
Enforce a report-only policy and revoke stale sessions
Every Report-only row in the sign-in logs for the pilot user looks right. Time to enforce `Contoso - Mobile - Require compliant device`. Last time a policy was switched On, users with already-open Outlook sessions kept working for hours because the old tokens were still valid.
Tasks
Set the policy state to enabled via Graph.
Revoke the pilot user’s sign-in sessions so tokens issued before enforcement die immediately.
Verify the policy state, and that a fresh sign-in from an unmanaged phone now fails with error 53003.
Requirements
Use az rest PATCH/POST against Graph.
Know the policy id and the pilot user’s UPN beforehand.
The 53003 check needs a real new sign-in attempt after the change; log rows from before do not count.
Solution
az rest --method patch --url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/<policy-id>" --body "{\"state\": \"enabled\"}"
az rest --method post --url "https://graph.microsoft.com/v1.0/users/alex@contoso.com/revokeSignInSessions"
Why
The trap is stopping at the PATCH. Flipping state to enabled changes evaluation for *new* token issuance; refresh tokens already issued keep working until they expire or are revoked. POST /users/{upn}/revokeSignInSessions is the second half of enforcement and is what makes the block effective immediately.
After enforcement, a fresh sign-in from an unmanaged phone fails the Require device to be marked as compliant grant and surfaces as error 53003 in auditLogs/signIns — the same error code the report-only Failure predicted, which is how you confirm the prediction was right.
Rollback is the reverse and takes effect within minutes: PATCH state back to disabled (or enabledForReportingButNotEnforced to keep observing). Intune-side policies (compliance, app protection) can stay in place during rollback; they do not gate sign-in on their own.
Verify
az rest --method get --url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/<policy-id>?$select=state" | jq -r '.state' # "enabled"
az rest --method get --url "https://graph.microsoft.com/v1.0/auditLogs/signIns?$filter=userPrincipalName eq 'alex@contoso.com' and status/errorCode eq 53003&$top=5" | jq '.value[] | {createdDateTime, appDisplayName, error: .status.errorCode}' # fresh 53003 rows after enforcement
coreRed Hat Certification
~20 min
Provision an LVM-backed shared directory with SGID inheritance
EX200-style task: build `/srv/app_data` on a fresh disk. A spare disk `/dev/sdb` is attached to your RHEL 10 VM, the team `devops` and users `dev_user` (member of devops) and `auditor_user` already exist or must be created, and the directory must survive reboots via fstab.
Tasks
Create PV, VG vg_projects and a 5 GB LV lv_appdata, then format it XFS.
Mount it persistently at /srv/app_data.
Set ownership root:devops and a mode that gives the group full access plus SGID inheritance.
Requirements
RHEL 10 VM with a spare disk at /dev/sdb.
Use a single numeric chmod that includes the special bit.
The mount must be registered in /etc/fstab, not just mounted by hand.
The order matters: PV then VG then LV then file system then mount — each layer consumes the one below it. mkfs.xfs goes on the LV device /dev/vg_projects/lv_appdata, and the fstab entry must match that path exactly or mount -a fails at boot.
The trap is `chmod 770` instead of `2770`. The leading 2 is the SGID bit; without it new files land in each creator’s primary group and the other teammates cannot write to them. SGID affects only the group, not the permission bits, which is why the mode is 2770 and not something exotic.
Validate fstab before rebooting with findmnt --verify — a typo there is a classic "boots to emergency mode" exam fail. mount -a after appending the line proves the entry parses.
Verify
ls -ld /srv/app_data # expect drwxrws--- root devops (note the s)
findmnt /srv/app_data # shows the LV as source, xfs, mounted
lvs # lv_appdata present in vg_projects
sudo -u dev_user touch /srv/app_data/probe && ls -l /srv/app_data/probe # group is devops, not the creator’s primary group
advancedRed Hat Certification
~20 min
Grant auditor read-only access with ACLs and make the SELinux context persistent
On the `/srv/app_data` volume from the previous lab, `auditor_user` must be able to read the directory and every file created in it, but must never get write access. At the same time a web service needs to write there, so the directory needs a persistent `httpd_sys_rw_content_t` label that survives a relabel.
Tasks
Grant auditor_user read+execute on the directory via an access ACL.
Add a default ACL so new files are automatically readable by the auditor.
Write a persistent SELinux file-context rule for /srv/app_data(/.*)? and apply it.
Requirements
RHEL 10 with SELinux enforcing.
The SELinux change must be done through semanage fcontext, not chcon.
The auditor gets read-only — never write — through the default ACL.
Two different inheritance mechanisms are in play. The default ACL (d:u:auditor_user:r) propagates read access to new files at creation time; the SGID bit from the previous lab propagates the *group*. They compose: a new file gets group devops from SGID and the auditor’s named entry from the default ACL.
The trap is using `chcon -t httpd_sys_rw_content_t /srv/app_data`. That changes only the live label; the next touch /.autorelabel + reboot (or fixfiles -F onboot) resets it, because chcon writes nothing to the policy database. semanage fcontext -a records the rule persistently, and restorecon -Rv applies it now — that pair is the exam-correct answer.
Note the default ACL grants r only, on top of the directory’s rx: the auditor can list and read, never write, which is exactly the read-only auditing requirement.
Verify
getfacl /srv/app_data # shows user:auditor_user:r-x and default:user:auditor_user:r--
ls -ld /srv/app_data # shows a trailing + (ACL present)
ls -Zd /srv/app_data # shows ...:httpd_sys_rw_content_t:...
semanage fcontext -l | grep app_data # rule listed as persistent (local, not chcon)
sudo -u auditor_user cat /srv/app_data/<a file dev_user created> # read works
coreRed Hat Certification
~10 min
Grow the XFS volume online after extending the LV
`/srv/app_data` on `/dev/vg_projects/lv_appdata` is at 95% capacity. You already ran `sudo lvextend -L +3G /dev/vg_projects/lv_appdata` and `lvs` shows the bigger LV, but `df -h /srv/app_data` still shows the old 5 GB.
Tasks
Expand the XFS file system to fill the enlarged LV, online, with the volume still mounted.
Prove the file system now reports the larger size.
Requirements
The volume must stay mounted the whole time (online grow).
Do not reformat or unmount.
Remember XFS cannot shrink — only grow commands are valid here.
Solution
sudo xfs_growfs /srv/app_data
Why
The trap is passing the device instead of the mount point.xfs_growfs operates on the mounted file system and takes the *mount point*: xfs_growfs /srv/app_data. xfs_growfs /dev/vg_projects/lv_appdata fails because the file system is mounted. (ext4 is the opposite: resize2fs takes the device.)
The one-liner alternative is lvextend -L +3G --resizefs /dev/vg_projects/lv_appdata, which grows the LV and the file system in a single step. Both are valid; knowing both is what the exam rewards.
XFS growth is online and instant because XFS allocates in AGs (allocation groups); shrinking would require rewriting those structures, which is why XFS refuses lvreduce on a live file system.
Verify
df -h /srv/app_data # now shows ~8G instead of 5G
xfs_info /srv/app_data # block counts grew, mount still active
lvs -o lv_name,lv_size,vg_name | grep lv_appdata
coreCloud Networking
~15 min
Audit a subnet’s NACL and an instance’s SG for the stateless trap
A web instance in a public subnet times out for users even though its security group allows inbound 443 from 0.0.0.0/0. Before changing anything you want a written picture of both firewalls: the NACL rules on the subnet (in and out, by rule number) and the SG rules on the instance.
Tasks
List the NACL entries of the instance’s subnet, ingress and egress separately, sorted by rule number.
List the security group rules of the instance and confirm whether any egress rule exists at all.
Decide from the output alone whether return traffic can leave the subnet.
Requirements
AWS CLI v2 configured against a lab account (aws sts get-caller-identity works).
The trap is trusting the SG view alone. A stateful SG with inbound 443 allowed needs *no* outbound rule for replies — but the subnet NACL is stateless and must explicitly allow egress to the client’s ephemeral range (TCP 1024–65535 to be safe across Linux/Windows/ELB clients). If the egress list lacks it, replies die at the NACL and users see a timeout.
Sorting NACL entries by RuleNumber matters because evaluation is lowest number first, first match wins — the order you read is the order AWS applies.
Also note what SGs *cannot* show: deny rules do not exist there, so any explicit block you see must live in the NACL.
Verify
aws ec2 describe-network-acls --filters Name=association.subnet-id,Values=subnet-11111111111111111 --query "NetworkAcls[0].Entries[?Egress==`true` && RuleAction==`allow`].PortRange" --output json # must include an ephemeral range for return traffic
aws ec2 describe-security-groups --group-ids sg-11111111111111111 --query "SecurityGroups[0].IpPermissionsEgress" --output json # SG egress may be open by default; that is NOT what blocks the reply
coreCloud Networking
~15 min
Add a NACL deny that actually fires, plus ephemeral egress
An abusive range, 203.0.113.0/24, is hammering the load balancer in your public subnet. The subnet’s NACL currently has rule 100 allowing inbound TCP 443 from 0.0.0.0/0, and nothing else. You need the bad range blocked *and* legitimate return traffic working.
Tasks
Add a NACL ingress deny for 203.0.113.0/24 at a rule number that is evaluated before the existing allow at 100.
Add a NACL egress allow for TCP 1024–65535 (return traffic to ephemeral client ports).
Add a NACL ingress allow for TCP 1024–65535 (responses to instance-initiated connections).
Requirements
A lab VPC with a custom NACL on a public subnet; do this in a lab account only.
Rules numbered in steps of 100 so future rules can slot in between.
The deny must have a lower rule number than any matching allow.
The trap is rule numbering. NACL rules evaluate lowest-number-first and stop at the first match. A deny at 200 behind an allow-all at 100 never fires; at 90 it blocks before the allow is read. Numbering in steps of 100 leaves room to slot future rules (e.g. 95) without renumbering.
The second trap is forgetting direction. A NACL is stateless: the egress allow for 1024–65535 is what lets replies to inbound 443 reach the client’s ephemeral port, and the ingress allow for 1024–65535 lets responses to instance-initiated outbound connections (yum, APIs) back in.
Remember the terminal * rule denies everything not matched — on a *custom* NACL that is every packet until you add rules, so add rules before associating it with a subnet.
Diagnose a blocked path with Flow Logs and Reachability Analyzer
Users report that https://your-app times out. The instance’s SG allows 443 inbound, the subnet NACL looks plausible, and you want a definitive answer about *which component* drops the packets — without rebooting or changing anything.
Tasks
Query recent VPC Flow Logs for the instance and find REJECT rows, noting direction.
Create a Network Insights Path from the internet gateway (or a test instance) to the instance on TCP 443 and run the analysis.
Read the analysis findings and name the blocking component.
Requirements
VPC Flow Logs enabled and delivered to CloudWatch Logs or S3, in a lab account.
AWS CLI v2 + jq.
An instance ID, its subnet ID, and an internet gateway ID (igw-...).
The insights-path creation is the only mutating call; the diagnosis itself is read-only.
Flow Logs give the signature, Reachability Analyzer gives the culprit. Inbound ACCEPT + outbound REJECT on the flow logs = the stateless NACL egress gap (missing ephemeral rule). A REJECT on the first inbound packet = SG or NACL ingress. Reachability Analyzer walks route table, NACL entries by number, and SG rules, and returns the exact blocking component in Explanations.
The trap is trusting "the SG is fine" as the whole diagnosis. The SG can be perfect and the NACL still drops the reply; only the two-directional view (flow logs both ways, or the analyzer) separates the two firewalls.
Network Insights Paths need the source to be an IGW, instance, ENI, or VPN — an IGW-to-instance path on TCP 443 exercises NACL ingress + SG ingress in one analysis.
Verify
aws ec2 describe-network-insights-analyses --network-insights-path-id $(aws ec2 describe-network-insights-paths --filters Name=tag:Name,Values=diag-443 --query "NetworkInsightsPaths[0].NetworkInsightsPathId" --output text) --query "NetworkInsightsAnalyses[0].Status" --output text # eventually "succeeded"
aws logs filter-log-events --log-group-name /vpc/flowlogs --filter-pattern "REJECT" --query "length(events[])" --output text # count of rejected flows; correlate src/dst with the instance ENI