Listings leave out the entries naming the listed directory or its
parent, and the names holding a backslash, which no request can
address
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
A user or admin changing its own password, profile or two-factor
settings wrote the whole account back and could overwrite a concurrent
administrator change. Self-service updates are now conditional on the
stored updated_at matching the value read, and every update stores a
strictly increasing updated_at.
Thanks to Thomas Toth for the report.
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Answering with another account has never been a supported configuration for
these two integrations. Mapping several login names onto a single account is a
feature of the external authentication hook
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
A directory rename authorized the whole tree from a walk performed before the
move, so the file_patterns filters and the per-directory rename permissions
decided on the tree as the walk had read it rather than on the one the move
carried.
The walk no longer authorizes the move. A gate decides from the configuration
alone whether anything can tell the entries of the tree apart: a move that
changes which file_patterns filters govern the contents, or a per-directory
permission that restricts renaming defined at or below either tree. When
something can, the object stores check every entry at the moment they move it,
since they rename a directory by recursing into it, and the filesystems that
rename it in a single operation refuse it, because nothing enumerates what that
operation carries. The type of the source is read again immediately before the
move.
Granting rename_files and rename_dirs separately keeps the pre-move walk.
So renaming a folder into or out of a path where a different file_patterns
filter applies is now refused on the local filesystem, SFTP and HTTP, and
checked entry by entry on the object stores; the same holds for a per-directory
permission that restricts renaming inside either tree. Renames the walk used to
refuse fail with the same error as before. IsRenameAtomic is derived from the
same capability instead of matching the filesystem name.
Reported-by: Wade Sparks (@wsparks-vc)
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Downloading a public share limited to a single directory as a zip archive
rewrote share.Paths[0] to "/" so the entry names came out relative to that
directory. The value it overwrote is the policy every later public request is
authorized against: the browsable listing resolves the requested path under
Paths[0] and the upload handler derives its destination prefix from it.
The rewrite stays confined to the copy the handler holds only if the data
provider returns a share that owns its paths. The memory provider did not:
getACopy assigned the Paths slice by header, so the copy kept pointing at the
array stored in the provider map and the rewrite reached the stored share.
From that moment the link served the whole filesystem of the share owner. Any
recipient, with no account and nothing beyond the share URL, could list and
download every file the owner can reach, and a read-write share also accepted
uploads at the owner's root. The archive returned to the recipient who
triggered the change was still limited to the shared directory, so the
response revealed nothing. The SQL and bolt providers rebuild the slice while
decoding the stored row and were not affected.
The list of files to archive is now local to the handler, so the share is no
longer written to, and getACopy clones the paths, so a copy never shares state
with the provider.
The regression test downloads a share limited to one directory twice and
checks the archive, the stored paths, the browsable listing and a file outside
the share in between, with whichever data provider the test suite runs with.
Thanks to Yutaka Sasaki (@SAYUTIM) for the report.
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
An instance that starts with an empty admins table caches that state until
restart, so it keeps serving the setup page after another instance completes
the initial configuration. Instances backed by a shared SQL provider now
re-check the database, rate limited to one query per second and only until
the first admin is found
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
A virtual folder mapping can now re-root the mount onto a sub-path of
the folder: the mount at the virtual path serves the folder starting
from the configured subpath, which stays invisible in the virtual tree.
The same folder can be mapped multiple times with distinct subpaths;
quota remains a property of the whole folder, so every mapping must use
the same limits.
Supported for local, encrypted, S3, GCS, Azure Blob and SFTP folders.
Fixes#2037
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Apply the fixes suggested by the modernize analyzers enabled by
default in gopls v0.23: range over int, errors.AsType, min/max,
slices.Backward/Contains, maps.Copy, strings.Cut/CutPrefix/CutSuffix/
SplitSeq, fmt.Appendf, WaitGroup.Go and b.Loop in tests, new(expr)
instead of to.Ptr, removal of ineffective omitempty tags on nested
struct fields.
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Confine all local filesystem access to an os.Root, with path resoultion
enforced by the underlying OS.
Behavior changes an upgrading deployment may notice:
- temp_path is removed and ignored if still configured. Its purpose
was to stage files (atomic uploads, scratch and streaming data)
outside the user's storage and rename them into place, and that is
fundamentally incompatible with the confinement: an os.Root cannot
rename a file in from outside the root, so every staged upload would
degrade to a full copy-then-delete even when temp_path is on the
same filesystem, defeating the very atomicity the option existed to
provide. It was not a great idea before this change either: a
temp_path on a different device already turned the final rename into
a silent full copy. Staged and scratch files (the atomic upload
temporary, the CryptFs download pipe backing file) now live in the
storage root itself, on the same filesystem as the data, so the
final rename stays atomic.
- Cross-folder / cross-root renames are no longer atomic. A rename
spanning the home and a local virtual folder, or two local folders,
falls back to copy-then-delete: slower for large data even on the
same device, and a midway failure can leave a partial tree at the
destination. Symlinks and other non-regular entries inside a moved
directory are skipped, not copied, and the rename still reports
success. File mode bits are not preserved; regular files keep their
modification time, directories do not.
- Absolute symlinks created outside SFTPGo, or by versions predating
this change, are no longer traversable and Readlink rejects them with
permission denied, even when the target is inside the root: os.Root
refuses an absolute link target regardless of where it points.
Symlinks created through SFTPGo are stored relative to the link
directory and keep working.
- Execute-only home directories (e.g. mode 0111) are unusable:
os.OpenRoot requires read permission, so every operation fails with
permission denied until the directory is made readable, where
previously only the listing failed. Since the root open is retried on
demand, fixing the permissions (or creating a missing home
externally) makes the same connection work again without
reconnecting.
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Share usage was a check-then-write race: the public share handler
loaded a copy of the share, IsUsable() checked UsedTokens on that
in-memory snapshot, and UpdateShareLastUse() incremented the counter
afterwards with no lock, transaction or compare-and-swap spanning the
check. Concurrent requests for the same share could all pass the limit
check before any of them incremented the counter, so a share with a
usage cap (for example max_tokens=1) could be downloaded or uploaded
more times than allowed under concurrent access.
The usage count is now a conditional atomic reservation. For a positive
delta the SQL providers run a single guarded UPDATE that only succeeds
while used_tokens + n <= max_tokens and reject the request when no row
is affected; the memory and bolt providers apply the same guard inside
their existing locked section. UpdateShareLastUse returns
ErrShareUsageExceeded when the reservation would exceed the limit, and
the share download/upload handlers reject the request instead of
serving it. A non-positive delta is a refund and is always applied
unconditionally, so failed transfers still return their reserved token.
Thanks to Vishal Shukla (@shukla304) for the report.
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Also factor the file-or-value resolution in util.ResolveConfigValue
and reuse it for OIDC, KMS and httpd signing passphrase.
Fixes#2206
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
The previous behavior was a leftover from an old refactor.
This change aligns the pre-login hook with the behavior of other hooks,
although it may break some edge cases that relied on the previous inconsistent
behavior.
Fixes#2107
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
skip availability check when updating the node timestamp.
If the update succeeds, the data provider is healthy
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
We replaced the jwtauth and jwx libraries with a minimal custom wrapper
around go-jose because we don’t need the full feature set provided by jwx.
Implementing our own wrapper simplifies the codebase and improves
maintainability.
Moreover, go-jose depends only on the standard library, resulting in a
leaner dependency that still meets all our requirements.
This change also reduces the SFTPGo binary size by approximately 1MB
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
rsync was executed as an external command, which means we have no insight
into or control over what it actually does.
From a security perspective, this is far from ideal.
To be clear, there's nothing inherently wrong with rsync itself. However,
if we were to support it properly within SFTPGo, we would need to implement
the low-level protocol internally rather than relying on launching an external
process. This would ensure it works seamlessly with any storage backend,
just as SFTP does, for example.
We recommend using one of the many alternatives that rely on the SFTP
protocol, such as rclone
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>