◇ OpenCyber Academy
Technical · Chapter 3 · English

Chapter 3 — Operating systems and identities

Open, free learning for anyone who wants to protect people and technology. No account is required to read this text or its worked answers.

Chapter 3 — Operating systems and identities

Lesson map

Lesson 03 published. Map, example, EX009.

flowchart TD
  N0["Learning outcomes and connection"]
  N1["3.1 What hardening means at technician level"]
  N2["3.2 Reading sshd configuration correctly"]
  N3["3.3 Accounts and identities"]
  N4["3.5 Practical activity"]
  N5["Chapter synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5
  N5 --> EX["Worked example"]
  EX --> Q["Exercise EX009"]

Logical drawing

flowchart LR
  N0["Learning outcomes and connection"]
  N1["3.1 What hardening means at technician level"]
  N2["3.2 Reading sshd configuration correctly"]
  N3["3.3 Accounts and identities"]
  N4["3.5 Practical activity"]
  N5["Chapter synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX009"]
[Learning outcomes and connection]
    |
    v
[3.1 What hardening means at technician level]
    |
    v
[3.2 Reading sshd configuration correctly]
    |
    v
[3.3 Accounts and identities]
    |
    v
[3.5 Practical activity]
    |
    v
[Chapter synthesis]
    |
    v
[Worked example] --> [Exercise EX009]

Learning outcomes and connection

After this chapter you should be able to:

Chapters 1–2 described the service and its traffic. The servers in the control centre — historian, SCADA server, jump host — run ordinary operating systems; this chapter makes their configuration something you can check, not assume.

3.1 What hardening means at technician level

Hardening is removing what is not needed and constraining what is. For a server in a control environment that means: services listening only on the interfaces they need; administrative access only through the management path; no shared or orphaned accounts; service accounts that cannot log in interactively; and configuration that is recorded so that drift can be detected (Chapter 14). NIST's general server guide gives the broader checklist (SP 800-123); the CISA Cross-Sector Cybersecurity Performance Goals summarize the account and credential practices most often missing (CISA CPGs).

3.2 Reading sshd configuration correctly

OpenSSH's daemon reads sshd_config from top to bottom and, for most keywords, the first value obtained is used; later lines for the same keyword are ignored (sshd_config(5)). Comments begin with #. So a file that has PasswordAuthentication yes near the top and someone's later fix PasswordAuthentication no at the bottom still allows passwords. The reliable check is the effective configuration: on a real host, sshd -T prints it; in Lab 3 the parser keeps the first value, as the daemon does.

Settings that matter on a control-centre host: ListenAddress restricted to the management interface; PermitRootLogin no; PasswordAuthentication no once keys or certificates work; X11Forwarding no; a low MaxAuthTries; and AllowGroups naming a group whose membership you also review.

3.3 Accounts and identities

On Linux, privilege is a number: UID 0 is root, whatever the account's name. An extra UID-0 account ("backup2", "support") is a second root password that nobody rotates. Service accounts (UIDs below 1000 by convention on many distributions) run daemons; they should have a non-interactive shell such as /usr/sbin/nologin, so that a stolen service credential cannot be used to log in. Person accounts should map to someone on the current authorization roster; a vendor account that sits in sudo permanently is standing privilege that should be time-bound (Chapter 10). Group membership is part of the identity: being in sudo or ops-admins is often more important than the account itself.

3.4 Worked example: auditing HIST-02 (synthetic)

hist-02 stores Kestrel Water's time series. KVROC policy: SSH only on the management address 10.90.0.21; no root login; keys only; at most three attempts; only J. Tan and R. Kaur authorized.

Lab 3 (save as lab03.py, run with python3 lab03.py):

# Lab 3 - audit a host configuration and its local accounts (offline, standard library only)
# Synthetic files from historian host HIST-02 at the Kestrel Vale operations centre.
sshd_config = """
Port 22
ListenAddress 0.0.0.0
PermitRootLogin yes
PasswordAuthentication yes
# PasswordAuthentication no
PubkeyAuthentication yes
X11Forwarding yes
MaxAuthTries 6
AllowGroups ops-admins
"""
passwd = """root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
histsvc:x:998:998:historian service:/opt/hist:/bin/bash
jtan:x:1001:1001:J. Tan:/home/jtan:/bin/bash
rkaur:x:1002:1002:R. Kaur:/home/rkaur:/bin/bash
backup2:x:0:0:temp backup:/root:/bin/bash
vendor:x:1003:1003:vendor support:/home/vendor:/bin/bash"""
group = """ops-admins:x:1100:jtan,vendor
sudo:x:27:jtan,rkaur,vendor"""
policy = {"PermitRootLogin": "no", "PasswordAuthentication": "no", "X11Forwarding": "no",
          "ListenAddress": "10.90.0.21", "MaxAuthTries": "3"}
roster = {"jtan", "rkaur"}                     # people currently authorized on HIST-02
eff = {}
for line in sshd_config.splitlines():
    line = line.split("#", 1)[0].strip()
    if not line: continue
    key, val = line.split(None, 1)
    eff.setdefault(key, val)                   # sshd uses the FIRST value it reads for a keyword
findings = []
for k, want in policy.items():
    if eff.get(k) != want: findings.append(f"sshd {k} is {eff.get(k)!r}, policy {want!r}")
for line in passwd.splitlines():
    name, _, uid, gid, gecos, home, shell = line.split(":")
    if uid == "0" and name != "root": findings.append(f"account {name} has UID 0 (root equivalent)")
    if 0 < int(uid) < 1000 and shell.endswith("sh") and not shell.endswith("nologin"):
        findings.append(f"service account {name} has interactive shell {shell}")
    if int(uid) >= 1000 and name not in roster: findings.append(f"person account {name} not on roster")
for line in group.splitlines():
    g, _, _, members = line.split(":")
    for m in members.split(","):
        if m and m not in roster: findings.append(f"group {g} contains {m} (not on roster)")
for f in findings: print(f)
print("findings:", len(findings))

Expected output (Python 3.13.5):

sshd PermitRootLogin is 'yes', policy 'no'
sshd PasswordAuthentication is 'yes', policy 'no'
sshd X11Forwarding is 'yes', policy 'no'
sshd ListenAddress is '0.0.0.0', policy '10.90.0.21'
sshd MaxAuthTries is '6', policy '3'
service account histsvc has interactive shell /bin/bash
account backup2 has UID 0 (root equivalent)
person account vendor not on roster
group ops-admins contains vendor (not on roster)
group sudo contains vendor (not on roster)
findings: 10

Ten findings. Note the PasswordAuthentication line: the file contains a commented no, which some reviewers read as "already fixed". It is a comment, and the effective value is yes. The most dangerous findings are backup2 with UID 0 (an unmanaged second root) and vendor in sudo (standing privileged supplier access). The historian service account with /bin/bash matters because the service's credentials are stored on disk for the historian to use.

A safe hardening order: (1) confirm a working key-based login for J. Tan and R. Kaur from jump-01; (2) restrict ListenAddress, PermitRootLogin, PasswordAuthentication in one change, keeping an existing session open while you test a new one; (3) remove backup2 after preserving its login history; (4) remove vendor from sudo and move vendor access to a time-bound procedure; (5) set the service account's shell to nologin after checking that no maintenance script logs in as it.

3.5 Practical activity

On a virtual machine you own, run sshd -T | sort and compare the output with the file. Add a duplicate keyword lower in the file, reload, and confirm that the first value wins. Record the exact OpenSSH version, because behaviour of Include and Match blocks varies by version.

Record the OpenSSH version and the path of every file that sshd -T shows as included. After you reload, open a second session before you close the first, so a bad ListenAddress or AllowGroups change cannot lock you out. Compare the effective values with Lab 3's policy table and note any keyword that appears twice in the file.

3.6 Common errors and limits

Exercises — chapter 3

EX009 — First value wins. Objective: reason about effective configuration. Inputs: Lab 3's sshd_config. Steps: (1) predict the effective PasswordAuthentication if the commented line is uncommented and left where it is; (2) say where the fix must go. Expected evidence: prediction and placement. Rubric: 3 = still yes because the earlier line wins; the fix replaces the first occurrence (or the earlier line is deleted), verified with sshd -T; 2 = right answer, no verification; 1 = predicts no; 0 = none.

EX010 — Prioritize the HIST-02 findings. Objective: order changes by risk and safety. Inputs: Lab 3 output. Steps: group the ten findings into today / this week / next maintenance window, with one-line reasons and a preservation step where needed. Expected evidence: a three-group list. Rubric: 3 = UID-0 backup2 and vendor sudo today with login history preserved first; SSH binding and root/password settings as one tested change; service shell after script check; 2 = sensible but no preservation; 1 = unordered; 0 = none.

EX011 — Extend the audit. Objective: add a check. Inputs: Lab 3. Steps: add a check that flags any account other than root whose shell is interactive and whose home directory is /root or /. Report the new output. Expected evidence: code and output. Rubric: 3 = flags backup2 (home /root) and only it; explains why shared home directories hide accountability; 2 = works without explanation; 1 = attempted; 0 = none.

EX012 — Service account design. Objective: design a non-interactive identity. Inputs: the historian service needs to read files in /opt/hist and write to its database. Steps: specify UID range, shell, home, group, file permissions and how its credential is stored and rotated. Expected evidence: a short specification. Rubric: 3 = system UID, nologin shell, dedicated group, least-privilege file modes, credential readable only by the service, rotation owner and interval; 2 = one gap; 1 = generic; 0 = none.

Chapter synthesis

Operating-system hardening is checkable: effective settings, UIDs, shells and group memberships can be read and compared with a written policy. Know how the daemon actually reads its file, look at numbers rather than names, and order changes so that you never lock out the people who run the service.

Continue studying — chapter 03 of 18

← Chapter 02 · Course chapter index · Chapter 04 →

Continue to the next authored lesson.

Worked answers

EX009 — First value wins. If the commented line is uncommented where it stands (after PasswordAuthentication yes), the effective value is still yes because sshd uses the first value read. The fix must replace the first occurrence (or delete it), then be confirmed with sshd -T | grep -i passwordauthentication.

EX010 — Prioritize the HIST-02 findings. Today: preserve last/lastlog/auth logs for backup2 and vendor; remove backup2 (UID 0) and remove vendor from sudo and ops-admins. This week, as one tested change from an open session: ListenAddress 10.90.0.21, PermitRootLogin no, PasswordAuthentication no (after confirming key logins for both authorized people), X11Forwarding no, MaxAuthTries 3. Next window: set histsvc shell to /usr/sbin/nologin after checking scheduled jobs and scripts. Vendor access moves to the time-bound process of Chapter 10.

EX011 — Extend the audit. Add inside the passwd loop: if name != "root" and home in ("/root", "/") and not shell.endswith("nologin"): findings.append(f"account {name} has interactive shell and home {home}"). Output adds account backup2 has interactive shell and home /root, findings 11. A shared home directory mixes shell history and keys between identities, so actions cannot be attributed.

EX012 — Service account design. System UID (for example 998), shell /usr/sbin/nologin, home /opt/hist owned by root and readable by group histsvc; data directory writable only by histsvc; database credential in a file mode 0400 owned by histsvc, or in the platform's secret store; rotated yearly and after any staff change with access, owner KVROC platform team; the account is never used by people.