Case Study • Secure SMB over WireGuard
Case Study Post-incident technical analysis

Secure SMB File Sharing over WireGuard (VPN).
An end-to-end security implementation.

A practical case study showing how an SMB share can appear “configured” yet fail in real conditions (VPN path, Windows policies, Samba auth, and Linux filesystem authorization) — and how it was fixed using evidence, not guesswork.

SOC mindset WireGuard VPN Samba (SMB2/SMB3) UFW segmentation Windows security policy Linux ownership + mode bits

What looked “fixed”… wasn’t !

Symptom: “Windows can’t access \\web-vm\Public”
False signal: SMB login succeeded — access still failed
The trap: a certificate prompt that wasn’t the real root cause
Twist: Windows policy/protocol choices blocked first → Linux permissions traversal did.
Policy ≠ Protocol Auth ≠ Access Path permissions matter

How it was solved

Gate 1 (Client): Windows stopped hijacking the connection (policy disabled)
Gate 2 (Boundary): SMB stayed reachable only through WireGuard (VPN-only)
Gate 3 (Network): port 445 allowed on the VPN interface, never on WAN
Gate 4 (Access): ownership + execute/traverse bits aligned across the full path
Proof: Explorer listing + file ops + smbclient //127.0.0.1/Public -U adminsmb
Access restored VPN-only SMB Verified end-to-end
Exposure
VPN-only
SMB stays inside WireGuard
Controls
Port 445 scoped
UFW Opens only on the VPN interface
Incident
Policy + perms
Windows policy → Linux traversal (gates)
Proof
End-to-end
Explorer, localhost test, and many ...
Chapter 01

Architecture that makes the boundary obvious

Architecture Overview
WireGuard VM + SMB VM (vm-resume) + Windows client path, security boundaries included.
Trust boundary
Architecture diagram
Key learnings
Trust boundary: SMB stays VPN-only (10.8.0.0/24); public exposure is HTTP/HTTPS via Nginx/Cloudflare.
Interface-aware security: UFW must allow 445 on the correct WireGuard interface (wg0-client), not “any VPN”.
Name resolution matters: \\web-vm\Public vs \\10.8.0.3\Public highlights an extra layer (DNS/hosts/NetBIOS) that can mask root causes.
Chapter 02

Authentication flow — when “login succeeded” still means “access denied”

The layered gate (Windows first, Linux second)
Windows → SMB session → Samba mapping → PAM session → Linux filesystem checks.
Root cause lens
Authentication Flow
Key learnings
Auth ≠ Access: login success doesn’t guarantee file access.
Windows policy: client-side policies can block before SMB fully works.
Local test: smbclient on 127.0.0.1 isolates the server layer.
Auth ≠ Access: “net use succeeded” can still fail if Linux permissions block directory traversal [execute bit (x) on parents].
Cert prompt was a trap: Windows WebClient/WebDAV provider can trigger certificate selection; disabling it removes noise, not root cause.
Isolate layers: smbclient to 127.0.0.1 validated Samba locally → remaining issue lived in Windows policy/provider + filesystem permissions.
Chapter 03

Incident timeline — the moment the “certificate issue” stopped making sense

Checkpoints & evidence
Each step ties a hypothesis to a measurable test (what changed, what proved it).
Evidence trail
Incident Timeline
Key learnings
Hypothesis loop: every change must be test-backed.
Layered checks: port → share → auth → filesystem.
End state: validate with Windows mapping + server logs.
Facts first: testparm + ss(445) confirmed server readiness; UFW confirmed VPN-scoped exposure.
Noise removal: eliminated Windows WebDAV/WebClient interference to stop certificate prompts and focus on SMB.
Proof-based closure: success criteria is Explorer listing + file operations, not “service running” or “port open”.
Chapter 04

Permission model — Where “access” actually gets decided.

The hidden gate: traversal + ownership decide everything.
SMB can authenticate cleanly while Linux blocks you silently. The outcome is decided by the execute bit on parent directories and a clean ownership / mode-bit alignment across the whole path.
Permission alignment
Permission Model
Key learnings
The real lock is traversal: a root-owned 700 on /srv/samba stops everything — the share can exist, auth can pass, and still never reach /srv/samba/public.
Fix = path alignment: give the parent directories the right execute (x) for traversal, then align the target with the SMB identity using chown + chmod (end-to-end, not just the folder).
Enterprise takeaway: the loudest “SMB problem” is often not SMB. Always audit the full permission chain (parents → share path), before changing protocols or blaming the VPN (I almost did !).
Minimal proof (screenshots)

Only what matters: mapping success, server state checks, and Explorer access.

No “pretty” screenshots — just hard evidence. A mapped drive that authenticates, Explorer behavior that reveals post-auth failures, and server-side validation (share config + port state) to eliminate guesswork.

