Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub

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

LayerComponentsResponsibility
Layer 1: macOS HostFinder, Docker CLI, User TerminalClient interface and container orchestration
Layer 2: Docker Containersmbd, sshfs, filesystem integrationProtocol translation and filesystem bridging
Layer 3: Remote InfrastructureSSH server with target filesystemData 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:

Directory Structure:

User Context:

  • sshuser account 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:

README.md49

OptionPurposeConsequence if Omitted
allow_otherPermits access by users other than mount ownerSamba cannot read mounted files
uid=1000Sets file owner to match sshuser UIDWrite operations fail (read-only)
gid=1000Sets file group to match sshuser GIDWrite 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:

  1. SSHFS mount must include uid=1000,gid=1000 README.md53
  2. Samba must use force user = sshuser smb.conf19
  3. Directory permissions must be 777 Dockerfile21

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:

README.md31

FlagPurposeSecurity Implication
--privilegedAllows FUSE operationsContainer has elevated privileges
-p 127.0.0.1:139:139Forward NetBIOS port to localhost onlyNot accessible from external network
-p 127.0.0.1:445:445Forward SMB port to localhost onlyNot accessible from external network

Exposed Ports in Dockerfile:

Dockerfile27

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:

README.md60

Connection String:

smb://172.17.0.2  # Example IP, varies per container

README.md65

Network Topology Table

Network ElementAddress/PortAccess ScopeConfiguration Source
Samba NetBIOSContainer:139Container internalDockerfile27
Samba SMBContainer:445Container internalDockerfile27
Host NetBIOS127.0.0.1:139Localhost onlyREADME.md31
Host SMB127.0.0.1:445Localhost onlyREADME.md31
Container IP172.17.0.0/16Docker bridge networkDocker runtime
Remote SSHRemote:22Internet/VPNUser 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:

smb.conf:3-14

SettingValueEffect
securityuserRequires user-level authentication
map to guestbad userInvalid usernames map to guest account
guest okyesAllow guest connections
guest onlyyesForce 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:

smb.conf19

Purpose: All file operations, regardless of the connecting user's identity, execute as sshuser (UID 1000). This ensures:

  1. Consistent ownership of created files
  2. Write permissions match SSHFS mount options
  3. No privilege escalation beyond container boundaries

User Creation:

Dockerfile9

Privileged Container Requirement

Flag:

README.md31

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:

smb.conf:7-8

Rationale: SMB1 has known vulnerabilities. Enforcing SMB2 minimum ensures secure communication between Finder and the container.

Filesystem Permission Model

Samba Share Permissions:

Dockerfile21

PermissionUserGroupOthers
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:

README.md49

Forces all files in the remote mount to appear owned by UID 1000 (sshuser), enabling write operations.

Security Boundaries Summary

BoundaryProtectionThreat Model
Docker isolationContainer → HostLimits impact of container compromise
Localhost bindingExternal → ContainerPrevents network exposure
Forced user identityClient → FilesystemPrevents privilege escalation via SMB
SMB2 enforcementClient → ServerPrevents SMB1 vulnerabilities
SSH encryptionContainer → RemoteProtects 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