-
-
Notifications
You must be signed in to change notification settings - Fork 242
feat: SFTP access control, enforcement mode, file transfer logging, and user role expiry#1681
feat: SFTP access control, enforcement mode, file transfer logging, and user role expiry#1681mrmm wants to merge 38 commits intowarp-tech:mainfrom
Conversation
Closes #1679
What this does
Adds SFTP file transfer access control, an instance-wide enforcement mode, audit logging, and user role expiry management.
Key changes
SFTP Access Control
- Role defaults -- upload/download toggles, allowed paths, blocked extensions, max file size
- Target-role overrides -- inherit from role OR explicitly allow/deny per target
- Hierarchy: Target override - Role default - System default (allow)
API: GET/PUT /role/{id}/file-transfer, GET/PUT /targets/{id}/roles/{role_id}/file-transfer
Enforcement Mode (strict / permissive)
- Strict: Shell, exec, port forwarding blocked when SFTP restrictions active
- Permissive: SFTP restrictions enforced, shell still works
- SFTP EXTENDED packets handled via allowlist (safe/write/unknown categories)
API: GET/PUT /parameters (field: sftp_permission_mode)
SCP Removal
Removed the legacy SCP module per maintainer feedback -- legacy scp commands now pass through as regular exec (blocked in strict mode).
Audit Logging
All SFTP operations logged: user, target, direction, path, size, SHA256 hash, status, denial reason. Stored in FileTransferLog table.
User Role Expiry
- Optional
expires_aton role assignments with automatic revocation - Re-activation via expiry update/removal
- Full history tracking (granted - expiry_changed - revoked - re-enabled)
API: POST/GET/PUT/DELETE /users/{id}/roles/{role_id}/expiry, GET /users/{id}/roles/{role_id}/history
Admin UI
- Global enforcement mode setting with contextual help
- Role-level file transfer defaults with collapsible advanced restrictions
- Target-level per-role overrides with 3-state controls (inherit/allow/deny)
- User role expiry with countdown display and quick presets (4h-7d)
- Role assignment history timeline
Design Decisions
- Inheritance model over explicit booleans -- Target-role permissions default to NULL (inherit from role) rather than explicit true/false. This avoids permission drift when role defaults change and reduces per-target configuration burden.
- Strict mode as default -- When restrictions exist, strict mode blocks shell/exec/forwarding to prevent SFTP bypass. Permissive mode exists for gradual rollout or audit-only use.
- SCP removed, not restricted -- SCP's protocol makes reliable interception fragile. Rather than half-working restrictions, SCP was removed entirely. SFTP covers all the same use cases with better protocol support.
- EXTENDED packet allowlist -- Unknown SFTP extensions are blocked when restrictions are active. Known-safe extensions (statvfs, fsync, limits) always pass through. Write extensions (posix-rename, hardlink) check upload permission.
- Cumulative max_file_size -- Tracked per Write operation across the stream, not per-packet. Denial happens mid-transfer when the limit is exceeded.
- Session-scoped permission caching -- Permissions resolved once at session start, not per-packet. Trades real-time revocation for performance.
- Soft-delete for role revocation --
revoked_attimestamp instead of row deletion. Allows re-activation and preserves audit trail integrity.
Database Migrations
m00031--target_roles: addallow_file_upload,allow_file_download(bool, default true),allowed_paths,blocked_extensions(json),max_file_size(bigint)m00032--parameters: addfile_transfer_hash_threshold_bytes(bigint, default 10MB) -- controls when SHA256 hashing kicks in for audit logsm00033--user_roles: addgranted_at,granted_by,expires_at,revoked_at,revoked_by. Createsuser_role_historytable with indexes onuser_id,role_id,occurred_atm00034--roles: addallow_file_upload,allow_file_download(bool, default true),allowed_paths,blocked_extensions(json),max_file_size(bigint) -- role-level defaultsm00035--target_roles: convertallow_file_upload/allow_file_downloadfrom non-nullable bool to nullable bool. Migrates existingtrue-NULL(inherit), preservesfalse(explicit deny)m00036--parameters: addsftp_permission_mode(string, default"strict")
Breaking Changes
None -- defaults match current behavior. Existing targets/roles work as before.
Related
- Terraform provider: warp-tech/terraform-provider-warpgate#18
Screenshots
Role UI - Upload/Download Toggles
Target UI - Role Attribution for Upload/Download Override
User UI - Role Assignment History
User UI - Role Expiry Setting
User UI - Role Expiry Countdown
User UI - Make Role Permanent
User UI - Role History
|
Hello @Eugeny, I am not sure why the test aer failing in the Github Action as I have ran them locally and they passed without an issue. Looks like the issue is 2 different test timeout in CI each time due to Docker resource exhaustion after ~160 tests (maybe). |
|
Thank you for the PR! After a cursory glance, it doesn't seem to actually prevent the user from writing/reading data from the FS as there's still nothing stopping them from doing so from within their shell session? |
|
@Eugeny Thanks fro the feedback, this PR design was mainly around the SCP/SFTP subsystem of SSH, It is possible to intercept the SSH session commands as it is the same-ish mechanism used for recording the SSH session. This PR also does not cover these use-cases too:
If you think it is mandatory feature to be able to have this in this PR, I will work on it but I will need sometime before being able to deliver this Thanks again fro the review |
|
I see - unfortunately there's no security gained by this PR as-is unless shell and exec sessions are also forbidden, because there's still (a very easy) way to read/write arbitrary files. So while it might tick the boxes for a specific security audit, it doesn't actually prevent unauthorized access. It's fundamentally impossible to selectively restrict command execution in shell sessions too, since the SSH server only sees user keystrokes and display output and has no concept of "commands" per se (except exec requests, but again the user can bypass that by simply running commands in the shell). It's not possible to "recognize" and parse shell input either because that's trivially bypassable by encrypting the commands/inputs, saving it remotely and decrypting it there, so it's never visible on the SSH protocol. As it is, I'm currently tending towards not accepting this as a feature, however I'm still interested in some parts of this PR, if you could separate them out:
|
|
@Eugeny I will separate the TTL role assignment feature, and look into the usage of rssh-sftp for the SFTP audit logging, for audit log purposes only ! While I am at it I added some screenshot to this PR, even if it will not move forward I really appreciate the review |
|
@Eugeny
Follow-up question, would it be acceptable to update this feature to:
|
|
Could you please reformulate the second half of your reply? I couldn't parse it |
|
Sorry @Eugeny for the not clear comment, It was done little bit too lat in the night To address this, I suggest adding a Restricted Shell mode to make targets truly "File Transfer Only." Regarding this proposal:
|
|
Thanks for getting back to me and sorry for the delay!
Yes - blocking
See above - enabling SFTP permissions should not be possible when interactive sessions are allowed.
We can leave these out - no reason to use legacy SCP in 2026. |
3209fca to
0dec921
Compare
control upload/download access on a per-role-per-target basis.
- Add database migrations for permission columns on target_roles table
- Add SFTP protocol parser with fine-grained operation blocking
- Add SCP command parser for upload/download detection
- Integrate permission checks into SSH session handling
- Add Admin API endpoints for managing file transfer permissions
- Add Admin UI toggles in Target configuration page
- Add E2E tests for permission enforcement
- Add SCP command parsing for upload/download detection
- Implement permission enforcement at packet level in SSH session
- Track file transfers with size and SHA256 hash calculation
- Log file_transfer events with status, direction, protocol, and metadata
- Add pending_reads tracking for accurate SFTP download byte counting
- Update LogViewer UI to display file transfer events with direction arrows
- Update docker-compose with warpgate and ssh-server services
- Ignore docker/data/ directory for persistent test data
- 2 logging tests verifying file_transfer events in JSON logs
- Tests use wait_port for reliable warpgate restart handling
- Permission enforcement now happens at operation level (Open, Read, Write)
- Added detailed error messages including action and target name
- Example: 'Permission denied: file upload is not allowed on target prod-server'
This improves UX when users have restricted file transfer permissions,
replacing cryptic 'subsystem request failed' errors with actionable messages.
Features:
- User role assignments can expire at a specified timestamp
- Expired roles are filtered out during authorization
- Full history tracking with UserRoleHistory entity
- API endpoints for managing and viewing role expiry
- Re-enable expired roles by updating/removing expiry
Features:
- Role-level allowFileUpload and allowFileDownload flags
- API endpoints to get and update role file transfer defaults
- Defaults are inherited by target-role assignments when not overridden
Features:
- Nullable allow_file_upload and allow_file_download columns
- null = inherit from role, true = force allow, false = force deny
- API endpoints to get and update target-role file transfer permissions
- Updated authorization logic to respect override hierarchy
- E2E tests for file transfer permission scenarios
Features:
- Quick expiry presets (4h, 8h, 12h, 1d, 3d, 7d) for common durations
- Custom datetime picker for precise expiry control
- Real-time expiry status display (e.g., "Expires in 3h 59min")
- Expired roles treated as unassigned and can be re-enabled
- Re-enabling expired role removes expiry (makes permanent)
- Inline role history section with load more pagination
- History shows all role changes with actor and timestamp information
- Modal with improved layout and horizontal button arrangement
Features:
- Role page: Simple checkboxes for default upload/download permissions
- Target page: Collapsible file transfer section per role
- Three-way select dropdowns (Inherit/Allow/Deny) for upload and download
- Collapsed state shows current permission summary
- Compact horizontal layout with input groups
- Shows inherited role defaults in select options for clarity
in resource-constrained CI environments. This may help prevent timeout
issues when many Docker containers are running concurrently.
Tests continue to pass locally (12/12 in 14s).
- AGENTS.md (OpenCode agent instructions)
- mise.toml (personal mise configuration)
- .github/workflows/opencode.yml (local workflow)
- *.log files (session logs)
- thoughts/ directory (documentation drafts)
- docker/*.sample.yaml (personal config samples)
(~150 lines), achieving 49% code reduction in the SFTP module.
Changes:
- Add russh-sftp 2.1 dependency
- Create new codec.rs with packet_to_operation(), packet_to_response(),
and build_denial_response() wrappers
- Remove parser.rs (465 lines) and response.rs (126 lines)
- Update session.rs to use codec functions instead of custom parser
- Simplify types.rs by removing redundant type definitions
The russh-sftp library provides proper SFTP protocol parsing while
Warpgate's codec.rs handles access control and metadata extraction.
Refs: thoughts/plans/russh-sftp_migration.md
- RoleHistoryModal: handle PaginatedResponse type, fix Date formatting
- CreateApiTokenModal: remove semicolons from toLocalISO function
- User.svelte: fix lint warnings
- Rust API files: minor fixes for consistency
- File transfer permissions (default, download, upload, API, multi-role)
- Transfer logging and denied transfer logging
- Strict mode (shell blocked, shell allowed, SFTP works, port forwarding)
- Permissive mode (shell allowed, SFTP enforced)
- Advanced restrictions (allowed_paths, blocked_extensions, case sensitivity)
- Role-level defaults and target-role overrides
- Exec and remote forwarding blocking in strict mode
Fix test_ssh_client_auth_config.py: add missing AddUserRoleRequest
argument required by updated SDK from user role expiry feature.
to resolve cargo-deny vulnerability warnings in CI.
- Show expired roles with strikethrough, reduced opacity, and red Expired badge
- Add Re-enable button for expired roles that opens expiry modal
- Inline expiry modal on new role assignment (permanent default, presets)
- Add labeled Expiry button (clock icon + text) replacing icon-only
- Past-date validation on datetime-local input with min constraint
- Live countdown timer (60s interval) showing time remaining
- Confirmation dialog before revoking a role
- Auto-load role history on first expand
- Add 30-day preset to expiry options
revoke, reactivate, history audit trail, API state listing
- 18 SFTP operation tests: remove, rename, mkdir, rmdir, setstat,
symlink, max_file_size enforcement, extended packets (statvfs),
metadata ops, and strict mode streamlocal blocking
8330df8 to
040f7d2
Compare
|
@Eugeny Here's an update addressing all your feedback points: What changedEnforcement mode (addresses session/exec blocking requirement)
SCP removed
SFTP codec replaced
Permission inheritance model
User role expiry
PR description updatedRewrote to be more concise with a design decisions section explaining the rationale behind each architectural choice. Terraform providerCompanion PR: https://github.com/warp-tech/terraform-provider-warpgate/pull/18`` |
be762fc to
286ed7d
Compare
permissions, never deny. Previously if ANY role had file_transfer_only=true,
shell was blocked. Now only if ALL matching roles agree is it enforced,
so a single role without the flag restores shell access.
|
@Eugeny Please let me know if you think this needs more changed or updated to match the expected behavior. Edit: I am planning to rewrite git history to make it easier to grasp as there was some changes and fixes that could have been merge. Let me know if you want me to keep it as is ! |
|
Sorry for taking so long - I have a large backlog and can spend very little time on it right now - it might take up to 2 weeks for me to finish a review so please hold tight |