net use success
Server: testparm validation (share definitions)
SMB local validation (smbclient)
Server-side sanity check: smbclient validates the share locally (removes VPN/Windows from the equation)
Firewall rules (SMB scoped to VPN)
Firewall control: SMB (445) is allowed only on the WireGuard interface — blocked from the public side
Server listening on SMB port 445
Service check: SMB is actually listening (port 445 open on the server)
Windows net use mapped drive success
Windows handshake: credentials accepted and the share maps successfully (net use)
Windows Explorer share accessible
User-level proof: Explorer lists the share and allows real file operations (post-auth access)
Proof, not vibes: “net use succeeded” confirms the SMB handshake + credentials, but it doesn’t prove you can traverse the share or touch files.
Layer isolation wins: smbclient to localhost + ss(445) + firewall rules remove guesswork and pin the failure to the next layer (Windows policy/provider or Linux permission chain).
Closure criteria: the incident is “done” only when Explorer can list + create/rename/delete (real file ops), with SMB still restricted to the WireGuard boundary.
Chapter 06

Solution playbook — make the noise disappear, then fix the gate.

Solution playbook (POC)

One incident, two blockers — Windows policy noise first, Linux permission gate second.

This is the exact “how-to-fix” path if you ever hit the same deceptive SMB failure over VPN: silence the certificate trap, enforce a clean SMB client policy, then align the filesystem permission chain.

Client-side remediation (Windows) — stop the certificate trap, force clean SMB behavior
Remove provider noise (WebDAV/WebClient), then apply consistent SMB client policies using PowerShell. This does not “disable security globally” — it prevents the wrong provider from hijacking the connection.
Client controls
# PowerShell (Run as Administrator)

# 1) Silence the certificate prompt source (WebDAV redirector)
Get-Service WebClient
Stop-Service WebClient -Force
Set-Service WebClient -StartupType Disabled

# (Optional) confirm provider order (WebClient should NOT be preferred)
reg query "HKLM\SYSTEM\CurrentControlSet\Control\NetworkProvider\Order" /v ProviderOrder

# 2) Force predictable SMB client behavior (SMB2/SMB3 only)
# Ensure SMB1 client is disabled
Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol -NoRestart

# Verify SMB client configuration
Get-SmbClientConfiguration | Select EnableSMB1Protocol, EnableSMB2Protocol, RequireSecuritySignature, EnableSecuritySignature

# 3) Clean stale sessions / credentials (prevents phantom logons)
net use * /delete /y
cmdkey /list
# (Remove only the relevant one if it exists)
# cmdkey /delete:TERMSRV/web-vm
# cmdkey /delete:web-vm

# 4) Reconnect (use hostname for your story consistency)
net use Z: \\web-vm\Public /user:adminsmb /persistent:yes
What this solves
Certificate popups: caused by the wrong redirector/provider (WebClient/WebDAV) competing with SMB.
Consistency: SMB2/3 enforced, reducing protocol confusion and legacy fallbacks.
False success cases: stale cached sessions can make “net use succeeded” look correct while access still breaks.
Server-side remediation (Linux) — align ownership + mode bits across the full path
Samba can authenticate successfully while Linux blocks traversal. The parent directory execute bit (x) and ownership decide access.
Permission alignment
# On the SMB server (vm-resume)

# 0) Inspect current permissions (this is where your story started)
sudo ls -la /srv/samba
sudo namei -l /srv/samba/public
sudo namei -l /srv/samba/direction

# 1) Make the parent path traversable (execute bit on directories)
# Keep it minimal: allow traversal without opening everything.
sudo chmod 755 /srv
sudo chmod 755 /srv/samba

# 2) Align ownership to the SMB user model (adminsmb)
sudo chown -R adminsmb:adminsmb /srv/samba/public
sudo chown -R adminsmb:adminsmb /srv/samba/direction

# 3) Set sane directory permissions (POC defaults)
# Directories: allow traverse + read, and controlled write
sudo find /srv/samba/public -type d -exec chmod 775 {} \;
sudo find /srv/samba/public -type f -exec chmod 664 {} \;

sudo find /srv/samba/direction -type d -exec chmod 775 {} \;
sudo find /srv/samba/direction -type f -exec chmod 664 {} \;

# 4) Validate Samba config and service state
sudo testparm -s
sudo ss -lntp | grep 445

# 5) Local functional test (removes Windows + VPN from the equation)
smbclient //127.0.0.1/Public -U adminsmb -c "ls"
smbclient //127.0.0.1/Direction -U adminsmb -c "ls"
Why this works
Traversal is the real gate: without execute (x) on /srv and /srv/samba, clients authenticate but cannot enter the share path.
Ownership removes ambiguity: aligning to adminsmb ensures Samba’s mapped user can pass filesystem checks.
Local smbclient is the truth: if localhost works, the server is fine — remaining issues are Windows/VPN policies.
Success criteria: Explorer lists the share, file ops work, and localhost smbclient confirms server behavior independently.
Security preserved: SMB remains VPN-only; port 445 stays scoped to the WireGuard interface.
Reusable pattern: silence client-side noise → isolate layers → fix the true authorization gate.
Chapter 06

Reproduce quickly — a portable lab with deterministic validation.

Quickstart (10 minutes)
Provision → configure → align permissions → verify locally → verify remotely.
# 1) On the SMB VM (vm-resume)
sudo apt update
sudo apt install -y samba smbclient ufw

