Week 02 · lesson
Least Privilege Is a Relationship
“Run it as administrator” is a troubleshooting shortcut that often hides the real problem.
If a service cannot read one required file, giving it access to everything may make the error disappear. It also destroys the boundary that was supposed to limit the service.
Least privilege asks a better question:
What is the smallest set of permissions this identity needs to perform its required function?
Continue the status-service scenario
From Lesson 2, the application log reported:
11:04:02Z level=ERROR event=template-load path=/opt/rnx/templates/status.html result=permission-denied
You are now given a synthetic permission snapshot.
RESOURCE OWNER SERVICE ACCESS
/opt/rnx/templates/status.html root no read
/var/lib/rnx/status.json rnx-status read
/var/log/rnx/status.log rnx-status append
/etc/rnx/admin-secrets.txt root no access
The service needs the template to render the page.
The service does not need the admin-secrets file.
Bad fix: remove the boundary
A rushed response might be:
Make the service run as root so it can read the template.
Would that probably remove the immediate read error?
Maybe.
Would it be a good control?
No. It grants the status service access far beyond its stated purpose.
The service needs one additional capability: read the template.
The control should be close to that requirement.
Better fix: change the resource boundary
A narrower design is:
- keep the service running as
rnx-status; - allow that identity read access to the required template;
- preserve read-only access to status data;
- preserve append-only/logging behavior where appropriate; and
- continue denying unrelated protected resources.
The exact production implementation would depend on the operating system, ownership model, groups, ACLs, deployment process, and change policy.
This lesson is not asking you to alter a real machine. It is asking you to design the permission relationship correctly.
Least privilege has two sides
People often focus only on the identity:
Is this account privileged?
Also inspect the resource:
Which operations does this service need on this resource?
For a simple status service:
| Resource | Read | Write | Execute/admin |
|---|---|---|---|
| status template | yes | no | no |
| generated status data | yes | maybe not | no |
| service log | maybe read/append | append as designed | no |
| admin secrets | no | no | no |
This matrix is more useful than “low privilege” as a slogan.
A service account is a containment boundary
Why not let every background service share one powerful account?
Because separate identities allow separate permissions and separate evidence.
If rnx-status and rnx-database are different identities, the operating system can limit which files, sockets, and operations each service receives. Logs can also make responsibility clearer.
A failure in one service does not automatically become permission to use every resource available to another.
That is containment by design.
Proposed control
For the fictional lab, write the control in plain language:
Preserve the
rnx-statusservice identity and grant it read access only to the required status template rather than elevating the entire service to a broadly privileged identity.
Then state what should remain denied:
The service should continue to have no access to
/etc/rnx/admin-secrets.txt.
Controls are clearer when they define both the intended permission and the preserved boundary.
Retest design
We need more than “page loads now.”
Design three cases.
Required-function test
Can rnx-status read the template and render /status successfully?
Expected: yes.
Boundary test
Can rnx-status read the unrelated admin-secrets resource?
Expected: no.
Service-identity test
Does the status service still run as rnx-status rather than a broadly privileged identity?
Expected: yes.
Supplied post-change evidence
PROCESS
PID=733 user=rnx-status command=status-web
RESOURCE CHECK
rnx-status -> /opt/rnx/templates/status.html -> READ ALLOWED
rnx-status -> /etc/rnx/admin-secrets.txt -> READ DENIED
APPLICATION LOG
11:30:03Z event=template-load result=success
11:30:03Z event=response route=/status result=200
Now the evidence supports a stronger statement:
In the supplied post-change state, the status service remains under the
rnx-statusidentity, can read the required template, continues to be denied access to the unrelated admin-secrets resource, and records a successful/statusresponse.
Still not:
The service is secure.
You tested a permission relationship and required function, not every possible failure.
Activity: privilege budget
For each fictional service below, identify the minimum resource relationship it needs.
A. Log viewer
Job: display existing application logs to an instructor.
Does it need:
- read application logs?
- delete logs?
- edit user accounts?
- write application code?
B. Backup exporter
Job: copy a designated classroom dataset to a backup folder.
Does it need:
- read the designated dataset?
- write the backup folder?
- read all user home directories?
- administer the network?
C. Status collector
Job: read robot-test status files and publish a summary.
Does it need:
- read status files?
- rewrite source test data?
- access unrelated credentials?
Your answer should follow the job, not the title of the account.
Finish the Service Responsibility Map
Your Week 2 artifact should now include:
- identity;
- process;
- service purpose;
- required resources;
- permissions actually needed;
- evidence that showed the failure;
- proposed least-privilege control;
- required-function retest;
- boundary retest; and
- one limitation.
Carry this forward
When a service fails later in the course, do not begin with “give it more permission.”
Begin with:
- what job must work;
- which identity performs it;
- which process implements it;
- which resource is required;
- which operation is required on that resource; and
- which evidence proves the result.
Security gets stronger when permissions follow system responsibilities instead of convenience.