This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Architecture
Relevant source files
Purpose and Scope
This document provides a detailed examination of the sshfs-mac-docker system architecture, explaining how the three-tier design enables remote filesystem access on macOS without kernel extensions. It covers the protocol translation mechanism, network topology, and security model.
For step-by-step usage instructions, see Getting Started. For implementation details of the Docker container, see Container Implementation. For configuration reference, see Configuration Reference.
3.1 Three-Tier Design
The system implements a three-tier architecture that isolates filesystem operations within a Docker container, avoiding the need for macFUSE or kernel extensions on the macOS host.
Layer Overview
| Layer | Components | Responsibility |
|---|---|---|
| Layer 1: macOS Host | Finder, Docker CLI, User Terminal | Client interface and container orchestration |
| Layer 2: Docker Container | smbd, sshfs, filesystem integration | Protocol translation and filesystem bridging |
| Layer 3: Remote Infrastructure | SSH server with target filesystem | Data source |
Container as Protocol Bridge
The Docker container serves as an isolation boundary where FUSE operations can execute without host kernel modifications. The container contains:
Core Services:
smbdprocess running as foreground service Dockerfile33sshfsclient for SSH filesystem mounting Dockerfile6
Directory Structure:
/remote- SSHFS mount point Dockerfile12/samba-share- Samba share root with777permissions Dockerfile:15-21/samba-share/remote- Symbolic link to/remoteDockerfile30
User Context:
sshuseraccount with UID 1000 Dockerfile9- Used by both SSHFS mounts and Samba file operations smb.conf19
Architectural Decision: Why Containerization
Design Rationale: By moving FUSE operations into a Linux container where FUSE is a standard kernel feature, the system avoids the security and compatibility issues of third-party kernel extensions on macOS.
Sources: README.md:3-5 Dockerfile:1-6
3.2 Protocol Translation
The system bridges SSH and SMB protocols through a filesystem-based integration using SSHFS mounts and symbolic links.
flowchart LR
subgraph Remote["Remote SSH Server"]
RemoteFS["Remote Filesystem\nuser@host:path"]
end
subgraph Container["docker-sshfs Container"]
direction TB
subgraph SSHLayer["SSH/SSHFS Layer"]
sshfsCmd["sshfs command\n-o allow_other\n-o uid=1000\n-o gid=1000"]
remoteMnt["/remote mount point"]
end
subgraph FSLayer["Filesystem Integration"]
symlink["/samba-share/remote\nsymbolic link"]
sambaShare["/samba-share\nchmod 777"]
end
subgraph SMBLayer["Samba Layer"]
smbConf["smb.conf\nforce user = sshuser"]
smbd["smbd process\nports 139/445"]
end
sshfsCmd -->|Mounts| remoteMnt
remoteMnt -.->|ln -s /remote| symlink
symlink --> sambaShare
smbConf -->|Configures| smbd
smbd -->|Serves| sambaShare
end
subgraph macOS["macOS Host"]
Finder["Finder SMB Client\nsmb://container-ip"]
end
RemoteFS -->|SSH Protocol| sshfsCmd
smbd -->|SMB Protocol| Finder
Protocol Flow
Critical Integration Points
SSHFS Mount Command:
| Option | Purpose | Consequence if Omitted |
|---|---|---|
allow_other | Permits access by users other than mount owner | Samba cannot read mounted files |
uid=1000 | Sets file owner to match sshuser UID | Write operations fail (read-only) |
gid=1000 | Sets file group to match sshuser GID | Write operations fail (read-only) |
Symbolic Link Integration:
The link at Dockerfile30 connects the SSHFS mount point to the Samba share:
This creates a zero-copy integration where files are not duplicated. The symbolic link allows Samba to serve files that exist only in the FUSE mount.
FUSE Configuration:
The user_allow_other setting Dockerfile24 modifies /etc/fuse.conf to enable the allow_other mount option. Without this, FUSE would reject the option even if specified.
sequenceDiagram
participant Finder as "macOS Finder"
participant smbd as "smbd process"
participant fs as "/samba-share"
participant link as "Symbolic Link"
participant remote as "/remote (FUSE)"
participant sshfs as "sshfs client"
participant ssh as "Remote SSH Server"
Note over Finder,ssh: Read Operation
Finder->>smbd: SMB read request
smbd->>fs: Access /samba-share/remote/file.txt
fs->>link: Follow symlink
link->>remote: Read from /remote/file.txt
remote->>sshfs: FUSE read operation
sshfs->>ssh: SSH SFTP read
ssh-->>sshfs: File data
sshfs-->>remote: Return data
remote-->>link: Return data
link-->>fs: Return data
fs-->>smbd: Return data
smbd-->>Finder: SMB read response
Note over Finder,ssh: Write Operation
Finder->>smbd: SMB write request
smbd->>fs: Write to /samba-share/remote/file.txt\n(as sshuser via force user)
fs->>link: Follow symlink
link->>remote: Write to /remote/file.txt\n(uid=1000 allows write)
remote->>sshfs: FUSE write operation
sshfs->>ssh: SSH SFTP write
ssh-->>sshfs: Write confirmation
sshfs-->>remote: Write complete
remote-->>link: Write complete
link-->>fs: Write complete
fs-->>smbd: Write complete
smbd-->>Finder: SMB write response
Bidirectional Data Flow
Write Access Requirements:
- SSHFS mount must include
uid=1000,gid=1000README.md53 - Samba must use
force user = sshusersmb.conf19 - Directory permissions must be
777Dockerfile21
Without all three, writes from macOS will fail with permission errors.
Sources: README.md:46-53 Dockerfile:23-30 smb.conf:10-19
graph TB
subgraph HostNetwork["macOS Host (127.0.0.1)"]
direction TB
HostPort139["Host Port 139\nNetBIOS Session"]
HostPort445["Host Port 445\nSMB over TCP"]
FinderClient["Finder SMB Client"]
DockerEngine["Docker Engine"]
end
subgraph DockerNetwork["Docker Bridge Network"]
ContainerIP["Container IP\n(e.g., 172.17.0.2)\nDynamic Assignment"]
subgraph Container["docker-sshfs Container"]
ContainerPort139["Container Port 139\nEXPOSE 139"]
ContainerPort445["Container Port 445\nEXPOSE 445"]
smbd["smbd --foreground"]
end
end
subgraph External["External Network"]
RemoteSSH["Remote SSH Server\nPort 22"]
end
DockerEngine -->|-p 127.0.0.1:139:139| HostPort139
DockerEngine -->|-p 127.0.0.1:445:445| HostPort445
HostPort139 -.->|Forwarded| ContainerPort139
HostPort445 -.->|Forwarded| ContainerPort445
ContainerPort139 --> smbd
ContainerPort445 --> smbd
FinderClient -.->|Must use Container IP NOT localhost| ContainerIP
ContainerIP --> smbd
Container -->|Outbound SSH Port 22| RemoteSSH
3.3 Network Architecture
The network topology uses port forwarding to expose Samba services while maintaining localhost-only access from the host.
Port Mapping Configuration
Port Forwarding Analysis
Docker Run Command:
| Flag | Purpose | Security Implication |
|---|---|---|
--privileged | Allows FUSE operations | Container has elevated privileges |
-p 127.0.0.1:139:139 | Forward NetBIOS port to localhost only | Not accessible from external network |
-p 127.0.0.1:445:445 | Forward SMB port to localhost only | Not accessible from external network |
Exposed Ports in Dockerfile:
These declarations document the ports but do not create actual port mappings. The mappings are created by the -p flags in the docker run command.
Localhost Connection Limitation
Issue: Despite port forwarding to 127.0.0.1, Finder cannot connect using smb://localhost or smb://127.0.0.1.
Reason: The SMB protocol implementation in macOS Finder requires discovery of the container's actual IP address within the Docker bridge network.
Discovery Command:
Connection String:
smb://172.17.0.2 # Example IP, varies per container
Network Topology Table
| Network Element | Address/Port | Access Scope | Configuration Source |
|---|---|---|---|
| Samba NetBIOS | Container:139 | Container internal | Dockerfile27 |
| Samba SMB | Container:445 | Container internal | Dockerfile27 |
| Host NetBIOS | 127.0.0.1:139 | Localhost only | README.md31 |
| Host SMB | 127.0.0.1:445 | Localhost only | README.md31 |
| Container IP | 172.17.0.0/16 | Docker bridge network | Docker runtime |
| Remote SSH | Remote:22 | Internet/VPN | User specified |
Sources: README.md:31-65 Dockerfile27
graph TB
subgraph Boundary["Security Boundary: Docker Container"]
direction TB
subgraph Access["Access Control Layer"]
GuestAuth["Guest Authentication\nmap to guest = bad user\nguest ok = yes"]
ForceUser["Force User Mapping\nforce user = sshuser\nAll operations as UID 1000"]
end
subgraph Privilege["Privilege Layer"]
PrivContainer["Privileged Container\n--privileged flag\nRequired for FUSE"]
SSHUserCtx["sshuser Context\nUID=1000 GID=1000\nPassword: sshpass"]
end
subgraph Protocol["Protocol Security"]
SMB2["SMB2+ Only\nclient min protocol = SMB2\nserver min protocol = SMB2"]
SSHEncrypt["SSH Encryption\nSFTP operations"]
end
subgraph FileSystem["Filesystem Permissions"]
Dir777["/samba-share\nchmod 777\nWorld writable"]
FUSEOpts["SSHFS Options\nuid=1000 gid=1000\nForce ownership"]
end
end
subgraph Threats["Mitigated Threats"]
NoAuth["Unauthenticated Access\nfrom macOS"]
MultiUser["Multiple User Identities"]
Legacy["Legacy SMB1 Protocol"]
end
GuestAuth -.->|Mitigates| NoAuth
ForceUser -.->|Mitigates| MultiUser
SMB2 -.->|Mitigates| Legacy
subgraph Accepted["Accepted Risks"]
PrivEsc["Container Privilege Escalation"]
GuestAccess["Guest Access Model"]
LocalOnly["Localhost Trust Model"]
end
PrivContainer -.->|Accepts| PrivEsc
GuestAuth -.->|Accepts| GuestAccess
3.4 Security Model
The system implements a multi-layer security model balancing convenience (guest access) with controlled access through user identity enforcement.
Security Architecture
Guest Authentication Model
Samba Configuration:
| Setting | Value | Effect |
|---|---|---|
security | user | Requires user-level authentication |
map to guest | bad user | Invalid usernames map to guest account |
guest ok | yes | Allow guest connections |
guest only | yes | Force all connections to use guest account |
Workflow: When Finder connects with any credentials (or no credentials), Samba maps the connection to the guest account, which is then forced to operate as sshuser.
Forced User Identity
Configuration:
Purpose: All file operations, regardless of the connecting user's identity, execute as sshuser (UID 1000). This ensures:
- Consistent ownership of created files
- Write permissions match SSHFS mount options
- No privilege escalation beyond container boundaries
User Creation:
Privileged Container Requirement
Flag:
Justification: FUSE operations require device access (/dev/fuse) and the ability to perform mount operations, which are restricted in unprivileged containers.
Risk: The privileged flag grants the container extensive capabilities. Compromise of the container could affect the host system.
Mitigation:
- Localhost-only port binding (
127.0.0.1) README.md31 - No direct host filesystem mounts
- Isolated network namespace (Docker bridge)
Protocol Enforcement
SMB Protocol Version:
Rationale: SMB1 has known vulnerabilities. Enforcing SMB2 minimum ensures secure communication between Finder and the container.
Filesystem Permission Model
Samba Share Permissions:
| Permission | User | Group | Others |
|---|---|---|---|
| Read | ✓ | ✓ | ✓ |
| Write | ✓ | ✓ | ✓ |
| Execute | ✓ | ✓ | ✓ |
Why 777: The directory must be writable by the Samba process (running as smbd) and accessible through the symbolic link to the FUSE mount. Combined with force user, all writes ultimately occur as sshuser.
SSHFS Mount Ownership:
Forces all files in the remote mount to appear owned by UID 1000 (sshuser), enabling write operations.
Security Boundaries Summary
| Boundary | Protection | Threat Model |
|---|---|---|
| Docker isolation | Container → Host | Limits impact of container compromise |
| Localhost binding | External → Container | Prevents network exposure |
| Forced user identity | Client → Filesystem | Prevents privilege escalation via SMB |
| SMB2 enforcement | Client → Server | Prevents SMB1 vulnerabilities |
| SSH encryption | Container → Remote | Protects data in transit |
Limitations:
- Guest authentication provides no user accountability
- Privileged container has extensive host capabilities
- No authentication on SMB connection
- Password stored in image (
sshpass) Dockerfile9
Sources: smb.conf:1-19 Dockerfile:9-21 README.md31