# 2) Apply sanitized config + restart
sudo cp ./server/smb.conf.sanitized /etc/samba/smb.conf
sudo testparm -s
sudo systemctl restart smbd nmbd
sudo ss -lntp | grep ':445'

# 3) Create share paths + enforce ownership/permissions
sudo mkdir -p /srv/samba/public /srv/samba/direction
sudo chown -R adminsmb:adminsmb /srv/samba/public /srv/samba/direction

# Parent traversal must be possible (execute bit on the chain)
sudo chmod 755 /srv /srv/samba

# Directory defaults (share folders)
sudo find /srv/samba/public -type d -exec chmod 775 {} \;
sudo find /srv/samba/public -type f -exec chmod 664 {} \;

# 4) Local truth test (removes Windows/VPN entirely)
smbclient //127.0.0.1/Public -U adminsmb -c "ls"

# 5) Remote functional test (Windows after WireGuard is connected)
powershell -ExecutionPolicy Bypass -File .\windows\map-drive.ps1
  • End state: local SMB lists files, remote Windows maps + performs file operations.
  • Design constraint: SMB remains VPN-only (boundary enforced by firewall).
Validation script (single command)
Verifies config integrity, service state, local SMB access, and boundary intent.
# On SMB VM (from the pack root)
chmod +x ./server/validate.sh
./server/validate.sh

# validate.sh should cover:
# - testparm -s                         (config parses; shares present)
# - ss -lntp | grep :445                (smbd listening)
# - smbclient //127.0.0.1/Public ...    (local functional test)
# - ufw status verbose                  (VPN-only boundary reflected)
Determinism: the lab returns the same verdict after changes — pass/fail is explicit, not inferred.
Fast isolation: if localhost SMB succeeds, the server layer is confirmed; remaining variance is client/VPN policy.
Repeatability: validation becomes a routine gate (before/after edits), not a one-time debug step.
Success indicators
Concrete signals that confirm each layer is healthy.
  • testparm -s → no fatal errors; [Public] and [Direction] appear.
  • ss -lntp | grep :445smbd is listening (scope matches the boundary design).
  • smbclient localhost → lists content (no timeout, no NT_STATUS_ACCESS_DENIED).
  • net use → “The command completed successfully.”
  • Explorer → listing + file operations succeed (create/rename/delete).
Guardrails (prevent relapse)
Controls that keep the boundary strict and reduce “shape-shifting” failure modes.
# Server-side intent (documented in smb.conf.sanitized)
# - Enforce modern dialects
#   server min protocol = SMB2
#   server max protocol = SMB3

# Boundary intent (firewall)
# - Allow TCP/445 only from the WireGuard subnet/interface
# - Deny TCP/445 on the public NIC

# Client-side stability (Windows)
# - Disable WebClient if it reintroduces provider/certificate noise
# - Clear stale SMB sessions if mapping becomes inconsistent
  • Anti-pattern: exposing 445 publicly to “test quickly”.
  • Preferred: interface-aware allow rules + localhost validation gates.
Rollback (safe reversibility)
Snapshot first. Revert configuration without improvisation.
# BEFORE changes (snapshot habit)
sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.bak.$(date +%F-%H%M)
ls -la /srv/samba /srv/samba/public /srv/samba/direction

# OPTIONAL: capture ACL state if ACLs are used
getfacl -R /srv/samba > /srv/samba.acl.bak 2>/dev/null || true

# Rollback smb.conf quickly
sudo cp /etc/samba/smb.conf.bak.* /etc/samba/smb.conf
sudo systemctl restart smbd nmbd
Portable pack (sanitized, self-contained)
Minimal artifacts required to replicate, validate, and understand — without leaking secrets.
pack/
├─ ci/                       # Documentation for CI runners
│  ├─ README.md
│  └─ github-actions.yml
├─ diagram/                  # All the diagrams shown in this demo
│  ├─ architecture.svg
│  ├─ auth-flow.svg
│  ├─ incident-timeline.svg
│  └─ permission-model.svg
├─ proof/                    # The 6 evidence screenshots used in this case study
├─ server/
│  ├─ apply-perms.sh
│  ├─ reset-smb.sh
│  ├─ run-demo.sh
│  ├─ set-ufw-rules.sh
│  ├─ smb.conf.sanitized
│  ├─ snapshot.sh
│  └─ validate.sh
├─ windows/
│  ├─ disable-webclient.ps1
│  ├─ map-drive.ps1
│  ├─ policy-check.ps1
│  └─ reset-smb.ps1
└─ README.md
                    
README.md: environment + verification steps + expected outputs in one place.
policy-notes.md: Windows-side changes documented as decisions, not guesses.
validate.sh: repeatable health gate after any configuration change.

From observation to verified resolution

This case study documents the resolution of an SMB access failure over WireGuard, treated as a production incident rather than a configuration exercise. Each layer was isolated, tested, and validated against observable behavior, with security boundaries preserved throughout the process.

Access the complete case study

The Secure SMB over WireGuard repository contains the full, sanitized incident package: architecture diagrams, configuration artifacts, validation scripts, and evidence used to confirm the final state.

Check the SMB Pack