initial commit
This commit is contained in:
166
README.md
Normal file
166
README.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# AAD Local File Share (Samba + Web UI)
|
||||
|
||||
Dockerized SMB/CIFS file sharing system for a single on-prem Linux host. Authentication is via on-prem AD DS (Samba `security = ADS` with winbind). Authorization and share management live exclusively in local SQLite, driven by the web UI.
|
||||
|
||||
## Architecture highlights
|
||||
|
||||
- **Authentication:** Samba joins AD as a member server (`security = ADS`). SMB1 is disabled; SMB2+ only. NTLMv2 is allowed for non-domain-joined devices.
|
||||
- **Authorization:** SQLite is the source of truth (no AD groups). Share membership is expanded to per-user lists in a generated Samba config file.
|
||||
- **Share model:**
|
||||
- `\\server\private` is a single share that maps to `/data/private/%U` with per-user content.
|
||||
- `\\server\<shareName>` are separate shares pointing to `/data/shares/<shareName>`.
|
||||
- **No per-folder ACLs:** Samba config uses `force user = filesvc` and `force group = filesvc` for shared areas. `nt acl support = no` and related toggles prevent ACL edits from clients.
|
||||
- **Dynamic share reload:** Web UI regenerates `/etc/samba/shares.generated.conf` atomically and Samba reloads via `smbcontrol all reload-config` on change.
|
||||
- **Backups:** Scheduled ISO backups with a tar-based payload (`data.tar.zst`) + SQLite `.backup` file + `manifest.json` containing hashes/sizes.
|
||||
|
||||
## Directory layout
|
||||
|
||||
Host directories:
|
||||
|
||||
- `/srv/files/data` -> `/data`
|
||||
- `/data/private/<username>`
|
||||
- `/data/shares/<shareName>`
|
||||
- `/srv/files/remote-backup` -> `/remote` (bind-mounted remote share)
|
||||
|
||||
SQLite location:
|
||||
|
||||
- `/var/lib/webui/app.db` (inside `webui` container, persisted via volume)
|
||||
|
||||
## Quick start
|
||||
|
||||
1. **Clone repo and create env file**
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. **Generate self-signed TLS certs for Traefik**
|
||||
|
||||
```bash
|
||||
./scripts/generate-self-signed.sh ./traefik/certs files.example.com
|
||||
```
|
||||
|
||||
3. **Create host directories**
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /srv/files/data /srv/files/remote-backup
|
||||
```
|
||||
|
||||
4. **Start the stack**
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
5. **Open the Web UI**
|
||||
|
||||
- `https://files.example.com/`
|
||||
|
||||
## Samba security notes
|
||||
|
||||
- **SMB1 is disabled** (`server min protocol = SMB2`).
|
||||
- **NTLMv2** is allowed for non-domain-joined clients (`ntlm auth = ntlmv2-only`).
|
||||
- Recommended hardening: prefer Kerberos and domain-joined devices, set `server signing = mandatory`, and enable `smb encrypt = desired` or `required` based on performance and compliance needs.
|
||||
- **No ACL editing**: `nt acl support = no`, `dos filemode = no`, and `unix extensions = no` reduce client-side permission manipulation.
|
||||
|
||||
## Share behavior
|
||||
|
||||
- **Private share:** `\\server\private` maps to `/data/private/%U` and creates the user directory on first connect. Directory is `0700` and owned by the user (mapped via winbind).
|
||||
- **Shared shares:** Each share is a distinct Samba stanza in `/etc/samba/shares.generated.conf`. Users are allowed via per-user lists from SQLite:
|
||||
- `valid users = <owner+rw+ro users>`
|
||||
- `write list = <owner+rw users>`
|
||||
- `read only = yes` (writes allowed via `write list`)
|
||||
|
||||
**Important:** Shared area permissions are enforced by Samba config (lists), not filesystem ACLs. All files are owned by `filesvc` inside the Samba container due to `force user/group`.
|
||||
|
||||
The `filesvc` UID/GID is `10050` by default in both `webui` and `samba` containers so ownership stays consistent on the host bind mount.
|
||||
|
||||
## Web UI
|
||||
|
||||
The Web UI authenticates via Entra ID OIDC (authorization code flow) and stores sessions in an HTTP-only cookie. Users are authorized if their UPN suffix matches `ALLOWED_UPN_SUFFIX`.
|
||||
|
||||
### Admin page
|
||||
|
||||
`/admin` shows access logs and aggregated statistics with Chart.js (daily activity and action breakdown). Access is restricted to the admin user via a bcrypt hash in `.env`.
|
||||
|
||||
To generate a bcrypt hash for the admin UPN:
|
||||
|
||||
```bash
|
||||
node -e "const bcrypt=require('bcryptjs'); const upn=process.argv[1]; console.log(bcrypt.hashSync(upn, 10));" "admin@example.com"
|
||||
```
|
||||
|
||||
Set the output in `ADMIN_UPN_BCRYPT`.
|
||||
|
||||
### Pages
|
||||
|
||||
- `/` list shares, create share
|
||||
- `/shares/:id` manage membership
|
||||
- `/admin` logs and statistics
|
||||
|
||||
### APIs
|
||||
|
||||
- `POST /api/shares` create share
|
||||
- `GET /api/shares` list shares visible to current user
|
||||
- `GET /api/shares/:id` share detail
|
||||
- `POST /api/shares/:id/members` add/remove users or local groups
|
||||
- `DELETE /api/shares/:id` disable share
|
||||
|
||||
Local groups are managed in the UI on each share page and can be assigned roles per share.
|
||||
|
||||
### Create share flow
|
||||
|
||||
1. Validate share name (strict regex, length limits, reserved names).
|
||||
2. Insert into SQLite with state `creating`.
|
||||
3. Create `/data/shares/<shareName>`.
|
||||
4. `chown root:filesvc` and `chmod 2770`.
|
||||
5. Regenerate `/samba-generated/shares.generated.conf` atomically.
|
||||
6. Mark share `ready`.
|
||||
|
||||
## Backup job
|
||||
|
||||
Backups run on the `BACKUP_CRON` schedule and produce:
|
||||
|
||||
- `data.tar.zst` (tar archive of `/data` with xattrs/acl metadata)
|
||||
- `app.db` (SQLite `.backup`)
|
||||
- `manifest.json` (timestamp, sizes, sha256)
|
||||
- `backup-<timestamp>.iso` containing the above, then compressed to `.iso.zst` (or `.iso.gz`)
|
||||
|
||||
**Note:** The ISO file itself does not preserve POSIX ACLs; the tar file inside does.
|
||||
|
||||
### Restore steps
|
||||
|
||||
1. Copy the compressed ISO back from `/remote`.
|
||||
2. Decompress it.
|
||||
3. Extract the ISO contents.
|
||||
4. Decompress `data.tar.zst` and restore `/data` on the host.
|
||||
5. Restore `app.db` to the web UI volume.
|
||||
6. Redeploy compose. Re-join AD if the Samba secrets volume is lost.
|
||||
|
||||
## Connecting clients
|
||||
|
||||
### Windows
|
||||
|
||||
- `\\server\private`
|
||||
- `\\server\shareName`
|
||||
|
||||
Use `DOMAIN\user` or `user@domain` when prompted. NTLMv2 is supported for non-domain-joined devices.
|
||||
|
||||
### macOS
|
||||
|
||||
- Finder -> Go -> Connect to Server
|
||||
- `smb://server/private`
|
||||
- `smb://server/shareName`
|
||||
|
||||
### Linux
|
||||
|
||||
```bash
|
||||
smbclient -U user@domain //server/private
|
||||
smbclient -U user@domain //server/shareName
|
||||
```
|
||||
|
||||
## Files of interest
|
||||
|
||||
- `docker-compose.yml`
|
||||
- `samba/smb.conf.template`
|
||||
- `webui/src/index.js`
|
||||
- `backupd/backup.sh`
|
||||
Reference in New Issue
Block a user