Compare commits

...

737 commits
2.3.x ... main

Author SHA1 Message Date
Nicola Murino
00155eaaf6
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-26 19:04:08 +02:00
Nicola Murino
d94f80c8da
replace utils.Contains with slices.Contains
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-24 18:27:13 +02:00
Nicola Murino
bd5eb03d9c
replace hand-written slice utilities with methods from slices package
SFTPGo depends on Go 1.22 so we can use slices package

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-24 18:17:55 +02:00
Nicola Murino
6ba1198c47
sftpd: remove unused folder prefix from Connection struct
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-24 16:44:25 +02:00
Nicola Murino
b5c821795a
allow to customize name and log from the WebUI
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-24 09:14:27 +02:00
Nicola Murino
b2926377b7
WebUI: switch favicon from ico to png
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-20 16:11:21 +02:00
Nicola Murino
99f47ca4e7
sftpfs: cache and reuse parsed private keys
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-16 19:20:28 +02:00
Nicola Murino
fef388d8cb
don't track quota for private virtual folders
they are included within the user quota.
This is a backward incompatible change.

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-13 21:02:40 +02:00
Nicola Murino
92849ca473
quota: move user and folder management to a common method
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-13 19:30:40 +02:00
Nicola Murino
0952887157
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-13 17:44:13 +02:00
Nicola Murino
d010b26e1c
deb packages: update copyright year
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-13 14:06:11 +02:00
Nicola Murino
58de410850
nt: fix unused write warnings
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-03 20:42:51 +02:00
Nicola Murino
54bc3ea87d
restore: fix quota scan for users with folders associated via groups
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-03 20:35:12 +02:00
Nicola Murino
64a2f7aa4f
oidc refresh token: validate nonce only if set
As clarified in OpenID core spec errata 2, section 12.2

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-07-01 19:06:11 +02:00
Nicola Murino
55be9f0b9c
EventManager: allow to configure the timezone to use for the scheduler
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-30 18:52:59 +02:00
Nicola Murino
97ffa0394f
update deps
adapt smtp configuration to changes in upstream library

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-30 09:18:04 +02:00
dependabot[bot]
dc91ec2056
Bump docker/build-push-action from 5 to 6 (#1668)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 09:29:56 +02:00
Nicola Murino
356795f8b0
add a test case for listing files with long names
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-22 19:23:02 +02:00
Nicola Murino
3efcd94e14
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-21 20:25:35 +02:00
Nicola Murino
34bc21b3b7
update deps
fixes a bug in chi compressor Handler

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-21 18:32:59 +02:00
Nicola Murino
37845c2936
smtp: hide commit hash in user agent
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-21 18:31:42 +02:00
Nicola Murino
47924716c1
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-19 21:04:35 +02:00
Nicola Murino
1d60505629
fix test case failure on macOS with bolt provider
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-19 10:45:14 +02:00
Nicola Murino
9daf0ba767
update swagger UI to 5.17.14
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-18 20:47:39 +02:00
Nicola Murino
bdae378569
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-18 19:13:21 +02:00
Nicola Murino
363770ab84
WebClient shares: add a logout button
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-18 19:10:32 +02:00
Nicola Murino
8bc08b25dc
sftp: limit max file list
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-17 19:24:03 +02:00
Nicola Murino
e0c1b974c9
add cgo to build constraints
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-16 09:46:17 +02:00
Nicola Murino
39cf9f6943
Web UIs: remove duplication of supported languages
Fixes #1660

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-15 19:35:23 +02:00
Nicola Murino
d650defa08
remove duplicated jwt tokens validation
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-15 16:19:37 +02:00
Nicola Murino
c5c42f072b
squash database migrations
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-15 16:02:09 +02:00
Nicola Murino
bd5b32101f
csrf: reuse the cookie in reset password
no need to generate a new cookie each time.

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-15 15:18:17 +02:00
Nicola Murino
8208ac817d
html pages: add robots meta tag
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-15 10:17:37 +02:00
Nicola Murino
a99c4879de
update dependencies
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-15 10:10:15 +02:00
Nicola Murino
01b666a78f
WebUIs: check login conditions before allowing password reset
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-14 19:34:42 +02:00
Nicola Murino
8294952474
WebUIs: refactor CSRF
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-14 18:09:32 +02:00
Nicola Murino
7fb5b1b996
reduce share token duration
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-08 12:13:38 +02:00
Nicola Murino
2749a98f26
CI: update workflow to 1.22.4
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-07 18:19:52 +02:00
Nicola Murino
08526da153
REST API: fix token invalidation after password change
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-07 18:19:05 +02:00
Nicola Murino
8269adf176
Windows: allow to override most of the "serve" flags from env files
The Windows specific code path was missing in 07710ad98

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-05 17:34:28 +02:00
Nicola Murino
0cddcba5a7
EventManager: add an action to rotate the log file
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-04 19:51:52 +02:00
Nicola Murino
3bd1eeacc1
make sure to return a fully populated user after plugin auth
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-06-04 18:14:09 +02:00
Nicola Murino
1698ec2eb3
EventManager: fix adding ObjectDataString for provider events
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-31 20:01:38 +02:00
Nicola Murino
07710ad98d
allow to override most of the "serve" flags from env files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-31 18:49:23 +02:00
Nicola Murino
f63bf7093c
logs: redact plugin arguments
may contain sensitive data

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-30 18:10:12 +02:00
Nicola Murino
0597bf1047
Windows setup: update MinVersion
Starting from Go version 1.21, Windows 10 or Windows Server 2016 are
required

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-30 18:07:17 +02:00
Nicola Murino
5bde4b92a2
fix test cases
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-29 19:35:42 +02:00
Nicola Murino
faa994e3b3
update UI theme and dependencies
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-29 19:20:56 +02:00
Nicola Murino
68cc1a8e2c
fix proxy protocol policy
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-28 19:40:37 +02:00
Nicola Murino
9c775e2213
transfer logs: add error field
Fixes #1638

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-27 19:35:48 +02:00
Nicola Murino
6c94173ca1
WebUI branding: remove unused login_image_path from config
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-27 18:43:44 +02:00
Nicola Murino
d1e0560d28
WebAdmin status page: update the color of the labels
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-26 19:34:29 +02:00
Nicola Murino
52a94b2593
docker: build Alpine based image using golang:1.22-alpine3.20
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-25 16:53:13 +02:00
dependabot[bot]
9550fd2921
Bump alpine from 3.19 to 3.20 (#1636)
Bumps alpine from 3.19 to 3.20.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 16:51:48 +02:00
Nicola Murino
a6549b08f9
dependabot: remove gomod
it is not really required, we update Go dependencies regularly

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-25 16:40:31 +02:00
Nicola Murino
ba3e2ecb5f
WebAdmin events page: fix rendering of some nullable strings
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-25 16:17:33 +02:00
Nicola Murino
2bd3b46e3f
update swagger ui
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-25 16:14:42 +02:00
Nicola Murino
7831ddaede
WebAdmin events page: set fixed sizes for potentially long fields
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-24 18:24:05 +02:00
Nicola Murino
613f2f1c24
WebUIs: set the lang attribute based on the chosen language
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-24 18:23:41 +02:00
Nicola Murino
525f33a07a
WebUIs: fix css loading order
Fixes #1628

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-24 18:22:58 +02:00
Nicola Murino
3f2604d33f
ssh: use 3072-bits for the auto-generated RSA key
This is the same as ssh-keygen

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-24 18:22:36 +02:00
Nicola Murino
b823bb04d2
WebAdmin: make the description visible in IP lists page
Fixes #1631

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-23 20:07:49 +02:00
Nicola Murino
9ba92d9495
WebUIs: fix datatables processing class name
was changed to dt-processing in datatables 2.0

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-23 19:47:45 +02:00
Nicola Murino
0127fc188b
SSH: allow to configure minimum key size for DHGEX
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-23 18:08:16 +02:00
Nicola Murino
3c7a651d27
plugin: don't consider file extension for env prefix
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-18 13:10:16 +02:00
Nicola Murino
50a3c0d911
defender: allow to impose a delay between login attempts
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-18 10:35:54 +02:00
Nicola Murino
b2bea85add
update README
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-16 10:40:48 +02:00
Nicola Murino
61bc0065f9
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-16 04:54:46 +02:00
Nicola Murino
19e9857fea
set version to 2.6.0
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-15 17:36:10 +02:00
Nicola Murino
665a980d62
improve error wrapping
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-14 19:10:36 +02:00
Nicola Murino
eb0c6549c4
micro optimization
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-12 18:10:03 +02:00
Nicola Murino
e7627bfcd3
fix test cases after the change in the previous commit
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-10 15:58:07 +02:00
Nicola Murino
62f5d4cb89
fix the error message for errors that occur during file transfers
we should special case path errors and replace the fs path with the
virtual path.

Thanks to @nezzzumi for reporting this issue

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-10 15:12:05 +02:00
Nicola Murino
4502509c2d
pgsql: validate target_session_attrs
silently ignore invalid values

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-09 19:55:12 +02:00
Nicola Murino
2f577c9884
fix lint warnings
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-08 19:11:03 +02:00
Nicola Murino
499c7a432d
examples and tests: update dependencies
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-08 19:03:49 +02:00
Nicola Murino
5d24d665bd
add an util method to convert []byte to string
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-08 19:01:58 +02:00
Nicola Murino
65753fe23e
WebUIs: update datatables library
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-07 18:27:17 +02:00
Nicola Murino
96825be11b
update deps and workflows
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-07 18:17:06 +02:00
Nicola Murino
ce2e65d776
remove DCO
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-07 18:04:12 +02:00
Nicola Murino
ab320c9ecc
WebUIs: remove regex search
The default DataTables2 search is easier for end users

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-04 12:41:16 +02:00
Nicola Murino
76c912083e
update theme and js dependencies
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-04 12:03:55 +02:00
Nicola Murino
ea898ed104
silence lint warning
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-04 09:52:27 +02:00
Nicola Murino
0da12ef47b
ftp login: log is TLS is enabled
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-03 18:47:01 +02:00
Nicola Murino
92aa89263b
WebClient: hide submit button in profile page if no change is allowed
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-03 18:38:13 +02:00
Nicola Murino
a1af33c6aa
WebClient: allow to set TLS certificates
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-03 18:30:03 +02:00
Nicola Murino
58a8b2b860
S3: add support for STS temporary credentials
Fixes #1558

Co-authored-by: Nazarii Mediukh <nazar.medykh@gmail.com>
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-02 20:01:30 +02:00
Nicola Murino
d9b91d074f
update README
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-02 19:27:41 +02:00
Nicola Murino
de62be6f21
update swagger ui
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-02 19:01:32 +02:00
Nicola Murino
acfd4c3e55
ftpd: allow to ignore ASCII transfer types
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-02 19:00:29 +02:00
Nicola Murino
dd446c805d
update sponsors section
remove Dendi

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-02 09:53:35 +02:00
Nicola Murino
d3f42e39db
move server version setting to common section
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-05-01 19:42:09 +02:00
Nicola Murino
7b5ad6c38d
workflows: update actions version
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-30 20:45:27 +02:00
Nicola Murino
8edce2055d
ftpd: fix random test cases failure on FreeBSD
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-30 19:50:52 +02:00
Nicola Murino
d19976cc3f
setup: update support link
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-28 17:08:59 +02:00
Nicola Murino
193d11587d
examples: update docs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-27 14:40:20 +02:00
Nicola Murino
4d4d2ad801
remove obsolete rest-api-cli
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-27 14:11:14 +02:00
Nicola Murino
9b8407aeb0
remove fail2ban docs. Built-in defender should be used
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-27 14:09:41 +02:00
Nicola Murino
aa4a7aa6f6
update some descriptions
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-27 14:01:33 +02:00
Nicola Murino
4bac74a149
remove docs and add a link to new documentation website
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-27 13:47:14 +02:00
Nicola Murino
dd9b0b151f
sftpfs: simplify client creation
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-27 12:03:38 +02:00
Nicola Murino
0a8a0ee771
revert #450
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-27 10:50:25 +02:00
Nicola Murino
2bcf05ca45
refactor for secrets management in API and private key handling in SFTPFs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-26 16:17:24 +02:00
Nicola Murino
aa426016f2
sftpd: remove folder_prefix
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-26 11:43:25 +02:00
Nicola Murino
1fc0f21506
hooks: remove logging output from external programs
This reverts #1208 because the contributor did not respond to our
request to sign the CLA

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-26 11:13:16 +02:00
Nicola Murino
e1fdc10ef8
remove robots.txt endpoint
This reverts #833 because the contributor did not respond to our
request to sign the CLA

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-26 11:00:55 +02:00
Nicola Murino
26d19abf61
remove reading data provider username and password from file
This reverts #1455 because the contributor cannot sign the CLA

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-26 10:57:38 +02:00
Nicola Murino
590a1f1429
update deps and CI
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-26 10:26:53 +02:00
Nicola Murino
a020a4e0ed
update dependencies in tests and examples
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-20 17:22:14 +02:00
Nicola Murino
ad7dcdb628
ssh: remove the ability to fully customize the software version
many clients rely on the version string to enable/disable some features.
We only allow to hide the version number, clients must be able to reliably
identify SFTPGo

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-20 17:15:15 +02:00
Nicola Murino
a38fd26cf6
minor refactor to memory provider initialization
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-20 16:45:20 +02:00
Nicola Murino
950cf67e4c
dataprovider: small refactor for password check
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-18 18:23:16 +02:00
Nicola Murino
d8341509e7
micro optimization for external process wrapping
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-16 18:34:40 +02:00
JK
2bbd8b3a5f
fix using rsync if running sftpgo as non-root user (#1535)
Signed-off-by: Jerome Küttner <j.kuettner@mittwald.de>
2024-04-15 12:52:08 +02:00
Nicola Murino
e315e48c39
CI: re-enable FreeBSD testing now that Go 1.22 is in quarterly
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-14 15:40:12 +02:00
Nicola Murino
150a338166
removed unused methods
these methods were used in the old UIs

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-14 09:23:57 +02:00
Nicola Murino
a957474740
SMTP: document why we always load templates in service mode
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-13 14:27:59 +02:00
oftenoccur
019edf38f3
chore: fix function name in comment (#1586)
Signed-off-by: oftenoccur <ezc5@sina.com>
2024-04-12 19:51:51 +02:00
Nicola Murino
8ca069f6de
add a template for pull requests
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-12 18:01:35 +02:00
Nicola Murino
2f16b06ffe
README: add a link to the compliance page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-12 17:58:40 +02:00
Nicola Murino
456517af87
notifier plugin: add support for login succeeded events
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-10 18:39:08 +02:00
Nicola Murino
e8140d7310
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-09 19:21:06 +02:00
Nicola Murino
ff48386cc8
store used data transfer as big integer
we originally stored these values as MB but since we use bytes now,
an integer field is not enough.

Fixes #1575

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-02 18:38:22 +02:00
Nicola Murino
70cb71acfa
WebClient: don't hide initial errors in files page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-02 18:37:29 +02:00
Nicola Murino
13418e9324
update deps and npm to the latest version
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-02 18:36:41 +02:00
Nicola Murino
1196727448
dataretention: remove ignore_user_permissions
Required permissions are now automatically granted as for any other
filesystem action

Fixes #1564

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-01 15:07:03 +02:00
Nicola Murino
aaae191710
WebAPI: ensure to check rootfs before creating directories
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-01 12:40:35 +02:00
Nicola Murino
1620e16b89
WebClient: fix move and copy
Regression introduced in fc023748c1

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-01 12:00:06 +02:00
Nicola Murino
db577b154e
webclient: add more test cases for shares
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-01 11:42:22 +02:00
Nicola Murino
c6164b8ae7
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-31 20:43:48 +02:00
Nicola Murino
fc023748c1
WebClient: improve file uploads
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-31 20:42:28 +02:00
Nicola Murino
cb3bc3f604
update OpenAPI definition
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-18 19:32:01 +01:00
Nicola Murino
1dd63c29ec
update translations
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-17 11:53:45 +01:00
Nicola Murino
cc9a0d4dc2
add time-based access restrictions
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-17 11:30:03 +01:00
Nicola Murino
74dd2a3b9a
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-16 10:30:46 +01:00
Nicola Murino
55c8677443
restored the log if retrieving directory entries fails
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-12 18:31:01 +01:00
Nicola Murino
26d3105f54
groups: add role placeholder
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-12 18:21:50 +01:00
Nicola Murino
ca2757d41e
copy: fix quota for FsFileCopier
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-12 08:43:23 +01:00
Nicola Murino
f38966c6ac
WebClient: refactor long-running tasks to improve browser compatibility
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-11 18:19:57 +01:00
Nicola Murino
baaef63d1d
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-07 18:11:26 +01:00
Nicola Murino
4d357a6a57
EventManager: allow to check for inactive users
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-04 19:48:10 +01:00
Nicola Murino
8b2188fcb6
remove some useless nil checks
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-03-02 18:49:07 +01:00
Nicola Murino
799fdd7098
allow IPs in defender safe list to exceed max per-host connections
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-27 18:22:21 +01:00
Nicola Murino
12f599fd65
WebUI: skip checks for static resource
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-25 18:19:21 +01:00
Nicola Murino
be2ed1089c
ssh: add username to sftp auth errors
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-25 15:45:50 +01:00
Nicola Murino
92911bda2b
require at least 2048 bits for RSA certificates/keys
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-25 11:12:57 +01:00
Nicola Murino
f7d9e56cac
ssh: remove moduli, log negotiated algorithms
Fixes #1324

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-24 20:35:09 +01:00
Nicola Murino
a577d8b3cd
WebAdmin: allow to disable 2FA
Before it was only possible using REST API

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-23 18:24:07 +01:00
Nicola Murino
76ffa107dd
check admins' two-factor requirements in the disable API as well
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-22 19:05:16 +01:00
Nicola Murino
9a6a65931e
two-factor auth: fixed validation of conflicting settings
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-22 18:20:51 +01:00
Nicola Murino
de089e51fd
Web: allow to require password change and two-factor for admins
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-21 20:45:10 +01:00
Nicola Murino
51ae2d7301
add copy permission
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-20 18:19:09 +01:00
Nicola Murino
e5fc1bd574
docs: replace the images relating to the old theme
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 19:31:37 +01:00
Nicola Murino
aaf310ffff
add some notes about internationalization support
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 13:13:48 +01:00
Nicola Murino
3ad86274d8
CI: disable tests on FreeBSD until Go 1.22 is available
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 12:29:59 +01:00
Nicola Murino
b4afdac8a0
fix test cases on Windows (again)
in Go 1.22 Readdir now works on Windows in the same way as on other
platforms

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 12:17:51 +01:00
Nicola Murino
19d405fa3a
WebClient: make directory loading message more evident
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 11:51:15 +01:00
Nicola Murino
d92f85d1dd
WebClient: improve error message when trying to move non-empty folder
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 11:22:41 +01:00
Nicola Murino
5a319dc64f
update to Go 1.22
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 10:14:14 +01:00
Nicola Murino
a45aeb3bd6
WebAdmin: allow to reorder and search groups and actions
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 10:14:10 +01:00
Nicola Murino
162376fd74
add a nil check for attributes
just defensive code

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 10:14:05 +01:00
Nicola Murino
0d4e4175a8
CI: update golangci-lint action to v4
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 10:14:00 +01:00
Nicola Murino
7ca390c85a
CI: fix codecov action
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 10:13:57 +01:00
Nicola Murino
d413775060
vfs: log progress after each page iteration
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 10:13:51 +01:00
Nicola Murino
db0a467d33
refactor metadata support
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-18 10:13:46 +01:00
Nicola Murino
e2ff12c589
fix test cases on Windows
on Windows f.Readdir returns no error if f is closed

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-17 13:41:40 +01:00
Nicola Murino
849f0bd0a8
WebAdmin: clearly indicate that metadata check is no longer supported
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-17 12:33:16 +01:00
Nicola Murino
e61fb42cbc
remove metadata plugin
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-17 12:30:47 +01:00
Nicola Murino
d8339ab967
WebClient: update pdfobject
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-16 18:56:17 +01:00
Nicola Murino
410b7cd512
fix remaining lint warnings
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-16 18:55:58 +01:00
Nicola Murino
ad75543172
fix some new lint warnings
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-15 21:13:45 +01:00
Nicola Murino
757185256c
i18next: fix fallback language
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-15 20:57:19 +01:00
Nicola Murino
04dcb65eb0
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-15 20:55:18 +01:00
Nicola Murino
1ff55bbfa7
add DirLister interface
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-15 20:53:56 +01:00
Nicola Murino
c60eb050ef
WebAdmin: improve the error message when trying to delete referenced resources
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-05 19:18:37 +01:00
Nicola Murino
d7975d8d76
WebAdmin: add expired to the status in users page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-05 19:03:05 +01:00
Nicola Murino
6b07908084
WebAdmin: add groups to users page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-05 19:02:43 +01:00
Nicola Murino
c49553abd0
keyboard interactive: ask only the passcode if it is the second step
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-05 19:02:01 +01:00
Nicola Murino
ae309d64c4
WebClient: disable indicator if we redirect from the login page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-04 21:13:04 +01:00
Nicola Murino
8385acd0e3
Redirect to two-factor auth page after creating the first admin
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-04 20:58:29 +01:00
Nicola Murino
e5836c8118
WebUI: add a JSON helper function
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-04 18:16:10 +01:00
Nicola Murino
c23d779280
WebClient: load shares using an async request
instead of rendering them directly within the template

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-04 14:33:51 +01:00
Nicola Murino
364c9c8162
WebClient: improve rendering of read only fields in profile page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-04 12:36:39 +01:00
Nicola Murino
3158190945
WebClient: respect second factor requirements enforced at group level
Fixes #1506

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-04 12:09:47 +01:00
Nicola Murino
c8da72a7f7
add WP Engine to the sponsors section, thank you!!!
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-03 16:41:28 +01:00
Nicola Murino
6e041895c7
workflows: update actions
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-03 16:29:44 +01:00
Nicola Murino
0aa6013342
docker: add back Distroless image
this is possible thanks to a new project sponsor

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-03 15:38:43 +01:00
Nicola Murino
6074ed21f7
dataproviders: return an uniform error for foreign key violations
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-03 14:24:50 +01:00
Nicola Murino
dcecb79f63
fix expected strings in some test cases
plain strings were converted to translation codes

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-03 12:51:12 +01:00
Nicola Murino
71e01ab26d
new WebAdmin: add test cases
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-03 12:42:05 +01:00
Nicola Murino
7ad6d99bd7
WebUI: remove now unused assets
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-01 20:48:52 +01:00
Nicola Murino
ad80d4e475
WIP new WebAdmin: event rules
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-02-01 20:32:43 +01:00
Nicola Murino
c85601146d
WIP new WebAdmin: event actions
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-31 20:49:25 +01:00
Nicola Murino
b18b37042d
WIP new WebAdmin: add missing translations for events page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-29 10:49:21 +01:00
Nicola Murino
0900a63b83
WIP new WebAdmin: fix back pagination in events page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-28 19:54:24 +01:00
Nicola Murino
143d4611ba
WIP new WebAdmin: events page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-28 19:38:01 +01:00
Nicola Murino
caa1d70aab
WebUI: add a base template for info messages
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-28 10:22:16 +01:00
Nicola Murino
a275ef17a8
relax Unix domain socket permissions so that they are group writable
Fixes #1507

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-28 09:34:07 +01:00
Nicola Murino
856aed2d60
WebUI: fix long texts in message pages
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-28 09:02:50 +01:00
Nicola Murino
b52a517b16
CI FreeBSD: update to FreeBSD 14
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-27 12:50:13 +01:00
Nicola Murino
69da5c10c6
WIP new WebAdmin: configs page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-27 12:48:15 +01:00
Nicola Murino
d01fccf28c
WIP new WebAdmin: maintenance page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-26 21:03:41 +01:00
Nicola Murino
9fcff83f8f
WIP new WebAdmin: status page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-25 19:26:51 +01:00
Nicola Murino
eec9c449d4
vfs: make PipeReader an interface
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-24 19:59:50 +01:00
Nicola Murino
8180b75ef1
WIP new WebAdmin: IP lists pages
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-24 19:23:15 +01:00
Nicola Murino
d381304136
WIP new WebAdmin: admin/admins pages
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-22 20:22:41 +01:00
Nicola Murino
d67f00546a
WebUI: improve style for readonly fields
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-21 18:10:14 +01:00
Nicola Murino
810bf4542f
WebUI: add autocomplete="new-password" for internal password fields
See:

https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-21 17:39:08 +01:00
Nicola Murino
e38350e8b3
WIP new WebAdmin: role page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-21 17:19:25 +01:00
Nicola Murino
3f479c5537
WIP new WebAdmin: roles page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-21 16:49:04 +01:00
Nicola Murino
0d387d9799
prefer errors.As to errors.Is
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-21 15:46:38 +01:00
Nicola Murino
8648351fc7
WIP new WebAdmin: connections page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-20 15:35:05 +01:00
Nicola Murino
73b2573b14
WIP new WebAdmin: two factor auth page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-18 20:25:07 +01:00
Nicola Murino
91802fad3e
WIP new WebAdmin: profile, change password, message pages
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-18 19:18:57 +01:00
Nicola Murino
87451560e3
normalize common database errors
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-17 17:36:35 +01:00
Nicola Murino
5ac99ee556
WIP new WebAdmin: folder page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-16 19:51:37 +01:00
Nicola Murino
d939a82225
user: add TLS certificates
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-14 21:36:23 +01:00
Nicola Murino
0722c4369b
WIP new WebAdmin: folders page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-14 16:59:27 +01:00
Nicola Murino
1a0f734a9c
WIP new WebAdmin: remove some hard coded strings
so they can be localized

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-14 09:41:39 +01:00
Nicola Murino
bf94f8b87c
WIP new WebAdmin: group page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-14 09:09:42 +01:00
Nicola Murino
5c8214e121
WIP new WebAdmin: groups page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-11 19:26:13 +01:00
Nicola Murino
e6c8b0c86b
Merge branch 'main' of github.com:drakkan/sftpgo
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-10 20:16:28 +01:00
Nicola Murino
03ebd5b841
fix a lint warning from the previous PR
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-10 20:15:51 +01:00
Anthrazz
c21b434c4e
defender: implement logging of events and bans (#1495)
defender: implement logging of events and bans

Signed-off-by: Anthrazz <25553648+Anthrazz@users.noreply.github.com>
2024-01-10 20:12:57 +01:00
Nicola Murino
113724f340
Merge branch 'main' of github.com:drakkan/sftpgo
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-10 20:01:54 +01:00
Nicola Murino
9cde0909b0
test cases: replace expired TLS certificates
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-10 19:53:48 +01:00
Nicola Murino
86eab21be8
WebAdmin: fix parsing form field
some field names changed with the new UI

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-10 18:49:20 +01:00
Nicola Murino
73d7779d89
Docker Alpine: update to 3.19
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-09 20:12:58 +01:00
Nicola Murino
9c31111249
docs: update OpenSUSE instructions to auto refresh the repo
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-09 20:09:38 +01:00
Nicola Murino
e1b5d2fe39
WebAdmin: use the new UI for user pages
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-09 19:54:08 +01:00
Nicola Murino
ca880f6cbb
WebAdmin: completed base page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-01 20:09:15 +01:00
Nicola Murino
784b7585c1
remove end year from Copyright notice in files
so we don't have to update all the files every year

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-01 11:31:45 +01:00
Nicola Murino
ce0693feda
WebUIs: move more shared components to common/base.html
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-31 17:35:14 +01:00
Nicola Murino
3e47a4f664
WebAdmin: use the new theme for the login and setup page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-30 19:12:22 +01:00
Nicola Murino
7318d1f32a
Web: move baselogin template to common
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-30 14:13:25 +01:00
Nicola Murino
259566fcce
WebUI: allow absolute URLs for disclaimers
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-28 19:59:06 +01:00
Nicola Murino
3121c35437
WebClient: do not silently overwrite files/directories
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-28 18:43:07 +01:00
Nicola Murino
e35e07acdb
WebClient: propose to add files for empty dirs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-26 19:07:50 +01:00
Nicola Murino
a65e7782de
WebClient shares: improve feedback after link copy
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-26 09:23:43 +01:00
Nicola Murino
a9341d7c0f
WebClient: various UI/UX improvements
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-26 08:59:52 +01:00
Nicola Murino
723c15fb3e
add IDCS to the sponsors section, thank you!!!
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-24 14:07:59 +01:00
Nicola Murino
c7ba326540
update deps in tests and example projects
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-21 18:07:03 +01:00
Nicola Murino
61b5f97bf2
scp: close transfers before sending upload errors
This change should fix the random failure in TestSCPTransferQuotaLimits
because the quota is already updated when the scp command ends.

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-21 18:03:07 +01:00
Nicola Murino
d396c24ad4
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-18 20:49:36 +01:00
Nicola Murino
5f30ea3658
tests: add some logs to debug some sporadic test failures
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-17 10:46:21 +01:00
Nicola Murino
ba472c3c67
portable mode: fix disabling services if enabled using a config file
clarify that a config file/env vars can still be used for further
customizations

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-17 09:09:18 +01:00
Nicola Murino
00ce4e4685
EventManager: add uid and extension placeholders
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-16 11:39:32 +01:00
Nicola Murino
26a3c3085b
WebClient: uniform translation indentation
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-14 19:45:15 +01:00
Nicola Murino
f6fac68e1f
update crowdin.yml
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-14 19:44:50 +01:00
Nicola Murino
4cc95f7269 Update Crowdin configuration file 2023-12-14 19:40:06 +01:00
Nicola Murino
fe41109c76
WebClient: add toast notifications
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-14 10:36:25 +01:00
Nicola Murino
cec6420909
add some spaces between sponsor logos
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-13 19:02:01 +01:00
Nicola Murino
55847e7f0e
add Jump Trading to the sponsors section, thank you!!!
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-13 18:10:00 +01:00
Nicola Murino
c76a18168b
WebClient: add language switcher, complete localization support
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-13 18:03:42 +01:00
Nicola Murino
f721cf5c40
WebClient: fix test cases
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-12 19:04:32 +01:00
Nicola Murino
ff2eed8ee9
portable mode: fix panic while validating TLS certificates
Fixes #1480

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-12 18:18:19 +01:00
Nicola Murino
61fe7c39a7
WebClient: allow to pass args for localized errors from the backend
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-12 18:04:14 +01:00
Nicola Murino
691133d7c8
WebClient: improve test coverage
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-10 18:34:09 +01:00
Nicola Murino
8ce9af4adf
dataprovider: sort related resources by name
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-10 17:50:48 +01:00
Nicola Murino
d8b040e57c
refuse to start if the config file is invalid
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-10 16:50:15 +01:00
Nicola Murino
c71f0426ae
WebClient WIP: add support for localizations
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-12-10 16:40:13 +01:00
Nicola Murino
7572daf9cc
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-28 20:14:57 +01:00
Nicola Murino
56d305fde4
CI: re-enable tests on FreeBSD
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-25 19:03:28 +01:00
Nicola Murino
74836af66e
WebUI: extract a common struct for all pages
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-25 18:30:56 +01:00
Nicola Murino
ed828458ab
WebUI add title to all pages
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-25 18:11:10 +01:00
Nicola Murino
6175acb572
add support for reading more secrets from files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-24 20:43:50 +01:00
patrickap
a91cf22e0f
provider: support for username and password file (#1455)
Signed-off-by: patrickap <patrick.schlageter@web.de>
2023-11-24 20:28:51 +01:00
Nicola Murino
62854e4802
WebClient: use flatpickr as time picker
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-22 20:14:49 +01:00
Nicola Murino
bde5713ed6
WebClient: cleanup some js code
also returns an error if file or directory names contain a slash
instead of silently replacing slashes with a similar symbol

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-22 15:57:33 +01:00
Nicola Murino
c14484856e
WebClient: update pdfobject
also add csp nonce when loading javascript files

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-21 16:24:43 +01:00
Nicola Murino
84e387cc9c
WebClient: fix state for shares page
rebuilt the theme and removed unused components

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-19 13:27:49 +01:00
Nicola Murino
ac309cf9a3
WebClient: remove data schema usage from mfa page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-18 20:06:31 +01:00
Nicola Murino
59bdd4bc4e
WebClient: add support for more languages to the editor
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-18 14:17:28 +01:00
Nicola Murino
271d958acf
S3: fix compatibility with the latest SDK
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-18 12:00:53 +01:00
Nicola Murino
bfa17314c6
keyboard interactive auth: respect hook disabled setting
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-18 11:28:15 +01:00
Nicola Murino
6439569f36
WebClient: add csp nonce to CodeMirror
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-18 11:18:31 +01:00
Nicola Murino
50a9ac0163
WebClient: use standard HTML5 video tag
video-js does not work well with CSP

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-17 21:42:22 +01:00
Nicola Murino
1a765c7ff7
WebClient share: add a download page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-17 19:10:03 +01:00
Nicola Murino
61e6cc6985
WebClient: remove remaining inline onclick events
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-16 18:55:14 +01:00
Nicola Murino
37b0c229fc
Web UI: propagate CSPNonce to templates
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-15 18:48:16 +01:00
Nicola Murino
d32d0d7587
WebClient: remove href to javascript
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-14 19:59:53 +01:00
Nicola Murino
3c522961af
WebClient: remove inline onclick from file edit page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-14 19:41:20 +01:00
Nicola Murino
2d9e7dfba2
WebClient: remove inline onclick from MFA page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-14 19:38:09 +01:00
Nicola Murino
4a737be421
WebClient: replace some inline onclick with event listeners
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-13 22:09:55 +01:00
Nicola Murino
450ae868ff
WebClient: update theme to the latest version
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-12 16:28:21 +01:00
Nicola Murino
c8531a5492
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-10 20:39:51 +01:00
Nicola Murino
c5c5860012
ssh: allow to configure public key auth algorithms
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-09 20:03:04 +01:00
Nicola Murino
f83600225b
remove support for sha256-simd
the performance difference are no longer relavant.
We can restore this support if anyone reports performance regression
on any particular hardware

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-09 09:34:20 +01:00
Nicola Murino
a1346aa071
httpd: fixed logging of refused requests due to rate limiting/blocklisting
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-08 19:11:00 +01:00
Nicola Murino
894e12e285
WebClient: refactor alerts
Fix events handling on disabling MFA

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-07 18:52:05 +01:00
Nicola Murino
96c614550f
WebClient: remove inline style from HTML elements
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-07 18:09:24 +01:00
Nicola Murino
6295be786f
WebClient: add a ping URL
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-06 19:58:39 +01:00
Nicola Murino
789d61f170
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-06 19:17:02 +01:00
Nicola Murino
d5a9bec3da
WebClient: allow bulk move or copy actions
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-06 19:10:35 +01:00
Nicola Murino
9e9d6a5585
WebClient: allow to share multiple items from the files page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-06 18:46:12 +01:00
Nicola Murino
654ce2e349
s3: allow to skip TLS verification
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 19:27:11 +01:00
Nicola Murino
9456884584
WebClient: fix display of long usernames in dropdown menu
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:33:16 +01:00
Nicola Murino
010c36cab5
WebClient: allow to set a list of default CSS
The new WIP WebClient requires 2 CSS files

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:30:33 +01:00
Nicola Murino
b872c423ee
Remove external integrations, they are not supported in the new WIP WebClient
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:30:21 +01:00
Nicola Murino
2ee2098a48
WebClient: add test cases for new backend code
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:30:17 +01:00
Nicola Murino
1acc2151cf
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:30:11 +01:00
Nicola Murino
0671178e29
WebClient: fix test cases
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:30:06 +01:00
Nicola Murino
7991b07165
WebClient: update video js
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:29:21 +01:00
Nicola Murino
37facd21d4
WebClient shares: fix view pdf files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:29:15 +01:00
Nicola Murino
b4d9bf9c16
update issue templates
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:29:10 +01:00
Nicola Murino
5452c3c121
update swagger ui
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:29:06 +01:00
Nicola Murino
9322701615
WIP: new WebClient UI
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-05 17:26:29 +01:00
Nicola Murino
2fdcb44c14
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-03 17:26:45 +01:00
Nicola Murino
87b12af932
static files: refactor neutered http.FileSystem
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-03 17:22:28 +01:00
Nicola Murino
75c2bcff8f
TLS: disable by default cipher suites using RSA key exchange
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-01 18:35:23 +01:00
Nicola Murino
822a05aa20
TLS ciphers: use a more secure default if no preference is specified
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-01 16:39:04 +01:00
Nicola Murino
4139c79a77
improve docs and update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-01 10:58:07 +01:00
Nicola Murino
379f87f571
loaddata: do not reveal the existence of the files in error messages
return a generic error message

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-01 10:54:20 +01:00
Nicola Murino
51febb19fa
httpd: add database based token manager
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-11-01 10:54:14 +01:00
Nicola Murino
5c938e46b7
allow to restrict the env vars passed to plugins
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-29 15:19:30 +01:00
Nicola Murino
9a7a3b00dc
EventManager commands: allow to retrieve env vars from the process env
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-29 11:52:53 +01:00
Nicola Murino
daf643596d
WebClient: fix icon for 0 byte files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-29 08:27:00 +01:00
Nicola Murino
bc8d71dfc7
editfiles: fix label
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-29 08:18:58 +01:00
Nicola Murino
8c31cc47b0
web UIs: fix dismissable alerts
alerts can now be shown again after the user dismissal

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-29 08:17:24 +01:00
Nicola Murino
59378104b7
webclient: fix link for shares with a trailing space
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-29 07:33:56 +01:00
Nicola Murino
116be362ba
update nfpm to 2.34.0
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-29 07:15:14 +01:00
Nicola Murino
e1c3097546
event rules: add test case for rename after upload
This is a common pattern in WinSCP

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-28 21:02:14 +02:00
Nicola Murino
9bcdc90ca8
add basic test cases for ALPN protocols
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-28 13:07:23 +02:00
Nicola Murino
7da5d8fcea
config: rename protocols to tls_protocols
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-28 12:42:05 +02:00
Nicola Murino
4a15775f65
allow to configure ALPN protocols
Fixes #1406

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-28 12:35:26 +02:00
Nicola Murino
691e44c1dc
add more upload modes
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-25 19:05:37 +02:00
Nicola Murino
90bce505c4
improve conditional resuming of uploads
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-24 19:14:33 +02:00
Nicola Murino
320e404e4d
vfs: make PipeWriter an interface
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-23 09:56:46 +02:00
Nicola Murino
e3c4ee0833
add support for conditional resuming of uploads
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-23 09:56:40 +02:00
CUI Hao
f1e52d99ba
webadmin: fix typo on webpages (#1438)
Signed-off-by: CUI Hao <cuihao.leo@gmail.com>
2023-10-23 09:54:50 +02:00
Nicola Murino
fc460922ad
events: fix event type string conversion
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-21 11:25:39 +02:00
Nicola Murino
ba9df51b2e
fix or suppress lint warnings detected by golangci-lint 1.55.0
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-20 20:31:17 +02:00
Nicola Murino
6282f95bd3
improve temp dirs handling an some logs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-17 18:06:52 +02:00
Nicola Murino
254824b781
Docker: update to bookworm
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-13 15:55:27 +02:00
Nicola Murino
40d0945450
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-11 18:48:29 +02:00
Nicola Murino
63972edb96
httpd: add a test case for StripSlash middleware
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-08 10:46:17 +02:00
Nicola Murino
da0eb5037e
httpd: skip StripSlash middleware for URL ending with multiple slashes
Fixes #1434

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-08 10:40:08 +02:00
Nicola Murino
4b685b21a2
configs: fix backward compatibility
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-07 22:02:10 +02:00
Nicola Murino
f05fe78737
ssh: refactor host key algorithm restriction
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-07 16:07:19 +02:00
Nicola Murino
19a95d8c55
httpfs: limit body size
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-07 11:28:16 +02:00
Nicola Murino
64c7588a44
sftpd: improve permissions checking test cases
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-04 19:41:50 +02:00
Nicola Murino
c55196a525
portable mode: allow to set config dir/config file
The -c flag is no longer used for SSH commands.
This is a backward incompatible change

Fixes #1423

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-09-25 18:20:09 +02:00
Nicola Murino
1da24ea0af
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-09-24 08:44:16 +02:00
Nicola Murino
75278d64de
docs repo: add instructions for Suse/OpenSuse
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-09-14 19:41:48 +02:00
Nicola Murino
e54fd46a9e
SQL providers: make sure we don't exceed the allowed placeholders
Fixes #1415

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-09-12 19:15:40 +02:00
Nicola Murino
fac022090d
httpd: disable directory index for static files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-09-08 19:55:45 +02:00
Nicola Murino
dc7c829b73
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-09-08 19:19:12 +02:00
Nicola Murino
aefcea034a
validate API key scope
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-09-08 18:54:11 +02:00
Nicola Murino
1cbaa7c77b
WebUIs: update the css to hide the theme hard coded background image
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-09-08 18:07:23 +02:00
Nicola Murino
5ef0a2ed4b
External/plugin auth: check for password change after empty response
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-26 12:04:41 +02:00
Nicola Murino
a592e388cd
ftpd: advertise TLS support only if really enabled
if we don't have a global TLS configuration, advertise TLS only on the
bindings where it is configured instead of failing at runtime

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-21 15:48:29 +02:00
Nicola Murino
5d4145900f
CI FreeBSD: disable until Go 1.21 is available in Quarterly
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-20 23:05:40 +02:00
Nicola Murino
b94ec7597c
smtp: set default port to 587
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-20 22:32:03 +02:00
Nicola Murino
c437f0ad76
logger: update mail adapter
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-20 21:42:41 +02:00
Nicola Murino
7f7d2e57c2
docs: minor improvements
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-20 19:22:38 +02:00
Nicola Murino
397cad93df
httpd request logger: set log level based on the status code
Fixes #1393

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-20 19:01:16 +02:00
Nicola Murino
ce8dbda44b
CI FreeBSD: use Go 1.21
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-20 16:07:12 +02:00
Nicola Murino
62b87083bb
ftpd: add support for TLS session reuse
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-20 16:00:36 +02:00
Nicola Murino
de35eb77cb
ftpd: use the extra field for certificate authentication
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-18 14:39:28 +02:00
Nicola Murino
163662a65a
eventmanager: replace placeholders in multipart filename
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-14 14:34:25 +02:00
Nicola Murino
6395fa0b67
eventmanager: fix params copy
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-12 19:03:47 +02:00
Nicola Murino
f03fdd1155
add object metadata to notification events
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-12 18:51:47 +02:00
Nicola Murino
8ab4a9aa70
all: update to Go 1.21
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-10 19:23:55 +02:00
Nicola Murino
6c482a248d
portable mode: add WebClient
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-10 19:02:55 +02:00
Nicola Murino
25450d9efc
fix event validation test case
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-09 19:00:59 +02:00
Nicola Murino
60cc07bc81
eventmanager: add DELETE method to HTTP notifications
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-09 18:44:17 +02:00
Nicola Murino
d8dd4b2131
CI: fix "Build packages" workflow after Go 1.21 release
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-08 19:47:12 +02:00
Nicola Murino
b7b54b54c3
remove Chinese README
It's constantly outdated and I'm unable to maintain it.

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-08 18:31:48 +02:00
Nicola Murino
5011002d84
allow to set umask on *NIX platforms
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-08 18:30:42 +02:00
Nicola Murino
f5f56129df
add VPS2day to the sponsors section, thank you!!!
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-07 19:36:31 +02:00
Nicola Murino
63212bb033
remove the legacy PreferServerCipherSuites configuration
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-07 19:11:48 +02:00
Nicola Murino
830116bcf2
shares: allow to force an expiration date
this is a soft requirement, users can reactivate expired shares by
updating the expiration date

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-07 19:07:20 +02:00
Nicola Murino
ea96fe9a26
postgres provider: add support for "allow" and "prefer" SSL modes
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-05 20:01:14 +02:00
Nicola Murino
ebdda1b62e
pre-login hook doc: add a note about partial updates
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-05 12:12:16 +02:00
Nicola Murino
54a76e8c45
s3: remove usage of the now deprecated EndpointResolver
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-05 11:58:01 +02:00
Nicola Murino
132d18d5d1
sftpd: fix keyboard interactive test cases
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-04 21:32:14 +02:00
Nicola Murino
75e6ef6132
sftpd: remove diffie-hellman-group18-sha512 KEX
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-04 21:10:39 +02:00
Nicola Murino
af0d7b48ad
sftpd: refactor multi-step authentication
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-08-04 20:56:23 +02:00
guangwu
c03bcb3a8a
fix: typo (#1381)
Signed-off-by: guoguangwu <guoguangwu@magic-shield.com>
2023-08-04 09:20:25 +02:00
Nicola Murino
39259ad6a9
Add a link to the Azure Kubernetes Service offering
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-07-31 19:47:04 +02:00
Nicola Murino
2c070b7eda
update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-07-31 19:44:36 +02:00
Nicola Murino
0413c0471c
add a specific permission to manage folders
creating/updating folders embedded in users is no longer supported.

Fixes #1349

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-07-23 18:48:49 +02:00
Bruce Weirdan
e4be4048e3
Expand reference to http clients in hooks docs (#1359)
* Expand reference to http clients in hooks docs

Before, it wasn't really clear where clients were configured and that their configuration also affected headers.

Signed-off-by: Bruce Weirdan <weirdan@gmail.com>

* Update more mentions of HTTP clients

Signed-off-by: Bruce Weirdan <weirdan@gmail.com>

---------

Signed-off-by: Bruce Weirdan <weirdan@gmail.com>
2023-07-18 15:37:57 +02:00
Nicola Murino
00366fce07
shares: respect password strength
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-07-16 16:51:38 +02:00
Nicola Murino
e88172dd7e
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-07-15 12:35:30 +02:00
Nicola Murino
a5cb26daf2
pgx: revert to an older version
pgx 5.4.1 has a memory leak

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-07-08 17:08:31 +02:00
Nicola Murino
4f8794a255
file patterns: fix denied except rules
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-07-08 17:02:47 +02:00
Nicola Murino
5e5a09f164
make GroupConditionPatterns uniform with the accepted PR
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-07-02 10:57:29 +02:00
David Stäheli
f78e4b0443
check for multiple inverse matches (#1332)
* update check for multiple inverse matches

Signed-off-by: David Stäheli <mistrdave@gmail.com>

* after match, direct return true

Signed-off-by: David Stäheli <mistrdave@gmail.com>

* apply same behaviour to checkEventGroupConditionPatterns

Signed-off-by: David Stäheli <mistrdave@gmail.com>

* fix spellmistake of function name

Signed-off-by: David Stäheli <mistrdave@gmail.com>

---------

Signed-off-by: David Stäheli <mistrdave@gmail.com>
2023-07-02 09:49:21 +02:00
Nicola Murino
51d8f3b436
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-29 14:42:16 +02:00
Nicola Murino
ecc01f4f37
Windows setup: add PrepareToInstall event function
so the service is stopped before the installation starts and
we avoid the force close app warning

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-29 11:23:04 +02:00
Joshua Treudler
2af42da371 fixed typo
Signed-off-by: Joshua Treudler <joshua@treudler.net>
2023-06-28 13:35:53 +02:00
Nicola Murino
d1e4ee7bc8
config: fix loading commands args from env vars
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-25 21:31:57 +02:00
Nicola Murino
4440c49174
add auth plugin
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-25 18:57:30 +02:00
Nicola Murino
76964a6b85
check second factor after plugin authentication
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-25 07:16:26 +02:00
Nicola Murino
66f360e66c
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-18 07:15:39 +02:00
Nicola Murino
a38ce460bb
WebClient: show user quota
Also remove per-source data transfer limits. This was an
oversight

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-16 21:06:21 +02:00
Nicola Murino
80f21d1c91
WebAdmin: don't show hidden deny policy for allowed patterns
The deny policy only applies to denied patterns, showing an allowed
pattern as hidden will confuse users

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-14 19:00:52 +02:00
Nicola Murino
1c1b76011f
WebAdmin: relax key prefix validation
try to automatically fix leading and trailing slashes

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-12 19:13:16 +02:00
Nicola Murino
957d3a7b4d
CockroachDB: use unordered_unique_rowid for primary keys
sequential values in a primary key does not perform as well

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-10 18:23:35 +02:00
Nicola Murino
d7d7b0bbf0
dataprovider: fix sql for CockroachDB
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-10 15:32:51 +02:00
Nicola Murino
a3156de4a8
CI: fix MariaDB initialization
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-10 14:09:50 +02:00
Nicola Murino
99424bfa58
squash database migrations
SQLite: remove AUTOINCREMENT from primary keys. It is not needed.

Postgres: switch from serial to identity for primary keys.
This means Postgres < 10 will not work in v2.6.x

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-10 13:06:24 +02:00
Nicola Murino
d120957736
CI: set Go version to 1.20.5
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-08 19:44:19 +02:00
Nicola Murino
324d695d93
try to fix a randomly failing test case
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-08 19:41:58 +02:00
Nicola Murino
9d60972743
WebClient: redirect to the requested URL after login
This feature is only useful and enabled for file manager urls

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-08 18:14:47 +02:00
Nicola Murino
f938af5a61
WebClient: fix sorting by size
Fixes #1313

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-04 21:45:31 +02:00
Nicola Murino
9ccdc3a597
add code of conduct
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-04 09:03:58 +02:00
Nicola Murino
3499edd5c2
WebUI: remove leading and trailing spaces from user-submitted input
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-04 08:45:17 +02:00
Nicola Murino
9470cd6e69
multi-node installations: use a different backup path for each node
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-03 17:54:24 +02:00
Nicola Murino
1f7433e798
getting started guide: add a link to the available installation methods
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-03 17:29:12 +02:00
Nicola Murino
4ba3d026b4
update README
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-03 17:19:51 +02:00
Nicola Murino
98c639579f
add issue templates
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-03 17:13:43 +02:00
Nicola Murino
74e5999c63
added support for verifying sha256/sha512 passwords hash
this simplifies the migration of users from some proprietary products

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-03 16:58:45 +02:00
Nicola Murino
48939b2b4f
add XOAUTH2
start the countdown, let's see how long it takes for your favorite
Go-based proprietary SFTP server to notice this change, copy the SFTPGo
code and thus violate its license, and announce the same feature :)

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-06-03 16:17:32 +02:00
Nicola Murino
8339fee69d
smtp: add debug option
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-30 19:11:28 +02:00
Nicola Murino
a2fc7d3cc5
update security policy
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-26 19:22:37 +02:00
Nicola Murino
ae7954eee2
WebUIs: fix disclaimer paths
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-26 17:59:38 +02:00
Nicola Murino
8f934f7c82
email action: allow to configure Bcc
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-25 19:55:27 +02:00
Nicola Murino
b2781e0bfc
WebAdmin: Set TLS username to empty string if disabled
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-25 18:24:51 +02:00
Nicola Murino
e11473cf52
config: limit the size for env files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-25 05:25:28 +02:00
Nicola Murino
f8f8962ccb
file patterns: evaluate allowed filters before the denied ones
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-24 19:56:53 +02:00
Nicola Murino
2238043efd
EventManager: add email field placeholder
Fixes #1288

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-24 19:08:51 +02:00
Nicola Murino
d9426cef20
docker: update docs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-23 13:06:42 +02:00
Nicola Murino
052d586364
docker: remove distroless
Fix #1295

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-23 13:04:42 +02:00
Nicola Murino
11ba41e903
Revert "Docker: try to add CAP_NET_BIND_SERVICE to the binary"
This reverts commit 8d12872608.

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-23 12:59:49 +02:00
Nicola Murino
255985b7b0
Windows: start the service in a goroutine
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-23 12:59:27 +02:00
Nicola Murino
2b77709a04
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-21 09:23:24 +02:00
Nicola Murino
5b4a1bda2e
set version to 2.5.1
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-20 17:39:23 +02:00
Nicola Murino
3f94f6d0e7
proxy protocol: fix require policy in some edge cases
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-20 16:08:57 +02:00
Nicola Murino
d28a53a6cf
webdav: fix caching with external auth/plugins
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-20 12:39:07 +02:00
Nicola Murino
963cec124e
oidc docs: fix typo
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-18 18:43:46 +02:00
Nicola Murino
bbaca578cd
EventManager: add content type option for email config
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-17 19:28:13 +02:00
Nicola Murino
da30389989
fix OpenAPI schema, update js deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-17 18:24:33 +02:00
Nicola Murino
52ec36dbd6
update pwd reset template. Update deps and use new features from the OIDC library
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-17 18:10:57 +02:00
Artem Kajalainen
b524d178dd
docs: add info about IRSA for S3 authn
Signed-off-by: Artem Kajalainen <artem@iki.fi>
2023-05-16 19:35:13 +02:00
Nicola Murino
e0d9b8bddf
WebClient: update password change timestamp after password reset
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-16 19:15:45 +02:00
Nicola Murino
19da923369
webdav: add support for parsing more time formats
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-16 18:51:42 +02:00
Nicola Murino
824a70b22d
Docker Alpine: update to 3.18
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-16 18:23:41 +02:00
Nicola Murino
cea70d5d6b
update security policy
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-16 18:17:35 +02:00
Nicola Murino
adad8e658b
osfs: add optional buffering
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-16 18:08:14 +02:00
Nicola Murino
e10487ad57
EventManager: improve automatic detection of JSON body
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-12 19:22:50 +02:00
Nicola Murino
4eded56d5f
add support for log events
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-12 18:34:59 +02:00
Krasimir Popov
43d011f125 Fixing double the typo in README
Signed-off-by: Krasimir Popov <kjpopovbg@gmail.com>
2023-05-08 15:50:28 +02:00
Daniel Hammer
a292044501 Aligned help example with v2.5.0 output
Signed-off-by: Daniel Hammer <daniel.hammer+oss@gmail.com>
2023-05-06 13:11:48 +02:00
Nicola Murino
05c54614b2
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-05 19:12:50 +02:00
Nicola Murino
32020e236f
set version to 2.5.0
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-05-03 13:07:48 +02:00
Nicola Murino
b9cf6e5083
Add the link to the new Azure offer for Windows
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-26 13:53:05 +02:00
Nicola Murino
ee5b7290a0
EventManager: add more debug logs for HTTP actions
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-25 20:27:40 +02:00
Nicola Murino
fd6a44c562
OpenAPI: fix filesystem action types enum
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-23 14:46:09 +02:00
Nicola Murino
8d12872608
Docker: try to add CAP_NET_BIND_SERVICE to the binary
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-19 13:41:59 +02:00
Nicola Murino
712f2053a4
REST API dumpdata: allow to specify the resources to dump
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-18 18:11:23 +02:00
Nicola Murino
54462c26f2
WebAdmin: display undefined js objects as empty string
This is probably something that changed in the recent datatables update,
before it was handled automatically

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-16 15:38:49 +02:00
Nicola Murino
d0a171558d
fix test cases for system commands
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-15 16:09:53 +02:00
Nicola Murino
1ade850557
add a log to better debug a randomically failing test case
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-15 15:08:42 +02:00
Nicola Murino
466f2e88b3
WebClient: fix rename
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-15 14:16:26 +02:00
Nicola Murino
3cb53b2c33
fix cross folder copy
also update css/js deps and other minor changes

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-13 18:23:42 +02:00
Nicola Murino
6279216c2e
webdav: fix GET as PROPFIND if a prefix is defined
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-09 20:17:37 +02:00
Nicola Murino
5219c1fdd1
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-08 19:00:05 +02:00
Nicola Murino
4294659785
try harder to convert transfer errors in well-known error types
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-08 14:55:04 +02:00
Nicola Murino
f03f1b0156
improve test cases coverage
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-08 10:01:48 +02:00
Nicola Murino
184b99d500
user: add a field to indicate whether the password is set
A structure similar to the one used for secrets would be better,
but we don't want to break backwards compatibility.

Also document that omitting the password field in the request body
will preserve the current password when updating a user using the
REST API. Added a test case for this.

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-06 18:22:09 +02:00
Nicola Murino
74f05e5305
EventManager: check the parent directory before creating a zip
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-03 18:53:13 +02:00
Nicola Murino
aefa7f77c2
add a link to the Terraform provider
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-04-02 18:48:56 +02:00
Nicola Murino
084d4109b8
WebAdmin: ensure to sanitize data before rendering
Thanks to Polina Zvorykina, VK for reporting this issue

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-28 12:28:38 +02:00
Nicola Murino
b60d3f680e
user as JSON: rename 2fa_protocols to two_factor_protocols
This is a breaking change, but it is necessary to make JSON serialization of
users more compatible.
For example, Terraform does not allow JSON fields starting with numbers

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-26 15:57:53 +02:00
Nicola Murino
ee90bfb506
add unixcrypt build tag
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-26 10:33:30 +02:00
Nicola Murino
e17068a76f
postgres provider: add support for load balancing
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-25 09:29:13 +01:00
Nicola Murino
354fc9b3d6
OIDC: allow to extract custom fields from sub-structs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-23 18:15:07 +01:00
Nicola Murino
e29f6857db
EventManager: add IDP login trigger and check account action
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-22 19:02:54 +01:00
Nicola Murino
40344ec0ff
CI FreeBSD: compile and run tests using the same user
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-17 12:51:37 +01:00
Nicola Murino
783dff369b
CI FreeBSD: install git
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-15 20:20:52 +01:00
Nicola Murino
72e0325d05
run test cases also on FreeBSD
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-15 19:44:45 +01:00
Nicola Murino
2710207779
update jquery, go deps, actions/setup-go to v4
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-15 18:44:08 +01:00
Nicola Murino
b719d03ebe
WebAdmin: improve fs config layout
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-12 15:08:32 +01:00
Nicola Murino
84396343da
fix some codeql warnings
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-10 17:30:06 +01:00
Nicola Murino
14242b59a2
oidc docs: add env vars config
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-09 18:58:36 +01:00
Nicola Murino
dad346cee8
add codeql
update deps

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-05 16:38:29 +01:00
Nicola Murino
04282f94a4
update js and css deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-04 16:14:16 +01:00
Nicola Murino
0423e8f157
httpd: generate defender events for failed 2fa and password resets
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-04 13:55:48 +01:00
Nicola Murino
bdcee06665
WebClient: remove the default upload size limit
Users who want a limit can still set it.
By default, we want to allow uploads of any size

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-02 18:26:21 +01:00
Nicola Murino
ae90ed2ba0
Docker: try again to add armv7 support
Let's see if the actions are more stable now

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-02 18:11:26 +01:00
Nicola Murino
4ba3ae876d
allow to set password strength at user/group level
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-02 09:11:30 +01:00
Nicola Murino
662164c7ff
smtp: require templates only if a server is configured or in service mode
This regression was introduced after recent changes to allow setting the SMTP
settings from the WebAdmin UI.

Fixes #1217

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-03-01 18:31:02 +01:00
Nicola Murino
fad6af11e5
don't expose error messages from pre-actions and post connect hooks
always return a generic error instead to avoid leaking internal info

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-28 18:01:09 +01:00
Nicola Murino
dba088daed
printf: replace %#v with the more explicit %q
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-27 19:19:57 +01:00
Nicola Murino
a23fdea9e3
ftpd: allow hostnames as passive IP
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-27 19:19:50 +01:00
Nicola Murino
561976bcd0
WebClient: return proper status code for http.MaxBytesError
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-27 11:03:05 +01:00
Nicola Murino
874776bd12
also capture logs for pre-login and check-password commands
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-26 15:15:34 +01:00
Felix Eckhofer
ec67b67e9e Send output from external_auth_hook to logs
Signed-off-by: Felix Eckhofer <felix@eckhofer.com>
2023-02-26 07:39:34 +01:00
Felix Eckhofer
71f691b208 Fix potential ldap injection
Signed-off-by: Felix Eckhofer <felix@eckhofer.com>
2023-02-26 07:10:58 +01:00
Nicola Murino
e0cbb966f0
eventmanager: skip password expiration check for expired users
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-25 16:33:39 +01:00
Nicola Murino
df9d47900a
eventmanager: add user/folders as comma separated string in errors
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-25 15:26:38 +01:00
Nicola Murino
b8496c4d6e
eventmanager: add user expiration check
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-25 13:06:09 +01:00
Nicola Murino
b0cfaf189c
portable mode: allow to read the password from a file
Fixes #1206

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-25 10:24:23 +01:00
Nicola Murino
195cb9f081
enable keyboard interactive authentication by default
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-24 20:22:32 +01:00
Nicola Murino
9a10740218
allow ACME HTTP-01 challenge with https redirect from port 80
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-24 20:08:14 +01:00
Nicola Murino
7bcd79a70a
telemetry: improve test cases
remove an unnecessary nil check in tlsutils added as workaround
to make telemetry test cases work

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-24 11:05:46 +01:00
Nicola Murino
beb8822df4
examples: update deps
to silence dependabot alerts

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-23 19:25:30 +01:00
Nicola Murino
8805d85377
configs: add ACME section
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-23 19:25:20 +01:00
Nicola Murino
fcf9a8c673
scheduler: disable verbose logs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-21 18:18:24 +01:00
Nicola Murino
2c1319985d
sql providers: remove unnecessary []byte to string conversion
always check affected rows for updates

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-20 18:14:02 +01:00
Nicola Murino
a3fff56da5
WebAdmin: add configs section
Setting configurations is an experimental feature and is not currently
supported in the REST API

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-19 19:03:45 +01:00
Nicola Murino
14961a573f
examples: update deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-18 13:46:06 +01:00
Nicola Murino
78cd5d8eba
groups: add expiration date override
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-13 19:32:36 +01:00
Nicola Murino
2df2803a37
ipfilter plugin: add protocol
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-13 13:45:45 +01:00
Nicola Murino
7738faa040
events: add elapsed to UI and exports
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-13 12:58:21 +01:00
Nicola Murino
157d1db0b1
fs events: add elapsed field to notifications
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-12 18:56:53 +01:00
Nicola Murino
7e85356325
WebClient shares: replace basic auth with a login form
basic auth will continue to work for REST API

Fixes #1166

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-12 08:29:53 +01:00
Nicola Murino
a3d0cf5ddf
fix lint errors
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-10 19:59:03 +01:00
Nicola Murino
04ab8e72f6
WebUI: make error messages user dismissible
Fixes #1171

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-10 18:07:23 +01:00
Nicola Murino
e0c3a13ac5
azblob: update to the latest SDK
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-09 13:04:12 +01:00
Nicola Murino
1b1745b7f7
move IP/Network lists to the data provider
this is a backward incompatible change, all previous file based IP/network
lists will not work anymore

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-09 09:33:33 +01:00
Nicola Murino
2412a0a369
add Dendi to the sponsors section, thank you!!!
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-02-02 18:12:21 +01:00
Nicola Murino
1e14d006b1
defender: set score_no_auth to 0 as default
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-29 18:00:27 +01:00
Nicola Murino
27c4ffd663
sftpd: fix duplicate defender error introduced in the previous commit
improve the defender test cases by verifying the expected score

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-25 21:57:27 +01:00
Nicola Murino
c0fe08b597
defender: allow to set a different score for "no auth tried" events
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-25 18:49:03 +01:00
Nicola Murino
5550a5d2c0
update users: also disconnect users from remote nodes when requested
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-24 18:53:34 +01:00
Nicola Murino
2066ad7c83
WebDAV: allow to define custom MIME type mappings
Fixes #1154

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-23 18:43:25 +01:00
Nicola Murino
61199172d0
add support for monitoring and reloading externally provided TLS certs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-22 18:31:14 +01:00
Nicola Murino
3ce4d04b27
EventManager: support placeholders within URL paths
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-22 08:46:58 +01:00
Nicola Murino
707729ee61
acme: allow to separate multiple domains with spaces
This change is required to be able to set multiple domains for the same
certificate using env vars.
The change is backward compatible for general use cases but may be
backward incompatible in some edge cases, for example:

- "sftpgo.com,www.sftpgo.com" will work as before
- "sftpgo.com, www.sftpgo.com" will not work anymore

Check the logs to see if you are affected and rename the certificate and key
to fix

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-21 18:00:23 +01:00
Nicola Murino
7b5bebc588
EventManager: add "on-demand" trigger
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-21 15:41:24 +01:00
Nicola Murino
53f17b5715
allow to disable event rules
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-19 18:33:04 +01:00
Nicola Murino
496c8bc785
allow to start if only httpd service is enabled
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-17 18:22:04 +01:00
Nicola Murino
396d67bb2c
web: add spellcheck hint to some more fields
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-16 19:45:12 +01:00
Daniel Hammer
bbebd9b163 "Spell-Jacking" mitigation ~ prevent sensitive data leak from spell checker.
@see https://www.otto-js.com/news/article/chrome-and-edge-enhanced-spellcheck-features-expose-pii-even-your-passwords

Signed-off-by: Daniel Hammer <daniel.hammer+oss@gmail.com>
2023-01-16 19:23:43 +01:00
Nicola Murino
c8d94f0a27
add a health check command
Useful in restricted environments where commands like curl and such
are not available.

Fixes #1129

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-16 18:54:42 +01:00
Nicola Murino
8be8343fee
README: fix link to Fs interface
Fixes #1142

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-15 15:28:55 +01:00
Nicola Murino
f3995901e3
OpenAPI: fix group settings documentation
the OpenAPI docs should really be improved, but nobody seems interested
enough to sponsor this work

Fixes #1141

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-15 15:28:52 +01:00
Nicola Murino
f2618e7de6
switch from go-simple-mail to go-mail
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-15 15:28:31 +01:00
Nicola Murino
6afbd77fd5
update css and js deps
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-07 18:11:46 +01:00
Nicola Murino
93e5cb36df
copy: use server side copy if available
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-07 16:28:46 +01:00
Nicola Murino
09dea57850
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-07 13:07:41 +01:00
Nicola Murino
8cad436421
conditional support for recursive renaming for cloud providers
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-06 12:33:50 +01:00
Nicola Murino
f0dedbfabf
eventmanager: auto-create destination folder for renames
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-03 18:13:01 +01:00
Nicola Murino
51f0ded222
update test certificates
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-03 11:48:08 +01:00
Jon Bendtsen
6b555cf0d8 metrics only available in telemetry server
I do not know which version removed /metrics from the HTTP server, but it does not seem to be available in 2.4.2, so I updated the metrics documentation to reflect this. Replaced with links to telemetry configuration.

Signed-off-by: Jon Bendtsen <github@jonb.dk>
2023-01-03 10:27:15 +01:00
Nicola Murino
0190d0b849
update Copyright year
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-03 10:18:30 +01:00
Nicola Murino
9977c64459
docs eventmanager: update index
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-02 19:22:26 +01:00
Nicola Murino
20706e45b0
docs: basic example for a Recycle Bin function
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-02 18:51:45 +01:00
Jon Bendtsen
53864fd8c1 Add warning of docker grace vs. SFTPGo grace
Dockers default grace period is only 10 seconds, so added a warning to alert users to those cases where their SFTPGO_GRACE_TIME is larger than the docker grace
2023-01-02 17:09:59 +01:00
Nicola Murino
7fa0959af4
eventmanager: add support for global star path matching
This introduce a backward incompatible change for filesystem path matching
in the Event Manager, now patterns like "*.txt" will no longer match any
file with the "txt" suffix, you need to change them to "/**/*.txt".

Also change pre-delete behaviour, now if an error is returned the client
will get a permission denied error. This is the same as the other pre-*
action. Previously it was not possible to deny deletion of a file.

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-02 15:59:00 +01:00
Nicola Murino
2611dd2c98
eventmanager: add support for pre-* actions
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-01-01 17:59:41 +01:00
Nicola Murino
6cebc037a0
eventmanager: check disk quota before executing the compress action
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-31 16:41:32 +01:00
Nicola Murino
15ad31da54
WebClient: add copy action
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-30 19:30:16 +01:00
Nicola Murino
fe9904a54d
docs full-configuration: improve formatting
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-28 18:51:25 +01:00
Nicola Murino
831851c0c3
change the default value for naming rules
WebAdmin does not work properly is trimming trailing and leading white
spaces is disabled

Fixes #1119

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-27 18:57:48 +01:00
Nicola Murino
ea4c4dd57f
eventmanager: add copy action
refactor sftpgo-copy and sftpgo-remove commands

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-27 18:51:53 +01:00
Nicola Murino
e5a8220b8a
REST API: add location header to 201 responses
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-23 13:08:04 +01:00
Jon Bendtsen
ed949604d3
Added graceful shutdown description to docker (#1112)
* Added graceful shutdown description to docker

Describing how to use the graceful shutdown period in a docker SFTPGO container and giving some examples of what happens with both existing and new connections.

Signed-off-by: Jon Bendtsen <github@jonb.dk>
2022-12-23 12:11:15 +01:00
Nicola Murino
0841c7d7bd
REST API: remove merging of fields on updates
we use PUT verb not PATCH. We keep merging only to allow to preserve
hidden/encrypted fields.

This is a backward incompatible change, but is necessary to avoid unexpected
issues.
You have to pass complete objects on updates.

Fixes #1088

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-23 09:36:20 +01:00
Nicola Murino
e17975ed7d
dataprovider: include port in node name and make it a hash
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-20 16:40:32 +01:00
Nicola Murino
f4eb9e7cd6
OpenAPI: set charset also for text/plain responses
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-19 18:38:23 +01:00
Nicola Murino
1085f9e5ec
httpfs OpenAPI: added charset=utf-8 to application/json content type
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-19 18:35:06 +01:00
Jon Bendtsen
37eceffed9
OpenAPI: added charset=utf-8 to application/json content type (#1108)
* Added charset=utf-8 to application/json content type

This change is linked to https://github.com/drakkan/sftpgo/issues/1101 and should partially alleviate the need to change the content type in the files generated by openapi-generator-cli

Signed-off-by: Jon Bendtsen <github@jonb.dk>

* extra newline

Signed-off-by: Jon Bendtsen <github@jonb.dk>

* Signed-off-by: Jon Bendtsen github@jonb.dk

Signed-off-by: Jon Bendtsen github@jonb.dk
Signed-off-by: Jon Bendtsen <github@jonb.dk>

* This change is linked to #1101 and should partially alleviate the need to change the content type in the files generated by openapi-generator-cli.

Signed-off-by: Jon Bendtsen <github@jonb.dk>

Signed-off-by: Jon Bendtsen <github@jonb.dk>
Signed-off-by: Jon Bendtsen github@jonb.dk
2022-12-19 18:30:27 +01:00
Nicola Murino
6270b2c2d3
eventmanager: log a get task error only when required
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-19 18:10:40 +01:00
Nicola Murino
ad5bd18dd0
CI: add nosqlite build tag when CGO is disabled
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-18 15:21:32 +01:00
Nicola Murino
0296e0cafa
gcsfs: allow to customize upload part size/time
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-18 11:51:46 +01:00
Nicola Murino
147ad3b230
respect token validation mode for CSRF header
Fixes #1104

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-16 19:14:56 +01:00
Nicola Murino
2da3eabc12
eventmanager: add password notification check action
this action allow to send an email notification to users whose
password is about to expire

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-16 18:51:29 +01:00
Nicola Murino
ac91170d65
S3: improve "directories" detection
Fixes #1097

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-13 08:55:01 +01:00
Nicola Murino
f13b901f2d
local fs: fixed paths validation for some Windows specific edge cases
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-12 10:40:04 +01:00
Nicola Murino
c23c73ed34
update OpenAPI definition
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-11 17:53:41 +01:00
Nicola Murino
ad5d657a1a
add support for password policies
you can now set a password expiration and the password change requirement

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-11 17:15:34 +01:00
Nicola Murino
e2bebc99d1
AzureBlobs: update SDK to v0.6.1
Remove path escape for blob names, this issue is now fixed within
the SDK

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-10 09:44:14 +01:00
Nicola Murino
926dcbbc63
add a CLI command to reset admin passwords
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-09 18:28:16 +01:00
Nicola Murino
a7f9581d99
provider events: add support for omit_object_data search param
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-08 10:02:12 +01:00
Nicola Murino
75d911f29e
WebAdmin: allow to search and export event logs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-07 18:47:38 +01:00
Nicola Murino
91e4a54385
fix build with some features disabled
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-04 08:44:45 +01:00
Nicola Murino
221a4878aa
eventmanager: allow to filter based on role name
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-03 17:47:43 +01:00
Nicola Murino
2ea43647ed
ftpd: check the TYPE parameter in a case-insensitive manner
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-03 13:09:25 +01:00
Nicola Murino
04bdd3a5e4
docker: bump alpine to 3.17
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-03 12:30:53 +01:00
Nicola Murino
1f9cf194fe
add role to events
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-12-03 11:45:27 +01:00
Nicola Murino
e87118d2a8
allow WebClient login with multi-step auth enabled
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-29 18:43:48 +01:00
Nicola Murino
fe888729f9
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-27 12:15:56 +01:00
Nicola Murino
d7cd2ac803
add CODEOWNERS file
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-24 18:53:59 +01:00
Nicola Murino
ba9fe38b8b
azblob: handle dirs metadata
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-24 18:14:24 +01:00
Nicola Murino
7b00fe3d5a
update nfpm to 2.22.1
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-20 15:23:45 +01:00
Nicola Murino
fc1ba36ae5
fix SeaweedFS rename compatibility
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-20 13:06:58 +01:00
Nicola Murino
2290137868
WebDAV: add support for X-OC-Mtime header
it is used by Nextcloud compatible clients to set the modification time

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-19 19:39:28 +01:00
Nicola Murino
6ebe7691db
WebClient: add drag and drop upload UI
thanks to @wooneusean for the help

Fixes #951

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-19 12:31:03 +01:00
Nicola Murino
29d1993a3b
Docker: add a default moduli file
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-18 18:13:03 +01:00
Nicola Murino
81c693de4e
Ignore denied patterns for stat on "/"
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-18 18:12:37 +01:00
Nicola Murino
2017cb60e9
Per-directory permissions: add wildcards support
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-18 18:12:04 +01:00
Nicola Murino
ec4cc33364
WebAdmin users form: trim spaces from some form fields
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-17 18:26:19 +01:00
Nicola Murino
a22282f275
add support for DHGEX
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-17 18:15:53 +01:00
Nicola Murino
67de4c9c07
check more mime types for SeaweedFS dirs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-16 21:38:27 +01:00
Amir.h Yeganemehr
6591769a07 Handle empty directories with mimetype
Signed-off-by: Amir.h Yeganemehr <yeganemehr@jeyserver.com>
2022-11-16 19:47:22 +01:00
Nicola Murino
5a222807b7
add roles
Fixes #837

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-16 19:04:50 +01:00
Nicola Murino
a9207857cf
webdav: add a test case for PROPFIND with infinity Depth
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-06 07:33:56 +01:00
Nicola Murino
37ffa3b55a
portable mode: remove support for services discovery via multicast DNS
The library used for mDNS doesn't seem well maintained and I think this
feature is rarely used

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-05 18:32:36 +01:00
Nicola Murino
048591553a
allow to set a default expiration for newly created users
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-05 18:01:24 +01:00
Nicola Murino
33bfd61a0c
plugins: fix hash check
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-04 20:25:01 +01:00
Nicola Murino
965d059400
WebUI: try harder to prevent browsers from auto-filling in password fields
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-03 19:57:43 +01:00
Nicola Murino
676286182a
webdav: always open files for reading in lazy mode
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-03 08:31:40 +01:00
Nicola Murino
3b2002d9ef
shared providers: allow to immediately re-add soft-deleted event rules
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-01 17:39:53 +01:00
Nicola Murino
9d7e30807d
WebDAV: make test cases more robust
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-01 13:42:42 +01:00
Nicola Murino
91fae5c4d4
shared providers: allow to immediately re-add soft-deleted users
there is no need to wait for cache updates

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-01 12:53:08 +01:00
Nicola Murino
e3e85867b1
sftpfs: reuse connections
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-01 12:22:54 +01:00
Nicola Murino
5618b95372
improve some docs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-30 08:34:16 +01:00
Nicola Murino
bf45d04600
eventmanager: add placeholder to get the parent directory
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-29 15:49:24 +02:00
Nicola Murino
80244bd83b
eventmanager: allow to access the backup file
so it can be used in email and other actions

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-29 14:04:31 +02:00
Nicola Murino
9a9e7d1a7f
squash database migrations
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-28 14:28:37 +02:00
Nicola Murino
6f422c3d8b
WebClient: make folder deletion recursive
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-27 08:27:44 +02:00
Nicola Murino
222f0c735b
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-23 09:20:00 +02:00
Nicola Murino
63bf8eb1a1
set version to 2.4.0
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-23 04:47:41 +02:00
Nicola Murino
db0e58ae7e
Add support for graceful shutdown
Fixes #1014

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-22 11:56:41 +02:00
Nicola Murino
87045284cc
make connections lookups constant time
Performance improves if there are many active connections.
For a few connections there is a small (unnoticeable) performance
degradation

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-20 18:17:13 +02:00
Nicola Murino
f3ee20980a
fix build in bundle mode
added bunlde mode build to CI to prevent this from happening again
in the future

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-20 07:58:34 +02:00
Nicola Murino
54f1946aba
OIDC: allow to skip JWT signature validation
It's intended for special cases where providers,such as Azure,
use the "none" algorithm

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-19 18:38:09 +02:00
Nicola Murino
47842ae614
script based hooks: don't propagate global env vars
env vars must be explicitly set

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-19 09:29:40 +02:00
Nicola Murino
7e0b62b703
update swagger-ui, codemirror, video-js
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-16 18:29:42 +02:00
Nicola Murino
15b4194e8f
event rules: allow to set min/max file size using "human" notation
10MB or 1GB instead of the size in bytes

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-16 15:28:47 +02:00
Nicola Murino
5a199acbb2
howto: add event manager
add groups section in the getting started guide.
Suggest to prefer configuration with env vars instead of modifying
the default configuration file

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-16 08:26:03 +02:00
Nicola Murino
07b3f2f4d6
config: fix for slices with default values
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-14 16:45:20 +02:00
Nicola Murino
13ee236884
Allow to read env vars from files inside the "env.d" directory
This makes it easier to set environment variables on some operating systems.
Setting configuration options from environment variables is recommended if
you want to avoid the time-consuming task of merging your changes with the
default configuration file after upgrading SFTPGo

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-13 18:43:58 +02:00
Nicola Murino
3822b7d3f7
workflows: replace deprecated set-output command
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-13 13:11:22 +02:00
Nicola Murino
2b2b69fb23
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-12 22:03:47 +02:00
Nicola Murino
4b4edef0ad
disable self connections by default
now that the event manager can create files, self connections may create
even more issues than before

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-12 18:12:12 +02:00
Nicola Murino
aa1e73326f
FTPD: fix APPE to new files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-12 11:37:31 +02:00
Nicola Murino
07012aa812
WebDAV: allow to set last modification time
This commit add a minimal dead properties implementation

Fixes #1018

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-11 19:20:58 +02:00
Nicola Murino
0e54fa5655
cryptfs: fix quota for overwrites if upload fails
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-10 19:34:15 +02:00
Nicola Murino
3e44a1dd2d
eventmanager: add support for file/directory compression
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-10 18:53:58 +02:00
Nicola Murino
a417df60b3
azblob: use UUIDs as block IDs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-07 06:54:26 +02:00
Nicola Murino
2067c5c527
azblob: rename method to initialize from SAS URL
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-02 15:03:38 +02:00
Nicola Murino
8a43486730
postgres driver: add multi hosts support
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-02 12:43:26 +02:00
Nicola Murino
2636fedce8
node token: add/pars admin username
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-02 09:51:47 +02:00
Nicola Murino
a42e9ffa6b
azblob: add support for the latest SDK
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-01 14:04:53 +02:00
Nicola Murino
0e8c41bbd1
sftpd: fix relative symlinks handling
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-30 19:23:54 +02:00
Nicola Murino
1e21aa9453
add support for checking sha256crypt passwords
they will be converted to the configured password hashing algorithm after
the first user login

Fixes #1000

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-28 19:15:02 +02:00
Nicola Murino
f9eadd7f04
API data retention check: send CSV reports for email notifications
replace the HTML email with the same CSV report used in the
event manager

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-28 18:37:32 +02:00
Nicola Murino
04dc97072b
eventmanager: add metadata check
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-26 19:00:34 +02:00
Nicola Murino
ddda0b5ece
SQLite provider: remove code only used for shared providers
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-25 20:29:43 +02:00
Nicola Murino
76e89d07d4
add support for inter-node communications
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-25 19:48:55 +02:00
Nicola Murino
a538255034
httpclient: add leaf certificates
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-23 17:49:42 +02:00
Nicola Murino
4ad2a9c1fa
WebClient: validate PDF files before rendering
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-22 20:41:28 +02:00
Nicola Murino
7ae9303c99
allow to disable REST API
Fixes #987

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-22 17:27:00 +02:00
Nicola Murino
6c7b3ac5bb
oidc: update user after token refresh
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-22 08:30:22 +02:00
Nicola Murino
bd294bb3cf
WebAdmin: allow to simplify the user page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-21 19:36:08 +02:00
Nicola Murino
7349598b19
command hooks: allow to pass custom arguments
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-20 13:58:44 +02:00
Nicola Murino
7f19f9f39c
WebClient: allow partial download of shared files
each partial download will count as a share usage

Fixes #970

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-19 19:58:35 +02:00
Nicola Murino
f19691250d
zip downloads: make zip entries relative to the current dir when possible
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-19 17:06:42 +02:00
Nicola Murino
554a1cb1f4
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-18 13:08:48 +02:00
Nicola Murino
e54237ff70
allow a client if its IP is both allowed and denied
this allows you to define a group deny policy that can be overridden
on a per-user basis.

This is a backward incompatible change

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-15 19:51:17 +02:00
Nicola Murino
e58709c822
WebAdmin: allow to specify quota and upload size in human format
For example 1 GB

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-14 21:18:32 +02:00
Nicola Murino
5eca73a399
give some hints if we fail to load HTML templates
Fixes #986

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-14 12:25:19 +02:00
Nicola Murino
f8a19f747d
WebUI: improve HTML escaping
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-13 19:16:07 +02:00
Nicola Murino
ea3c1d7a3b
WebAdmin: allow to pre-select groups on add user page
The admin will still be able to choose different groups

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-13 18:04:27 +02:00
Nicola Murino
bd585d8e52
CI: add commit info in vendored sources
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-08 17:32:28 +02:00
Nicola Murino
a40fa93d7b
CI: use the shortened 8-digit commit hash everywhere
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-08 13:44:14 +02:00
Nicola Murino
4498bbf2e4
CI: use Docker to build x86_64 Linux packages
therefore Linux packages are compiled with Docker for all supported
architectures

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-08 09:42:36 +02:00
Nicola Murino
63e3891808
WebClient/HTTP API: ensure to check home dir, when needed, in multi-node setups
Behind a load balancer with no sticky sessions enabled is not enough to check
the home dir only when the client logs in

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-07 16:23:56 +02:00
Nicola Murino
3ebdfa9b2d
data providers: allow to disable SNI for TLS connections
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-07 14:31:50 +02:00
Nicola Murino
8debde842c
eventmanager: improve docs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-06 19:46:06 +02:00
Nicola Murino
3e5cf56460
eventmanager: add data retention reports
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-06 19:09:23 +02:00
Nicola Murino
f264b005ff
event rules: allow filtering based on group names
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-04 17:48:09 +02:00
Nicola Murino
bf76b0b158
docs external auth: clarify the meaning of the empty response from the hooks
Fixes #961

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-03 19:46:08 +02:00
Nicola Murino
c2a65a9a74
http actions: add multipart support
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-03 16:29:07 +02:00
Nicola Murino
3267a50ae3
MFA: allow recovery codes only if two-factor auth is enabled
Fixes #965

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-31 09:29:39 +02:00
Nicola Murino
f0839519a8
FTP: always generate a defender event if the client does not authenticate
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-30 17:14:57 +02:00
Nicola Murino
95e9106902
use the new atomic types introduced in Go 1.19
we depend on Go 1.19 anyway

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-30 15:47:41 +02:00
Nicola Murino
da03f6c4e3
eventmanager commands: allow to pass custom arguments
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-30 12:37:18 +02:00
Nicola Murino
9e77cd1a26
clarify support policy
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-29 19:14:48 +02:00
Nicola Murino
56bf51277c
eventmanager placeholders: add StatusString and ErrorString
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-29 19:03:31 +02:00
Nicola Murino
37d98ca290
users: add a setting to set the default expiration for shares
Fixes #960

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-28 14:41:42 +02:00
Nicola Murino
9473dc3937
WebAdmin: fix saving email event actions without attachments
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-22 20:17:45 +02:00
Nicola Murino
6777008aec
eventmanager: allow to add attachments to email actions
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-22 19:04:17 +02:00
Nicola Murino
3e8254e398
fs actions: add first upload/download
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-21 19:01:08 +02:00
Nicola Murino
9ddd2d3588
eventmanager: add path exists filesystem action
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-20 14:13:43 +02:00
Nicola Murino
57935f585c
eventmanager: allow to execute fs actions based on schedules
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-19 15:04:00 +02:00
Nicola Murino
2b463d61e3
use epoch timestamp instead of current timestamp for unknown modification times
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-16 17:59:13 +02:00
Nicola Murino
ced4206c5f
allow cross folder renaming if the underlying resource is the same
this was only allowed for the local filesystem before this change

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-15 21:39:04 +02:00
Nicola Murino
c86db09cd8
event manager: add Certificate renewal trigger
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-12 17:37:29 +02:00
Nicola Murino
194c3c13ac
event manager: add IP blocked trigger
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-11 20:09:53 +02:00
Nicola Murino
d65c00728a
docker: add a variant with official plugins included
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-11 11:27:35 +02:00
Nicola Murino
526f6e0f6b
cloud storage providers: remove head bucket requests
let's just assume the bucket exists on "stat" requests for the "/" path

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-11 08:31:51 +02:00
Nicola Murino
a61211d32c
OIDC: allow to get the role field from a sub-struct
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-10 21:42:58 +02:00
Nicola Murino
78f75cdcb9
eventmanager: don't fail if a directory to be created already exists
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-10 19:33:02 +02:00
Nicola Murino
4cd340e07f
eventmanager: add support for filesystem actions
Fixes #931

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-10 18:41:59 +02:00
Nicola Murino
890dde0e00
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-05 18:48:58 +02:00
Nicola Murino
b1efe8d0b5
eventmanager: add support for data retention checks
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-04 21:50:38 +02:00
Nicola Murino
71fff28d29
add Aledade to the sponsors section, thank you!!!
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-03 21:47:13 +02:00
Nicola Murino
6bfdf941bc
webdav: allow to disable the WWW-Authenticate header
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-02 19:06:49 +02:00
Nicola Murino
fdc10aa6c7
CORS: add support for more parameters
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-02 18:44:34 +02:00
Nicola Murino
455bb550ee
azblob: fix SAS URL with embedded container name
Fixes #944

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-01 21:32:40 +02:00
Nicola Murino
2a827544ef
allow to edit profile to users logged in via OIDC
Fixes #942

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-01 19:41:18 +02:00
Nicola Murino
9d2b5dc07d
refactor: move eventmanager to common package
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-01 18:48:54 +02:00
Nicola Murino
3ca62d76d7
back to development
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-30 10:07:09 +02:00
Nicola Murino
00b9280834
docs: some improvements and clarifications
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-27 20:49:22 +02:00
Nicola Murino
ef0a3bc571
add support for anonymous users
Fixes #935

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-27 18:54:25 +02:00
Nicola Murino
e3c5cf981f
download as zip: improve filename
include username and also filename/directory name if the user downloads
a single file/directory

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-26 19:05:42 +02:00
Nicola Murino
ec5da8b4a5
ftpd: allow to require TLS on a per-user basis
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-26 18:51:39 +02:00
Nicola Murino
81de7d271e
add support for embedding templates and other static resources
This feature is disabled by default and can be enabled using the
"bundle" build tag

Fixes #823

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-24 20:02:37 +02:00
Nicola Murino
c8158e14e0
move SFTPGo package to the internal folder
SFTPGo is a daemon and command line tool, not a library.

The public API are provided by the SDK

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-24 16:18:54 +02:00
Nicola Murino
e96ae5ca51
add folders to data provider actions
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-24 08:10:23 +02:00
Nicola Murino
e059197398
WebClient: show images as gallery
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-23 21:12:16 +02:00
Nicola Murino
a2e73228d2
initprovider: don't execute actions
we are not running as service here

Fixes #932

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-23 19:38:15 +02:00
Nicola Murino
1470018054
web UI: allow to enable OIDC login and/or login forms
any combination is now supported

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-22 20:55:33 +02:00
Nicola Murino
e6bfbcd489
OIDC: allow to debug the received id_token
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-22 11:11:35 +02:00
Nicola Murino
a0bbcf6ebb
web client: add HTML5 player
See #914

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-21 18:42:22 +02:00
Nicola Murino
7f5a13d185
fix unused parameter lint warnings
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-19 23:28:33 +02:00
Nicola Murino
d5946da1e2
OIDC: allow to enable only OIDC login for Web UIs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-19 22:25:00 +02:00
Nicola Murino
21682d1c1d
add license header to source files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-17 20:16:00 +02:00
Nicola Murino
fd52475ae2
shared mode: ensure to clear webdav cache for deleted users
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-17 18:48:41 +02:00
Nicola Murino
55b47cf741
sftp realpath: resolve symlinks
Fixes #890

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-17 16:02:45 +02:00
Nicola Murino
e0ce2e2e8a
allow to customize the log level
The old log-verbose flag is not appropriate anymore.
You should now use the log-level flag to set your preferred log level.
The default level is "debug" as before, you can also set "info", "warn",
"error"

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-13 10:40:24 +02:00
Nicola Murino
8fc4971df1
ftpd: fix wildcards handling for backend with virtual dirs
Fixes #915

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-12 12:04:01 +02:00
Nicola Murino
20e8cb898a
always check root dir in multi node setups
Fixes #920

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-12 08:32:31 +02:00
Nicola Murino
b5894b257f
try to better highlight donations and sponsorships options ...
... and to better explain why they are required.

Please don't say "someone else will help the project, I'll just use it"

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-11 16:14:44 +02:00
Nicola Murino
cb517a3595
SFTPGo is now available on Elest.io
Purchasing from there will help keep SFTPGo a long-term sustainable project.

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-11 09:39:01 +02:00
Nicola Murino
1b8f94c08f
add event manager
auto backup removed from setting. You can now schedule backups with
the event manager

Fixes #762

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-11 08:17:36 +02:00
Nicola Murino
e46051299f
s3: improve rename performance
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-30 18:22:58 +02:00
maximethebault
bf2dcfe307
S3: Fix timeout error when renaming large files (#899)
Remove AWS SDK Transport ResponseHeaderTimeout (finer-grained timeout are already handled by the callers)
Lower the threshold for MultipartCopy (5GB -> 500MB) to improve copy performance and reduce chance of hitting Single part copy timeout

Fixes #898

Signed-off-by: Maxime Thébault <contact@maximethebault.me>
2022-06-30 10:23:39 +02:00
Nicola Murino
719f6077ab
don't expose underlying errors to clients
log them and return a generic failure

Fixes #896

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-28 22:08:16 +02:00
Nicola Murino
101783ee86
config: fix replace from env vars for some sub list
ensure to merge configuration from files with configuration from env for
all the sub lists

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-28 19:44:12 +02:00
Nicola Murino
6843402d2e
fix get branding from env
Fixes #895

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-28 10:48:15 +02:00
Nicola Murino
88feda6bf9
clarify licensing
Fixes #891

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-27 18:04:24 +02:00
Nicola Murino
5c446ff645
subsystem mode: advertise the supported extensions
Fixes #889

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-25 18:05:13 +02:00
Nicola Murino
9a6b1a1315
Fix issues found in PR #887
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-25 10:42:17 +02:00
Andre Mainka
90009a649d
Allow OAuth Scope to be configured (#887)
Signed-off-by: BobSilent <andre_1@gmx.net>
2022-06-25 10:40:39 +02:00
Nicola Murino
8762628481
backup: include folders set on group
also fix sql tables prefix handling and add the sql prefix to CI

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-24 13:16:45 +02:00
Nicola Murino
a5e41c9336
S3: allow empty region
the region may be embedded within the endpoint for some S3 compatible
object storage

Fixes #884

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-20 19:55:01 +02:00
Nicola Murino
729f30aebf
allow to refuse an upload from a sync upload hook
Fixes #880

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-16 18:42:17 +02:00
Nicola Murino
1da213a6e3
CI: test resetprovider
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-15 20:08:31 +02:00
Nicola Murino
2b0b19da9e
add metrics for httpgs and sftpfs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-14 19:37:25 +02:00
Nicola Murino
686166f2ce
remove deprecated APIs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-14 18:30:57 +02:00
Nicola Murino
93ce593ed0
squash database migrations and remove the credentials_path setting
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-13 20:08:49 +02:00
Nicola Murino
6f4475ff72
httpfs: add support for UNIX domain sockets
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-12 18:29:49 +02:00
Nicola Murino
0b9a96ec6b
restore fast path for recursive permissions check and update some docs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-12 12:04:48 +02:00
Nicola Murino
f0f5ee392b
OpenAPI schema: improve compatibility with some generators
Fixes #875

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-11 19:07:05 +02:00
Nicola Murino
dadaca141a
sql providers: remove prepared statements
preparing a statement without reusing it is useless

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-11 11:57:06 +02:00
Nicola Murino
7ab30099dd
add httpfs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-11 10:41:34 +02:00
Nicola Murino
3170991aa8
MySQL: groups is a reserved keyword since MySQL 8.0.2
add MySQL to CI, testing with MariaDB is not enough

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-10 18:08:15 +02:00
Nicola Murino
118744a860
parse IP proxy header also if listening on UNIX domain socket
Fixes #867

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-09 08:37:37 +02:00
Nicola Murino
fe6a3f2ce8
web UIs: fix date formatting on Safari
Fixes #869

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-09 07:37:50 +02:00
Nicola Murino
75efaa9741
APT and YUM repo are now available
This is possible thanks to the Oregon State University's free
mirroring service

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-09 07:30:09 +02:00
638 changed files with 113253 additions and 71822 deletions

31
.cirrus.yml Normal file
View file

@ -0,0 +1,31 @@
freebsd_task:
name: FreeBSD
matrix:
- name: FreeBSD 14.0
freebsd_instance:
image_family: freebsd-14-0
pkginstall_script:
- pkg update -f
- pkg install -y go122
- pkg install -y git
setup_script:
- ln -s /usr/local/bin/go122 /usr/local/bin/go
- pw groupadd sftpgo
- pw useradd sftpgo -g sftpgo -w none -m
- mkdir /home/sftpgo/sftpgo
- cp -R . /home/sftpgo/sftpgo
- chown -R sftpgo:sftpgo /home/sftpgo/sftpgo
compile_script:
- su sftpgo -c 'cd ~/sftpgo && go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo'
- su sftpgo -c 'cd ~/sftpgo/tests/eventsearcher && go build -trimpath -ldflags "-s -w" -o eventsearcher'
- su sftpgo -c 'cd ~/sftpgo/tests/ipfilter && go build -trimpath -ldflags "-s -w" -o ipfilter'
check_script:
- su sftpgo -c 'cd ~/sftpgo && ./sftpgo initprovider && ./sftpgo resetprovider --force'
test_script:
- su sftpgo -c 'cd ~/sftpgo && go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 20m ./... -coverprofile=coverage.txt -covermode=atomic'

108
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View file

@ -0,0 +1,108 @@
name: Open Source Bug Report
description: "Submit a report and help us improve SFTPGo"
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
### 👍 Thank you for contributing to our project!
Before asking for help please check the [support policy](https://github.com/drakkan/sftpgo#support-policy).
If you are a commercial user or a project sponsor please contact us using the dedicated [email address](mailto:support@sftpgo.com).
- type: checkboxes
id: before-posting
attributes:
label: "⚠️ This issue respects the following points: ⚠️"
description: All conditions are **required**.
options:
- label: This is a **bug**, not a question or a configuration issue.
required: true
- label: This issue is **not** already reported on Github _(I've searched it)_.
required: true
- type: textarea
id: bug-description
attributes:
label: Bug description
description: |
Provide a description of the bug you're experiencing.
Don't just expect someone will guess what your specific problem is and provide full details.
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: |
Describe the steps to reproduce the bug.
The better your description is the fastest you'll get an _(accurate)_ answer.
value: |
1.
2.
3.
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: Describe what you expected to happen instead.
validations:
required: true
- type: input
id: version
attributes:
label: SFTPGo version
validations:
required: true
- type: input
id: data-provider
attributes:
label: Data provider
validations:
required: true
- type: dropdown
id: install-method
attributes:
label: Installation method
description: |
Select installation method you've used.
_Describe the method in the "Additional info" section if you chose "Other"._
options:
- "Community Docker image"
- "Community Deb package"
- "Community RPM package"
- "Other"
validations:
required: true
- type: textarea
attributes:
label: Configuration
description: "Describe your customizations to the configuration: both config file changes and overrides via environment variables"
value: config
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: dropdown
id: usecase
attributes:
label: What are you using SFTPGo for?
description: We'd like to understand your SFTPGo usecase more
multiple: true
options:
- "Private user, home usecase (home backup/VPS)"
- "Professional user, 1 person business"
- "Small business (3-person firm with file exchange?)"
- "Medium business"
- "Enterprise"
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional info
description: Any additional information related to the issue.

9
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,9 @@
blank_issues_enabled: false
contact_links:
- name: Commercial Support
url: https://sftpgo.com/
about: >
If you need Professional support, so your reports are prioritized and resolved more quickly.
- name: GitHub Community Discussions
url: https://github.com/drakkan/sftpgo/discussions
about: Please ask and answer questions here.

View file

@ -0,0 +1,42 @@
name: 🚀 Feature request
description: Suggest an idea for SFTPGo
labels: ["suggestion"]
body:
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe.
description: A clear and concise description of what the problem is.
validations:
required: false
- type: textarea
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
- type: dropdown
id: usecase
attributes:
label: What are you using SFTPGo for?
description: We'd like to understand your SFTPGo usecase more
multiple: true
options:
- "Private user, home usecase (home backup/VPS)"
- "Professional user, 1 person business"
- "Small business (3-person firm with file exchange?)"
- "Medium business"
- "Enterprise"
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.
validations:
required: false

5
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,5 @@
# Checklist for Pull Requests
- [ ] Have you signed the [Contributor License Agreement](https://sftpgo.com/cla.html)?
---

View file

@ -1,11 +1,11 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 2
#- package-ecosystem: "gomod"
# directory: "/"
# schedule:
# interval: "weekly"
# open-pull-requests-limit: 2
- package-ecosystem: "docker"
directory: "/"

36
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,36 @@
name: "Code scanning - action"
on:
push:
pull_request:
schedule:
- cron: '30 1 * * 6'
jobs:
CodeQL-Build:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

View file

@ -11,43 +11,45 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: [1.18]
go: ['1.22']
os: [ubuntu-latest, macos-latest]
upload-coverage: [true]
include:
- go: 1.18
- go: '1.22'
os: windows-latest
upload-coverage: false
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Build for Linux/macOS x86_64
if: startsWith(matrix.os, 'windows-') != true
run: |
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
cd tests/eventsearcher
go build -trimpath -ldflags "-s -w" -o eventsearcher
cd -
cd tests/ipfilter
go build -trimpath -ldflags "-s -w" -o ipfilter
cd -
./sftpgo initprovider
./sftpgo resetprovider --force
- name: Build for macOS arm64
if: startsWith(matrix.os, 'macos-') == true
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
- name: Build for Windows
if: startsWith(matrix.os, 'windows-')
run: |
$GIT_COMMIT = (git describe --always --dirty) | Out-String
$GIT_COMMIT = (git describe --always --abbrev=8 --dirty) | Out-String
$DATE_TIME = ([datetime]::Now.ToUniversalTime().toString("yyyy-MM-ddTHH:mm:ssZ")) | Out-String
$LATEST_TAG = ((git describe --tags $(git rev-list --tags --max-count=1)) | Out-String).Trim()
$REV_LIST=$LATEST_TAG+"..HEAD"
@ -55,7 +57,7 @@ jobs:
$FILE_VERSION = $LATEST_TAG.substring(1) + "." + $COMMITS_FROM_TAG
go install github.com/tc-hib/go-winres@latest
go-winres simply --arch amd64 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/version.date=$DATE_TIME" -o sftpgo.exe
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o sftpgo.exe
cd tests/eventsearcher
go build -trimpath -ldflags "-s -w" -o eventsearcher.exe
cd ../..
@ -67,42 +69,43 @@ jobs:
$Env:GOOS='windows'
$Env:GOARCH='arm64'
go-winres simply --arch arm64 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
mkdir x86
$Env:GOARCH='386'
go-winres simply --arch 386 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
Remove-Item Env:\CGO_ENABLED
Remove-Item Env:\GOOS
Remove-Item Env:\GOARCH
- name: Run test cases using SQLite provider
run: go test -v -p 1 -timeout 15m ./... -coverprofile=coverage.txt -covermode=atomic
run: go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -coverprofile=coverage.txt -covermode=atomic
- name: Upload coverage to Codecov
if: ${{ matrix.upload-coverage }}
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
file: ./coverage.txt
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
- name: Run test cases using bolt provider
run: |
go test -v -p 1 -timeout 2m ./config -covermode=atomic
go test -v -p 1 -timeout 5m ./common -covermode=atomic
go test -v -p 1 -timeout 5m ./httpd -covermode=atomic
go test -v -p 1 -timeout 8m ./sftpd -covermode=atomic
go test -v -p 1 -timeout 5m ./ftpd -covermode=atomic
go test -v -p 1 -timeout 5m ./webdavd -covermode=atomic
go test -v -p 1 -timeout 2m ./telemetry -covermode=atomic
go test -v -p 1 -timeout 2m ./mfa -covermode=atomic
go test -v -p 1 -timeout 2m ./command -covermode=atomic
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 2m ./internal/config -covermode=atomic
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 5m ./internal/common -covermode=atomic
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 5m ./internal/httpd -covermode=atomic
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 8m ./internal/sftpd -covermode=atomic
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 5m ./internal/ftpd -covermode=atomic
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 5m ./internal/webdavd -covermode=atomic
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 2m ./internal/telemetry -covermode=atomic
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 2m ./internal/mfa -covermode=atomic
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 2m ./internal/command -covermode=atomic
env:
SFTPGO_DATA_PROVIDER__DRIVER: bolt
SFTPGO_DATA_PROVIDER__NAME: 'sftpgo_bolt.db'
- name: Run test cases using memory provider
run: go test -v -p 1 -timeout 15m ./... -covermode=atomic
run: go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -covermode=atomic
env:
SFTPGO_DATA_PROVIDER__DRIVER: memory
SFTPGO_DATA_PROVIDER__NAME: ''
@ -175,21 +178,21 @@ jobs:
- name: Upload Windows installer x86_64 artifact
if: ${{ startsWith(matrix.os, 'windows-') && github.event_name != 'pull_request' }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_windows_installer_x86_64
path: ./sftpgo_windows_x86_64.exe
- name: Upload Windows installer arm64 artifact
if: ${{ startsWith(matrix.os, 'windows-') && github.event_name != 'pull_request' }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_windows_installer_arm64
path: ./sftpgo_windows_arm64.exe
- name: Upload Windows installer x86 artifact
if: ${{ startsWith(matrix.os, 'windows-') && github.event_name != 'pull_request' }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_windows_installer_x86
path: ./sftpgo_windows_x86.exe
@ -215,40 +218,30 @@ jobs:
- name: Upload build artifact
if: startsWith(matrix.os, 'ubuntu-') != true
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo-${{ matrix.os }}-go-${{ matrix.go }}
path: output
test-goarch-386:
name: Run test cases on 32-bit arch
test-build-flags:
name: Test build flags
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: 1.18
go-version: '1.22'
- name: Build
run: |
cd tests/eventsearcher
go build -trimpath -ldflags "-s -w" -o eventsearcher
cd -
cd tests/ipfilter
go build -trimpath -ldflags "-s -w" -o ipfilter
cd -
env:
GOARCH: 386
- name: Run test cases
run: go test -v -p 1 -timeout 15m ./... -covermode=atomic
env:
SFTPGO_DATA_PROVIDER__DRIVER: memory
SFTPGO_DATA_PROVIDER__NAME: ''
GOARCH: 386
go build -trimpath -tags nopgxregisterdefaulttypes,nogcs,nos3,noportable,nobolt,nomysql,nopgsql,nosqlite,nometrics,noazblob,unixcrypt -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
./sftpgo -v
cp -r openapi static templates internal/bundle/
go build -trimpath -tags nopgxregisterdefaulttypes,bundle -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
./sftpgo -v
test-postgresql-mysql-crdb:
name: Test with PgSQL/MySQL/Cockroach
@ -276,23 +269,39 @@ jobs:
MYSQL_USER: sftpgo
MYSQL_PASSWORD: sftpgo
options: >-
--health-cmd "mysqladmin status -h 127.0.0.1 -P 3306 -u root -p$MYSQL_ROOT_PASSWORD"
--health-cmd "mariadb-admin status -h 127.0.0.1 -P 3306 -u root -p$MYSQL_ROOT_PASSWORD"
--health-interval 10s
--health-timeout 5s
--health-retries 6
ports:
- 3307:3306
mysql:
image: mysql:latest
env:
MYSQL_ROOT_PASSWORD: mysql
MYSQL_DATABASE: sftpgo
MYSQL_USER: sftpgo
MYSQL_PASSWORD: sftpgo
options: >-
--health-cmd "mysqladmin status -h 127.0.0.1 -P 3306 -u root -p$MYSQL_ROOT_PASSWORD"
--health-interval 10s
--health-timeout 5s
--health-retries 6
ports:
- 3308:3306
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: 1.18
go-version: '1.22'
- name: Build
run: |
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
cd tests/eventsearcher
go build -trimpath -ldflags "-s -w" -o eventsearcher
cd -
@ -300,9 +309,24 @@ jobs:
go build -trimpath -ldflags "-s -w" -o ipfilter
cd -
- name: Run tests using MySQL provider
run: |
./sftpgo initprovider
./sftpgo resetprovider --force
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -covermode=atomic
env:
SFTPGO_DATA_PROVIDER__DRIVER: mysql
SFTPGO_DATA_PROVIDER__NAME: sftpgo
SFTPGO_DATA_PROVIDER__HOST: localhost
SFTPGO_DATA_PROVIDER__PORT: 3308
SFTPGO_DATA_PROVIDER__USERNAME: sftpgo
SFTPGO_DATA_PROVIDER__PASSWORD: sftpgo
- name: Run tests using PostgreSQL provider
run: |
go test -v -p 1 -timeout 15m ./... -covermode=atomic
./sftpgo initprovider
./sftpgo resetprovider --force
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -covermode=atomic
env:
SFTPGO_DATA_PROVIDER__DRIVER: postgresql
SFTPGO_DATA_PROVIDER__NAME: sftpgo
@ -311,9 +335,11 @@ jobs:
SFTPGO_DATA_PROVIDER__USERNAME: postgres
SFTPGO_DATA_PROVIDER__PASSWORD: postgres
- name: Run tests using MySQL provider
- name: Run tests using MariaDB provider
run: |
go test -v -p 1 -timeout 15m ./... -covermode=atomic
./sftpgo initprovider
./sftpgo resetprovider --force
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -covermode=atomic
env:
SFTPGO_DATA_PROVIDER__DRIVER: mysql
SFTPGO_DATA_PROVIDER__NAME: sftpgo
@ -321,13 +347,16 @@ jobs:
SFTPGO_DATA_PROVIDER__PORT: 3307
SFTPGO_DATA_PROVIDER__USERNAME: sftpgo
SFTPGO_DATA_PROVIDER__PASSWORD: sftpgo
SFTPGO_DATA_PROVIDER__SQL_TABLES_PREFIX: prefix_
- name: Run tests using CockroachDB provider
run: |
docker run --rm --name crdb --health-cmd "curl -I http://127.0.0.1:8080" --health-interval 10s --health-timeout 5s --health-retries 6 -p 26257:26257 -d cockroachdb/cockroach:latest start-single-node --insecure --listen-addr :26257
sleep 10
docker exec crdb cockroach sql --insecure -e 'create database "sftpgo"'
go test -v -p 1 -timeout 15m ./... -covermode=atomic
./sftpgo initprovider
./sftpgo resetprovider --force
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -covermode=atomic
docker stop crdb
env:
SFTPGO_DATA_PROVIDER__DRIVER: cockroachdb
@ -336,15 +365,18 @@ jobs:
SFTPGO_DATA_PROVIDER__PORT: 26257
SFTPGO_DATA_PROVIDER__USERNAME: root
SFTPGO_DATA_PROVIDER__PASSWORD:
SFTPGO_DATA_PROVIDER__TARGET_SESSION_ATTRS: any
SFTPGO_DATA_PROVIDER__SQL_TABLES_PREFIX: prefix_
build-linux-packages:
name: Build Linux packages
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: amd64
go: 1.18
distro: ubuntu:18.04
go: latest
go-arch: amd64
- arch: aarch64
distro: ubuntu18.04
@ -359,19 +391,39 @@ jobs:
go: latest
go-arch: arm7
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
if: ${{ matrix.arch == 'amd64' }}
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Get commit SHA
id: get_commit
run: echo "COMMIT=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
shell: bash
- name: Build on amd64
if: ${{ matrix.arch == 'amd64' }}
run: |
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
echo '#!/bin/bash' > build.sh
echo '' >> build.sh
echo 'set -e' >> build.sh
echo 'apt-get update -q -y' >> build.sh
echo 'apt-get install -q -y curl gcc' >> build.sh
if [ ${{ matrix.go }} == 'latest' ]
then
echo 'GO_VERSION=$(curl -L https://go.dev/VERSION?m=text | head -n 1)' >> build.sh
else
echo 'GO_VERSION=${{ matrix.go }}' >> build.sh
fi
echo 'GO_DOWNLOAD_ARCH=${{ matrix.go-arch }}' >> build.sh
echo 'curl --retry 5 --retry-delay 2 --connect-timeout 10 -o go.tar.gz -L https://go.dev/dl/${GO_VERSION}.linux-${GO_DOWNLOAD_ARCH}.tar.gz' >> build.sh
echo 'tar -C /usr/local -xzf go.tar.gz' >> build.sh
echo 'export PATH=$PATH:/usr/local/go/bin' >> build.sh
echo 'go version' >> build.sh
echo 'cd /usr/local/src' >> build.sh
echo 'go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo' >> build.sh
chmod 755 build.sh
docker run --rm --name ubuntu-build --mount type=bind,source=`pwd`,target=/usr/local/src ${{ matrix.distro }} /usr/local/src/build.sh
mkdir -p output/{init,bash_completion,zsh_completion}
cp sftpgo.json output/
cp -r templates output/
@ -398,10 +450,10 @@ jobs:
shell: /bin/bash
install: |
apt-get update -q -y
apt-get install -q -y curl gcc git
apt-get install -q -y curl gcc
if [ ${{ matrix.go }} == 'latest' ]
then
GO_VERSION=$(curl -L https://go.dev/VERSION?m=text)
GO_VERSION=$(curl -L https://go.dev/VERSION?m=text | head -n 1)
else
GO_VERSION=${{ matrix.go }}
fi
@ -414,11 +466,12 @@ jobs:
tar -C /usr/local -xzf go.tar.gz
run: |
export PATH=$PATH:/usr/local/go/bin
go version
if [ ${{ matrix.arch}} == 'armv7' ]
then
export GOARM=7
fi
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
mkdir -p output/{init,bash_completion,zsh_completion}
cp sftpgo.json output/
cp -r templates output/
@ -432,7 +485,7 @@ jobs:
cp sftpgo output/
- name: Upload build artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo-linux-${{ matrix.arch }}-go-${{ matrix.go }}
path: output
@ -444,16 +497,16 @@ jobs:
cd pkgs
./build.sh
PKG_VERSION=$(cat dist/version)
echo "::set-output name=pkg-version::${PKG_VERSION}"
echo "pkg-version=${PKG_VERSION}" >> $GITHUB_OUTPUT
- name: Upload Debian Package
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo-${{ steps.build_linux_pkgs.outputs.pkg-version }}-${{ matrix.go-arch }}-deb
path: pkgs/dist/deb/*
- name: Upload RPM Package
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo-${{ steps.build_linux_pkgs.outputs.pkg-version }}-${{ matrix.go-arch }}-rpm
path: pkgs/dist/rpm/*
@ -463,11 +516,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: 1.18
- uses: actions/checkout@v3
go-version: '1.22'
- uses: actions/checkout@v4
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
version: latest

View file

@ -28,9 +28,12 @@ jobs:
- os: ubuntu-latest
docker_pkg: distroless
optional_deps: false
- os: ubuntu-latest
docker_pkg: debian-plugins
optional_deps: true
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Gather image information
id: info
@ -39,6 +42,7 @@ jobs:
DOCKERFILE=Dockerfile
MINOR=""
MAJOR=""
FEATURES="nopgxregisterdefaulttypes"
if [ "${{ github.event_name }}" = "schedule" ]; then
VERSION=nightly
elif [[ $GITHUB_REF == refs/tags/* ]]; then
@ -64,6 +68,13 @@ jobs:
VERSION="${VERSION}-distroless"
VERSION_SLIM="${VERSION}-slim"
DOCKERFILE=Dockerfile.distroless
FEATURES="${FEATURES},nosqlite"
elif [[ $DOCKER_PKG == debian-plugins ]]; then
VERSION="${VERSION}-plugins"
VERSION_SLIM="${VERSION}-slim"
FEATURES="${FEATURES},unixcrypt"
elif [[ $DOCKER_PKG == debian ]]; then
FEATURES="${FEATURES},unixcrypt"
fi
DOCKER_IMAGES=("drakkan/sftpgo" "ghcr.io/drakkan/sftpgo")
TAGS="${DOCKER_IMAGES[0]}:${VERSION}"
@ -89,6 +100,13 @@ jobs:
fi
TAGS="${TAGS},${DOCKER_IMAGE}:distroless"
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:distroless-slim"
elif [[ $DOCKER_PKG == debian-plugins ]]; then
if [[ -n $MAJOR && -n $MINOR ]]; then
TAGS="${TAGS},${DOCKER_IMAGE}:${MINOR}-plugins,${DOCKER_IMAGE}:${MAJOR}-plugins"
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:${MINOR}-plugins-slim,${DOCKER_IMAGE}:${MAJOR}-plugins-slim"
fi
TAGS="${TAGS},${DOCKER_IMAGE}:plugins"
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:plugins-slim"
else
if [[ -n $MAJOR && -n $MINOR ]]; then
TAGS="${TAGS},${DOCKER_IMAGE}:${MINOR}-alpine,${DOCKER_IMAGE}:${MAJOR}-alpine"
@ -101,37 +119,43 @@ jobs:
done
if [[ $OPTIONAL_DEPS == true ]]; then
echo ::set-output name=version::${VERSION}
echo ::set-output name=tags::${TAGS}
echo ::set-output name=full::true
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
echo "full=true" >> $GITHUB_OUTPUT
else
echo ::set-output name=version::${VERSION_SLIM}
echo ::set-output name=tags::${TAGS_SLIM}
echo ::set-output name=full::false
echo "version=${VERSION_SLIM}" >> $GITHUB_OUTPUT
echo "tags=${TAGS_SLIM}" >> $GITHUB_OUTPUT
echo "full=false" >> $GITHUB_OUTPUT
fi
echo ::set-output name=dockerfile::${DOCKERFILE}
echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
echo ::set-output name=sha::${GITHUB_SHA::8}
if [[ $DOCKER_PKG == debian-plugins ]]; then
echo "plugins=true" >> $GITHUB_OUTPUT
else
echo "plugins=false" >> $GITHUB_OUTPUT
fi
echo "dockerfile=${DOCKERFILE}" >> $GITHUB_OUTPUT
echo "features=${FEATURES}" >> $GITHUB_OUTPUT
echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "sha=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
env:
DOCKER_PKG: ${{ matrix.docker_pkg }}
OPTIONAL_DEPS: ${{ matrix.optional_deps }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up builder
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
id: builder
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
if: ${{ github.event_name != 'pull_request' }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@ -139,24 +163,26 @@ jobs:
if: ${{ github.event_name != 'pull_request' }}
- name: Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v6
with:
context: .
builder: ${{ steps.builder.outputs.name }}
file: ./${{ steps.info.outputs.dockerfile }}
platforms: linux/amd64,linux/arm64,linux/ppc64le
platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/arm/v7
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.info.outputs.tags }}
build-args: |
COMMIT_SHA=${{ steps.info.outputs.sha }}
INSTALL_OPTIONAL_PACKAGES=${{ steps.info.outputs.full }}
DOWNLOAD_PLUGINS=${{ steps.info.outputs.plugins }}
FEATURES=${{ steps.info.outputs.features }}
labels: |
org.opencontainers.image.title=SFTPGo
org.opencontainers.image.description=Fully featured and highly configurable SFTP server with optional HTTP, FTP/S and WebDAV support
org.opencontainers.image.description=Full-featured and highly configurable file transfer server: SFTP, HTTP/S,FTP/S, WebDAV
org.opencontainers.image.url=https://github.com/drakkan/sftpgo
org.opencontainers.image.documentation=https://github.com/drakkan/sftpgo/blob/${{ github.sha }}/docker/README.md
org.opencontainers.image.source=https://github.com/drakkan/sftpgo
org.opencontainers.image.version=${{ steps.info.outputs.version }}
org.opencontainers.image.created=${{ steps.info.outputs.created }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.licenses=AGPL-3.0
org.opencontainers.image.licenses=AGPL-3.0-only

View file

@ -5,33 +5,34 @@ on:
tags: 'v*'
env:
GO_VERSION: 1.18.3
GO_VERSION: 1.22.4
jobs:
prepare-sources-with-deps:
name: Prepare sources with deps
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Get SFTPGo version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
- name: Prepare release
run: |
go mod vendor
echo "${SFTPGO_VERSION}" > VERSION.txt
echo "${GITHUB_SHA::8}" >> VERSION.txt
tar cJvf sftpgo_${SFTPGO_VERSION}_src_with_deps.tar.xz *
env:
SFTPGO_VERSION: ${{ steps.get_version.outputs.VERSION }}
- name: Upload build artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_src_with_deps.tar.xz
path: ./sftpgo_${{ steps.get_version.outputs.VERSION }}_src_with_deps.tar.xz
@ -42,18 +43,18 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-11, windows-2022]
os: [macos-12, windows-2022]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Get SFTPGo version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
shell: bash
- name: Get OS name
@ -61,9 +62,9 @@ jobs:
run: |
if [[ $MATRIX_OS =~ ^macos.* ]]
then
echo ::set-output name=OS::macOS
echo "OS=macOS" >> $GITHUB_OUTPUT
else
echo ::set-output name=OS::windows
echo "OS=windows" >> $GITHUB_OUTPUT
fi
shell: bash
env:
@ -71,31 +72,31 @@ jobs:
- name: Build for macOS x86_64
if: startsWith(matrix.os, 'windows-') != true
run: go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
run: go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
- name: Build for macOS arm64
if: startsWith(matrix.os, 'macos-') == true
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
- name: Build for Windows
if: startsWith(matrix.os, 'windows-')
run: |
$GIT_COMMIT = (git describe --always --dirty) | Out-String
$GIT_COMMIT = (git describe --always --abbrev=8 --dirty) | Out-String
$DATE_TIME = ([datetime]::Now.ToUniversalTime().toString("yyyy-MM-ddTHH:mm:ssZ")) | Out-String
$FILE_VERSION = $Env:SFTPGO_VERSION.substring(1) + ".0"
go install github.com/tc-hib/go-winres@latest
go-winres simply --arch amd64 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/version.date=$DATE_TIME" -o sftpgo.exe
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o sftpgo.exe
mkdir arm64
$Env:CGO_ENABLED='0'
$Env:GOOS='windows'
$Env:GOARCH='arm64'
go-winres simply --arch arm64 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
mkdir x86
$Env:GOARCH='386'
go-winres simply --arch 386 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
Remove-Item Env:\CGO_ENABLED
Remove-Item Env:\GOOS
Remove-Item Env:\GOARCH
@ -206,7 +207,7 @@ jobs:
- name: Upload macOS x86_64 artifact
if: startsWith(matrix.os, 'macos-')
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_x86_64.tar.xz
path: ./sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_x86_64.tar.xz
@ -214,7 +215,7 @@ jobs:
- name: Upload macOS arm64 artifact
if: startsWith(matrix.os, 'macos-')
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_arm64.tar.xz
path: ./sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_arm64.tar.xz
@ -222,7 +223,7 @@ jobs:
- name: Upload Windows installer x86_64 artifact
if: startsWith(matrix.os, 'windows-')
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_x86_64.exe
path: ./sftpgo_windows_x86_64.exe
@ -230,7 +231,7 @@ jobs:
- name: Upload Windows installer arm64 artifact
if: startsWith(matrix.os, 'windows-')
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_arm64.exe
path: ./sftpgo_windows_arm64.exe
@ -238,7 +239,7 @@ jobs:
- name: Upload Windows installer x86 artifact
if: startsWith(matrix.os, 'windows-')
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_x86.exe
path: ./sftpgo_windows_x86.exe
@ -246,7 +247,7 @@ jobs:
- name: Upload Windows portable artifact
if: startsWith(matrix.os, 'windows-')
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_portable.zip
path: ./sftpgo_portable.zip
@ -254,11 +255,12 @@ jobs:
prepare-linux:
name: Prepare Linux binaries
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: amd64
distro: ubuntu:18.04
go-arch: amd64
deb-arch: amd64
rpm-arch: x86_64
@ -283,18 +285,14 @@ jobs:
tar-arch: armv7
steps:
- uses: actions/checkout@v3
- name: Set up Go
if: ${{ matrix.arch == 'amd64' }}
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v4
- name: Get versions
id: get_version
run: |
echo ::set-output name=SFTPGO_VERSION::${GITHUB_REF/refs\/tags\//}
echo ::set-output name=GO_VERSION::${GO_VERSION}
echo "SFTPGO_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
echo "GO_VERSION=${GO_VERSION}" >> $GITHUB_OUTPUT
echo "COMMIT=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
shell: bash
env:
GO_VERSION: ${{ env.GO_VERSION }}
@ -302,7 +300,20 @@ jobs:
- name: Build on amd64
if: ${{ matrix.arch == 'amd64' }}
run: |
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
echo '#!/bin/bash' > build.sh
echo '' >> build.sh
echo 'set -e' >> build.sh
echo 'apt-get update -q -y' >> build.sh
echo 'apt-get install -q -y curl gcc' >> build.sh
echo 'curl --retry 5 --retry-delay 2 --connect-timeout 10 -o go.tar.gz -L https://go.dev/dl/go${{ steps.get_version.outputs.GO_VERSION }}.linux-${{ matrix.go-arch }}.tar.gz' >> build.sh
echo 'tar -C /usr/local -xzf go.tar.gz' >> build.sh
echo 'export PATH=$PATH:/usr/local/go/bin' >> build.sh
echo 'go version' >> build.sh
echo 'cd /usr/local/src' >> build.sh
echo 'go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_version.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo' >> build.sh
chmod 755 build.sh
docker run --rm --name ubuntu-build --mount type=bind,source=`pwd`,target=/usr/local/src ${{ matrix.distro }} /usr/local/src/build.sh
mkdir -p output/{init,sqlite,bash_completion,zsh_completion}
echo "For documentation please take a look here:" > output/README.txt
echo "" >> output/README.txt
@ -340,7 +351,7 @@ jobs:
shell: /bin/bash
install: |
apt-get update -q -y
apt-get install -q -y curl gcc git xz-utils
apt-get install -q -y curl gcc xz-utils
GO_DOWNLOAD_ARCH=${{ matrix.go-arch }}
if [ ${{ matrix.arch}} == 'armv7' ]
then
@ -350,7 +361,8 @@ jobs:
tar -C /usr/local -xzf go.tar.gz
run: |
export PATH=$PATH:/usr/local/go/bin
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
go version
go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_version.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
mkdir -p output/{init,sqlite,bash_completion,zsh_completion}
echo "For documentation please take a look here:" > output/README.txt
echo "" >> output/README.txt
@ -373,7 +385,7 @@ jobs:
cd ..
- name: Upload build artifact for ${{ matrix.arch }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_${{ matrix.tar-arch }}.tar.xz
path: ./output/sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_${{ matrix.tar-arch }}.tar.xz
@ -386,19 +398,19 @@ jobs:
cd pkgs
./build.sh
PKG_VERSION=${SFTPGO_VERSION:1}
echo "::set-output name=pkg-version::${PKG_VERSION}"
echo "pkg-version=${PKG_VERSION}" >> $GITHUB_OUTPUT
env:
SFTPGO_VERSION: ${{ steps.get_version.outputs.SFTPGO_VERSION }}
- name: Upload Deb Package
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_${{ steps.build_linux_pkgs.outputs.pkg-version }}-1_${{ matrix.deb-arch}}.deb
path: ./pkgs/dist/deb/sftpgo_${{ steps.build_linux_pkgs.outputs.pkg-version }}-1_${{ matrix.deb-arch}}.deb
retention-days: 1
- name: Upload RPM Package
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo-${{ steps.build_linux_pkgs.outputs.pkg-version }}-1.${{ matrix.rpm-arch}}.rpm
path: ./pkgs/dist/rpm/sftpgo-${{ steps.build_linux_pkgs.outputs.pkg-version }}-1.${{ matrix.rpm-arch}}.rpm
@ -413,26 +425,26 @@ jobs:
- name: Get versions
id: get_version
run: |
echo ::set-output name=SFTPGO_VERSION::${GITHUB_REF/refs\/tags\//}
echo "SFTPGO_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
shell: bash
- name: Download amd64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_x86_64.tar.xz
- name: Download arm64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_arm64.tar.xz
- name: Download ppc64le artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_ppc64le.tar.xz
- name: Download armv7 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_armv7.tar.xz
@ -455,7 +467,7 @@ jobs:
SFTPGO_VERSION: ${{ steps.get_version.outputs.SFTPGO_VERSION }}
- name: Upload Linux bundle
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_bundle.tar.xz
path: ./bundle/sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_bundle.tar.xz
@ -467,113 +479,113 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Get versions
id: get_version
run: |
SFTPGO_VERSION=${GITHUB_REF/refs\/tags\//}
PKG_VERSION=${SFTPGO_VERSION:1}
echo ::set-output name=SFTPGO_VERSION::${SFTPGO_VERSION}
echo "::set-output name=PKG_VERSION::${PKG_VERSION}"
echo "SFTPGO_VERSION=${SFTPGO_VERSION}" >> $GITHUB_OUTPUT
echo "PKG_VERSION=${PKG_VERSION}" >> $GITHUB_OUTPUT
shell: bash
- name: Download amd64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_x86_64.tar.xz
- name: Download arm64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_arm64.tar.xz
- name: Download ppc64le artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_ppc64le.tar.xz
- name: Download armv7 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_armv7.tar.xz
- name: Download Linux bundle artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_linux_bundle.tar.xz
- name: Download Deb amd64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.PKG_VERSION }}-1_amd64.deb
- name: Download Deb arm64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.PKG_VERSION }}-1_arm64.deb
- name: Download Deb ppc64le artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.PKG_VERSION }}-1_ppc64el.deb
- name: Download Deb armv7 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.PKG_VERSION }}-1_armhf.deb
- name: Download RPM x86_64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo-${{ steps.get_version.outputs.PKG_VERSION }}-1.x86_64.rpm
- name: Download RPM aarch64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo-${{ steps.get_version.outputs.PKG_VERSION }}-1.aarch64.rpm
- name: Download RPM ppc64le artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo-${{ steps.get_version.outputs.PKG_VERSION }}-1.ppc64le.rpm
- name: Download RPM armv7 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo-${{ steps.get_version.outputs.PKG_VERSION }}-1.armv7hl.rpm
- name: Download macOS x86_64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_macOS_x86_64.tar.xz
- name: Download macOS arm64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_macOS_arm64.tar.xz
- name: Download Windows installer x86_64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_windows_x86_64.exe
- name: Download Windows installer arm64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_windows_arm64.exe
- name: Download Windows installer x86 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_windows_x86.exe
- name: Download Windows portable artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_windows_portable.zip
- name: Download source with deps artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: sftpgo_${{ steps.get_version.outputs.SFTPGO_VERSION }}_src_with_deps.tar.xz

View file

@ -1,5 +1,5 @@
run:
timeout: 5m
timeout: 10m
issues-exit-code: 1
tests: true

1
CODEOWNERS Normal file
View file

@ -0,0 +1 @@
* @drakkan

128
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
support@sftpgo.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

34
DCO
View file

@ -1,34 +0,0 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.

View file

@ -1,7 +1,9 @@
FROM golang:1.18-bullseye as builder
FROM golang:1.22-bookworm as builder
ENV GOFLAGS="-mod=readonly"
RUN apt-get update && apt-get -y upgrade && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /workspace
WORKDIR /workspace
@ -20,15 +22,20 @@ ARG FEATURES
COPY . .
RUN set -xe && \
export COMMIT_SHA=${COMMIT_SHA:-$(git describe --always --dirty)} && \
go build $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=${COMMIT_SHA} -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -v -o sftpgo
export COMMIT_SHA=${COMMIT_SHA:-$(git describe --always --abbrev=8 --dirty)} && \
go build $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${COMMIT_SHA} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -v -o sftpgo
FROM debian:bullseye-slim
# Set to "true" to download the "official" plugins in /usr/local/bin
ARG DOWNLOAD_PLUGINS=false
RUN if [ "${DOWNLOAD_PLUGINS}" = "true" ]; then apt-get update && apt-get install --no-install-recommends -y curl && ./docker/scripts/download-plugins.sh; fi
FROM debian:bookworm-slim
# Set to "true" to install jq and the optional git and rsync dependencies
ARG INSTALL_OPTIONAL_PACKAGES=false
RUN apt-get update && apt-get install --no-install-recommends -y ca-certificates media-types && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get -y upgrade && apt-get install --no-install-recommends -y ca-certificates media-types && rm -rf /var/lib/apt/lists/*
RUN if [ "${INSTALL_OPTIONAL_PACKAGES}" = "true" ]; then apt-get update && apt-get install --no-install-recommends -y jq git rsync && rm -rf /var/lib/apt/lists/*; fi
@ -43,7 +50,7 @@ COPY --from=builder /workspace/sftpgo.json /etc/sftpgo/sftpgo.json
COPY --from=builder /workspace/templates /usr/share/sftpgo/templates
COPY --from=builder /workspace/static /usr/share/sftpgo/static
COPY --from=builder /workspace/openapi /usr/share/sftpgo/openapi
COPY --from=builder /workspace/sftpgo /usr/local/bin/
COPY --from=builder /workspace/sftpgo /usr/local/bin/sftpgo-plugin-* /usr/local/bin/
# Log to the stdout so the logs will be available using docker logs
ENV SFTPGO_LOG_FILE_PATH=""

View file

@ -1,8 +1,8 @@
FROM golang:1.18-alpine3.16 AS builder
FROM golang:1.22-alpine3.20 AS builder
ENV GOFLAGS="-mod=readonly"
RUN apk add --update --no-cache bash ca-certificates curl git gcc g++
RUN apk -U upgrade --no-cache && apk add --update --no-cache bash ca-certificates curl git gcc g++
RUN mkdir -p /workspace
WORKDIR /workspace
@ -22,23 +22,18 @@ ARG FEATURES
COPY . .
RUN set -xe && \
export COMMIT_SHA=${COMMIT_SHA:-$(git describe --always --dirty)} && \
go build $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=${COMMIT_SHA} -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -v -o sftpgo
export COMMIT_SHA=${COMMIT_SHA:-$(git describe --always --abbrev=8 --dirty)} && \
go build $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${COMMIT_SHA} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -v -o sftpgo
FROM alpine:3.16
FROM alpine:3.20
# Set to "true" to install jq and the optional git and rsync dependencies
ARG INSTALL_OPTIONAL_PACKAGES=false
RUN apk add --update --no-cache ca-certificates tzdata mailcap
RUN apk -U upgrade --no-cache && apk add --update --no-cache ca-certificates tzdata mailcap
RUN if [ "${INSTALL_OPTIONAL_PACKAGES}" = "true" ]; then apk add --update --no-cache jq git rsync; fi
# set up nsswitch.conf for Go's "netgo" implementation
# https://github.com/gliderlabs/docker-alpine/issues/367#issuecomment-424546457
RUN test ! -e /etc/nsswitch.conf && echo 'hosts: files dns' > /etc/nsswitch.conf
RUN mkdir -p /etc/sftpgo /var/lib/sftpgo /usr/share/sftpgo /srv/sftpgo/data /srv/sftpgo/backups
RUN addgroup -g 1000 -S sftpgo && \

View file

@ -1,7 +1,9 @@
FROM golang:1.18-bullseye as builder
FROM golang:1.22-bookworm as builder
ENV CGO_ENABLED=0 GOFLAGS="-mod=readonly"
RUN apt-get update && apt-get -y upgrade && apt-get install --no-install-recommends -y media-types && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /workspace
WORKDIR /workspace
@ -15,24 +17,22 @@ ARG COMMIT_SHA
# This ARG allows to disable some optional features and it might be useful if you build the image yourself.
# For this variant we disable SQLite support since it requires CGO and so a C runtime which is not installed
# in distroless/static-* images
ARG FEATURES=nosqlite
ARG FEATURES
COPY . .
RUN set -xe && \
export COMMIT_SHA=${COMMIT_SHA:-$(git describe --always --dirty)} && \
go build $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=${COMMIT_SHA} -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -v -o sftpgo
export COMMIT_SHA=${COMMIT_SHA:-$(git describe --always --abbrev=8 --dirty)} && \
go build $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${COMMIT_SHA} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -v -o sftpgo
# Modify the default configuration file
RUN sed -i 's|"users_base_dir": "",|"users_base_dir": "/srv/sftpgo/data",|' sftpgo.json && \
sed -i 's|"backups"|"/srv/sftpgo/backups"|' sftpgo.json && \
sed -i 's|"sqlite"|"bolt"|' sftpgo.json
RUN apt-get update && apt-get install --no-install-recommends -y media-types && rm -rf /var/lib/apt/lists/*
RUN mkdir /etc/sftpgo /var/lib/sftpgo /srv/sftpgo
FROM gcr.io/distroless/static-debian11
FROM gcr.io/distroless/static-debian12
COPY --from=builder --chown=1000:1000 /etc/sftpgo /etc/sftpgo
COPY --from=builder --chown=1000:1000 /srv/sftpgo /srv/sftpgo
@ -54,4 +54,4 @@ ENV HOME=/var/lib/sftpgo
WORKDIR /var/lib/sftpgo
USER 1000:1000
CMD ["sftpgo", "serve"]
CMD ["sftpgo", "serve"]

328
README.md
View file

@ -1,304 +1,67 @@
# SFTPGo
![CI Status](https://github.com/drakkan/sftpgo/workflows/CI/badge.svg?branch=main&event=push)
[![CI Status](https://github.com/drakkan/sftpgo/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/drakkan/sftpgo/workflows/CI/badge.svg?branch=main&event=push)
[![Code Coverage](https://codecov.io/gh/drakkan/sftpgo/branch/main/graph/badge.svg)](https://codecov.io/gh/drakkan/sftpgo/branch/main)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPLv3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
[![Docker Pulls](https://img.shields.io/docker/pulls/drakkan/sftpgo)](https://hub.docker.com/r/drakkan/sftpgo)
[![License: AGPL-3.0-only](https://img.shields.io/badge/License-AGPLv3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)
[English](./README.md) | [简体中文](./README.zh_CN.md)
Full-featured and highly configurable event-driven file transfer solution.
Server protocols: SFTP, HTTP/S, FTP/S, WebDAV.
Storage backends: local filesystem, encrypted local filesystem, S3 (compatible) Object Storage, Google Cloud Storage, Azure Blob Storage, other SFTP servers.
Fully featured and highly configurable SFTP server with optional HTTP/S, FTP/S and WebDAV support.
Several storage backends are supported: local filesystem, encrypted local filesystem, S3 (compatible) Object Storage, Google Cloud Storage, Azure Blob Storage, SFTP.
With SFTPGo you can leverage local and cloud storage backends for exchanging and storing files internally or with business partners using the same tools and processes you are already familiar with.
## Features
The WebAdmin UI allows to easily create and manage your users, folders, groups and other resources.
- Support for serving local filesystem, encrypted local filesystem, S3 Compatible Object Storage, Google Cloud Storage, Azure Blob Storage or other SFTP accounts over SFTP/SCP/FTP/WebDAV.
- Virtual folders are supported: a virtual folder can use any of the supported storage backends. So you can have, for example, an S3 user that exposes a GCS bucket (or part of it) on a specified path and an encrypted local filesystem on another one. Virtual folders can be private or shared among multiple users, for shared virtual folders you can define different quota limits for each user.
- Configurable [custom commands and/or HTTP hooks](./docs/custom-actions.md) on upload, pre-upload, download, pre-download, delete, pre-delete, rename, mkdir, rmdir on SSH commands and on user add, update and delete.
- Virtual accounts stored within a "data provider".
- SQLite, MySQL, PostgreSQL, CockroachDB, Bolt (key/value store in pure Go) and in-memory data providers are supported.
- Chroot isolation for local accounts. Cloud-based accounts can be restricted to a certain base path.
- Per-user and per-directory virtual permissions, for each exposed path you can allow or deny: directory listing, upload, overwrite, download, delete, rename, create directories, create symlinks, change owner/group/file mode and modification time.
- [REST API](./docs/rest-api.md) for users and folders management, data retention, backup, restore and real time reports of the active connections with possibility of forcibly closing a connection.
- [Web based administration interface](./docs/web-admin.md) to easily manage users, folders and connections.
- [Web client interface](./docs/web-client.md) so that end users can change their credentials, manage and share their files in the browser.
- Public key and password authentication. Multiple public keys per-user are supported.
- SSH user [certificate authentication](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?rev=1.8).
- Keyboard interactive authentication. You can easily setup a customizable multi-factor authentication.
- Partial authentication. You can configure multi-step authentication requiring, for example, the user password after successful public key authentication.
- Per-user authentication methods.
- [Two-factor authentication](./docs/howto/two-factor-authentication.md) based on time-based one time passwords (RFC 6238) which works with Authy, Google Authenticator and other compatible apps.
- Simplified user administrations using [groups](./docs/groups.md).
- Custom authentication via external programs/HTTP API.
- Web Client and Web Admin user interfaces support [OpenID Connect](https://openid.net/connect/) authentication and so they can be integrated with identity providers such as [Keycloak](https://www.keycloak.org/). You can find more details [here](./docs/oidc.md).
- [Data At Rest Encryption](./docs/dare.md).
- Dynamic user modification before login via external programs/HTTP API.
- Quota support: accounts can have individual disk quota expressed as max total size and/or max number of files.
- Bandwidth throttling, with separate settings for upload and download and overrides based on the client's IP address.
- Data transfer bandwidth limits, with total limit or separate settings for uploads and downloads and overrides based on the client's IP address. Limits can be reset using the REST API.
- Per-protocol [rate limiting](./docs/rate-limiting.md) is supported and can be optionally connected to the built-in defender to automatically block hosts that repeatedly exceed the configured limit.
- Per-user maximum concurrent sessions.
- Per-user and global IP filters: login can be restricted to specific ranges of IP addresses or to a specific IP address.
- Per-user and per-directory shell like patterns filters: files can be allowed, denied and optionally hidden based on shell like patterns.
- Automatically terminating idle connections.
- Automatic blocklist management using the built-in [defender](./docs/defender.md).
- Geo-IP filtering using a [plugin](https://github.com/sftpgo/sftpgo-plugin-geoipfilter).
- Atomic uploads are configurable.
- Per-user files/folders ownership mapping: you can map all the users to the system account that runs SFTPGo (all platforms are supported) or you can run SFTPGo as root user and map each user or group of users to a different system account (\*NIX only).
- Support for Git repositories over SSH.
- SCP and rsync are supported.
- FTP/S is supported. You can configure the FTP service to require TLS for both control and data connections.
- [WebDAV](./docs/webdav.md) is supported.
- ACME protocol is supported. SFTPGo can obtain and automatically renew TLS certificates for HTTPS, WebDAV and FTPS from `Let's Encrypt` or other ACME compliant certificate authorities, using the the `HTTP-01` or `TLS-ALPN-01` [challenge types](https://letsencrypt.org/docs/challenge-types/).
- Two-Way TLS authentication, aka TLS with client certificate authentication, is supported for REST API/Web Admin, FTPS and WebDAV over HTTPS.
- Per-user protocols restrictions. You can configure the allowed protocols (SSH/HTTP/FTP/WebDAV) for each user.
- [Prometheus metrics](./docs/metrics.md) are exposed.
- Support for HAProxy PROXY protocol: you can proxy and/or load balance the SFTP/SCP/FTP service without losing the information about the client's address.
- Easy [migration](./examples/convertusers) from Linux system user accounts.
- [Portable mode](./docs/portable-mode.md): a convenient way to share a single directory on demand.
- [SFTP subsystem mode](./docs/sftp-subsystem.md): you can use SFTPGo as OpenSSH's SFTP subsystem.
- Performance analysis using built-in [profiler](./docs/profiling.md).
- Configuration format is at your choice: JSON, TOML, YAML, HCL, envfile are supported.
- Log files are accurate and they are saved in the easily parsable JSON format ([more information](./docs/logs.md)).
- SFTPGo supports a [plugin system](./docs/plugins.md) and therefore can be extended using external plugins.
The WebClient UI allows end users to change their credentials, browse and manage their files in the browser and setup two-factor authentication which works with Microsoft Authenticator, Google Authenticator, Authy and other compatible apps.
## Platforms
## Sponsors
SFTPGo is developed and tested on Linux. After each commit, the code is automatically built and tested on Linux, macOS and Windows using [GitHub Actions](./.github/workflows/development.yml). The test cases are regularly manually executed and passed on FreeBSD. Other *BSD variants should work too.
We strongly believe in Open Source software model, so we decided to make SFTPGo available to everyone, but maintaining and evolving SFTPGo takes a lot of time and work. To make development and maintenance sustainable you should consider to support the project with a [sponsorship](https://github.com/sponsors/drakkan).
## Requirements
We also provide [professional services](https://sftpgo.com/#pricing) to support you in using SFTPGo to the fullest.
- Go as build only dependency. We support the Go version(s) used in [continuous integration workflows](./.github/workflows).
- A suitable SQL server to use as data provider: PostgreSQL 9.4+, MySQL 5.6+, SQLite 3.x, CockroachDB stable.
- The SQL server is optional: you can choose to use an embedded bolt database as key/value store or an in memory data provider.
The open source license grant you freedom but not assurance of help. So why would you rely on free software without support or any guarantee it will stay healthy and maintained for the upcoming years?
## Installation
Supporting the project benefit businesses and the community because if the project is financially sustainable, using this business model, we don't have to restrict features and/or switch to an [Open-core](https://en.wikipedia.org/wiki/Open-core_model) model. The technology stays truly open source. Everyone wins.
Binary releases for Linux, macOS, and Windows are available. Please visit the [releases](https://github.com/drakkan/sftpgo/releases "releases") page.
It is important to understand that you should support SFTPGo and any other Open Source project you rely on for ongoing maintenance, even if you don't have any questions or need new features, to mitigate the business risk of a project you depend on going unmaintained, with its security and development velocity implications.
An official Docker image is available. Documentation is [here](./docker/README.md).
### Thank you to our sponsors
<details>
#### Platinum sponsors
<summary>Some Linux distro packages are available</summary>
[<img src="./img/Aledade_logo.png" alt="Aledade logo" width="202" height="70">](https://www.aledade.com/)
</br></br>
[<img src="./img/jumptrading.png" alt="Jump Trading logo" width="362" height="63">](https://www.jumptrading.com/)
</br></br>
[<img src="./img/wpengine.png" alt="WP Engine logo" width="331" height="63">](https://wpengine.com/)
- For Arch Linux via AUR:
- [sftpgo](https://aur.archlinux.org/packages/sftpgo/). This package follows stable releases. It requires `git`, `gcc` and `go` to build.
- [sftpgo-bin](https://aur.archlinux.org/packages/sftpgo-bin/). This package follows stable releases downloading the prebuilt linux binary from GitHub. It does not require `git`, `gcc` and `go` to build.
- [sftpgo-git](https://aur.archlinux.org/packages/sftpgo-git/). This package builds and installs the latest git `main` branch. It requires `git`, `gcc` and `go` to build.
- Deb and RPM packages are built after each commit and for each release.
- For Ubuntu a PPA is available [here](https://launchpad.net/~sftpgo/+archive/ubuntu/sftpgo).
- Void Linux provides an [official package](https://github.com/void-linux/void-packages/tree/master/srcpkgs/sftpgo).
#### Silver sponsors
</details>
[<img src="./img/IDCS.png" alt="IDCS logo" width="212" height="51">](https://idcs.ip-paris.fr/)
SFTPGo is also available on [AWS Marketplace](https://aws.amazon.com/marketplace/seller-profile?id=6e849ab8-70a6-47de-9a43-13c3fa849335) and [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/prasselsrl1645470739547.sftpgo_linux), purchasing from there will help keep SFTPGo a long-term sustainable project.
#### Bronze sponsors
<details><summary>Windows packages</summary>
[<img src="./img/7digital.png" alt="7digital logo" width="178" height="56">](https://www.7digital.com/)
</br></br>
[<img src="./img/vps2day.png" alt="VPS2day logo" width="234" height="56">](https://www.vps2day.com/)
- The Windows installer to install and run SFTPGo as a Windows service.
- The portable package to start SFTPGo on demand.
- The [winget](https://docs.microsoft.com/en-us/windows/package-manager/winget/install) package to install and run SFTPGo as a Windows service: `winget install SFTPGo`.
- The [Chocolatey package](https://community.chocolatey.org/packages/sftpgo) to install and run SFTPGo as a Windows service.
## Support policy
</details>
You can use SFTPGo for free, respecting the obligations of the Open Source license, but please do not ask or expect free support as well.
On macOS you can install from the Homebrew [Formula](https://formulae.brew.sh/formula/sftpgo).
On FreeBSD you can install from the [SFTPGo port](https://www.freshports.org/ftp/sftpgo).
On DragonFlyBSD you can install SFTPGo from [DPorts](https://github.com/DragonFlyBSD/DPorts/tree/master/ftp/sftpgo).
Use [discussions](https://github.com/drakkan/sftpgo/discussions) to ask questions and get support from the community.
You can easily test new features selecting a commit from the [Actions](https://github.com/drakkan/sftpgo/actions) page and downloading the matching build artifacts for Linux, macOS or Windows. GitHub stores artifacts for 90 days.
If you report an invalid issue and/or ask for step-by-step support, your issue will be closed as invalid without further explanation and/or the "support request" label will be added. Invalid bug reports may confuse other users. Thanks for understanding.
Alternately, you can [build from source](./docs/build-from-source.md).
## Documentation
[Getting Started Guide for the Impatient](./docs/howto/getting-started.md).
## Configuration
A full explanation of all configuration methods can be found [here](./docs/full-configuration.md).
Please make sure to [initialize the data provider](#data-provider-initialization-and-management) before running the daemon.
To start SFTPGo with the default settings, simply run:
```bash
sftpgo serve
```
Check out [this documentation](./docs/service.md) if you want to run SFTPGo as a service.
### Data provider initialization and management
Before starting the SFTPGo server please ensure that the configured data provider is properly initialized/updated.
For PostgreSQL, MySQL and CockroachDB providers, you need to create the configured database. For SQLite, the configured database will be automatically created at startup. Memory and bolt data providers do not require an initialization but they could require an update to the existing data after upgrading SFTPGo.
SFTPGo will attempt to automatically detect if the data provider is initialized/updated and if not, will attempt to initialize/ update it on startup as needed.
Alternately, you can create/update the required data provider structures yourself using the `initprovider` command.
For example, you can simply execute the following command from the configuration directory:
```bash
sftpgo initprovider
```
Take a look at the CLI usage to learn how to specify a different configuration file:
```bash
sftpgo initprovider --help
```
You can disable automatic data provider checks/updates at startup by setting the `update_mode` configuration key to `1`.
You can also reset your provider by using the `resetprovider` sub-command. Take a look at the CLI usage for more details:
```bash
sftpgo resetprovider --help
```
:warning: Please note that some data providers (e.g. MySQL and CockroachDB) do not support schema changes within a transaction, this means that you may end up with an inconsistent schema if migrations are forcibly aborted. CockroachDB doesn't support database-level locks, so make sure you don't execute migrations concurrently.
## Create the first admin
To start using SFTPGo you need to create an admin user, you can do it in several ways:
- by using the web admin interface. The default URL is [http://127.0.0.1:8080/web/admin](http://127.0.0.1:8080/web/admin)
- by loading initial data
- by enabling `create_default_admin` in your configuration file and setting the environment variables `SFTPGO_DEFAULT_ADMIN_USERNAME` and `SFTPGO_DEFAULT_ADMIN_PASSWORD`
## Upgrading
SFTPGo supports upgrading from the previous release branch to the current one.
Some examples for supported upgrade paths are:
- from 1.2.x to 2.0.x
- from 2.0.x to 2.1.x and so on.
For supported upgrade paths, the data and schema are migrated automatically, alternately you can use the `initprovider` command.
So if, for example, you want to upgrade from a version before 1.2.x to 2.0.x, you must first install version 1.2.x, update the data provider and finally install the version 2.0.x. It is recommended to always install the latest available minor version, ie do not install 1.2.0 if 1.2.2 is available.
Loading data from a provider independent JSON dump is supported from the previous release branch to the current one too. After upgrading SFTPGo it is advisable to regenerate the JSON dump from the new version.
## Downgrading
If for some reason you want to downgrade SFTPGo, you may need to downgrade your data provider schema and data as well. You can use the `revertprovider` command for this task.
As for upgrading, SFTPGo supports downgrading from the previous release branch to the current one.
So, if you plan to downgrade from 2.0.x to 1.2.x, before uninstalling 2.0.x version, you can prepare your data provider executing the following command from the configuration directory:
```shell
sftpgo revertprovider --to-version 4
```
Take a look at the CLI usage to see the supported parameter for the `--to-version` argument and to learn how to specify a different configuration file:
```shell
sftpgo revertprovider --help
```
The `revertprovider` command is not supported for the memory provider.
Please note that we only support the current release branch and the current main branch, if you find a bug it is better to report it rather than downgrading to an older unsupported version.
## Users and folders management
After starting SFTPGo you can manage users and folders using:
- the [web based administration interface](./docs/web-admin.md)
- the [REST API](./docs/rest-api.md)
To support embedded data providers like `bolt` and `SQLite` we can't have a CLI that directly write users and folders to the data provider, we always have to use the REST API.
Full details for users, folders, admins and other resources are documented in the [OpenAPI](./openapi/openapi.yaml) schema. If you want to render the schema without importing it manually, you can explore it on [Stoplight](https://sftpgo.stoplight.io/docs/sftpgo/openapi.yaml).
## Tutorials
Some step-to-step tutorials can be found inside the source tree [howto](./docs/howto "How-to") directory.
## Authentication options
<details><summary> External Authentication</summary>
Custom authentication methods can easily be added. SFTPGo supports external authentication modules, and writing a new backend can be as simple as a few lines of shell script. More information can be found [here](./docs/external-auth.md).
</details>
<details><summary> Keyboard Interactive Authentication</summary>
Keyboard interactive authentication is, in general, a series of questions asked by the server with responses provided by the client.
This authentication method is typically used for multi-factor authentication.
More information can be found [here](./docs/keyboard-interactive.md).
</details>
## Dynamic user creation or modification
A user can be created or modified by an external program just before the login. More information about this can be found [here](./docs/dynamic-user-mod.md).
## Custom Actions
SFTPGo allows you to configure custom commands and/or HTTP hooks to receive notifications about file uploads, deletions and several other events.
More information about custom actions can be found [here](./docs/custom-actions.md).
## Virtual folders
Directories outside the user home directory or based on a different storage provider can be exposed as virtual folders, more information [here](./docs/virtual-folders.md).
## Other hooks
You can get notified as soon as a new connection is established using the [Post-connect hook](./docs/post-connect-hook.md) and after each login using the [Post-login hook](./docs/post-login-hook.md).
You can use your own hook to [check passwords](./docs/check-password-hook.md).
## Storage backends
### S3/GCP/Azure
Each user can be mapped with a [S3 Compatible Object Storage](./docs/s3.md) /[Google Cloud Storage](./docs/google-cloud-storage.md)/[Azure Blob Storage](./docs/azure-blob-storage.md) bucket or a bucket virtual folder that is exposed over SFTP/SCP/FTP/WebDAV.
### SFTP backend
Each user can be mapped to another SFTP server account or a subfolder of it. More information can be found [here](./docs/sftpfs.md).
### Encrypted backend
Data at-rest encryption is supported via the [cryptfs backend](./docs/dare.md).
### Other Storage backends
Adding new storage backends is quite easy:
- implement the [Fs interface](./vfs/vfs.go#L28 "interface for filesystem backends").
- update the user method `GetFilesystem` to return the new backend
- update the web interface and the REST API CLI
- add the flags for the new storage backed to the `portable` mode
Anyway, some backends require a pay per-use account (or they offer free account for a limited time period only). To be able to add support for such backends or to review pull requests, please provide a test account. The test account must be available for enough time to be able to maintain the backend and do basic tests before each new release.
## Brute force protection
SFTPGo supports a built-in [defender](./docs/defender.md).
Alternately you can use the [connection failed logs](./docs/logs.md) for integration in tools such as [Fail2ban](http://www.fail2ban.org/). Example of [jails](./fail2ban/jails) and [filters](./fail2ban/filters) working with `systemd`/`journald` are available in fail2ban directory.
## Account's configuration properties
Details information about account configuration properties can be found [here](./docs/account.md).
## Performance
SFTPGo can easily saturate a Gigabit connection on low end hardware with no special configuration, this is generally enough for most use cases.
More in-depth analysis of performance can be found [here](./docs/performance.md).
You can read more about supported features and documentation at [sftpgo.github.io](https://sftpgo.github.io/).
## Release Cadence
SFTPGo releases are feature-driven, we don't have a fixed time based schedule. As a rough estimate, you can expect 1 or 2 new releases per year.
SFTPGo releases are feature-driven, we don't have a fixed time based schedule. As a rough estimate, you can expect 1 or 2 new major releases per year and several bug fix releases.
## Acknowledgements
@ -306,16 +69,25 @@ SFTPGo makes use of the third party libraries listed inside [go.mod](./go.mod).
We are very grateful to all the people who contributed with ideas and/or pull requests.
Thank you [ysura](https://www.ysura.com/) for granting me stable access to a test AWS S3 account.
Thank you to [ysura](https://www.ysura.com/) for granting us stable access to a test AWS S3 account.
## Sponsors
Thank you to [KeenThemes](https://keenthemes.com/) for granting us a custom license to use their amazing [Mega Bundle](https://keenthemes.com/products/templates-mega-bundle) for SFTPGo UI.
I'd like to make SFTPGo into a sustainable long term project and your [sponsorship](https://github.com/sponsors/drakkan) will really help :heart:
Thank you to [Crowdin](https://crowdin.com/) for granting us an Open Source License.
Thank you to our sponsors!
[<img src="https://www.7digital.com/wp-content/themes/sevendigital/images/top_logo.png" alt="7digital logo">](https://www.7digital.com/)
Thank you to [Incode](https://www.incode.it/) for helping us to improve the UI/UX.
## License
GNU AGPLv3
SFTPGo source code is licensed under the GNU AGPL-3.0-only.
The [theme](https://keenthemes.com/products/templates-mega-bundle) used in WebAdmin and WebClient user interfaces is proprietary, this means:
- KeenThemes HTML/CSS/JS components are allowed for use only within the SFTPGo product and restricted to be used in a resealable HTML template that can compete with KeenThemes products anyhow.
- The SFTPGo WebAdmin and WebClient user interfaces (HTML, CSS and JS components) based on this theme are allowed for use only within the SFTPGo product and therefore cannot be used in derivative works/products without an explicit grant from the [SFTPGo Team](mailto:support@sftpgo.com).
More information about [compliance](https://sftpgo.com/compliance.html).
## Copyright
Copyright (C) 2019 Nicola Murino

View file

@ -1,318 +0,0 @@
# SFTPGo
![CI Status](https://github.com/drakkan/sftpgo/workflows/CI/badge.svg?branch=main&event=push)
[![Code Coverage](https://codecov.io/gh/drakkan/sftpgo/branch/main/graph/badge.svg)](https://codecov.io/gh/drakkan/sftpgo/branch/main)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPLv3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
[![Docker Pulls](https://img.shields.io/docker/pulls/drakkan/sftpgo)](https://hub.docker.com/r/drakkan/sftpgo)
[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)
[English](./README.md) | [简体中文](./README.zh_CN.md)
功能齐全、高度可配置化、支持自定义 HTTP/SFTP/S 和 WebDAV 的 SFTP 服务。
一些存储后端支持本地文件系统、加密本地文件系统、S3兼容对象存储Google Cloud 存储Azure Blob 存储SFTP。
## 特性
- 支持服务本地文件系统、加密本地文件系统、S3 兼容对象存储、Google Cloud 存储、Azure Blob 存储或其它基于 SFTP/SCP/FTP/WebDAV 协议的 SFTP 账户。
- 虚拟目录支持:一个虚拟目录可以用于支持的存储后端。你可以,比如,一个 S3 用户暴露了一个 GCS bucket或者其中一部分在特定的路径下、一个加密本地文件系统在另一个。虚拟目录可以对于大量用户作为私密或者共享分享虚拟目录你可以为每个用户定义不同的配额。
- 可配置的 [自定义命令 和/或 HTTP 钩子](./docs/custom-actions.md) 在 SSH 命令的 upload, pre-upload, download, pre-download, delete, pre-delete, rename, mkdir, rmdir 阶段,和用户添加、更新、删除阶段。
- 存储在 “数据提供程序” 中的虚拟账户。
- 支持 SQLite, MySQL, PostgreSQL, CockroachDB, Bolt (Go 原生键/值存储) 和内存数据提供程序。
- 为本地账户提供 Chroot 隔离。云端账户可以限制为特定的基本路径。
- 每个用户和每个目录虚拟权限,对于每个暴露的路径你可以允许或禁止:目录展示、上传、覆盖、下载、删除、重命名、创建文件夹、创建软连接、修改 owner/group/file 模式和更改时间。
- 为用户和目录管理提供、数据保留、备份、恢复和即时活动连接的实时报告,可能会强制关闭连接,提供 [REST API](./docs/rest-api.md)。
- [基于 Web 的管理员界面](./docs/web-admin.md) 可以容易地管理用户、目录和连接。
- [Web 客户端界面](./docs/web-client.md) 以便终端用户可以在浏览器中更改他们的凭据、管理和共享他们的文件。
- 公钥和密码认证。支持每个用户多个公钥。
- SSH 用户 [证书认证](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?rev=1.8).
- 键盘交互认证。您可以轻松设置可定制的多因素身份认证。
- 部分验证。你可以配置多步验证请求,例如,用户密码在公钥验证之后。
- 每个用户的身份验证方法。
- [双重验证](./docs/howto/two-factor-authentication.md) 基于实现一次性密码 (RFC 6238) 可以与 Authy、Google Authenticator 和其他兼容的应用程序配合使用。
- 通过 [群组](./docs/groups.md) 精简用户管理。
- 通过外部 程序/HTTP API 自定义验证。
- Web 客户端和 Web 管理员他用户界面支持 [OpenID Connect](https://openid.net/connect/) 验证,所以它们很容易被集成在诸如 [Keycloak](https://www.keycloak.org/) 之类的身份认证程序。你可以在 [](./docs/oidc.md) 获取更多信息。
- [静态数据加密](./docs/dare.md)。
- 在登录之前通过 程序/HTTP API 进行动态用户修改。
- 配额支持:账户拥有独立的磁盘配额表示为总计最大体积 和/或 最大文件数量。
- 带宽节流,基于客户端 IP 地址独立设置上传、下载和覆盖。
- 数据传输带宽限制,限制总量或基于客户端 IP 地址设置上传、下载和覆盖。限制可以通过 REST API 重置。
- 支持每个协议[限速](./docs/rate-limiting.md),可以可选与内置的防护连接实现自动封禁重复超过设置限制的主机。
- 每个用户的最大并发会话。
- 每个用户和全局 IP 过滤:登录可以被限制在特定的 IP 段和指定的 IP 地址。
- 每个用户和每个文件夹类似于 shell 的模式过滤:文件可以被允许、禁止和隐藏基于类 shell 模式。
- 自动使 idle 连接终止。
- 通过内置的 [防护](./docs/defender.md) 自动管理禁止名单。
- 通过 [插件](https://github.com/sftpgo/sftpgo-plugin-geoipfilter) 实现 地理-IP 过滤。
- 原子上传是可配置的。
- 每个用户 文件/目录 所有权映射:你可以将所有用户映射到运行 SFEPGo 的系统账户(所有的平台都是支持的),或者你可以使用 root 用户运行 SFTPGo 并且映射每个用户或用户组到一个不同系统账户(仅支持 \*NIX
- 通过 SSH 支持 Git 仓库。
- 支持 SCP 和 rsync。
- 支持 FTP/S。你可以配置 FTP 服务为控制和数据连接都需要 TLS。
- [WebDAV](./docs/webdav.md) 是支持的。
- 两步 TLS 验证,具有客户端证书身份验证的 aka TLS支持 REST API/Web Admin、FTPS 和 基于 HTTPS 的 WebDAV。
- 每个用户协议限制。你可以为每个用户配置允许的协议(SSH/HTTP/FTP/WebDAV)。
- 暴露 [输出指标](./docs/metrics.md)。
- 支持 HAProxy PROXY 协议:你可以不需要丢失客户端地址信息代理 和/或 负载平衡 SFTP/SCP/FTP 服务。
- 简单从 Linux 系统用户账户进行 [迁移](./examples/convertusers)。
- [可携带模式](./docs/portable-mode.md):按需共享单个目录的便捷方式。
- [SFTP 子系统模式](./docs/sftp-subsystem.md):你可以使用 SFTPGo 作为 OpenSSH 的 SFTP 子系统。
- 性能分析基于内置的 [分析器](./docs/profiling.md)。
- 配置项格式基于你的选择JSON, TOML, YAML, HCL, envfile 都是支持的。
- 日志文件是精确的,它们被存储为易被解析的 JSON 格式。([更多信息](./docs/logs.md)
- SFTPGo 支持 [插件系统](./docs/plugins.md),因此可以使用外部插件拓展。
## 平台
SFTPGo 基于 Linux 开发和创建。在每一次提交之后,代码会自动通过 [GitHub Actions](./.github/workflows/development.yml) 在 Linux、macOS 和 Windows 构建和测试。测试用例定期手动在 FreeBSD 执行,其他的 *BSD 变体同样适用。
## 要求
- Go 作为构建仅有的依赖。我们支持 [持续集成工作流](./.github/workflows) 中使用的 Go 版本。
- 使用适配的 SQL 服务作为数据提供程序PostgreSQL 9.4+, MySQL 5.6+, SQLite 3.x, CockroachDB stable.
- SQL 服务是可选的:你可以使用一个内置的 bolt 数据库以 键/值 存储,或者一个内存中的数据提供程序。
## 安装
为 Linux、macOS 和 Windows 提供的二进制发行版是可用的。请参考 [发行版](https://github.com/drakkan/sftpgo/releases "releases") 页面。
一个官方的 Docker 镜像是可用的。文档参考 [Docker](./docker/README.md)。
<details>
<summary>一些 Linux 分支包是可用的</summary>
- Arch Linux 通过 AUR:
- [sftpgo](https://aur.archlinux.org/packages/sftpgo/)。这个包跟随稳定的发行版。需要 `git`、`gcc` 和 `go` 进行构建。
- [sftpgo-bin](https://aur.archlinux.org/packages/sftpgo-bin/)。这个包跟随稳定的发行版从 GitHub 下载预构建 Linux 二进制文件。不需要 `git`、`gcc` 和 `go` 进行构建。
- [sftpgo-git](https://aur.archlinux.org/packages/sftpgo-git/)。这个包构建和下载基于最新的 `git` 主分支。需要 `git`、`gcc` 和 `go` 进行构建。
- Deb and RPM 包在每次提交和发行之后构建。
- Ubuntu PPA 在 [](https://launchpad.net/~sftpgo/+archive/ubuntu/sftpgo) 可用。
- Void Linux 提供一个 [官方包](https://github.com/void-linux/void-packages/tree/master/srcpkgs/sftpgo)。
</details>
SFTPGo 在 [AWS Marketplace](https://aws.amazon.com/marketplace/seller-profile?id=6e849ab8-70a6-47de-9a43-13c3fa849335) 和 [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/prasselsrl1645470739547.sftpgo_linux) 同样可用,在此付费可以帮助 SFTPGo 成为一个可持续发展的长期项目。
<details><summary>Windows 包</summary>
- Windows installer 安装和运行 SFTPGo 作为一个 Windows 服务。
- 开箱即用的包启动按需使用的 SFTPGo。
- [winget](https://docs.microsoft.com/en-us/windows/package-manager/winget/install) 包下载和运行 SFTPGo 作为一个 Windows 服务:`winget install SFTPGo`。
- [Chocolatey 包](https://community.chocolatey.org/packages/sftpgo) 下载和运行 SFTPGo 作为一个 Windows 服务。
</details>
在 FreeBSD你可以从 [SFTPGo port](https://www.freshports.org/ftp/sftpgo) 下载。
在 DragonFlyBSD你可以从 [DPorts](https://github.com/DragonFlyBSD/DPorts/tree/master/ftp/sftpgo) 下载。
您可以从 [Actions](https://github.com/drakkan/sftpgo/Actions) 页面选择一个 commit 并下载 Linux、macOS 或 Windows 的匹配构建从而轻松测试新特性。GitHub 存储 90 天。
另外,你可以 [从源码构建](./docs/build-from-source.md)。
[不耐烦的快速上手指南](./docs/howto/getting-started.md).
## 配置项
可以完整的配置项方法说明可以参考 [配置项](./docs/full-configuration.md)。
请确保按需运行之前,[初始化数据提供程序](#data-provider-initialization-and-management)。
默认配置启动 STFPGo运行
```bash
sftpgo serve
```
如果你将 SFTPGo作为服务请参阅 [这篇文档](./docs/service.md)。
### 数据提供程序初始化和管理
在启动 SFTPGo 服务之前,请确保配置的数据提供程序已经被适当的 初始化/更新。
对于 PostgreSQL, MySQL 和 CockroachDB 提供,你需要创建一个配置数据库。对于 SQLite配置数据库将会在启动时被自动创建。内存和 bolt 数据提供程序不需要初始化,但是它们需要在升级 SFTPGo 之后更新现有的数据。
SFTPGo 会尝试自动探测数据提供程序是否被 初始化/更新;如果没有,将会在启动时尝试 初始化/更新。
或者,你可以通过 `initprovider` 命令自行 创建/更新 需要的数据提供程序结构。
比如,你可以执行在配置文件目录下面的命令:
```bash
sftpgo initprovider
```
看一看 CLI 用法学习如何指定一个不同的配置文件:
```bash
sftpgo initprovider --help
```
你可以在启动阶段通过设置 `update_mode` 配置项为 `1`,禁止自动数据提供程序 检查/更新。
你可以通过使用 `resetprovider` 子命令重置你的数据提供程序。看一看 CLI 用法获取更多细节信息:
```bash
sftpgo resetprovider --help
```
:warning: 请注意一些数据提供程序(比如 MySQL 和 CockroachDB不支持事务内的方案更改这意味着如果迁移被强制中止或由多个实例同时运行您可能会得到不一致的方案。
## 创建第一个管理员
开始使用 SFTPGo你需要创建一个管理员用户你可以通过不同的方式进行实现
- 通过 web 管理员界面。默认 URL 是 [http://127.0.0.1:8080/web/admin](http://127.0.0.1:8080/web/admin)
- 通过加载初始数据
- 通过在你的配置文件启用 `create_default_admin` 并设置环境变量 `SFTPGO_DEFAULT_ADMIN_USERNAME``SFTPGO_DEFAULT_ADMIN_PASSWORD`
## 升级
SFTPGo 支持从之前的发行版分支升级到当前分支。
一些支持的升级路径如下:
- 从 1.2.x 到 2.0.x
- 从 2.0.x 到 2.1.x 等。
对支持的升级路径,数据和方案将会自动迁移,你可以使用 `initprovider` 命令作为替代。
所以,比如,你想从 1.2.x 之前的版本升级到 2.0.x你必须首先安装 1.2.x 版本,升级数据提供程序并最终安装版本 2.0.x。建议安装最新的可用小版本如果 1.2.2 可用就不要安装 1.2.0 版本。
从以前发行版分支到当前版本,都支持从独立于数据提供程序的 JSON 转储中加载数据。升级 SFTPGo 后,建议从新版本重新生成 JSON 转储。
## 降级
如果因为一些原因你想降级 SFTPGo你可能需要降级你的用户数据提供程序方案和数据。你可以使用 `revertprovider` 命令执行这项任务。
对于升级SFTPGo 支持从先前的发行版分支降级到当前分支。
所以,如果你有计划从 2.0.x 降级到 1.2.x之前先卸载 2.0.x 版本,你可以通过从配置目录执行以下命令来准备你的数据提供程序:
```shell
sftpgo revertprovider --to-version 4
```
看一看 CLI 的用法、了解 `--to-version` 参数支持的参数,了解如何去指定一个不同的配置文件:
```shell
sftpgo revertprovider --help
```
`revertprovider` 命令不支持内存数据提供程序。
请注意我们只支持当前发行版分支和当前主分支,如果你发现了个 bug最好是报告这个问题而不是降级到一个老的、不被支持的版本。
## 用户和目录管理
在启动 SFTPGo 之后,你可以管理用户和目录使用:
- [基于 Web 的管理员界面](./docs/web-admin.md)
- [REST API](./docs/rest-api.md)
支持内置的数据提供程序比如 `bolt``SQLite`。我们不能使用 CLI 直接将用户和文件夹写到数据提供程序,通常使用 REAST API。
对于用户、目录、管理员和其它资源的细节,都记录在 [OpenAPI](./openapi/openapi.yaml) 方案。如果你想在不手动引入的情况下渲染方案,你可以在 [Stoplight](https://sftpgo.stoplight.io/docs/sftpgo/openapi.yaml) 上暴露它。
## 教程
一些手把手教程可以在源码文件树中的 [howto](./docs/howto "How-to") 目录找到。
## 认证选项
<details><summary>外部认证</summary>
自定义认证方法可以很容易被添加。SFTPGo 支持外部认证模块,编写一个后端可以如编写几行 shell 脚本那样简单。更多的信息可以参考 [外部认证](./docs/external-auth.md)。
</details>
<details><summary>键盘交互认证</summary>
一般来说,键盘交互身份验证是服务器提出的一系列问题,由客户端提供响应。
这种身份认证方法通常用于多因素身份认证。
更多信息参考 [键盘交互](./docs/keyboard-interactive.md)。
</details>
## 动态用户创建或修改
一个用户可以通过外部程序在登录之前被创建和修改。更多关于此可以参考 [动态用户修改](./docs/dynamic-user-mod.md)。
## 自定义动作
SFTPGo 允许你配置自定义的命令 和/或 HTTP 钩子去获取关于文件上传、删除和一些其它操作的通知。
更多关于自定义动作的信息你可以参考 [自定义动作](./docs/custom-actions.md)。
## 虚拟目录
用户 home 文件夹外或者基于不同存储提供的目录,可以作为虚拟目录进行暴露,详细信息参考 [虚拟目录](./docs/virtual-folders.md)。
## 其它钩子
你可以使用 [Post-connect 钩子](./docs/post-connect-hook.md) 及时获取新的连接建立,使用 [Post-login hook](./docs/post-login-hook.md) 获取每次登录之后的通知。你可以使用你自己的钩子去 [验证密码](./docs/check-password-hook.md)。
## 存储后端
### S3/GCP/Azure
每个用户可以被映射到 [S3 兼容对象存储](./docs/s3.md) /[Google Cloud 存储](./docs/google-cloud-storage.md)/[Azure Blob 存储](./docs/azure-blob-storage.md) bucket 或者一个 bucket 虚拟目录,通过 SFTP/SCP/FTP/WebDAV 进行暴露。
### SFTP 后端
每个用户可以被映射到另一个 SFTP 服务器账户或者它的子目录。更多的信息可以参考 [sftpfs](./docs/sftpfs.md)。
### 加密后端
数据静态加密通过 [cryptfs 后端](./docs/dare.md) 进行支持。
### 其它存储后端
添加新的存储后端非常简单:
- 实现 [Fs 接口](./vfs/vfs.go#L28 "interface for filesystem backends")
- 更新用户方法 `GetFilesystem` 返回新的后端
- 更新 web 接口和 REST API CLI
- 为新的存储后端添加向 `portable` 模式添加 flags
无论如何,一些后端需要按次付费账户(或者他们提供限制期限内提供免费账户)。为了能够添加这些账户支持或者预览 PRs请提供一个测试账户。测试账户必须在提供足够长时间维护此后端并且支持每一次新的发行版之前做基本测试。
## 强力保护
SFTPGo 支持内置 [防护](./docs/defender.md)。
你可以使用 [连接失败日志](./docs/logs.md) 在诸如 [Fail2ban](http://www.fail2ban.org/) 进行工具内集成。[jails](./fail2ban/jails) 和 [filters](./fail2ban/filters) 示例,在 fail2ban 目录中与 `systemd`/`journald` 是可以同时工作的。
## 账户配置属性
关于账户配置属性的细节信息,请参考 [账户](./docs/account.md)。
## 性能
SFTPGo 在没有特殊配置的情况下,可以实现低端硬件轻松达到 GB 量级连接,对于大多数场景足够使用了。
更多深度性能分析可以参考 [性能](./docs/performance.md)。
## 发行节奏
STFPGo 发行版是特性驱动的,我们没有基于计划的固定时间。粗略估计,你可以每年期待一到两个新的发行版。
## 感谢
SFTPGo 使用了 [go.mod](./go.mod) 中列出的第三方库。
我们非常感激所有贡献想法 和/或 PRs。
感谢 [ysura](https://www.ysura.com/) 给予我测试 AWS S3 账户的稳定权限。
## 赞助者
我希望可以使 STFPGo 成为一个可持续发展的长期项目,你的 [赞助](https://github.com/sponsors/drakkan) 对我很有帮助!:heart:
感谢我们的赞助者!
[<img src="https://www.7digital.com/wp-content/themes/sevendigital/images/top_logo.png" alt="7digital logo">](https://www.7digital.com/)
## 许可证
GNU AGPLv3

View file

@ -2,11 +2,9 @@
## Supported Versions
Only the current release of the software is actively supported. If you need
help backporting fixes into an older release, feel free to ask.
Only the current release of the software is actively supported.
[Contact us](mailto:support@sftpgo.com) if you need early security patches and enterprise-grade security.
## Reporting a Vulnerability
Email your vulnerability information to SFTPGo's maintainer:
Nicola Murino <nicola.murino@gmail.com>
To report (possible) security issues in SFTPGo, please either send a mail to the [SFTPGo Team](mailto:support@sftpgo.com) or use Github's [private reporting feature](https://github.com/drakkan/sftpgo/security/advisories/new).

View file

@ -1,55 +0,0 @@
package cmd
import (
"os"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/drakkan/sftpgo/v2/acme"
"github.com/drakkan/sftpgo/v2/config"
"github.com/drakkan/sftpgo/v2/logger"
"github.com/drakkan/sftpgo/v2/util"
)
var (
acmeCmd = &cobra.Command{
Use: "acme",
Short: "Obtain TLS certificates from ACME-based CAs like Let's Encrypt",
}
acmeRunCmd = &cobra.Command{
Use: "run",
Short: "Register your account and obtain certificates",
Long: `This command must be run to obtain TLS certificates the first time or every
time you add a new domain to your configuration file.
Certificates are saved in the configured "certs_path".
After this initial step, the certificates are automatically checked and
renewed by the SFTPGo service
`,
Run: func(cmd *cobra.Command, args []string) {
logger.DisableLogger()
logger.EnableConsoleLogger(zerolog.DebugLevel)
configDir = util.CleanDirInput(configDir)
err := config.LoadConfig(configDir, configFile)
if err != nil {
logger.ErrorToConsole("Unable to initialize data provider, config load error: %v", err)
return
}
acmeConfig := config.GetACMEConfig()
err = acmeConfig.Initialize(configDir, false)
if err != nil {
logger.ErrorToConsole("Unable to initialize ACME configuration: %v", err)
}
if err = acme.GetCertificates(); err != nil {
logger.ErrorToConsole("Cannot get certificates: %v", err)
os.Exit(1)
}
},
}
)
func init() {
addConfigFlags(acmeRunCmd)
acmeCmd.AddCommand(acmeRunCmd)
rootCmd.AddCommand(acmeCmd)
}

View file

@ -1,10 +0,0 @@
//go:build !awscontainer
// +build !awscontainer
package cmd
import (
"github.com/spf13/cobra"
)
func addAWSContainerFlags(cmd *cobra.Command) {}

View file

@ -1,12 +0,0 @@
package cmd
import "github.com/spf13/cobra"
var genCmd = &cobra.Command{
Use: "gen",
Short: "A collection of useful generators",
}
func init() {
rootCmd.AddCommand(genCmd)
}

View file

@ -1,10 +0,0 @@
//go:build noportable
// +build noportable
package cmd
import "github.com/drakkan/sftpgo/v2/version"
func init() {
version.AddFeature("-portable")
}

View file

@ -1,35 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/drakkan/sftpgo/v2/service"
)
var (
reloadCmd = &cobra.Command{
Use: "reload",
Short: "Reload the SFTPGo Windows Service sending a \"paramchange\" request",
Run: func(cmd *cobra.Command, args []string) {
s := service.WindowsService{
Service: service.Service{
Shutdown: make(chan bool),
},
}
err := s.Reload()
if err != nil {
fmt.Printf("Error sending reload signal: %v\r\n", err)
os.Exit(1)
} else {
fmt.Printf("Reload signal sent!\r\n")
}
},
}
)
func init() {
serviceCmd.AddCommand(reloadCmd)
}

View file

@ -1,35 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/drakkan/sftpgo/v2/service"
)
var (
rotateLogCmd = &cobra.Command{
Use: "rotatelogs",
Short: "Signal to the running service to rotate the logs",
Run: func(cmd *cobra.Command, args []string) {
s := service.WindowsService{
Service: service.Service{
Shutdown: make(chan bool),
},
}
err := s.RotateLogFile()
if err != nil {
fmt.Printf("Error sending rotate log file signal to the service: %v\r\n", err)
os.Exit(1)
} else {
fmt.Printf("Rotate log file signal sent!\r\n")
}
},
}
)
func init() {
serviceCmd.AddCommand(rotateLogCmd)
}

View file

@ -1,54 +0,0 @@
package cmd
import (
"os"
"github.com/spf13/cobra"
"github.com/drakkan/sftpgo/v2/service"
"github.com/drakkan/sftpgo/v2/util"
)
var (
serveCmd = &cobra.Command{
Use: "serve",
Short: "Start the SFTPGo service",
Long: `To start the SFTPGo with the default values for the command line flags simply
use:
$ sftpgo serve
Please take a look at the usage below to customize the startup options`,
Run: func(cmd *cobra.Command, args []string) {
service := service.Service{
ConfigDir: util.CleanDirInput(configDir),
ConfigFile: configFile,
LogFilePath: logFilePath,
LogMaxSize: logMaxSize,
LogMaxBackups: logMaxBackups,
LogMaxAge: logMaxAge,
LogCompress: logCompress,
LogVerbose: logVerbose,
LogUTCTime: logUTCTime,
LoadDataFrom: loadDataFrom,
LoadDataMode: loadDataMode,
LoadDataQuotaScan: loadDataQuotaScan,
LoadDataClean: loadDataClean,
Shutdown: make(chan bool),
}
if err := service.Start(disableAWSInstallationCode); err == nil {
service.Wait()
if service.Error == nil {
os.Exit(0)
}
}
os.Exit(1)
},
}
)
func init() {
rootCmd.AddCommand(serveCmd)
addServeFlags(serveCmd)
addAWSContainerFlags(serveCmd)
}

View file

@ -1,16 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
var (
serviceCmd = &cobra.Command{
Use: "service",
Short: "Manage the SFTPGo Windows Service",
}
)
func init() {
rootCmd.AddCommand(serviceCmd)
}

View file

@ -1,54 +0,0 @@
package cmd
import (
"os"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/drakkan/sftpgo/v2/config"
"github.com/drakkan/sftpgo/v2/logger"
"github.com/drakkan/sftpgo/v2/smtp"
"github.com/drakkan/sftpgo/v2/util"
)
var (
smtpTestRecipient string
smtpTestCmd = &cobra.Command{
Use: "smtptest",
Short: "Test the SMTP configuration",
Long: `SFTPGo will try to send a test email to the specified recipient.
If the SMTP configuration is correct you should receive this email.`,
Run: func(cmd *cobra.Command, args []string) {
logger.DisableLogger()
logger.EnableConsoleLogger(zerolog.DebugLevel)
configDir = util.CleanDirInput(configDir)
err := config.LoadConfig(configDir, configFile)
if err != nil {
logger.WarnToConsole("Unable to initialize data provider, config load error: %v", err)
os.Exit(1)
}
smtpConfig := config.GetSMTPConfig()
err = smtpConfig.Initialize(configDir)
if err != nil {
logger.ErrorToConsole("unable to initialize SMTP configuration: %v", err)
os.Exit(1)
}
err = smtp.SendEmail(smtpTestRecipient, "SFTPGo - Testing Email Settings", "It appears your SFTPGo email is setup correctly!",
smtp.EmailContentTypeTextPlain)
if err != nil {
logger.WarnToConsole("Error sending email: %v", err)
os.Exit(1)
}
logger.InfoToConsole("No errors were reported while sending an email. Please check your inbox to make sure.")
},
}
)
func init() {
addConfigFlags(smtpTestCmd)
smtpTestCmd.Flags().StringVar(&smtpTestRecipient, "recipient", "", `email address to send the test e-mail to`)
smtpTestCmd.MarkFlagRequired("recipient") //nolint:errcheck
rootCmd.AddCommand(smtpTestCmd)
}

View file

@ -1,52 +0,0 @@
package cmd
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/drakkan/sftpgo/v2/service"
"github.com/drakkan/sftpgo/v2/util"
)
var (
startCmd = &cobra.Command{
Use: "start",
Short: "Start the SFTPGo Windows Service",
Run: func(cmd *cobra.Command, args []string) {
configDir = util.CleanDirInput(configDir)
if !filepath.IsAbs(logFilePath) && util.IsFileInputValid(logFilePath) {
logFilePath = filepath.Join(configDir, logFilePath)
}
s := service.Service{
ConfigDir: configDir,
ConfigFile: configFile,
LogFilePath: logFilePath,
LogMaxSize: logMaxSize,
LogMaxBackups: logMaxBackups,
LogMaxAge: logMaxAge,
LogCompress: logCompress,
LogVerbose: logVerbose,
LogUTCTime: logUTCTime,
Shutdown: make(chan bool),
}
winService := service.WindowsService{
Service: s,
}
err := winService.RunService()
if err != nil {
fmt.Printf("Error starting service: %v\r\n", err)
os.Exit(1)
} else {
fmt.Printf("Service started!\r\n")
}
},
}
)
func init() {
serviceCmd.AddCommand(startCmd)
addServeFlags(startCmd)
}

View file

@ -1,35 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/drakkan/sftpgo/v2/service"
)
var (
statusCmd = &cobra.Command{
Use: "status",
Short: "Retrieve the status for the SFTPGo Windows Service",
Run: func(cmd *cobra.Command, args []string) {
s := service.WindowsService{
Service: service.Service{
Shutdown: make(chan bool),
},
}
status, err := s.Status()
if err != nil {
fmt.Printf("Error querying service status: %v\r\n", err)
os.Exit(1)
} else {
fmt.Printf("Service status: %#v\r\n", status.String())
}
},
}
)
func init() {
serviceCmd.AddCommand(statusCmd)
}

View file

@ -1,35 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/drakkan/sftpgo/v2/service"
)
var (
stopCmd = &cobra.Command{
Use: "stop",
Short: "Stop the SFTPGo Windows Service",
Run: func(cmd *cobra.Command, args []string) {
s := service.WindowsService{
Service: service.Service{
Shutdown: make(chan bool),
},
}
err := s.Stop()
if err != nil {
fmt.Printf("Error stopping service: %v\r\n", err)
os.Exit(1)
} else {
fmt.Printf("Service stopped!\r\n")
}
},
}
)
func init() {
serviceCmd.AddCommand(stopCmd)
}

View file

@ -1,35 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/drakkan/sftpgo/v2/service"
)
var (
uninstallCmd = &cobra.Command{
Use: "uninstall",
Short: "Uninstall the SFTPGo Windows Service",
Run: func(cmd *cobra.Command, args []string) {
s := service.WindowsService{
Service: service.Service{
Shutdown: make(chan bool),
},
}
err := s.Uninstall()
if err != nil {
fmt.Printf("Error removing service: %v\r\n", err)
os.Exit(1)
} else {
fmt.Printf("Service uninstalled\r\n")
}
},
}
)
func init() {
serviceCmd.AddCommand(uninstallCmd)
}

View file

@ -1,100 +0,0 @@
package command
import (
"fmt"
"os"
"strings"
"time"
)
const (
minTimeout = 1
maxTimeout = 300
defaultTimeout = 30
)
var (
config Config
)
// Command define the configuration for a specific commands
type Command struct {
// Path is the command path as defined in the hook configuration
Path string `json:"path" mapstructure:"path"`
// Timeout specifies a time limit, in seconds, for the command execution.
// This value overrides the global timeout if set.
// Do not use variables with the SFTPGO_ prefix to avoid conflicts with env
// vars that SFTPGo sets
Timeout int `json:"timeout" mapstructure:"timeout"`
// Env defines additional environment variable for the commands.
// Each entry is of the form "key=value".
// These values are added to the global environment variables if any
Env []string `json:"env" mapstructure:"env"`
}
// Config defines the configuration for external commands such as
// program based hooks
type Config struct {
// Timeout specifies a global time limit, in seconds, for the external commands execution
Timeout int `json:"timeout" mapstructure:"timeout"`
// Env defines additional environment variable for the commands.
// Each entry is of the form "key=value".
// Do not use variables with the SFTPGO_ prefix to avoid conflicts with env
// vars that SFTPGo sets
Env []string `json:"env" mapstructure:"env"`
// Commands defines configuration for specific commands
Commands []Command `json:"commands" mapstructure:"commands"`
}
func init() {
config = Config{
Timeout: defaultTimeout,
}
}
// Initialize configures commands
func (c Config) Initialize() error {
if c.Timeout < minTimeout || c.Timeout > maxTimeout {
return fmt.Errorf("invalid timeout %v", c.Timeout)
}
for _, env := range c.Env {
if len(strings.Split(env, "=")) != 2 {
return fmt.Errorf("invalid env var %#v", env)
}
}
for idx, cmd := range c.Commands {
if cmd.Path == "" {
return fmt.Errorf("invalid path %#v", cmd.Path)
}
if cmd.Timeout == 0 {
c.Commands[idx].Timeout = c.Timeout
} else {
if cmd.Timeout < minTimeout || cmd.Timeout > maxTimeout {
return fmt.Errorf("invalid timeout %v for command %#v", cmd.Timeout, cmd.Path)
}
}
for _, env := range cmd.Env {
if len(strings.Split(env, "=")) != 2 {
return fmt.Errorf("invalid env var %#v for command %#v", env, cmd.Path)
}
}
}
config = c
return nil
}
// GetConfig returns the configuration for the specified command
func GetConfig(command string) (time.Duration, []string) {
env := os.Environ()
timeout := time.Duration(config.Timeout) * time.Second
env = append(env, config.Env...)
for _, cmd := range config.Commands {
if cmd.Path == command {
timeout = time.Duration(cmd.Timeout) * time.Second
env = append(env, cmd.Env...)
break
}
}
return timeout, env
}

View file

@ -1,105 +0,0 @@
package command
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCommandConfig(t *testing.T) {
require.Equal(t, defaultTimeout, config.Timeout)
cfg := Config{
Timeout: 10,
Env: []string{"a=b"},
}
err := cfg.Initialize()
require.NoError(t, err)
assert.Equal(t, cfg.Timeout, config.Timeout)
assert.Equal(t, cfg.Env, config.Env)
assert.Len(t, cfg.Commands, 0)
timeout, env := GetConfig("cmd")
assert.Equal(t, time.Duration(config.Timeout)*time.Second, timeout)
assert.Contains(t, env, "a=b")
cfg.Commands = []Command{
{
Path: "cmd1",
Timeout: 30,
Env: []string{"c=d"},
},
{
Path: "cmd2",
Timeout: 0,
Env: []string{"e=f"},
},
}
err = cfg.Initialize()
require.NoError(t, err)
assert.Equal(t, cfg.Timeout, config.Timeout)
assert.Equal(t, cfg.Env, config.Env)
if assert.Len(t, config.Commands, 2) {
assert.Equal(t, cfg.Commands[0].Path, config.Commands[0].Path)
assert.Equal(t, cfg.Commands[0].Timeout, config.Commands[0].Timeout)
assert.Equal(t, cfg.Commands[0].Env, config.Commands[0].Env)
assert.Equal(t, cfg.Commands[1].Path, config.Commands[1].Path)
assert.Equal(t, cfg.Timeout, config.Commands[1].Timeout)
assert.Equal(t, cfg.Commands[1].Env, config.Commands[1].Env)
}
timeout, env = GetConfig("cmd1")
assert.Equal(t, time.Duration(config.Commands[0].Timeout)*time.Second, timeout)
assert.Contains(t, env, "a=b")
assert.Contains(t, env, "c=d")
assert.NotContains(t, env, "e=f")
timeout, env = GetConfig("cmd2")
assert.Equal(t, time.Duration(config.Timeout)*time.Second, timeout)
assert.Contains(t, env, "a=b")
assert.NotContains(t, env, "c=d")
assert.Contains(t, env, "e=f")
}
func TestConfigErrors(t *testing.T) {
c := Config{}
err := c.Initialize()
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "invalid timeout")
}
c.Timeout = 10
c.Env = []string{"a"}
err = c.Initialize()
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "invalid env var")
}
c.Env = nil
c.Commands = []Command{
{
Path: "",
},
}
err = c.Initialize()
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "invalid path")
}
c.Commands = []Command{
{
Path: "path",
Timeout: 10000,
},
}
err = c.Initialize()
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "invalid timeout")
}
c.Commands = []Command{
{
Path: "path",
Timeout: 30,
Env: []string{"b"},
},
}
err = c.Initialize()
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "invalid env var")
}
}

View file

@ -1,262 +0,0 @@
package common
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/sftpgo/sdk"
"github.com/sftpgo/sdk/plugin/notifier"
"github.com/drakkan/sftpgo/v2/command"
"github.com/drakkan/sftpgo/v2/dataprovider"
"github.com/drakkan/sftpgo/v2/httpclient"
"github.com/drakkan/sftpgo/v2/logger"
"github.com/drakkan/sftpgo/v2/plugin"
"github.com/drakkan/sftpgo/v2/util"
)
var (
errUnconfiguredAction = errors.New("no hook is configured for this action")
errNoHook = errors.New("unable to execute action, no hook defined")
errUnexpectedHTTResponse = errors.New("unexpected HTTP response code")
)
// ProtocolActions defines the action to execute on file operations and SSH commands
type ProtocolActions struct {
// Valid values are download, upload, pre-delete, delete, rename, ssh_cmd. Empty slice to disable
ExecuteOn []string `json:"execute_on" mapstructure:"execute_on"`
// Actions to be performed synchronously.
// The pre-delete action is always executed synchronously while the other ones are asynchronous.
// Executing an action synchronously means that SFTPGo will not return a result code to the client
// (which is waiting for it) until your hook have completed its execution.
ExecuteSync []string `json:"execute_sync" mapstructure:"execute_sync"`
// Absolute path to an external program or an HTTP URL
Hook string `json:"hook" mapstructure:"hook"`
}
var actionHandler ActionHandler = &defaultActionHandler{}
// InitializeActionHandler lets the user choose an action handler implementation.
//
// Do NOT call this function after application initialization.
func InitializeActionHandler(handler ActionHandler) {
actionHandler = handler
}
func handleUnconfiguredPreAction(operation string) error {
// for pre-delete we execute the internal handling on error, so we must return errUnconfiguredAction.
// Other pre action will deny the operation on error so if we have no configuration we must return
// a nil error
if operation == operationPreDelete {
return errUnconfiguredAction
}
return nil
}
// ExecutePreAction executes a pre-* action and returns the result
func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath string, fileSize int64, openFlags int) error {
var event *notifier.FsEvent
hasNotifiersPlugin := plugin.Handler.HasNotifiers()
hasHook := util.Contains(Config.Actions.ExecuteOn, operation)
if !hasHook && !hasNotifiersPlugin {
return handleUnconfiguredPreAction(operation)
}
event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "",
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, nil)
if hasNotifiersPlugin {
plugin.Handler.NotifyFsEvent(event)
}
if !hasHook {
return handleUnconfiguredPreAction(operation)
}
return actionHandler.Handle(event)
}
// ExecuteActionNotification executes the defined hook, if any, for the specified action
func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtualPath, target, virtualTarget, sshCmd string,
fileSize int64, err error,
) {
hasNotifiersPlugin := plugin.Handler.HasNotifiers()
hasHook := util.Contains(Config.Actions.ExecuteOn, operation)
if !hasHook && !hasNotifiersPlugin {
return
}
notification := newActionNotification(&conn.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd,
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, err)
if hasNotifiersPlugin {
plugin.Handler.NotifyFsEvent(notification)
}
if hasHook {
if util.Contains(Config.Actions.ExecuteSync, operation) {
actionHandler.Handle(notification) //nolint:errcheck
return
}
go actionHandler.Handle(notification) //nolint:errcheck
}
}
// ActionHandler handles a notification for a Protocol Action.
type ActionHandler interface {
Handle(notification *notifier.FsEvent) error
}
func newActionNotification(
user *dataprovider.User,
operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol, ip, sessionID string,
fileSize int64,
openFlags int,
err error,
) *notifier.FsEvent {
var bucket, endpoint string
status := 1
fsConfig := user.GetFsConfigForPath(virtualPath)
switch fsConfig.Provider {
case sdk.S3FilesystemProvider:
bucket = fsConfig.S3Config.Bucket
endpoint = fsConfig.S3Config.Endpoint
case sdk.GCSFilesystemProvider:
bucket = fsConfig.GCSConfig.Bucket
case sdk.AzureBlobFilesystemProvider:
bucket = fsConfig.AzBlobConfig.Container
if fsConfig.AzBlobConfig.Endpoint != "" {
endpoint = fsConfig.AzBlobConfig.Endpoint
}
case sdk.SFTPFilesystemProvider:
endpoint = fsConfig.SFTPConfig.Endpoint
}
if err == ErrQuotaExceeded {
status = 3
} else if err != nil {
status = 2
}
return &notifier.FsEvent{
Action: operation,
Username: user.Username,
Path: filePath,
TargetPath: target,
VirtualPath: virtualPath,
VirtualTargetPath: virtualTarget,
SSHCmd: sshCmd,
FileSize: fileSize,
FsProvider: int(fsConfig.Provider),
Bucket: bucket,
Endpoint: endpoint,
Status: status,
Protocol: protocol,
IP: ip,
SessionID: sessionID,
OpenFlags: openFlags,
Timestamp: time.Now().UnixNano(),
}
}
type defaultActionHandler struct{}
func (h *defaultActionHandler) Handle(event *notifier.FsEvent) error {
if !util.Contains(Config.Actions.ExecuteOn, event.Action) {
return errUnconfiguredAction
}
if Config.Actions.Hook == "" {
logger.Warn(event.Protocol, "", "Unable to send notification, no hook is defined")
return errNoHook
}
if strings.HasPrefix(Config.Actions.Hook, "http") {
return h.handleHTTP(event)
}
return h.handleCommand(event)
}
func (h *defaultActionHandler) handleHTTP(event *notifier.FsEvent) error {
u, err := url.Parse(Config.Actions.Hook)
if err != nil {
logger.Error(event.Protocol, "", "Invalid hook %#v for operation %#v: %v",
Config.Actions.Hook, event.Action, err)
return err
}
startTime := time.Now()
respCode := 0
var b bytes.Buffer
_ = json.NewEncoder(&b).Encode(event)
resp, err := httpclient.RetryablePost(Config.Actions.Hook, "application/json", &b)
if err == nil {
respCode = resp.StatusCode
resp.Body.Close()
if respCode != http.StatusOK {
err = errUnexpectedHTTResponse
}
}
logger.Debug(event.Protocol, "", "notified operation %#v to URL: %v status code: %v, elapsed: %v err: %v",
event.Action, u.Redacted(), respCode, time.Since(startTime), err)
return err
}
func (h *defaultActionHandler) handleCommand(event *notifier.FsEvent) error {
if !filepath.IsAbs(Config.Actions.Hook) {
err := fmt.Errorf("invalid notification command %#v", Config.Actions.Hook)
logger.Warn(event.Protocol, "", "unable to execute notification command: %v", err)
return err
}
timeout, env := command.GetConfig(Config.Actions.Hook)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, Config.Actions.Hook)
cmd.Env = append(env, notificationAsEnvVars(event)...)
startTime := time.Now()
err := cmd.Run()
logger.Debug(event.Protocol, "", "executed command %#v, elapsed: %v, error: %v",
Config.Actions.Hook, time.Since(startTime), err)
return err
}
func notificationAsEnvVars(event *notifier.FsEvent) []string {
return []string{
fmt.Sprintf("SFTPGO_ACTION=%v", event.Action),
fmt.Sprintf("SFTPGO_ACTION_USERNAME=%v", event.Username),
fmt.Sprintf("SFTPGO_ACTION_PATH=%v", event.Path),
fmt.Sprintf("SFTPGO_ACTION_TARGET=%v", event.TargetPath),
fmt.Sprintf("SFTPGO_ACTION_VIRTUAL_PATH=%v", event.VirtualPath),
fmt.Sprintf("SFTPGO_ACTION_VIRTUAL_TARGET=%v", event.VirtualTargetPath),
fmt.Sprintf("SFTPGO_ACTION_SSH_CMD=%v", event.SSHCmd),
fmt.Sprintf("SFTPGO_ACTION_FILE_SIZE=%v", event.FileSize),
fmt.Sprintf("SFTPGO_ACTION_FS_PROVIDER=%v", event.FsProvider),
fmt.Sprintf("SFTPGO_ACTION_BUCKET=%v", event.Bucket),
fmt.Sprintf("SFTPGO_ACTION_ENDPOINT=%v", event.Endpoint),
fmt.Sprintf("SFTPGO_ACTION_STATUS=%v", event.Status),
fmt.Sprintf("SFTPGO_ACTION_PROTOCOL=%v", event.Protocol),
fmt.Sprintf("SFTPGO_ACTION_IP=%v", event.IP),
fmt.Sprintf("SFTPGO_ACTION_SESSION_ID=%v", event.SessionID),
fmt.Sprintf("SFTPGO_ACTION_OPEN_FLAGS=%v", event.OpenFlags),
fmt.Sprintf("SFTPGO_ACTION_TIMESTAMP=%v", event.Timestamp),
}
}

View file

@ -1,51 +0,0 @@
package common
import (
"sync"
"sync/atomic"
"github.com/drakkan/sftpgo/v2/logger"
)
// clienstMap is a struct containing the map of the connected clients
type clientsMap struct {
totalConnections int32
mu sync.RWMutex
clients map[string]int
}
func (c *clientsMap) add(source string) {
atomic.AddInt32(&c.totalConnections, 1)
c.mu.Lock()
defer c.mu.Unlock()
c.clients[source]++
}
func (c *clientsMap) remove(source string) {
c.mu.Lock()
defer c.mu.Unlock()
if val, ok := c.clients[source]; ok {
atomic.AddInt32(&c.totalConnections, -1)
c.clients[source]--
if val > 1 {
return
}
delete(c.clients, source)
} else {
logger.Warn(logSender, "", "cannot remove client %v it is not mapped", source)
}
}
func (c *clientsMap) getTotal() int32 {
return atomic.LoadInt32(&c.totalConnections)
}
func (c *clientsMap) getTotalFrom(source string) int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.clients[source]
}

File diff suppressed because it is too large Load diff

View file

@ -1,479 +0,0 @@
package common
import (
"os"
"path"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/pkg/sftp"
"github.com/rs/xid"
"github.com/sftpgo/sdk"
"github.com/stretchr/testify/assert"
"github.com/drakkan/sftpgo/v2/dataprovider"
"github.com/drakkan/sftpgo/v2/kms"
"github.com/drakkan/sftpgo/v2/util"
"github.com/drakkan/sftpgo/v2/vfs"
)
// MockOsFs mockable OsFs
type MockOsFs struct {
vfs.Fs
hasVirtualFolders bool
}
// Name returns the name for the Fs implementation
func (fs *MockOsFs) Name() string {
return "mockOsFs"
}
// HasVirtualFolders returns true if folders are emulated
func (fs *MockOsFs) HasVirtualFolders() bool {
return fs.hasVirtualFolders
}
func (fs *MockOsFs) IsUploadResumeSupported() bool {
return !fs.hasVirtualFolders
}
func (fs *MockOsFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error {
return vfs.ErrVfsUnsupported
}
func newMockOsFs(hasVirtualFolders bool, connectionID, rootDir string) vfs.Fs {
return &MockOsFs{
Fs: vfs.NewOsFs(connectionID, rootDir, ""),
hasVirtualFolders: hasVirtualFolders,
}
}
func TestRemoveErrors(t *testing.T) {
mappedPath := filepath.Join(os.TempDir(), "map")
homePath := filepath.Join(os.TempDir(), "home")
user := dataprovider.User{
BaseUser: sdk.BaseUser{
Username: "remove_errors_user",
HomeDir: homePath,
},
VirtualFolders: []vfs.VirtualFolder{
{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: filepath.Base(mappedPath),
MappedPath: mappedPath,
},
VirtualPath: "/virtualpath",
},
},
}
user.Permissions = make(map[string][]string)
user.Permissions["/"] = []string{dataprovider.PermAny}
fs := vfs.NewOsFs("", os.TempDir(), "")
conn := NewBaseConnection("", ProtocolFTP, "", "", user)
err := conn.IsRemoveDirAllowed(fs, mappedPath, "/virtualpath1")
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "permission denied")
}
err = conn.RemoveFile(fs, filepath.Join(homePath, "missing_file"), "/missing_file",
vfs.NewFileInfo("info", false, 100, time.Now(), false))
assert.Error(t, err)
}
func TestSetStatMode(t *testing.T) {
oldSetStatMode := Config.SetstatMode
Config.SetstatMode = 1
fakePath := "fake path"
user := dataprovider.User{
BaseUser: sdk.BaseUser{
HomeDir: os.TempDir(),
},
}
user.Permissions = make(map[string][]string)
user.Permissions["/"] = []string{dataprovider.PermAny}
fs := newMockOsFs(true, "", user.GetHomeDir())
conn := NewBaseConnection("", ProtocolWebDAV, "", "", user)
err := conn.handleChmod(fs, fakePath, fakePath, nil)
assert.NoError(t, err)
err = conn.handleChown(fs, fakePath, fakePath, nil)
assert.NoError(t, err)
err = conn.handleChtimes(fs, fakePath, fakePath, nil)
assert.NoError(t, err)
Config.SetstatMode = 2
err = conn.handleChmod(fs, fakePath, fakePath, nil)
assert.NoError(t, err)
err = conn.handleChtimes(fs, fakePath, fakePath, &StatAttributes{
Atime: time.Now(),
Mtime: time.Now(),
})
assert.NoError(t, err)
Config.SetstatMode = oldSetStatMode
}
func TestRecursiveRenameWalkError(t *testing.T) {
fs := vfs.NewOsFs("", os.TempDir(), "")
conn := NewBaseConnection("", ProtocolWebDAV, "", "", dataprovider.User{})
err := conn.checkRecursiveRenameDirPermissions(fs, fs, "/source", "/target")
assert.ErrorIs(t, err, os.ErrNotExist)
}
func TestCrossRenameFsErrors(t *testing.T) {
fs := vfs.NewOsFs("", os.TempDir(), "")
conn := NewBaseConnection("", ProtocolWebDAV, "", "", dataprovider.User{})
res := conn.hasSpaceForCrossRename(fs, vfs.QuotaCheckResult{}, 1, "missingsource")
assert.False(t, res)
if runtime.GOOS != osWindows {
dirPath := filepath.Join(os.TempDir(), "d")
err := os.Mkdir(dirPath, os.ModePerm)
assert.NoError(t, err)
err = os.Chmod(dirPath, 0001)
assert.NoError(t, err)
res = conn.hasSpaceForCrossRename(fs, vfs.QuotaCheckResult{}, 1, dirPath)
assert.False(t, res)
err = os.Chmod(dirPath, os.ModePerm)
assert.NoError(t, err)
err = os.Remove(dirPath)
assert.NoError(t, err)
}
}
func TestRenameVirtualFolders(t *testing.T) {
vdir := "/avdir"
u := dataprovider.User{}
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: "name",
MappedPath: "mappedPath",
},
VirtualPath: vdir,
})
fs := vfs.NewOsFs("", os.TempDir(), "")
conn := NewBaseConnection("", ProtocolFTP, "", "", u)
res := conn.isRenamePermitted(fs, fs, "source", "target", vdir, "vdirtarget", nil)
assert.False(t, res)
}
func TestRenamePerms(t *testing.T) {
src := "source"
target := "target"
sub := "/sub"
subTarget := sub + "/target"
u := dataprovider.User{}
u.Permissions = map[string][]string{}
u.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermUpload, dataprovider.PermCreateSymlinks,
dataprovider.PermDeleteFiles}
conn := NewBaseConnection("", ProtocolSFTP, "", "", u)
assert.False(t, conn.hasRenamePerms(src, target, nil))
u.Permissions["/"] = []string{dataprovider.PermRename}
assert.True(t, conn.hasRenamePerms(src, target, nil))
u.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermUpload, dataprovider.PermDeleteFiles,
dataprovider.PermDeleteDirs}
assert.False(t, conn.hasRenamePerms(src, target, nil))
info := vfs.NewFileInfo(src, true, 0, time.Now(), false)
u.Permissions["/"] = []string{dataprovider.PermRenameFiles}
assert.False(t, conn.hasRenamePerms(src, target, info))
u.Permissions["/"] = []string{dataprovider.PermRenameDirs}
assert.True(t, conn.hasRenamePerms(src, target, info))
u.Permissions["/"] = []string{dataprovider.PermRename}
assert.True(t, conn.hasRenamePerms(src, target, info))
u.Permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDeleteDirs}
assert.False(t, conn.hasRenamePerms(src, target, info))
// test with different permissions between source and target
u.Permissions["/"] = []string{dataprovider.PermRename}
u.Permissions[sub] = []string{dataprovider.PermRenameFiles}
assert.False(t, conn.hasRenamePerms(src, subTarget, info))
u.Permissions[sub] = []string{dataprovider.PermRenameDirs}
assert.True(t, conn.hasRenamePerms(src, subTarget, info))
// test files
info = vfs.NewFileInfo(src, false, 0, time.Now(), false)
u.Permissions["/"] = []string{dataprovider.PermRenameDirs}
assert.False(t, conn.hasRenamePerms(src, target, info))
u.Permissions["/"] = []string{dataprovider.PermRenameFiles}
assert.True(t, conn.hasRenamePerms(src, target, info))
u.Permissions["/"] = []string{dataprovider.PermRename}
assert.True(t, conn.hasRenamePerms(src, target, info))
// test with different permissions between source and target
u.Permissions["/"] = []string{dataprovider.PermRename}
u.Permissions[sub] = []string{dataprovider.PermRenameDirs}
assert.False(t, conn.hasRenamePerms(src, subTarget, info))
u.Permissions[sub] = []string{dataprovider.PermRenameFiles}
assert.True(t, conn.hasRenamePerms(src, subTarget, info))
}
func TestUpdateQuotaAfterRename(t *testing.T) {
user := dataprovider.User{
BaseUser: sdk.BaseUser{
Username: userTestUsername,
HomeDir: filepath.Join(os.TempDir(), "home"),
},
}
mappedPath := filepath.Join(os.TempDir(), "vdir")
user.Permissions = make(map[string][]string)
user.Permissions["/"] = []string{dataprovider.PermAny}
user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
MappedPath: mappedPath,
},
VirtualPath: "/vdir",
QuotaFiles: -1,
QuotaSize: -1,
})
user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
MappedPath: mappedPath,
},
VirtualPath: "/vdir1",
QuotaFiles: -1,
QuotaSize: -1,
})
err := os.MkdirAll(user.GetHomeDir(), os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath, os.ModePerm)
assert.NoError(t, err)
fs, err := user.GetFilesystem("id")
assert.NoError(t, err)
c := NewBaseConnection("", ProtocolSFTP, "", "", user)
request := sftp.NewRequest("Rename", "/testfile")
if runtime.GOOS != osWindows {
request.Filepath = "/dir"
request.Target = path.Join("/vdir", "dir")
testDirPath := filepath.Join(mappedPath, "dir")
err := os.MkdirAll(testDirPath, os.ModePerm)
assert.NoError(t, err)
err = os.Chmod(testDirPath, 0001)
assert.NoError(t, err)
err = c.updateQuotaAfterRename(fs, request.Filepath, request.Target, testDirPath, 0)
assert.Error(t, err)
err = os.Chmod(testDirPath, os.ModePerm)
assert.NoError(t, err)
}
testFile1 := "/testfile1"
request.Target = testFile1
request.Filepath = path.Join("/vdir", "file")
err = c.updateQuotaAfterRename(fs, request.Filepath, request.Target, filepath.Join(mappedPath, "file"), 0)
assert.Error(t, err)
err = os.WriteFile(filepath.Join(mappedPath, "file"), []byte("test content"), os.ModePerm)
assert.NoError(t, err)
request.Filepath = testFile1
request.Target = path.Join("/vdir", "file")
err = c.updateQuotaAfterRename(fs, request.Filepath, request.Target, filepath.Join(mappedPath, "file"), 12)
assert.NoError(t, err)
err = os.WriteFile(filepath.Join(user.GetHomeDir(), "testfile1"), []byte("test content"), os.ModePerm)
assert.NoError(t, err)
request.Target = testFile1
request.Filepath = path.Join("/vdir", "file")
err = c.updateQuotaAfterRename(fs, request.Filepath, request.Target, filepath.Join(mappedPath, "file"), 12)
assert.NoError(t, err)
request.Target = path.Join("/vdir1", "file")
request.Filepath = path.Join("/vdir", "file")
err = c.updateQuotaAfterRename(fs, request.Filepath, request.Target, filepath.Join(mappedPath, "file"), 12)
assert.NoError(t, err)
err = os.RemoveAll(mappedPath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestErrorsMapping(t *testing.T) {
fs := vfs.NewOsFs("", os.TempDir(), "")
conn := NewBaseConnection("", ProtocolSFTP, "", "", dataprovider.User{BaseUser: sdk.BaseUser{HomeDir: os.TempDir()}})
osErrorsProtocols := []string{ProtocolWebDAV, ProtocolFTP, ProtocolHTTP, ProtocolHTTPShare,
ProtocolDataRetention, ProtocolOIDC}
for _, protocol := range supportedProtocols {
conn.SetProtocol(protocol)
err := conn.GetFsError(fs, os.ErrNotExist)
if protocol == ProtocolSFTP {
assert.ErrorIs(t, err, sftp.ErrSSHFxNoSuchFile)
} else if util.Contains(osErrorsProtocols, protocol) {
assert.EqualError(t, err, os.ErrNotExist.Error())
} else {
assert.EqualError(t, err, ErrNotExist.Error())
}
err = conn.GetFsError(fs, os.ErrPermission)
if protocol == ProtocolSFTP {
assert.EqualError(t, err, sftp.ErrSSHFxPermissionDenied.Error())
} else {
assert.EqualError(t, err, ErrPermissionDenied.Error())
}
err = conn.GetFsError(fs, os.ErrClosed)
if protocol == ProtocolSFTP {
assert.ErrorIs(t, err, sftp.ErrSSHFxFailure)
assert.Contains(t, err.Error(), os.ErrClosed.Error())
} else {
assert.EqualError(t, err, ErrGenericFailure.Error())
}
err = conn.GetFsError(fs, ErrPermissionDenied)
if protocol == ProtocolSFTP {
assert.ErrorIs(t, err, sftp.ErrSSHFxFailure)
assert.Contains(t, err.Error(), ErrPermissionDenied.Error())
} else {
assert.EqualError(t, err, ErrPermissionDenied.Error())
}
err = conn.GetFsError(fs, vfs.ErrVfsUnsupported)
if protocol == ProtocolSFTP {
assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error())
} else {
assert.EqualError(t, err, ErrOpUnsupported.Error())
}
err = conn.GetFsError(fs, vfs.ErrStorageSizeUnavailable)
if protocol == ProtocolSFTP {
assert.ErrorIs(t, err, sftp.ErrSSHFxOpUnsupported)
assert.Contains(t, err.Error(), vfs.ErrStorageSizeUnavailable.Error())
} else {
assert.EqualError(t, err, vfs.ErrStorageSizeUnavailable.Error())
}
err = conn.GetQuotaExceededError()
assert.True(t, conn.IsQuotaExceededError(err))
err = conn.GetReadQuotaExceededError()
if protocol == ProtocolSFTP {
assert.ErrorIs(t, err, sftp.ErrSSHFxFailure)
assert.Contains(t, err.Error(), ErrReadQuotaExceeded.Error())
} else {
assert.ErrorIs(t, err, ErrReadQuotaExceeded)
}
err = conn.GetNotExistError()
assert.True(t, conn.IsNotExistError(err))
err = conn.GetFsError(fs, nil)
assert.NoError(t, err)
err = conn.GetOpUnsupportedError()
if protocol == ProtocolSFTP {
assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error())
} else {
assert.EqualError(t, err, ErrOpUnsupported.Error())
}
}
}
func TestMaxWriteSize(t *testing.T) {
permissions := make(map[string][]string)
permissions["/"] = []string{dataprovider.PermAny}
user := dataprovider.User{
BaseUser: sdk.BaseUser{
Username: userTestUsername,
Permissions: permissions,
HomeDir: filepath.Clean(os.TempDir()),
},
}
fs, err := user.GetFilesystem("123")
assert.NoError(t, err)
conn := NewBaseConnection("", ProtocolFTP, "", "", user)
quotaResult := vfs.QuotaCheckResult{
HasSpace: true,
}
size, err := conn.GetMaxWriteSize(quotaResult, false, 0, fs.IsUploadResumeSupported())
assert.NoError(t, err)
assert.Equal(t, int64(0), size)
conn.User.Filters.MaxUploadFileSize = 100
size, err = conn.GetMaxWriteSize(quotaResult, false, 0, fs.IsUploadResumeSupported())
assert.NoError(t, err)
assert.Equal(t, int64(100), size)
quotaResult.QuotaSize = 1000
size, err = conn.GetMaxWriteSize(quotaResult, false, 50, fs.IsUploadResumeSupported())
assert.NoError(t, err)
assert.Equal(t, int64(100), size)
quotaResult.QuotaSize = 1000
quotaResult.UsedSize = 990
size, err = conn.GetMaxWriteSize(quotaResult, false, 50, fs.IsUploadResumeSupported())
assert.NoError(t, err)
assert.Equal(t, int64(60), size)
quotaResult.QuotaSize = 0
quotaResult.UsedSize = 0
size, err = conn.GetMaxWriteSize(quotaResult, true, 100, fs.IsUploadResumeSupported())
assert.True(t, conn.IsQuotaExceededError(err))
assert.Equal(t, int64(0), size)
size, err = conn.GetMaxWriteSize(quotaResult, true, 10, fs.IsUploadResumeSupported())
assert.NoError(t, err)
assert.Equal(t, int64(90), size)
fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir())
size, err = conn.GetMaxWriteSize(quotaResult, true, 100, fs.IsUploadResumeSupported())
assert.EqualError(t, err, ErrOpUnsupported.Error())
assert.Equal(t, int64(0), size)
}
func TestCheckParentDirsErrors(t *testing.T) {
permissions := make(map[string][]string)
permissions["/"] = []string{dataprovider.PermAny}
user := dataprovider.User{
BaseUser: sdk.BaseUser{
Username: userTestUsername,
Permissions: permissions,
HomeDir: filepath.Clean(os.TempDir()),
},
FsConfig: vfs.Filesystem{
Provider: sdk.CryptedFilesystemProvider,
},
}
c := NewBaseConnection(xid.New().String(), ProtocolSFTP, "", "", user)
err := c.CheckParentDirs("/a/dir")
assert.Error(t, err)
user.FsConfig.Provider = sdk.LocalFilesystemProvider
user.VirtualFolders = nil
user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
FsConfig: vfs.Filesystem{
Provider: sdk.CryptedFilesystemProvider,
},
},
VirtualPath: "/vdir",
})
user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
MappedPath: filepath.Clean(os.TempDir()),
},
VirtualPath: "/vdir/sub",
})
c = NewBaseConnection(xid.New().String(), ProtocolSFTP, "", "", user)
err = c.CheckParentDirs("/vdir/sub/dir")
assert.Error(t, err)
user = dataprovider.User{
BaseUser: sdk.BaseUser{
Username: userTestUsername,
Permissions: permissions,
HomeDir: filepath.Clean(os.TempDir()),
},
FsConfig: vfs.Filesystem{
Provider: sdk.S3FilesystemProvider,
S3Config: vfs.S3FsConfig{
BaseS3FsConfig: sdk.BaseS3FsConfig{
Bucket: "buck",
Region: "us-east-1",
AccessKey: "key",
},
AccessSecret: kms.NewPlainSecret("s3secret"),
},
},
}
c = NewBaseConnection(xid.New().String(), ProtocolSFTP, "", "", user)
err = c.CheckParentDirs("/a/dir")
assert.NoError(t, err)
user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
MappedPath: filepath.Clean(os.TempDir()),
},
VirtualPath: "/local/dir",
})
c = NewBaseConnection(xid.New().String(), ProtocolSFTP, "", "", user)
err = c.CheckParentDirs("/local/dir/sub-dir")
assert.NoError(t, err)
err = os.RemoveAll(filepath.Join(os.TempDir(), "sub-dir"))
assert.NoError(t, err)
}

View file

@ -1,328 +0,0 @@
package common
import (
"encoding/json"
"fmt"
"net"
"os"
"strings"
"sync"
"time"
"github.com/yl2chen/cidranger"
"github.com/drakkan/sftpgo/v2/dataprovider"
"github.com/drakkan/sftpgo/v2/logger"
"github.com/drakkan/sftpgo/v2/util"
)
// HostEvent is the enumerable for the supported host events
type HostEvent int
// Supported host events
const (
HostEventLoginFailed HostEvent = iota
HostEventUserNotFound
HostEventNoLoginTried
HostEventLimitExceeded
)
// Supported defender drivers
const (
DefenderDriverMemory = "memory"
DefenderDriverProvider = "provider"
)
var (
supportedDefenderDrivers = []string{DefenderDriverMemory, DefenderDriverProvider}
)
// Defender defines the interface that a defender must implements
type Defender interface {
GetHosts() ([]dataprovider.DefenderEntry, error)
GetHost(ip string) (dataprovider.DefenderEntry, error)
AddEvent(ip string, event HostEvent)
IsBanned(ip string) bool
GetBanTime(ip string) (*time.Time, error)
GetScore(ip string) (int, error)
DeleteHost(ip string) bool
Reload() error
}
// DefenderConfig defines the "defender" configuration
type DefenderConfig struct {
// Set to true to enable the defender
Enabled bool `json:"enabled" mapstructure:"enabled"`
// Defender implementation to use, we support "memory" and "provider".
// Using "provider" as driver you can share the defender events among
// multiple SFTPGo instances. For a single instance "memory" provider will
// be much faster
Driver string `json:"driver" mapstructure:"driver"`
// BanTime is the number of minutes that a host is banned
BanTime int `json:"ban_time" mapstructure:"ban_time"`
// Percentage increase of the ban time if a banned host tries to connect again
BanTimeIncrement int `json:"ban_time_increment" mapstructure:"ban_time_increment"`
// Threshold value for banning a client
Threshold int `json:"threshold" mapstructure:"threshold"`
// Score for invalid login attempts, eg. non-existent user accounts or
// client disconnected for inactivity without authentication attempts
ScoreInvalid int `json:"score_invalid" mapstructure:"score_invalid"`
// Score for valid login attempts, eg. user accounts that exist
ScoreValid int `json:"score_valid" mapstructure:"score_valid"`
// Score for limit exceeded events, generated from the rate limiters or for max connections
// per-host exceeded
ScoreLimitExceeded int `json:"score_limit_exceeded" mapstructure:"score_limit_exceeded"`
// Defines the time window, in minutes, for tracking client errors.
// A host is banned if it has exceeded the defined threshold during
// the last observation time minutes
ObservationTime int `json:"observation_time" mapstructure:"observation_time"`
// The number of banned IPs and host scores kept in memory will vary between the
// soft and hard limit for the "memory" driver. For the "provider" driver the
// soft limit is ignored and the hard limit is used to limit the number of entries
// to return when you request for the entire host list from the defender
EntriesSoftLimit int `json:"entries_soft_limit" mapstructure:"entries_soft_limit"`
EntriesHardLimit int `json:"entries_hard_limit" mapstructure:"entries_hard_limit"`
// Path to a file containing a list of IP addresses and/or networks to never ban
SafeListFile string `json:"safelist_file" mapstructure:"safelist_file"`
// Path to a file containing a list of IP addresses and/or networks to always ban
BlockListFile string `json:"blocklist_file" mapstructure:"blocklist_file"`
// List of IP addresses and/or networks to never ban.
// For large lists prefer SafeListFile
SafeList []string `json:"safelist" mapstructure:"safelist"`
// List of IP addresses and/or networks to always ban.
// For large lists prefer BlockListFile
BlockList []string `json:"blocklist" mapstructure:"blocklist"`
}
type baseDefender struct {
config *DefenderConfig
sync.RWMutex
safeList *HostList
blockList *HostList
}
// Reload reloads block and safe lists
func (d *baseDefender) Reload() error {
blockList, err := loadHostListFromFile(d.config.BlockListFile)
if err != nil {
return err
}
blockList = addEntriesToList(d.config.BlockList, blockList, "blocklist")
d.Lock()
d.blockList = blockList
d.Unlock()
safeList, err := loadHostListFromFile(d.config.SafeListFile)
if err != nil {
return err
}
safeList = addEntriesToList(d.config.SafeList, safeList, "safelist")
d.Lock()
d.safeList = safeList
d.Unlock()
return nil
}
func (d *baseDefender) isBanned(ip string) bool {
if d.blockList != nil && d.blockList.isListed(ip) {
// permanent ban
return true
}
return false
}
func (d *baseDefender) getScore(event HostEvent) int {
var score int
switch event {
case HostEventLoginFailed:
score = d.config.ScoreValid
case HostEventLimitExceeded:
score = d.config.ScoreLimitExceeded
case HostEventUserNotFound, HostEventNoLoginTried:
score = d.config.ScoreInvalid
}
return score
}
// HostListFile defines the structure expected for safe/block list files
type HostListFile struct {
IPAddresses []string `json:"addresses"`
CIDRNetworks []string `json:"networks"`
}
// HostList defines the structure used to keep the HostListFile in memory
type HostList struct {
IPAddresses map[string]bool
Ranges cidranger.Ranger
}
func (h *HostList) isListed(ip string) bool {
if _, ok := h.IPAddresses[ip]; ok {
return true
}
ok, err := h.Ranges.Contains(net.ParseIP(ip))
if err != nil {
return false
}
return ok
}
type hostEvent struct {
dateTime time.Time
score int
}
type hostScore struct {
TotalScore int
Events []hostEvent
}
// validate returns an error if the configuration is invalid
func (c *DefenderConfig) validate() error {
if !c.Enabled {
return nil
}
if c.ScoreInvalid >= c.Threshold {
return fmt.Errorf("score_invalid %v cannot be greater than threshold %v", c.ScoreInvalid, c.Threshold)
}
if c.ScoreValid >= c.Threshold {
return fmt.Errorf("score_valid %v cannot be greater than threshold %v", c.ScoreValid, c.Threshold)
}
if c.ScoreLimitExceeded >= c.Threshold {
return fmt.Errorf("score_limit_exceeded %v cannot be greater than threshold %v", c.ScoreLimitExceeded, c.Threshold)
}
if c.BanTime <= 0 {
return fmt.Errorf("invalid ban_time %v", c.BanTime)
}
if c.BanTimeIncrement <= 0 {
return fmt.Errorf("invalid ban_time_increment %v", c.BanTimeIncrement)
}
if c.ObservationTime <= 0 {
return fmt.Errorf("invalid observation_time %v", c.ObservationTime)
}
if c.EntriesSoftLimit <= 0 {
return fmt.Errorf("invalid entries_soft_limit %v", c.EntriesSoftLimit)
}
if c.EntriesHardLimit <= c.EntriesSoftLimit {
return fmt.Errorf("invalid entries_hard_limit %v must be > %v", c.EntriesHardLimit, c.EntriesSoftLimit)
}
return nil
}
func loadHostListFromFile(name string) (*HostList, error) {
if name == "" {
return nil, nil
}
if !util.IsFileInputValid(name) {
return nil, fmt.Errorf("invalid host list file name %#v", name)
}
info, err := os.Stat(name)
if err != nil {
return nil, err
}
// opinionated max size, you should avoid big host lists
if info.Size() > 1048576*5 { // 5MB
return nil, fmt.Errorf("host list file %#v is too big: %v bytes", name, info.Size())
}
content, err := os.ReadFile(name)
if err != nil {
return nil, fmt.Errorf("unable to read input file %#v: %v", name, err)
}
var hostList HostListFile
err = json.Unmarshal(content, &hostList)
if err != nil {
return nil, err
}
if len(hostList.CIDRNetworks) > 0 || len(hostList.IPAddresses) > 0 {
result := &HostList{
IPAddresses: make(map[string]bool),
Ranges: cidranger.NewPCTrieRanger(),
}
ipCount := 0
cdrCount := 0
for _, ip := range hostList.IPAddresses {
if net.ParseIP(ip) == nil {
logger.Warn(logSender, "", "unable to parse IP %#v", ip)
continue
}
result.IPAddresses[ip] = true
ipCount++
}
for _, cidrNet := range hostList.CIDRNetworks {
_, network, err := net.ParseCIDR(cidrNet)
if err != nil {
logger.Warn(logSender, "", "unable to parse CIDR network %#v: %v", cidrNet, err)
continue
}
err = result.Ranges.Insert(cidranger.NewBasicRangerEntry(*network))
if err == nil {
cdrCount++
}
}
logger.Info(logSender, "", "list %#v loaded, ip addresses loaded: %v/%v networks loaded: %v/%v",
name, ipCount, len(hostList.IPAddresses), cdrCount, len(hostList.CIDRNetworks))
return result, nil
}
return nil, nil
}
func addEntriesToList(entries []string, hostList *HostList, listName string) *HostList {
if len(entries) == 0 {
return hostList
}
if hostList == nil {
hostList = &HostList{
IPAddresses: make(map[string]bool),
Ranges: cidranger.NewPCTrieRanger(),
}
}
ipCount := 0
ipLoaded := 0
cdrCount := 0
cdrLoaded := 0
for _, entry := range entries {
entry = strings.TrimSpace(entry)
if strings.LastIndex(entry, "/") > 0 {
cdrCount++
_, network, err := net.ParseCIDR(entry)
if err != nil {
logger.Warn(logSender, "", "unable to parse CIDR network %#v: %v", entry, err)
continue
}
err = hostList.Ranges.Insert(cidranger.NewBasicRangerEntry(*network))
if err == nil {
cdrLoaded++
}
} else {
ipCount++
if net.ParseIP(entry) == nil {
logger.Warn(logSender, "", "unable to parse IP %#v", entry)
continue
}
hostList.IPAddresses[entry] = true
ipLoaded++
}
}
logger.Info(logSender, "", "%s from config loaded, ip addresses loaded: %v/%v networks loaded: %v/%v",
listName, ipLoaded, ipCount, cdrLoaded, cdrCount)
return hostList
}

File diff suppressed because it is too large Load diff

View file

@ -1,447 +0,0 @@
package common
import (
"crypto/tls"
"crypto/x509"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
const (
serverCert = `-----BEGIN CERTIFICATE-----
MIIEIDCCAgigAwIBAgIRAPOR9zTkX35vSdeyGpF8Rn8wDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMjU1WhcNMjIwNzAyMjEz
MDUxWjARMQ8wDQYDVQQDEwZzZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQCte0PJhCTNqTiqdwk/s4JanKIMKUVWr2u94a+JYy5gJ9xYXrQ49SeN
m+fwhTAOqctP5zNVkFqxlBytJZg3pqCKqRoOOl1qVgL3F3o7JdhZGi67aw8QMLPx
tLPpYWnnrlUQoXRJdTlqkDqO8lOZl9HO5oZeidPZ7r5BVD6ZiujAC6Zg0jIc+EPt
qhaUJ1CStoAeRf1rNWKmDsLv5hEaDWoaHF9sNVzDQg6atZ3ici00qQj+uvEZo8mL
k6egg3rqsTv9ml2qlrRgFumt99J60hTt3tuQaAruHY80O9nGy3SCXC11daa7gszH
ElCRvhUVoOxRtB54YBEtJ0gEpFnTO9J1AgMBAAGjcTBvMA4GA1UdDwEB/wQEAwID
uDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFAgDXwPV
nhztNz+H20iNWgoIx8adMB8GA1UdIwQYMBaAFO1yCNAGr/zQTJIi8lw3w5OiuBvM
MA0GCSqGSIb3DQEBCwUAA4ICAQCR5kgIb4vAtrtsXD24n6RtU1yIXHPLNmDStVrH
uaMYNnHlLhRlQFCjHhjWvZ89FQC7FeNOITc3FpibJySyw7JfnsyEOGxEbcAS4uLB
2pdAiJPqdQtxIVcyi5vu53m1T5tm0sy8sBrGxU466aDQ8VGqjcjfTwNIyoFMd3p/
ezFRvg2BudwU9hqApgfHfLi4WCuI3hLO2tbmgDinyH0HI0YYNNweGpiBYbTLF4Tx
H6vHgD9USMZeu4+HX0IIsBiHQD7TTIe5ceREkPcNPd5qTpIvT3zKQ/KwwT90/zjP
aWmz6pLxBfjRu7MY/bDfxfRUqsrLYJCVBoaDVRWR9rhiPIFkC5JzoWD/4hdj2iis
N0+OOaJ77L+/ArFprE+7Fu3cSdYlfiNjV8R5kE29cAxKLI92CjAiTKrEuxKcQPKO
+taWNKIYYjEDZwVnzlkTIl007X0RBuzu9gh4w5NwJdt8ZOJAp0JV0Cq+UvG+FC/v
lYk82E6j1HKhf4CXmrjsrD1Fyu41mpVFOpa2ATiFGvms913MkXuyO8g99IllmDw1
D7/PN4Qe9N6Zm7yoKZM0IUw2v+SUMIdOAZ7dptO9ZjtYOfiAIYN3jM8R4JYgPiuD
DGSM9LJBJxCxI/DiO1y1Z3n9TcdDQYut8Gqdi/aYXw2YeqyHXosX5Od3vcK/O5zC
pOJTYQ==
-----END CERTIFICATE-----`
serverKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEArXtDyYQkzak4qncJP7OCWpyiDClFVq9rveGviWMuYCfcWF60
OPUnjZvn8IUwDqnLT+czVZBasZQcrSWYN6agiqkaDjpdalYC9xd6OyXYWRouu2sP
EDCz8bSz6WFp565VEKF0SXU5apA6jvJTmZfRzuaGXonT2e6+QVQ+mYrowAumYNIy
HPhD7aoWlCdQkraAHkX9azVipg7C7+YRGg1qGhxfbDVcw0IOmrWd4nItNKkI/rrx
GaPJi5OnoIN66rE7/Zpdqpa0YBbprffSetIU7d7bkGgK7h2PNDvZxst0glwtdXWm
u4LMxxJQkb4VFaDsUbQeeGARLSdIBKRZ0zvSdQIDAQABAoIBAF4sI8goq7HYwqIG
rEagM4rsrCrd3H4KC/qvoJJ7/JjGCp8OCddBfY8pquat5kCPe4aMgxlXm2P6evaj
CdZr5Ypf8Xz3we4PctyfKgMhsCfuRqAGpc6sIYJ8DY4LC2pxAExe2LlnoRtv39np
QeiGuaYPDbIUL6SGLVFZYgIHngFhbDYfL83q3Cb/PnivUGFvUVQCfRBUKO2d8KYq
TrVB5BWD2GrHor24ApQmci1OOqfbkIevkK6bk8HUfSZiZGI9LUQiPHMxi5k2x43J
nIwhZnW2N28dorKnWHg2vh7viGvinVRZ3MEyX150oCw/L6SYM4fqR6t2ZSBgNQHT
ZNoDtwECgYEA4lXMgtYqKuSlZ3TKfxAj03tJ/gbRdKcUCEGXEbdpY70tTu6KESZS
etid4Ut/sWEoPTJsgYiGbgJl571t1O8oR1UZYgh9hBGHLV6UEIt9n2PbExhE2vL3
SB7+LfO+tMvM4qKUBN+uy4GpU0NiyEEecw4x4S7MRSyHFRIDR7B6RV0CgYEAxDgS
mDaNUfSdfB5mXekLUJAwqeKRdL9RjXYaHbnoZ5kIwQ73tFikRwyTsLQwMhjE1l3z
MItTzIAyTf/BlK3dsp6bHTaT7hXIjHBsuKATN5qAuUpzTrg9+QaCawVSlQgNeF3a
iyfD4dVp66Bzn3gO757TWqmroBZ2e1owbAQvF/kCgYAKT/Jze6KMNcK7hfy78VZQ
imuCoXjlob8t6R8i9YJdwv7Pe9rakS5s3nXDEBePU2fr8eIzvK6zUHSoLF9WtlbV
eTEg4FYnsEzCam7AmjptCrWulwp8F1ng9ViLa3Gi9y4snU+1MSPbrdqzKnzTtvPW
Ni1bnzA7bp3w/dMcbxQDGQKBgB50hY5SiUS7LuZg4YqZ7UOn3aXAoMr6FvJZ7lvG
yyepPQ6aACBh0b2lWhcHIKPl7EdJdcGHHo6TJzusAqPNCKf8rh6upe9COkpx+K3/
SnxK4sffol4JgrTwKbXqsZKoGU8hYhZPKbwXn8UOtmN+AvN2N1/PDfBfDCzBJtrd
G2IhAoGBAN19976xAMDjKb2+wd/mQYA2fR7E8lodxdX3LDnblYmndTKY67nVo94M
FHPKZSN590HkFJ+wmChnOrqjtosY+N25CKMS7939EUIDrq+B+bYTWM/gcwdLXNUk
Rygw/078Z3ZDJamXmyez5WpeLFrrbmI8sLnBBmSjQvMb6vCEtQ2Z
-----END RSA PRIVATE KEY-----`
caCRT = `-----BEGIN CERTIFICATE-----
MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0
QXV0aDAeFw0yMTAxMDIyMTIwNTVaFw0yMjA3MDIyMTMwNTJaMBMxETAPBgNVBAMT
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Tiho5xW
AC15JRkMwfp3/TJwI2As7MY5dele5cmdr5bHAE+sRKqC+Ti88OJWCV5saoyax/1S
CjxJlQMZMl169P1QYJskKjdG2sdv6RLWLMgwSNRRjxp/Bw9dHdiEb9MjLgu28Jro
9peQkHcRHeMf5hM9WvlIJGrdzbC4hUehmqggcqgARainBkYjf0SwuWxHeu4nMqkp
Ak5tcSTLCjHfEFHZ9Te0TIPG5YkWocQKyeLgu4lvuU+DD2W2lym+YVUtRMGs1Env
k7p+N0DcGU26qfzZ2sF5ZXkqm7dBsGQB9pIxwc2Q8T1dCIyP9OQCKVILdc5aVFf1
cryQFHYzYNNZXFlIBims5VV5Mgfp8ESHQSue+v6n6ykecLEyKt1F1Y/MWY/nWUSI
8zdq83jdBAZVjo9MSthxVn57/06s/hQca65IpcTZV2gX0a+eRlAVqaRbAhL3LaZe
bYsW3WHKoUOftwemuep3nL51TzlXZVL7Oz/ClGaEOsnGG9KFO6jh+W768qC0zLQI
CdE7v2Zex98sZteHCg9fGJHIaYoF0aJG5P3WI5oZf2fy7UIYN9ADLFZiorCXAZEh
CSU6mDoRViZ4RGR9GZxbDZ9KYn7O8M/KCR72bkQg73TlMsk1zSXEw0MKLUjtsw6c
rZ0Jt8t3sRatHO3JrYHALMt9vZfyNCZp0IsCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFO1yCNAGr/zQTJIi8lw3
w5OiuBvMMA0GCSqGSIb3DQEBCwUAA4ICAQA6gCNuM7r8mnx674dm31GxBjQy5ZwB
7CxDzYEvL/oiZ3Tv3HlPfN2LAAsJUfGnghh9DOytenL2CTZWjl/emP5eijzmlP+9
zva5I6CIMCf/eDDVsRdO244t0o4uG7+At0IgSDM3bpVaVb4RHZNjEziYChsEYY8d
HK6iwuRSvFniV6yhR/Vj1Ymi9yZ5xclqseLXiQnUB0PkfIk23+7s42cXB16653fH
O/FsPyKBLiKJArizLYQc12aP3QOrYoYD9+fAzIIzew7A5C0aanZCGzkuFpO6TRlD
Tb7ry9Gf0DfPpCgxraH8tOcmnqp/ka3hjqo/SRnnTk0IFrmmLdarJvjD46rKwBo4
MjyAIR1mQ5j8GTlSFBmSgETOQ/EYvO3FPLmra1Fh7L+DvaVzTpqI9fG3TuyyY+Ri
Fby4ycTOGSZOe5Fh8lqkX5Y47mCUJ3zHzOA1vUJy2eTlMRGpu47Eb1++Vm6EzPUP
2EF5aD+zwcssh+atZvQbwxpgVqVcyLt91RSkKkmZQslh0rnlTb68yxvUnD3zw7So
o6TAf9UvwVMEvdLT9NnFd6hwi2jcNte/h538GJwXeBb8EkfpqLKpTKyicnOdkamZ
7E9zY8SHNRYMwB9coQ/W8NvufbCgkvOoLyMXk5edbXofXl3PhNGOlraWbghBnzf5
r3rwjFsQOoZotA==
-----END CERTIFICATE-----`
caKey = `-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEA4Tiho5xWAC15JRkMwfp3/TJwI2As7MY5dele5cmdr5bHAE+s
RKqC+Ti88OJWCV5saoyax/1SCjxJlQMZMl169P1QYJskKjdG2sdv6RLWLMgwSNRR
jxp/Bw9dHdiEb9MjLgu28Jro9peQkHcRHeMf5hM9WvlIJGrdzbC4hUehmqggcqgA
RainBkYjf0SwuWxHeu4nMqkpAk5tcSTLCjHfEFHZ9Te0TIPG5YkWocQKyeLgu4lv
uU+DD2W2lym+YVUtRMGs1Envk7p+N0DcGU26qfzZ2sF5ZXkqm7dBsGQB9pIxwc2Q
8T1dCIyP9OQCKVILdc5aVFf1cryQFHYzYNNZXFlIBims5VV5Mgfp8ESHQSue+v6n
6ykecLEyKt1F1Y/MWY/nWUSI8zdq83jdBAZVjo9MSthxVn57/06s/hQca65IpcTZ
V2gX0a+eRlAVqaRbAhL3LaZebYsW3WHKoUOftwemuep3nL51TzlXZVL7Oz/ClGaE
OsnGG9KFO6jh+W768qC0zLQICdE7v2Zex98sZteHCg9fGJHIaYoF0aJG5P3WI5oZ
f2fy7UIYN9ADLFZiorCXAZEhCSU6mDoRViZ4RGR9GZxbDZ9KYn7O8M/KCR72bkQg
73TlMsk1zSXEw0MKLUjtsw6crZ0Jt8t3sRatHO3JrYHALMt9vZfyNCZp0IsCAwEA
AQKCAgAV+ElERYbaI5VyufvVnFJCH75ypPoc6sVGLEq2jbFVJJcq/5qlZCC8oP1F
Xj7YUR6wUiDzK1Hqb7EZ2SCHGjlZVrCVi+y+NYAy7UuMZ+r+mVSkdhmypPoJPUVv
GOTqZ6VB46Cn3eSl0WknvoWr7bD555yPmEuiSc5zNy74yWEJTidEKAFGyknowcTK
sG+w1tAuPLcUKQ44DGB+rgEkcHL7C5EAa7upzx0C3RmZFB+dTAVyJdkBMbFuOhTS
sB7DLeTplR7/4mp9da7EQw51ZXC1DlZOEZt++4/desXsqATNAbva1OuzrLG7mMKe
N/PCBh/aERQcsCvgUmaXqGQgqN1Jhw8kbXnjZnVd9iE7TAh7ki3VqNy1OMgTwOex
bBYWaCqHuDYIxCjeW0qLJcn0cKQ13FVYrxgInf4Jp82SQht5b/zLL3IRZEyKcLJF
kL6g1wlmTUTUX0z8eZzlM0ZCrqtExjgElMO/rV971nyNV5WU8Og3NmE8/slqMrmJ
DlrQr9q0WJsDKj1IMe46EUM6ix7bbxC5NIfJ96dgdxZDn6ghjca6iZYqqUACvmUj
cq08s3R4Ouw9/87kn11wwGBx2yDueCwrjKEGc0RKjweGbwu0nBxOrkJ8JXz6bAv7
1OKfYaX3afI9B8x4uaiuRs38oBQlg9uAYFfl4HNBPuQikGLmsQKCAQEA8VjFOsaz
y6NMZzKXi7WZ48uu3ed5x3Kf6RyDr1WvQ1jkBMv9b6b8Gp1CRnPqviRBto9L8QAg
bCXZTqnXzn//brskmW8IZgqjAlf89AWa53piucu9/hgidrHRZobs5gTqev28uJdc
zcuw1g8c3nCpY9WeTjHODzX5NXYRLFpkazLfYa6c8Q9jZR4KKrpdM+66fxL0JlOd
7dN0oQtEqEAugsd3cwkZgvWhY4oM7FGErrZoDLy273ZdJzi/vU+dThyVzfD8Ab8u
VxxuobVMT/S608zbe+uaiUdov5s96OkCl87403UNKJBH+6LNb3rjBBLE9NPN5ET9
JLQMrYd+zj8jQwKCAQEA7uU5I9MOufo9bIgJqjY4Ie1+Ex9DZEMUYFAvGNCJCVcS
mwOdGF8AWzIavTLACmEDJO7t/OrBdoo4L7IEsCNjgA3WiIwIMiWUVqveAGUMEXr6
TRI5EolV6FTqqIP6AS+BAeBq7G1ELgsTrWNHh11rW3+3kBMuOCn77PUQ8WHwcq/r
teZcZn4Ewcr6P7cBODgVvnBPhe/J8xHS0HFVCeS1CvaiNYgees5yA80Apo9IPjDJ
YWawLjmH5wUBI5yDFVp067wjqJnoKPSoKwWkZXqUk+zgFXx5KT0gh/c5yh1frASp
q6oaYnHEVC5qj2SpT1GFLonTcrQUXiSkiUudvNu1GQKCAQEAmko+5GFtRe0ihgLQ
4S76r6diJli6AKil1Fg3U1r6zZpBQ1PJtJxTJQyN9w5Z7q6tF/GqAesrzxevQdvQ
rCImAPtA3ZofC2UXawMnIjWHHx6diNvYnV1+gtUQ4nO1dSOFZ5VZFcUmPiZO6boF
oaryj3FcX+71JcJCjEvrlKhA9Es0hXUkvfMxfs5if4he1zlyHpTWYr4oA4egUugq
P0mwskikc3VIyvEO+NyjgFxo72yLPkFSzemkidN8uKDyFqKtnlfGM7OuA2CY1WZa
3+67lXWshx9KzyJIs92iCYkU8EoPxtdYzyrV6efdX7x27v60zTOut5TnJJS6WiF6
Do5MkwKCAQAxoR9IyP0DN/BwzqYrXU42Bi+t603F04W1KJNQNWpyrUspNwv41yus
xnD1o0hwH41Wq+h3JZIBfV+E0RfWO9Pc84MBJQ5C1LnHc7cQH+3s575+Km3+4tcd
CB8j2R8kBeloKWYtLdn/Mr/ownpGreqyvIq2/LUaZ+Z1aMgXTYB1YwS16mCBzmZQ
mEl62RsAwe4KfSyYJ6OtwqMoOJMxFfliiLBULK4gVykqjvk2oQeiG+KKQJoTUFJi
dRCyhD5bPkqR+qjxyt+HOqSBI4/uoROi05AOBqjpH1DVzk+MJKQOiX1yM0l98CKY
Vng+x+vAla/0Zh+ucajVkgk4mKPxazdpAoIBAQC17vWk4KYJpF2RC3pKPcQ0PdiX
bN35YNlvyhkYlSfDNdyH3aDrGiycUyW2mMXUgEDFsLRxHMTL+zPC6efqO6sTAJDY
cBptsW4drW/qo8NTx3dNOisLkW+mGGJOR/w157hREFr29ymCVMYu/Z7fVWIeSpCq
p3u8YX8WTljrxwSczlGjvpM7uJx3SfYRM4TUoy+8wU8bK74LywLa5f60bQY6Dye0
Gqd9O6OoPfgcQlwjC5MiAofeqwPJvU0hQOPoehZyNLAmOCWXTYWaTP7lxO1r6+NE
M3hGYqW3W8Ixua71OskCypBZg/HVlIP/lzjRzdx+VOB2hbWVth2Iup/Z1egW
-----END RSA PRIVATE KEY-----`
caCRL = `-----BEGIN X509 CRL-----
MIICpzCBkAIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
MjEwMTAyMjEzNDA1WhcNMjMwMTAyMjEzNDA1WjAkMCICEQC+l04DbHWMyC3fG09k
VXf+Fw0yMTAxMDIyMTM0MDVaoCMwITAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJc
N8OTorgbzDANBgkqhkiG9w0BAQsFAAOCAgEAEJ7z+uNc8sqtxlOhSdTGDzX/xput
E857kFQkSlMnU2whQ8c+XpYrBLA5vIZJNSSwohTpM4+zVBX/bJpmu3wqqaArRO9/
YcW5mQk9Anvb4WjQW1cHmtNapMTzoC9AiYt/OWPfy+P6JCgCr4Hy6LgQyIRL6bM9
VYTalolOm1qa4Y5cIeT7iHq/91mfaqo8/6MYRjLl8DOTROpmw8OS9bCXkzGKdCat
AbAzwkQUSauyoCQ10rpX+Y64w9ng3g4Dr20aCqPf5osaqplEJ2HTK8ljDTidlslv
9anQj8ax3Su89vI8+hK+YbfVQwrThabgdSjQsn+veyx8GlP8WwHLAQ379KjZjWg+
OlOSwBeU1vTdP0QcB8X5C2gVujAyuQekbaV86xzIBOj7vZdfHZ6ee30TZ2FKiMyg
7/N2OqW0w77ChsjB4MSHJCfuTgIeg62GzuZXLM+Q2Z9LBdtm4Byg+sm/P52adOEg
gVb2Zf4KSvsAmA0PIBlu449/QXUFcMxzLFy7mwTeZj2B4Ln0Hm0szV9f9R8MwMtB
SyLYxVH+mgqaR6Jkk22Q/yYyLPaELfafX5gp/AIXG8n0zxfVaTvK3auSgb1Q6ZLS
5QH9dSIsmZHlPq7GoSXmKpMdjUL8eaky/IMteioyXgsBiATzl5L2dsw6MTX3MDF0
QbDK+MzhmbKfDxs=
-----END X509 CRL-----`
client1Crt = `-----BEGIN CERTIFICATE-----
MIIEITCCAgmgAwIBAgIRAIppZHoj1hM80D7WzTEKLuAwDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzEwWhcNMjIwNzAyMjEz
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiVbJtH
XVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd20jP
yhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1UHw4
3Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZmH859
DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0habT
cDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSJ5GIv
zIrE4ZSQt2+CGblKTDswizAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
zDANBgkqhkiG9w0BAQsFAAOCAgEALh4f5GhvNYNou0Ab04iQBbLEdOu2RlbK1B5n
K9P/umYenBHMY/z6HT3+6tpcHsDuqE8UVdq3f3Gh4S2Gu9m8PRitT+cJ3gdo9Plm
3rD4ufn/s6rGg3ppydXcedm17492tbccUDWOBZw3IO/ASVq13WPgT0/Kev7cPq0k
sSdSNhVeXqx8Myc2/d+8GYyzbul2Kpfa7h9i24sK49E9ftnSmsIvngONo08eT1T0
3wAOyK2981LIsHaAWcneShKFLDB6LeXIT9oitOYhiykhFlBZ4M1GNlSNfhQ8IIQP
xbqMNXCLkW4/BtLhGEEcg0QVso6Kudl9rzgTfQknrdF7pHp6rS46wYUjoSyIY6dl
oLmnoAVJX36J3QPWelePI9e07X2wrTfiZWewwgw3KNRWjd6/zfPLe7GoqXnK1S2z
PT8qMfCaTwKTtUkzXuTFvQ8bAo2My/mS8FOcpkt2oQWeOsADHAUX7fz5BCoa2DL3
k/7Mh4gVT+JYZEoTwCFuYHgMWFWe98naqHi9lB4yR981p1QgXgxO7qBeipagKY1F
LlH1iwXUqZ3MZnkNA+4e1Fglsw3sa/rC+L98HnznJ/YbTfQbCP6aQ1qcOymrjMud
7MrFwqZjtd/SK4Qx1VpK6jGEAtPgWBTUS3p9ayg6lqjMBjsmySWfvRsDQbq6P5Ct
O/e3EH8=
-----END CERTIFICATE-----`
client1Key = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiV
bJtHXVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd
20jPyhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1
UHw43Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZm
H859DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0
habTcDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABAoIBAEBSjVFqtbsp0byR
aXvyrtLX1Ng7h++at2jca85Ihq//jyqbHTje8zPuNAKI6eNbmb0YGr5OuEa4pD9N
ssDmMsKSoG/lRwwcm7h4InkSvBWpFShvMgUaohfHAHzsBYxfnh+TfULsi0y7c2n6
t/2OZcOTRkkUDIITnXYiw93ibHHv2Mv2bBDu35kGrcK+c2dN5IL5ZjTjMRpbJTe2
44RBJbdTxHBVSgoGBnugF+s2aEma6Ehsj70oyfoVpM6Aed5kGge0A5zA1JO7WCn9
Ay/DzlULRXHjJIoRWd2NKvx5n3FNppUc9vJh2plRHalRooZ2+MjSf8HmXlvG2Hpb
ScvmWgECgYEA1G+A/2KnxWsr/7uWIJ7ClcGCiNLdk17Pv3DZ3G4qUsU2ITftfIbb
tU0Q/b19na1IY8Pjy9ptP7t74/hF5kky97cf1FA8F+nMj/k4+wO8QDI8OJfzVzh9
PwielA5vbE+xmvis5Hdp8/od1Yrc/rPSy2TKtPFhvsqXjqoUmOAjDP8CgYEAwZjH
9dt1sc2lx/rMxihlWEzQ3JPswKW9/LJAmbRBoSWF9FGNjbX7uhWtXRKJkzb8ZAwa
88azluNo2oftbDD/+jw8b2cDgaJHlLAkSD4O1D1RthW7/LKD15qZ/oFsRb13NV85
ZNKtwslXGbfVNyGKUVFm7fVA8vBAOUey+LKDFj8CgYEAg8WWstOzVdYguMTXXuyb
ruEV42FJaDyLiSirOvxq7GTAKuLSQUg1yMRBIeQEo2X1XU0JZE3dLodRVhuO4EXP
g7Dn4X7Th9HSvgvNuIacowWGLWSz4Qp9RjhGhXhezUSx2nseY6le46PmFavJYYSR
4PBofMyt4PcyA6Cknh+KHmkCgYEAnTriG7ETE0a7v4DXUpB4TpCEiMCy5Xs2o8Z5
ZNva+W+qLVUWq+MDAIyechqeFSvxK6gRM69LJ96lx+XhU58wJiFJzAhT9rK/g+jS
bsHH9WOfu0xHkuHA5hgvvV2Le9B2wqgFyva4HJy82qxMxCu/VG/SMqyfBS9OWbb7
ibQhdq0CgYAl53LUWZsFSZIth1vux2LVOsI8C3X1oiXDGpnrdlQ+K7z57hq5EsRq
GC+INxwXbvKNqp5h0z2MvmKYPDlGVTgw8f8JjM7TkN17ERLcydhdRrMONUryZpo8
1xTob+8blyJgfxZUIAKbMbMbIiU0WAF0rfD/eJJwS4htOW/Hfv4TGA==
-----END RSA PRIVATE KEY-----`
// client 2 crt is revoked
client2Crt = `-----BEGIN CERTIFICATE-----
MIIEITCCAgmgAwIBAgIRAL6XTgNsdYzILd8bT2RVd/4wDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzIwWhcNMjIwNzAyMjEz
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY+6hi
jcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN/4jQ
tNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2HkO/xG
oZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB1YFM
s8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhtsC871
nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTB84v5
t9HqhLhMODbn6oYkEQt3KzAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
zDANBgkqhkiG9w0BAQsFAAOCAgEALGtBCve5k8tToL3oLuXp/oSik6ovIB/zq4I/
4zNMYPU31+ZWz6aahysgx1JL1yqTa3Qm8o2tu52MbnV10dM7CIw7c/cYa+c+OPcG
5LF97kp13X+r2axy+CmwM86b4ILaDGs2Qyai6VB6k7oFUve+av5o7aUrNFpqGCJz
HWdtHZSVA3JMATzy0TfWanwkzreqfdw7qH0yZ9bDURlBKAVWrqnCstva9jRuv+AI
eqxr/4Ro986TFjJdoAP3Vr16CPg7/B6GA/KmsBWJrpeJdPWq4i2gpLKvYZoy89qD
mUZf34RbzcCtV4NvV1DadGnt4us0nvLrvS5rL2+2uWD09kZYq9RbLkvgzF/cY0fz
i7I1bi5XQ+alWe0uAk5ZZL/D+GTRYUX1AWwCqwJxmHrMxcskMyO9pXvLyuSWRDLo
YNBrbX9nLcfJzVCp+X+9sntTHjs4l6Cw+fLepJIgtgqdCHtbhTiv68vSM6cgb4br
6n2xrXRKuioiWFOrTSRr+oalZh8dGJ/xvwY8IbWknZAvml9mf1VvfE7Ma5P777QM
fsbYVTq0Y3R/5hIWsC3HA5z6MIM8L1oRe/YyhP3CTmrCHkVKyDOosGXpGz+JVcyo
cfYkY5A3yFKB2HaCwZSfwFmRhxkrYWGEbHv3Cd9YkZs1J3hNhGFZyVMC9Uh0S85a
6zdDidU=
-----END CERTIFICATE-----`
client2Key = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY
+6hijcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN
/4jQtNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2Hk
O/xGoZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB
1YFMs8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhts
C871nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABAoIBAFatstVb1KdQXsq0
cFpui8zTKOUiduJOrDkWzTygAmlEhYtrccdfXu7OWz0x0lvBLDVGK3a0I/TGrAzj
4BuFY+FM/egxTVt9in6fmA3et4BS1OAfCryzUdfK6RV//8L+t+zJZ/qKQzWnugpy
QYjDo8ifuMFwtvEoXizaIyBNLAhEp9hnrv+Tyi2O2gahPvCHsD48zkyZRCHYRstD
NH5cIrwz9/RJgPO1KI+QsJE7Nh7stR0sbr+5TPU4fnsL2mNhMUF2TJrwIPrc1yp+
YIUjdnh3SO88j4TQT3CIrWi8i4pOy6N0dcVn3gpCRGaqAKyS2ZYUj+yVtLO4KwxZ
SZ1lNvECgYEA78BrF7f4ETfWSLcBQ3qxfLs7ibB6IYo2x25685FhZjD+zLXM1AKb
FJHEXUm3mUYrFJK6AFEyOQnyGKBOLs3S6oTAswMPbTkkZeD1Y9O6uv0AHASLZnK6
pC6ub0eSRF5LUyTQ55Jj8D7QsjXJueO8v+G5ihWhNSN9tB2UA+8NBmkCgYEA+weq
cvoeMIEMBQHnNNLy35bwfqrceGyPIRBcUIvzQfY1vk7KW6DYOUzC7u+WUzy/hA52
DjXVVhua2eMQ9qqtOav7djcMc2W9RbLowxvno7K5qiCss013MeWk64TCWy+WMp5A
AVAtOliC3hMkIKqvR2poqn+IBTh1449agUJQqTMCgYEAu06IHGq1GraV6g9XpGF5
wqoAlMzUTdnOfDabRilBf/YtSr+J++ThRcuwLvXFw7CnPZZ4TIEjDJ7xjj3HdxeE
fYYjineMmNd40UNUU556F1ZLvJfsVKizmkuCKhwvcMx+asGrmA+tlmds4p3VMS50
KzDtpKzLWlmU/p/RINWlRmkCgYBy0pHTn7aZZx2xWKqCDg+L2EXPGqZX6wgZDpu7
OBifzlfM4ctL2CmvI/5yPmLbVgkgBWFYpKUdiujsyyEiQvWTUKhn7UwjqKDHtcsk
G6p7xS+JswJrzX4885bZJ9Oi1AR2yM3sC9l0O7I4lDbNPmWIXBLeEhGMmcPKv/Kc
91Ff4wKBgQCF3ur+Vt0PSU0ucrPVHjCe7tqazm0LJaWbPXL1Aw0pzdM2EcNcW/MA
w0kqpr7MgJ94qhXCBcVcfPuFN9fBOadM3UBj1B45Cz3pptoK+ScI8XKno6jvVK/p
xr5cb9VBRBtB9aOKVfuRhpatAfS2Pzm2Htae9lFn7slGPUmu2hkjDw==
-----END RSA PRIVATE KEY-----`
)
func TestLoadCertificate(t *testing.T) {
caCrtPath := filepath.Join(os.TempDir(), "testca.crt")
caCrlPath := filepath.Join(os.TempDir(), "testcrl.crt")
certPath := filepath.Join(os.TempDir(), "test.crt")
keyPath := filepath.Join(os.TempDir(), "test.key")
err := os.WriteFile(caCrtPath, []byte(caCRT), os.ModePerm)
assert.NoError(t, err)
err = os.WriteFile(caCrlPath, []byte(caCRL), os.ModePerm)
assert.NoError(t, err)
err = os.WriteFile(certPath, []byte(serverCert), os.ModePerm)
assert.NoError(t, err)
err = os.WriteFile(keyPath, []byte(serverKey), os.ModePerm)
assert.NoError(t, err)
keyPairs := []TLSKeyPair{
{
Cert: certPath,
Key: keyPath,
ID: DefaultTLSKeyPaidID,
},
{
Cert: certPath,
Key: keyPath,
ID: DefaultTLSKeyPaidID,
},
}
certManager, err := NewCertManager(keyPairs, configDir, logSenderTest)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "is duplicated")
}
assert.Nil(t, certManager)
keyPairs = []TLSKeyPair{
{
Cert: certPath,
Key: keyPath,
ID: DefaultTLSKeyPaidID,
},
}
certManager, err = NewCertManager(keyPairs, configDir, logSenderTest)
assert.NoError(t, err)
certFunc := certManager.GetCertificateFunc(DefaultTLSKeyPaidID)
if assert.NotNil(t, certFunc) {
hello := &tls.ClientHelloInfo{
ServerName: "localhost",
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
}
cert, err := certFunc(hello)
assert.NoError(t, err)
assert.Equal(t, certManager.certs[DefaultTLSKeyPaidID], cert)
}
certFunc = certManager.GetCertificateFunc("unknownID")
if assert.NotNil(t, certFunc) {
hello := &tls.ClientHelloInfo{
ServerName: "localhost",
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
}
_, err = certFunc(hello)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "no certificate for id unknownID")
}
}
certManager.SetCACertificates(nil)
err = certManager.LoadRootCAs()
assert.NoError(t, err)
certManager.SetCACertificates([]string{""})
err = certManager.LoadRootCAs()
assert.Error(t, err)
certManager.SetCACertificates([]string{"invalid"})
err = certManager.LoadRootCAs()
assert.Error(t, err)
// laoding the key as root CA must fail
certManager.SetCACertificates([]string{keyPath})
err = certManager.LoadRootCAs()
assert.Error(t, err)
certManager.SetCACertificates([]string{certPath})
err = certManager.LoadRootCAs()
assert.NoError(t, err)
rootCa := certManager.GetRootCAs()
assert.NotNil(t, rootCa)
err = certManager.Reload()
assert.NoError(t, err)
certManager.SetCARevocationLists(nil)
err = certManager.LoadCRLs()
assert.NoError(t, err)
certManager.SetCARevocationLists([]string{""})
err = certManager.LoadCRLs()
assert.Error(t, err)
certManager.SetCARevocationLists([]string{"invalid crl"})
err = certManager.LoadCRLs()
assert.Error(t, err)
// this is not a crl and must fail
certManager.SetCARevocationLists([]string{caCrtPath})
err = certManager.LoadCRLs()
assert.Error(t, err)
certManager.SetCARevocationLists([]string{caCrlPath})
err = certManager.LoadCRLs()
assert.NoError(t, err)
crt, err := tls.X509KeyPair([]byte(caCRT), []byte(caKey))
assert.NoError(t, err)
x509CAcrt, err := x509.ParseCertificate(crt.Certificate[0])
assert.NoError(t, err)
crt, err = tls.X509KeyPair([]byte(client1Crt), []byte(client1Key))
assert.NoError(t, err)
x509crt, err := x509.ParseCertificate(crt.Certificate[0])
if assert.NoError(t, err) {
assert.False(t, certManager.IsRevoked(x509crt, x509CAcrt))
}
crt, err = tls.X509KeyPair([]byte(client2Crt), []byte(client2Key))
assert.NoError(t, err)
x509crt, err = x509.ParseCertificate(crt.Certificate[0])
if assert.NoError(t, err) {
assert.True(t, certManager.IsRevoked(x509crt, x509CAcrt))
}
assert.True(t, certManager.IsRevoked(nil, nil))
err = os.Remove(caCrlPath)
assert.NoError(t, err)
err = certManager.Reload()
assert.Error(t, err)
err = os.Remove(certPath)
assert.NoError(t, err)
err = os.Remove(keyPath)
assert.NoError(t, err)
err = certManager.Reload()
assert.Error(t, err)
err = os.Remove(caCrtPath)
assert.NoError(t, err)
}
func TestLoadInvalidCert(t *testing.T) {
certManager, err := NewCertManager(nil, configDir, logSenderTest)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "no key pairs defined")
}
assert.Nil(t, certManager)
keyPairs := []TLSKeyPair{
{
Cert: "test.crt",
Key: "test.key",
ID: DefaultTLSKeyPaidID,
},
}
certManager, err = NewCertManager(keyPairs, configDir, logSenderTest)
assert.Error(t, err)
assert.Nil(t, certManager)
keyPairs = []TLSKeyPair{
{
Cert: "test.crt",
Key: "test.key",
},
}
certManager, err = NewCertManager(keyPairs, configDir, logSenderTest)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "TLS certificate without ID")
}
assert.Nil(t, certManager)
}

View file

@ -1,12 +0,0 @@
//go:build linux
// +build linux
package config
import "github.com/spf13/viper"
// linux specific config search path
func setViperAdditionalConfigPaths() {
viper.AddConfigPath("$HOME/.config/sftpgo")
viper.AddConfigPath("/etc/sftpgo")
}

View file

@ -1,6 +0,0 @@
//go:build !linux
// +build !linux
package config
func setViperAdditionalConfigPaths() {}

6
crowdin.yml Normal file
View file

@ -0,0 +1,6 @@
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_PERSONAL_TOKEN
files:
- source: /static/locales/en/translation.json
translation: /static/locales/%two_letters_code%/%original_file_name%
type: i18next_json

View file

@ -1,120 +0,0 @@
package dataprovider
import (
"bytes"
"context"
"fmt"
"net/url"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/sftpgo/sdk/plugin/notifier"
"github.com/drakkan/sftpgo/v2/command"
"github.com/drakkan/sftpgo/v2/httpclient"
"github.com/drakkan/sftpgo/v2/logger"
"github.com/drakkan/sftpgo/v2/plugin"
"github.com/drakkan/sftpgo/v2/util"
)
const (
// ActionExecutorSelf is used as username for self action, for example a user/admin that updates itself
ActionExecutorSelf = "__self__"
// ActionExecutorSystem is used as username for actions with no explicit executor associated, for example
// adding/updating a user/admin by loading initial data
ActionExecutorSystem = "__system__"
)
const (
actionObjectUser = "user"
actionObjectGroup = "group"
actionObjectAdmin = "admin"
actionObjectAPIKey = "api_key"
actionObjectShare = "share"
)
func executeAction(operation, executor, ip, objectType, objectName string, object plugin.Renderer) {
if plugin.Handler.HasNotifiers() {
plugin.Handler.NotifyProviderEvent(&notifier.ProviderEvent{
Action: operation,
Username: executor,
ObjectType: objectType,
ObjectName: objectName,
IP: ip,
Timestamp: time.Now().UnixNano(),
}, object)
}
if config.Actions.Hook == "" {
return
}
if !util.Contains(config.Actions.ExecuteOn, operation) ||
!util.Contains(config.Actions.ExecuteFor, objectType) {
return
}
go func() {
dataAsJSON, err := object.RenderAsJSON(operation != operationDelete)
if err != nil {
providerLog(logger.LevelError, "unable to serialize user as JSON for operation %#v: %v", operation, err)
return
}
if strings.HasPrefix(config.Actions.Hook, "http") {
var url *url.URL
url, err := url.Parse(config.Actions.Hook)
if err != nil {
providerLog(logger.LevelError, "Invalid http_notification_url %#v for operation %#v: %v",
config.Actions.Hook, operation, err)
return
}
q := url.Query()
q.Add("action", operation)
q.Add("username", executor)
q.Add("ip", ip)
q.Add("object_type", objectType)
q.Add("object_name", objectName)
q.Add("timestamp", fmt.Sprintf("%v", time.Now().UnixNano()))
url.RawQuery = q.Encode()
startTime := time.Now()
resp, err := httpclient.RetryablePost(url.String(), "application/json", bytes.NewBuffer(dataAsJSON))
respCode := 0
if err == nil {
respCode = resp.StatusCode
resp.Body.Close()
}
providerLog(logger.LevelDebug, "notified operation %#v to URL: %v status code: %v, elapsed: %v err: %v",
operation, url.Redacted(), respCode, time.Since(startTime), err)
} else {
executeNotificationCommand(operation, executor, ip, objectType, objectName, dataAsJSON) //nolint:errcheck // the error is used in test cases only
}
}()
}
func executeNotificationCommand(operation, executor, ip, objectType, objectName string, objectAsJSON []byte) error {
if !filepath.IsAbs(config.Actions.Hook) {
err := fmt.Errorf("invalid notification command %#v", config.Actions.Hook)
logger.Warn(logSender, "", "unable to execute notification command: %v", err)
return err
}
timeout, env := command.GetConfig(config.Actions.Hook)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, config.Actions.Hook)
cmd.Env = append(env,
fmt.Sprintf("SFTPGO_PROVIDER_ACTION=%v", operation),
fmt.Sprintf("SFTPGO_PROVIDER_OBJECT_TYPE=%v", objectType),
fmt.Sprintf("SFTPGO_PROVIDER_OBJECT_NAME=%v", objectName),
fmt.Sprintf("SFTPGO_PROVIDER_USERNAME=%v", executor),
fmt.Sprintf("SFTPGO_PROVIDER_IP=%v", ip),
fmt.Sprintf("SFTPGO_PROVIDER_TIMESTAMP=%v", util.GetTimeAsMsSinceEpoch(time.Now())),
fmt.Sprintf("SFTPGO_PROVIDER_OBJECT=%v", string(objectAsJSON)))
startTime := time.Now()
err := cmd.Run()
providerLog(logger.LevelDebug, "executed command %#v, elapsed: %v, error: %v", config.Actions.Hook,
time.Since(startTime), err)
return err
}

View file

@ -1,18 +0,0 @@
//go:build nobolt
// +build nobolt
package dataprovider
import (
"errors"
"github.com/drakkan/sftpgo/v2/version"
)
func init() {
version.AddFeature("-bolt")
}
func initializeBoltProvider(basePath string) error {
return errors.New("bolt disabled at build time")
}

View file

@ -1,62 +0,0 @@
package dataprovider
import (
"sync"
)
var cachedPasswords passwordsCache
func init() {
cachedPasswords = passwordsCache{
cache: make(map[string]string),
}
}
type passwordsCache struct {
sync.RWMutex
cache map[string]string
}
func (c *passwordsCache) Add(username, password string) {
if !config.PasswordCaching || username == "" || password == "" {
return
}
c.Lock()
defer c.Unlock()
c.cache[username] = password
}
func (c *passwordsCache) Remove(username string) {
if !config.PasswordCaching {
return
}
c.Lock()
defer c.Unlock()
delete(c.cache, username)
}
// Check returns if the user is found and if the password match
func (c *passwordsCache) Check(username, password string) (bool, bool) {
if username == "" || password == "" {
return false, false
}
c.RLock()
defer c.RUnlock()
pwd, ok := c.cache[username]
if !ok {
return false, false
}
return true, pwd == password
}
// CheckCachedPassword is an utility method used only in test cases
func CheckCachedPassword(username, password string) (bool, bool) {
return cachedPasswords.Check(username, password)
}

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
//go:build nomysql
// +build nomysql
package dataprovider
import (
"errors"
"github.com/drakkan/sftpgo/v2/version"
)
func init() {
version.AddFeature("-mysql")
}
func initializeMySQLProvider() error {
return errors.New("MySQL disabled at build time")
}

View file

@ -1,765 +0,0 @@
//go:build !nopgsql
// +build !nopgsql
package dataprovider
import (
"context"
"crypto/x509"
"database/sql"
"errors"
"fmt"
"strings"
"time"
// we import lib/pq here to be able to disable PostgreSQL support using a build tag
_ "github.com/lib/pq"
"github.com/drakkan/sftpgo/v2/logger"
"github.com/drakkan/sftpgo/v2/version"
"github.com/drakkan/sftpgo/v2/vfs"
)
const (
pgsqlResetSQL = `DROP TABLE IF EXISTS "{{api_keys}}" CASCADE;
DROP TABLE IF EXISTS "{{folders_mapping}}" CASCADE;
DROP TABLE IF EXISTS "{{users_folders_mapping}}" CASCADE;
DROP TABLE IF EXISTS "{{users_groups_mapping}}" CASCADE;
DROP TABLE IF EXISTS "{{groups_folders_mapping}}" CASCADE;
DROP TABLE IF EXISTS "{{admins}}" CASCADE;
DROP TABLE IF EXISTS "{{folders}}" CASCADE;
DROP TABLE IF EXISTS "{{shares}}" CASCADE;
DROP TABLE IF EXISTS "{{users}}" CASCADE;
DROP TABLE IF EXISTS "{{groups}}" CASCADE;
DROP TABLE IF EXISTS "{{defender_events}}" CASCADE;
DROP TABLE IF EXISTS "{{defender_hosts}}" CASCADE;
DROP TABLE IF EXISTS "{{active_transfers}}" CASCADE;
DROP TABLE IF EXISTS "{{shared_sessions}}" CASCADE;
DROP TABLE IF EXISTS "{{schema_version}}" CASCADE;
`
pgsqlInitial = `CREATE TABLE "{{schema_version}}" ("id" serial NOT NULL PRIMARY KEY, "version" integer NOT NULL);
CREATE TABLE "{{admins}}" ("id" serial NOT NULL PRIMARY KEY, "username" varchar(255) NOT NULL UNIQUE,
"description" varchar(512) NULL, "password" varchar(255) NOT NULL, "email" varchar(255) NULL, "status" integer NOT NULL,
"permissions" text NOT NULL, "filters" text NULL, "additional_info" text NULL, "last_login" bigint NOT NULL,
"created_at" bigint NOT NULL, "updated_at" bigint NOT NULL);
CREATE TABLE "{{defender_hosts}}" ("id" bigserial NOT NULL PRIMARY KEY, "ip" varchar(50) NOT NULL UNIQUE,
"ban_time" bigint NOT NULL, "updated_at" bigint NOT NULL);
CREATE TABLE "{{defender_events}}" ("id" bigserial NOT NULL PRIMARY KEY, "date_time" bigint NOT NULL, "score" integer NOT NULL,
"host_id" bigint NOT NULL);
ALTER TABLE "{{defender_events}}" ADD CONSTRAINT "{{prefix}}defender_events_host_id_fk_defender_hosts_id" FOREIGN KEY
("host_id") REFERENCES "{{defender_hosts}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
CREATE TABLE "{{folders}}" ("id" serial NOT NULL PRIMARY KEY, "name" varchar(255) NOT NULL UNIQUE, "description" varchar(512) NULL,
"path" text NULL, "used_quota_size" bigint NOT NULL, "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL,
"filesystem" text NULL);
CREATE TABLE "{{users}}" ("id" serial NOT NULL PRIMARY KEY, "username" varchar(255) NOT NULL UNIQUE, "status" integer NOT NULL,
"expiration_date" bigint NOT NULL, "description" varchar(512) NULL, "password" text NULL, "public_keys" text NULL,
"home_dir" text NOT NULL, "uid" bigint NOT NULL, "gid" bigint NOT NULL, "max_sessions" integer NOT NULL,
"quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "permissions" text NOT NULL, "used_quota_size" bigint NOT NULL,
"used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL, "upload_bandwidth" integer NOT NULL,
"download_bandwidth" integer NOT NULL, "last_login" bigint NOT NULL, "filters" text NULL, "filesystem" text NULL,
"additional_info" text NULL, "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "email" varchar(255) NULL);
CREATE TABLE "{{folders_mapping}}" ("id" serial NOT NULL PRIMARY KEY, "virtual_path" text NOT NULL,
"quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "folder_id" integer NOT NULL, "user_id" integer NOT NULL);
ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "{{prefix}}unique_mapping" UNIQUE ("user_id", "folder_id");
ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "{{prefix}}folders_mapping_folder_id_fk_folders_id"
FOREIGN KEY ("folder_id") REFERENCES "{{folders}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "{{prefix}}folders_mapping_user_id_fk_users_id"
FOREIGN KEY ("user_id") REFERENCES "{{users}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
CREATE TABLE "{{shares}}" ("id" serial NOT NULL PRIMARY KEY,
"share_id" varchar(60) NOT NULL UNIQUE, "name" varchar(255) NOT NULL, "description" varchar(512) NULL,
"scope" integer NOT NULL, "paths" text NOT NULL, "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL,
"last_use_at" bigint NOT NULL, "expires_at" bigint NOT NULL, "password" text NULL,
"max_tokens" integer NOT NULL, "used_tokens" integer NOT NULL, "allow_from" text NULL,
"user_id" integer NOT NULL);
ALTER TABLE "{{shares}}" ADD CONSTRAINT "{{prefix}}shares_user_id_fk_users_id" FOREIGN KEY ("user_id")
REFERENCES "{{users}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
CREATE TABLE "{{api_keys}}" ("id" serial NOT NULL PRIMARY KEY, "name" varchar(255) NOT NULL,
"key_id" varchar(50) NOT NULL UNIQUE, "api_key" varchar(255) NOT NULL UNIQUE, "scope" integer NOT NULL,
"created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "last_use_at" bigint NOT NULL,"expires_at" bigint NOT NULL,
"description" text NULL, "admin_id" integer NULL, "user_id" integer NULL);
ALTER TABLE "{{api_keys}}" ADD CONSTRAINT "{{prefix}}api_keys_admin_id_fk_admins_id" FOREIGN KEY ("admin_id")
REFERENCES "{{admins}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
ALTER TABLE "{{api_keys}}" ADD CONSTRAINT "{{prefix}}api_keys_user_id_fk_users_id" FOREIGN KEY ("user_id")
REFERENCES "{{users}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
CREATE INDEX "{{prefix}}folders_mapping_folder_id_idx" ON "{{folders_mapping}}" ("folder_id");
CREATE INDEX "{{prefix}}folders_mapping_user_id_idx" ON "{{folders_mapping}}" ("user_id");
CREATE INDEX "{{prefix}}api_keys_admin_id_idx" ON "{{api_keys}}" ("admin_id");
CREATE INDEX "{{prefix}}api_keys_user_id_idx" ON "{{api_keys}}" ("user_id");
CREATE INDEX "{{prefix}}users_updated_at_idx" ON "{{users}}" ("updated_at");
CREATE INDEX "{{prefix}}shares_user_id_idx" ON "{{shares}}" ("user_id");
CREATE INDEX "{{prefix}}defender_hosts_updated_at_idx" ON "{{defender_hosts}}" ("updated_at");
CREATE INDEX "{{prefix}}defender_hosts_ban_time_idx" ON "{{defender_hosts}}" ("ban_time");
CREATE INDEX "{{prefix}}defender_events_date_time_idx" ON "{{defender_events}}" ("date_time");
CREATE INDEX "{{prefix}}defender_events_host_id_idx" ON "{{defender_events}}" ("host_id");
INSERT INTO {{schema_version}} (version) VALUES (15);
`
pgsqlV16SQL = `ALTER TABLE "{{users}}" ADD COLUMN "download_data_transfer" integer DEFAULT 0 NOT NULL;
ALTER TABLE "{{users}}" ALTER COLUMN "download_data_transfer" DROP DEFAULT;
ALTER TABLE "{{users}}" ADD COLUMN "total_data_transfer" integer DEFAULT 0 NOT NULL;
ALTER TABLE "{{users}}" ALTER COLUMN "total_data_transfer" DROP DEFAULT;
ALTER TABLE "{{users}}" ADD COLUMN "upload_data_transfer" integer DEFAULT 0 NOT NULL;
ALTER TABLE "{{users}}" ALTER COLUMN "upload_data_transfer" DROP DEFAULT;
ALTER TABLE "{{users}}" ADD COLUMN "used_download_data_transfer" integer DEFAULT 0 NOT NULL;
ALTER TABLE "{{users}}" ALTER COLUMN "used_download_data_transfer" DROP DEFAULT;
ALTER TABLE "{{users}}" ADD COLUMN "used_upload_data_transfer" integer DEFAULT 0 NOT NULL;
ALTER TABLE "{{users}}" ALTER COLUMN "used_upload_data_transfer" DROP DEFAULT;
CREATE TABLE "{{active_transfers}}" ("id" bigserial NOT NULL PRIMARY KEY, "connection_id" varchar(100) NOT NULL,
"transfer_id" bigint NOT NULL, "transfer_type" integer NOT NULL, "username" varchar(255) NOT NULL,
"folder_name" varchar(255) NULL, "ip" varchar(50) NOT NULL, "truncated_size" bigint NOT NULL,
"current_ul_size" bigint NOT NULL, "current_dl_size" bigint NOT NULL, "created_at" bigint NOT NULL,
"updated_at" bigint NOT NULL);
CREATE INDEX "{{prefix}}active_transfers_connection_id_idx" ON "{{active_transfers}}" ("connection_id");
CREATE INDEX "{{prefix}}active_transfers_transfer_id_idx" ON "{{active_transfers}}" ("transfer_id");
CREATE INDEX "{{prefix}}active_transfers_updated_at_idx" ON "{{active_transfers}}" ("updated_at");
`
pgsqlV16DownSQL = `ALTER TABLE "{{users}}" DROP COLUMN "used_upload_data_transfer" CASCADE;
ALTER TABLE "{{users}}" DROP COLUMN "used_download_data_transfer" CASCADE;
ALTER TABLE "{{users}}" DROP COLUMN "upload_data_transfer" CASCADE;
ALTER TABLE "{{users}}" DROP COLUMN "total_data_transfer" CASCADE;
ALTER TABLE "{{users}}" DROP COLUMN "download_data_transfer" CASCADE;
DROP TABLE "{{active_transfers}}" CASCADE;
`
pgsqlV17SQL = `CREATE TABLE "{{groups}}" ("id" serial NOT NULL PRIMARY KEY, "name" varchar(255) NOT NULL UNIQUE,
"description" varchar(512) NULL, "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "user_settings" text NULL);
CREATE TABLE "{{groups_folders_mapping}}" ("id" serial NOT NULL PRIMARY KEY, "group_id" integer NOT NULL,
"folder_id" integer NOT NULL, "virtual_path" text NOT NULL, "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL);
CREATE TABLE "{{users_groups_mapping}}" ("id" serial NOT NULL PRIMARY KEY, "user_id" integer NOT NULL,
"group_id" integer NOT NULL, "group_type" integer NOT NULL);
DROP INDEX "{{prefix}}folders_mapping_folder_id_idx";
DROP INDEX "{{prefix}}folders_mapping_user_id_idx";
ALTER TABLE "{{folders_mapping}}" DROP CONSTRAINT "{{prefix}}unique_mapping";
ALTER TABLE "{{folders_mapping}}" RENAME TO "{{users_folders_mapping}}";
ALTER TABLE "{{users_folders_mapping}}" ADD CONSTRAINT "{{prefix}}unique_user_folder_mapping" UNIQUE ("user_id", "folder_id");
CREATE INDEX "{{prefix}}users_folders_mapping_folder_id_idx" ON "{{users_folders_mapping}}" ("folder_id");
CREATE INDEX "{{prefix}}users_folders_mapping_user_id_idx" ON "{{users_folders_mapping}}" ("user_id");
ALTER TABLE "{{users_groups_mapping}}" ADD CONSTRAINT "{{prefix}}unique_user_group_mapping" UNIQUE ("user_id", "group_id");
ALTER TABLE "{{groups_folders_mapping}}" ADD CONSTRAINT "{{prefix}}unique_group_folder_mapping" UNIQUE ("group_id", "folder_id");
CREATE INDEX "{{prefix}}users_groups_mapping_group_id_idx" ON "{{users_groups_mapping}}" ("group_id");
ALTER TABLE "{{users_groups_mapping}}" ADD CONSTRAINT "{{prefix}}users_groups_mapping_group_id_fk_groups_id"
FOREIGN KEY ("group_id") REFERENCES "{{groups}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION;
CREATE INDEX "{{prefix}}users_groups_mapping_user_id_idx" ON "{{users_groups_mapping}}" ("user_id");
ALTER TABLE "{{users_groups_mapping}}" ADD CONSTRAINT "{{prefix}}users_groups_mapping_user_id_fk_users_id"
FOREIGN KEY ("user_id") REFERENCES "{{users}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
CREATE INDEX "{{prefix}}groups_folders_mapping_folder_id_idx" ON "{{groups_folders_mapping}}" ("folder_id");
ALTER TABLE "{{groups_folders_mapping}}" ADD CONSTRAINT "{{prefix}}groups_folders_mapping_folder_id_fk_folders_id"
FOREIGN KEY ("folder_id") REFERENCES "{{folders}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
CREATE INDEX "{{prefix}}groups_folders_mapping_group_id_idx" ON "{{groups_folders_mapping}}" ("group_id");
ALTER TABLE "{{groups_folders_mapping}}" ADD CONSTRAINT "{{prefix}}groups_folders_mapping_group_id_fk_groups_id"
FOREIGN KEY ("group_id") REFERENCES "groups" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
CREATE INDEX "{{prefix}}groups_updated_at_idx" ON "{{groups}}" ("updated_at");
`
pgsqlV17DownSQL = `DROP TABLE "{{users_groups_mapping}}" CASCADE;
DROP TABLE "{{groups_folders_mapping}}" CASCADE;
DROP TABLE "{{groups}}" CASCADE;
DROP INDEX "{{prefix}}users_folders_mapping_folder_id_idx";
DROP INDEX "{{prefix}}users_folders_mapping_user_id_idx";
ALTER TABLE "{{users_folders_mapping}}" DROP CONSTRAINT "{{prefix}}unique_user_folder_mapping";
ALTER TABLE "{{users_folders_mapping}}" RENAME TO "{{folders_mapping}}";
ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "{{prefix}}unique_mapping" UNIQUE ("user_id", "folder_id");
CREATE INDEX "{{prefix}}folders_mapping_folder_id_idx" ON "{{folders_mapping}}" ("folder_id");
CREATE INDEX "{{prefix}}folders_mapping_user_id_idx" ON "{{folders_mapping}}" ("user_id");
`
pgsqlV19SQL = `CREATE TABLE "{{shared_sessions}}" ("key" varchar(128) NOT NULL PRIMARY KEY,
"data" text NOT NULL, "type" integer NOT NULL, "timestamp" bigint NOT NULL);
CREATE INDEX "{{prefix}}shared_sessions_type_idx" ON "{{shared_sessions}}" ("type");
CREATE INDEX "{{prefix}}shared_sessions_timestamp_idx" ON "{{shared_sessions}}" ("timestamp");`
pgsqlV19DownSQL = `DROP TABLE "{{shared_sessions}}" CASCADE;`
)
// PGSQLProvider defines the auth provider for PostgreSQL database
type PGSQLProvider struct {
dbHandle *sql.DB
}
func init() {
version.AddFeature("+pgsql")
}
func initializePGSQLProvider() error {
var err error
dbHandle, err := sql.Open("postgres", getPGSQLConnectionString(false))
if err == nil {
providerLog(logger.LevelDebug, "postgres database handle created, connection string: %#v, pool size: %v",
getPGSQLConnectionString(true), config.PoolSize)
dbHandle.SetMaxOpenConns(config.PoolSize)
if config.PoolSize > 0 {
dbHandle.SetMaxIdleConns(config.PoolSize)
} else {
dbHandle.SetMaxIdleConns(2)
}
dbHandle.SetConnMaxLifetime(240 * time.Second)
provider = &PGSQLProvider{dbHandle: dbHandle}
} else {
providerLog(logger.LevelError, "error creating postgres database handler, connection string: %#v, error: %v",
getPGSQLConnectionString(true), err)
}
return err
}
func getPGSQLConnectionString(redactedPwd bool) string {
var connectionString string
if config.ConnectionString == "" {
password := config.Password
if redactedPwd && password != "" {
password = "[redacted]"
}
connectionString = fmt.Sprintf("host='%v' port=%v dbname='%v' user='%v' password='%v' sslmode=%v connect_timeout=10",
config.Host, config.Port, config.Name, config.Username, password, getSSLMode())
if config.RootCert != "" {
connectionString += fmt.Sprintf(" sslrootcert='%v'", config.RootCert)
}
if config.ClientCert != "" && config.ClientKey != "" {
connectionString += fmt.Sprintf(" sslcert='%v' sslkey='%v'", config.ClientCert, config.ClientKey)
}
} else {
connectionString = config.ConnectionString
}
return connectionString
}
func (p *PGSQLProvider) checkAvailability() error {
return sqlCommonCheckAvailability(p.dbHandle)
}
func (p *PGSQLProvider) validateUserAndPass(username, password, ip, protocol string) (User, error) {
return sqlCommonValidateUserAndPass(username, password, ip, protocol, p.dbHandle)
}
func (p *PGSQLProvider) validateUserAndTLSCert(username, protocol string, tlsCert *x509.Certificate) (User, error) {
return sqlCommonValidateUserAndTLSCertificate(username, protocol, tlsCert, p.dbHandle)
}
func (p *PGSQLProvider) validateUserAndPubKey(username string, publicKey []byte, isSSHCert bool) (User, string, error) {
return sqlCommonValidateUserAndPubKey(username, publicKey, isSSHCert, p.dbHandle)
}
func (p *PGSQLProvider) updateTransferQuota(username string, uploadSize, downloadSize int64, reset bool) error {
return sqlCommonUpdateTransferQuota(username, uploadSize, downloadSize, reset, p.dbHandle)
}
func (p *PGSQLProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error {
return sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle)
}
func (p *PGSQLProvider) getUsedQuota(username string) (int, int64, int64, int64, error) {
return sqlCommonGetUsedQuota(username, p.dbHandle)
}
func (p *PGSQLProvider) setUpdatedAt(username string) {
sqlCommonSetUpdatedAt(username, p.dbHandle)
}
func (p *PGSQLProvider) updateLastLogin(username string) error {
return sqlCommonUpdateLastLogin(username, p.dbHandle)
}
func (p *PGSQLProvider) updateAdminLastLogin(username string) error {
return sqlCommonUpdateAdminLastLogin(username, p.dbHandle)
}
func (p *PGSQLProvider) userExists(username string) (User, error) {
return sqlCommonGetUserByUsername(username, p.dbHandle)
}
func (p *PGSQLProvider) addUser(user *User) error {
return sqlCommonAddUser(user, p.dbHandle)
}
func (p *PGSQLProvider) updateUser(user *User) error {
return sqlCommonUpdateUser(user, p.dbHandle)
}
func (p *PGSQLProvider) deleteUser(user User) error {
return sqlCommonDeleteUser(user, p.dbHandle)
}
func (p *PGSQLProvider) updateUserPassword(username, password string) error {
return sqlCommonUpdateUserPassword(username, password, p.dbHandle)
}
func (p *PGSQLProvider) dumpUsers() ([]User, error) {
return sqlCommonDumpUsers(p.dbHandle)
}
func (p *PGSQLProvider) getRecentlyUpdatedUsers(after int64) ([]User, error) {
return sqlCommonGetRecentlyUpdatedUsers(after, p.dbHandle)
}
func (p *PGSQLProvider) getUsers(limit int, offset int, order string) ([]User, error) {
return sqlCommonGetUsers(limit, offset, order, p.dbHandle)
}
func (p *PGSQLProvider) getUsersForQuotaCheck(toFetch map[string]bool) ([]User, error) {
return sqlCommonGetUsersForQuotaCheck(toFetch, p.dbHandle)
}
func (p *PGSQLProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) {
return sqlCommonDumpFolders(p.dbHandle)
}
func (p *PGSQLProvider) getFolders(limit, offset int, order string, minimal bool) ([]vfs.BaseVirtualFolder, error) {
return sqlCommonGetFolders(limit, offset, order, minimal, p.dbHandle)
}
func (p *PGSQLProvider) getFolderByName(name string) (vfs.BaseVirtualFolder, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
defer cancel()
return sqlCommonGetFolderByName(ctx, name, p.dbHandle)
}
func (p *PGSQLProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
return sqlCommonAddFolder(folder, p.dbHandle)
}
func (p *PGSQLProvider) updateFolder(folder *vfs.BaseVirtualFolder) error {
return sqlCommonUpdateFolder(folder, p.dbHandle)
}
func (p *PGSQLProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
return sqlCommonDeleteFolder(folder, p.dbHandle)
}
func (p *PGSQLProvider) updateFolderQuota(name string, filesAdd int, sizeAdd int64, reset bool) error {
return sqlCommonUpdateFolderQuota(name, filesAdd, sizeAdd, reset, p.dbHandle)
}
func (p *PGSQLProvider) getUsedFolderQuota(name string) (int, int64, error) {
return sqlCommonGetFolderUsedQuota(name, p.dbHandle)
}
func (p *PGSQLProvider) getGroups(limit, offset int, order string, minimal bool) ([]Group, error) {
return sqlCommonGetGroups(limit, offset, order, minimal, p.dbHandle)
}
func (p *PGSQLProvider) getGroupsWithNames(names []string) ([]Group, error) {
return sqlCommonGetGroupsWithNames(names, p.dbHandle)
}
func (p *PGSQLProvider) getUsersInGroups(names []string) ([]string, error) {
return sqlCommonGetUsersInGroups(names, p.dbHandle)
}
func (p *PGSQLProvider) groupExists(name string) (Group, error) {
return sqlCommonGetGroupByName(name, p.dbHandle)
}
func (p *PGSQLProvider) addGroup(group *Group) error {
return sqlCommonAddGroup(group, p.dbHandle)
}
func (p *PGSQLProvider) updateGroup(group *Group) error {
return sqlCommonUpdateGroup(group, p.dbHandle)
}
func (p *PGSQLProvider) deleteGroup(group Group) error {
return sqlCommonDeleteGroup(group, p.dbHandle)
}
func (p *PGSQLProvider) dumpGroups() ([]Group, error) {
return sqlCommonDumpGroups(p.dbHandle)
}
func (p *PGSQLProvider) adminExists(username string) (Admin, error) {
return sqlCommonGetAdminByUsername(username, p.dbHandle)
}
func (p *PGSQLProvider) addAdmin(admin *Admin) error {
return sqlCommonAddAdmin(admin, p.dbHandle)
}
func (p *PGSQLProvider) updateAdmin(admin *Admin) error {
return sqlCommonUpdateAdmin(admin, p.dbHandle)
}
func (p *PGSQLProvider) deleteAdmin(admin Admin) error {
return sqlCommonDeleteAdmin(admin, p.dbHandle)
}
func (p *PGSQLProvider) getAdmins(limit int, offset int, order string) ([]Admin, error) {
return sqlCommonGetAdmins(limit, offset, order, p.dbHandle)
}
func (p *PGSQLProvider) dumpAdmins() ([]Admin, error) {
return sqlCommonDumpAdmins(p.dbHandle)
}
func (p *PGSQLProvider) validateAdminAndPass(username, password, ip string) (Admin, error) {
return sqlCommonValidateAdminAndPass(username, password, ip, p.dbHandle)
}
func (p *PGSQLProvider) apiKeyExists(keyID string) (APIKey, error) {
return sqlCommonGetAPIKeyByID(keyID, p.dbHandle)
}
func (p *PGSQLProvider) addAPIKey(apiKey *APIKey) error {
return sqlCommonAddAPIKey(apiKey, p.dbHandle)
}
func (p *PGSQLProvider) updateAPIKey(apiKey *APIKey) error {
return sqlCommonUpdateAPIKey(apiKey, p.dbHandle)
}
func (p *PGSQLProvider) deleteAPIKey(apiKey APIKey) error {
return sqlCommonDeleteAPIKey(apiKey, p.dbHandle)
}
func (p *PGSQLProvider) getAPIKeys(limit int, offset int, order string) ([]APIKey, error) {
return sqlCommonGetAPIKeys(limit, offset, order, p.dbHandle)
}
func (p *PGSQLProvider) dumpAPIKeys() ([]APIKey, error) {
return sqlCommonDumpAPIKeys(p.dbHandle)
}
func (p *PGSQLProvider) updateAPIKeyLastUse(keyID string) error {
return sqlCommonUpdateAPIKeyLastUse(keyID, p.dbHandle)
}
func (p *PGSQLProvider) shareExists(shareID, username string) (Share, error) {
return sqlCommonGetShareByID(shareID, username, p.dbHandle)
}
func (p *PGSQLProvider) addShare(share *Share) error {
return sqlCommonAddShare(share, p.dbHandle)
}
func (p *PGSQLProvider) updateShare(share *Share) error {
return sqlCommonUpdateShare(share, p.dbHandle)
}
func (p *PGSQLProvider) deleteShare(share Share) error {
return sqlCommonDeleteShare(share, p.dbHandle)
}
func (p *PGSQLProvider) getShares(limit int, offset int, order, username string) ([]Share, error) {
return sqlCommonGetShares(limit, offset, order, username, p.dbHandle)
}
func (p *PGSQLProvider) dumpShares() ([]Share, error) {
return sqlCommonDumpShares(p.dbHandle)
}
func (p *PGSQLProvider) updateShareLastUse(shareID string, numTokens int) error {
return sqlCommonUpdateShareLastUse(shareID, numTokens, p.dbHandle)
}
func (p *PGSQLProvider) getDefenderHosts(from int64, limit int) ([]DefenderEntry, error) {
return sqlCommonGetDefenderHosts(from, limit, p.dbHandle)
}
func (p *PGSQLProvider) getDefenderHostByIP(ip string, from int64) (DefenderEntry, error) {
return sqlCommonGetDefenderHostByIP(ip, from, p.dbHandle)
}
func (p *PGSQLProvider) isDefenderHostBanned(ip string) (DefenderEntry, error) {
return sqlCommonIsDefenderHostBanned(ip, p.dbHandle)
}
func (p *PGSQLProvider) updateDefenderBanTime(ip string, minutes int) error {
return sqlCommonDefenderIncrementBanTime(ip, minutes, p.dbHandle)
}
func (p *PGSQLProvider) deleteDefenderHost(ip string) error {
return sqlCommonDeleteDefenderHost(ip, p.dbHandle)
}
func (p *PGSQLProvider) addDefenderEvent(ip string, score int) error {
return sqlCommonAddDefenderHostAndEvent(ip, score, p.dbHandle)
}
func (p *PGSQLProvider) setDefenderBanTime(ip string, banTime int64) error {
return sqlCommonSetDefenderBanTime(ip, banTime, p.dbHandle)
}
func (p *PGSQLProvider) cleanupDefender(from int64) error {
return sqlCommonDefenderCleanup(from, p.dbHandle)
}
func (p *PGSQLProvider) addActiveTransfer(transfer ActiveTransfer) error {
return sqlCommonAddActiveTransfer(transfer, p.dbHandle)
}
func (p *PGSQLProvider) updateActiveTransferSizes(ulSize, dlSize, transferID int64, connectionID string) error {
return sqlCommonUpdateActiveTransferSizes(ulSize, dlSize, transferID, connectionID, p.dbHandle)
}
func (p *PGSQLProvider) removeActiveTransfer(transferID int64, connectionID string) error {
return sqlCommonRemoveActiveTransfer(transferID, connectionID, p.dbHandle)
}
func (p *PGSQLProvider) cleanupActiveTransfers(before time.Time) error {
return sqlCommonCleanupActiveTransfers(before, p.dbHandle)
}
func (p *PGSQLProvider) getActiveTransfers(from time.Time) ([]ActiveTransfer, error) {
return sqlCommonGetActiveTransfers(from, p.dbHandle)
}
func (p *PGSQLProvider) addSharedSession(session Session) error {
return sqlCommonAddSession(session, p.dbHandle)
}
func (p *PGSQLProvider) deleteSharedSession(key string) error {
return sqlCommonDeleteSession(key, p.dbHandle)
}
func (p *PGSQLProvider) getSharedSession(key string) (Session, error) {
return sqlCommonGetSession(key, p.dbHandle)
}
func (p *PGSQLProvider) cleanupSharedSessions(sessionType SessionType, before int64) error {
return sqlCommonCleanupSessions(sessionType, before, p.dbHandle)
}
func (p *PGSQLProvider) close() error {
return p.dbHandle.Close()
}
func (p *PGSQLProvider) reloadConfig() error {
return nil
}
// initializeDatabase creates the initial database structure
func (p *PGSQLProvider) initializeDatabase() error {
dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, false)
if err == nil && dbVersion.Version > 0 {
return ErrNoInitRequired
}
if errors.Is(err, sql.ErrNoRows) {
return errSchemaVersionEmpty
}
logger.InfoToConsole("creating initial database schema, version 15")
providerLog(logger.LevelInfo, "creating initial database schema, version 15")
initialSQL := strings.ReplaceAll(pgsqlInitial, "{{schema_version}}", sqlTableSchemaVersion)
initialSQL = strings.ReplaceAll(initialSQL, "{{admins}}", sqlTableAdmins)
initialSQL = strings.ReplaceAll(initialSQL, "{{folders}}", sqlTableFolders)
initialSQL = strings.ReplaceAll(initialSQL, "{{users}}", sqlTableUsers)
initialSQL = strings.ReplaceAll(initialSQL, "{{folders_mapping}}", sqlTableFoldersMapping)
initialSQL = strings.ReplaceAll(initialSQL, "{{api_keys}}", sqlTableAPIKeys)
initialSQL = strings.ReplaceAll(initialSQL, "{{shares}}", sqlTableShares)
initialSQL = strings.ReplaceAll(initialSQL, "{{defender_events}}", sqlTableDefenderEvents)
initialSQL = strings.ReplaceAll(initialSQL, "{{defender_hosts}}", sqlTableDefenderHosts)
initialSQL = strings.ReplaceAll(initialSQL, "{{prefix}}", config.SQLTablesPrefix)
if config.Driver == CockroachDataProviderName {
// Cockroach does not support deferrable constraint validation, we don't need them,
// we keep these definitions for the PostgreSQL driver to avoid changes for users
// upgrading from old SFTPGo versions
initialSQL = strings.ReplaceAll(initialSQL, "DEFERRABLE INITIALLY DEFERRED", "")
}
return sqlCommonExecSQLAndUpdateDBVersion(p.dbHandle, []string{initialSQL}, 15, true)
}
func (p *PGSQLProvider) migrateDatabase() error { //nolint:dupl
dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, true)
if err != nil {
return err
}
switch version := dbVersion.Version; {
case version == sqlDatabaseVersion:
providerLog(logger.LevelDebug, "sql database is up to date, current version: %v", version)
return ErrNoInitRequired
case version < 15:
err = fmt.Errorf("database version %v is too old, please see the upgrading docs", version)
providerLog(logger.LevelError, "%v", err)
logger.ErrorToConsole("%v", err)
return err
case version == 15:
return updatePGSQLDatabaseFromV15(p.dbHandle)
case version == 16:
return updatePGSQLDatabaseFromV16(p.dbHandle)
case version == 17:
return updatePGSQLDatabaseFromV17(p.dbHandle)
case version == 18:
return updatePGSQLDatabaseFromV18(p.dbHandle)
default:
if version > sqlDatabaseVersion {
providerLog(logger.LevelError, "database version %v is newer than the supported one: %v", version,
sqlDatabaseVersion)
logger.WarnToConsole("database version %v is newer than the supported one: %v", version,
sqlDatabaseVersion)
return nil
}
return fmt.Errorf("database version not handled: %v", version)
}
}
func (p *PGSQLProvider) revertDatabase(targetVersion int) error {
dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, true)
if err != nil {
return err
}
if dbVersion.Version == targetVersion {
return errors.New("current version match target version, nothing to do")
}
switch dbVersion.Version {
case 16:
return downgradePGSQLDatabaseFromV16(p.dbHandle)
case 17:
return downgradePGSQLDatabaseFromV17(p.dbHandle)
case 18:
return downgradePGSQLDatabaseFromV18(p.dbHandle)
case 19:
return downgradePGSQLDatabaseFromV19(p.dbHandle)
default:
return fmt.Errorf("database version not handled: %v", dbVersion.Version)
}
}
func (p *PGSQLProvider) resetDatabase() error {
sql := sqlReplaceAll(pgsqlResetSQL)
return sqlCommonExecSQLAndUpdateDBVersion(p.dbHandle, []string{sql}, 0, false)
}
func updatePGSQLDatabaseFromV15(dbHandle *sql.DB) error {
if err := updatePGSQLDatabaseFrom15To16(dbHandle); err != nil {
return err
}
return updatePGSQLDatabaseFromV16(dbHandle)
}
func updatePGSQLDatabaseFromV16(dbHandle *sql.DB) error {
if err := updatePGSQLDatabaseFrom16To17(dbHandle); err != nil {
return err
}
return updatePGSQLDatabaseFromV17(dbHandle)
}
func updatePGSQLDatabaseFromV17(dbHandle *sql.DB) error {
if err := updatePGSQLDatabaseFrom17To18(dbHandle); err != nil {
return err
}
return updatePGSQLDatabaseFromV18(dbHandle)
}
func updatePGSQLDatabaseFromV18(dbHandle *sql.DB) error {
return updatePGSQLDatabaseFrom18To19(dbHandle)
}
func downgradePGSQLDatabaseFromV16(dbHandle *sql.DB) error {
return downgradePGSQLDatabaseFrom16To15(dbHandle)
}
func downgradePGSQLDatabaseFromV17(dbHandle *sql.DB) error {
if err := downgradePGSQLDatabaseFrom17To16(dbHandle); err != nil {
return err
}
return downgradePGSQLDatabaseFromV16(dbHandle)
}
func downgradePGSQLDatabaseFromV18(dbHandle *sql.DB) error {
if err := downgradePGSQLDatabaseFrom18To17(dbHandle); err != nil {
return err
}
return downgradePGSQLDatabaseFromV17(dbHandle)
}
func downgradePGSQLDatabaseFromV19(dbHandle *sql.DB) error {
if err := downgradePGSQLDatabaseFrom19To18(dbHandle); err != nil {
return err
}
return downgradePGSQLDatabaseFromV18(dbHandle)
}
func updatePGSQLDatabaseFrom15To16(dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 15 -> 16")
providerLog(logger.LevelInfo, "updating database version: 15 -> 16")
sql := strings.ReplaceAll(pgsqlV16SQL, "{{users}}", sqlTableUsers)
sql = strings.ReplaceAll(sql, "{{active_transfers}}", sqlTableActiveTransfers)
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
if config.Driver == CockroachDataProviderName {
// Cockroach does not allow to run this schema migration within a transaction
ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout)
defer cancel()
for _, q := range strings.Split(sql, ";") {
if strings.TrimSpace(q) == "" {
continue
}
_, err := dbHandle.ExecContext(ctx, q)
if err != nil {
return err
}
}
return sqlCommonUpdateDatabaseVersion(ctx, dbHandle, 16)
}
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 16, true)
}
func updatePGSQLDatabaseFrom16To17(dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 16 -> 17")
providerLog(logger.LevelInfo, "updating database version: 16 -> 17")
sql := pgsqlV17SQL
if config.Driver == CockroachDataProviderName {
sql = strings.ReplaceAll(sql, `ALTER TABLE "{{folders_mapping}}" DROP CONSTRAINT "{{prefix}}unique_mapping";`,
`DROP INDEX "{{prefix}}unique_mapping" CASCADE;`)
}
sql = strings.ReplaceAll(sql, "{{groups}}", sqlTableGroups)
sql = strings.ReplaceAll(sql, "{{users}}", sqlTableUsers)
sql = strings.ReplaceAll(sql, "{{folders}}", sqlTableFolders)
sql = strings.ReplaceAll(sql, "{{folders_mapping}}", sqlTableFoldersMapping)
sql = strings.ReplaceAll(sql, "{{users_folders_mapping}}", sqlTableUsersFoldersMapping)
sql = strings.ReplaceAll(sql, "{{users_groups_mapping}}", sqlTableUsersGroupsMapping)
sql = strings.ReplaceAll(sql, "{{groups_folders_mapping}}", sqlTableGroupsFoldersMapping)
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 17, true)
}
func updatePGSQLDatabaseFrom17To18(dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 17 -> 18")
providerLog(logger.LevelInfo, "updating database version: 17 -> 18")
if err := importGCSCredentials(); err != nil {
return err
}
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, nil, 18, true)
}
func updatePGSQLDatabaseFrom18To19(dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 18 -> 19")
providerLog(logger.LevelInfo, "updating database version: 18 -> 19")
sql := strings.ReplaceAll(pgsqlV19SQL, "{{shared_sessions}}", sqlTableSharedSessions)
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 19, true)
}
func downgradePGSQLDatabaseFrom16To15(dbHandle *sql.DB) error {
logger.InfoToConsole("downgrading database version: 16 -> 15")
providerLog(logger.LevelInfo, "downgrading database version: 16 -> 15")
sql := strings.ReplaceAll(pgsqlV16DownSQL, "{{users}}", sqlTableUsers)
sql = strings.ReplaceAll(sql, "{{active_transfers}}", sqlTableActiveTransfers)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 15, false)
}
func downgradePGSQLDatabaseFrom17To16(dbHandle *sql.DB) error {
logger.InfoToConsole("downgrading database version: 17 -> 16")
providerLog(logger.LevelInfo, "downgrading database version: 17 -> 16")
sql := pgsqlV17DownSQL
if config.Driver == CockroachDataProviderName {
sql = strings.ReplaceAll(sql, `ALTER TABLE "{{users_folders_mapping}}" DROP CONSTRAINT "{{prefix}}unique_user_folder_mapping";`,
`DROP INDEX "{{prefix}}unique_user_folder_mapping" CASCADE;`)
}
sql = strings.ReplaceAll(sql, "{{groups}}", sqlTableGroups)
sql = strings.ReplaceAll(sql, "{{users}}", sqlTableUsers)
sql = strings.ReplaceAll(sql, "{{folders}}", sqlTableFolders)
sql = strings.ReplaceAll(sql, "{{folders_mapping}}", sqlTableFoldersMapping)
sql = strings.ReplaceAll(sql, "{{users_folders_mapping}}", sqlTableUsersFoldersMapping)
sql = strings.ReplaceAll(sql, "{{users_groups_mapping}}", sqlTableUsersGroupsMapping)
sql = strings.ReplaceAll(sql, "{{groups_folders_mapping}}", sqlTableGroupsFoldersMapping)
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 16, false)
}
func downgradePGSQLDatabaseFrom18To17(dbHandle *sql.DB) error {
logger.InfoToConsole("downgrading database version: 18 -> 17")
providerLog(logger.LevelInfo, "downgrading database version: 18 -> 17")
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, nil, 17, false)
}
func downgradePGSQLDatabaseFrom19To18(dbHandle *sql.DB) error {
logger.InfoToConsole("downgrading database version: 19 -> 18")
providerLog(logger.LevelInfo, "downgrading database version: 19 -> 18")
sql := strings.ReplaceAll(pgsqlV19DownSQL, "{{shared_sessions}}", sqlTableSharedSessions)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 18, false)
}

View file

@ -1,18 +0,0 @@
//go:build nopgsql
// +build nopgsql
package dataprovider
import (
"errors"
"github.com/drakkan/sftpgo/v2/version"
)
func init() {
version.AddFeature("-pgsql")
}
func initializePGSQLProvider() error {
return errors.New("PostgreSQL disabled at build time")
}

View file

@ -1,96 +0,0 @@
package dataprovider
import (
"fmt"
"sync/atomic"
"time"
"github.com/robfig/cron/v3"
"github.com/drakkan/sftpgo/v2/logger"
"github.com/drakkan/sftpgo/v2/metric"
"github.com/drakkan/sftpgo/v2/util"
)
var (
scheduler *cron.Cron
lastCachesUpdate int64
// used for bolt and memory providers, so we avoid iterating all users
// to find recently modified ones
lastUserUpdate int64
)
func stopScheduler() {
if scheduler != nil {
scheduler.Stop()
scheduler = nil
}
}
func startScheduler() error {
stopScheduler()
scheduler = cron.New()
_, err := scheduler.AddFunc("@every 30s", checkDataprovider)
if err != nil {
return fmt.Errorf("unable to schedule dataprovider availability check: %w", err)
}
if config.AutoBackup.Enabled {
spec := fmt.Sprintf("0 %v * * %v", config.AutoBackup.Hour, config.AutoBackup.DayOfWeek)
_, err = scheduler.AddFunc(spec, config.doBackup)
if err != nil {
return fmt.Errorf("unable to schedule auto backup: %w", err)
}
}
err = addScheduledCacheUpdates()
if err != nil {
return err
}
scheduler.Start()
return nil
}
func addScheduledCacheUpdates() error {
lastCachesUpdate = util.GetTimeAsMsSinceEpoch(time.Now())
_, err := scheduler.AddFunc("@every 10m", checkCacheUpdates)
if err != nil {
return fmt.Errorf("unable to schedule cache updates: %w", err)
}
return nil
}
func checkDataprovider() {
err := provider.checkAvailability()
if err != nil {
providerLog(logger.LevelError, "check availability error: %v", err)
}
metric.UpdateDataProviderAvailability(err)
}
func checkCacheUpdates() {
providerLog(logger.LevelDebug, "start caches check, update time %v", util.GetTimeFromMsecSinceEpoch(lastCachesUpdate))
checkTime := util.GetTimeAsMsSinceEpoch(time.Now())
users, err := provider.getRecentlyUpdatedUsers(lastCachesUpdate)
if err != nil {
providerLog(logger.LevelError, "unable to get recently updated users: %v", err)
return
}
for _, user := range users {
providerLog(logger.LevelDebug, "invalidate caches for user %#v", user.Username)
webDAVUsersCache.swap(&user)
cachedPasswords.Remove(user.Username)
}
lastCachesUpdate = checkTime
providerLog(logger.LevelDebug, "end caches check, new update time %v", util.GetTimeFromMsecSinceEpoch(lastCachesUpdate))
}
func setLastUserUpdate() {
atomic.StoreInt64(&lastUserUpdate, util.GetTimeAsMsSinceEpoch(time.Now()))
}
func getLastUserUpdate() int64 {
return atomic.LoadInt64(&lastUserUpdate)
}

View file

@ -1,34 +0,0 @@
package dataprovider
import (
"errors"
"fmt"
)
// SessionType defines the supported session types
type SessionType int
// Supported session types
const (
SessionTypeOIDCAuth SessionType = iota + 1
SessionTypeOIDCToken
SessionTypeResetCode
)
// Session defines a shared session persisted in the data provider
type Session struct {
Key string
Data any
Type SessionType
Timestamp int64
}
func (s *Session) validate() error {
if s.Key == "" {
return errors.New("unable to save a session with an empty key")
}
if s.Type < SessionTypeOIDCAuth || s.Type > SessionTypeResetCode {
return fmt.Errorf("invalid session type: %v", s.Type)
}
return nil
}

View file

@ -1,18 +0,0 @@
//go:build nosqlite
// +build nosqlite
package dataprovider
import (
"errors"
"github.com/drakkan/sftpgo/v2/version"
)
func init() {
version.AddFeature("-sqlite")
}
func initializeSQLiteProvider(basePath string) error {
return errors.New("SQLite disabled at build time")
}

View file

@ -1,712 +0,0 @@
package dataprovider
import (
"fmt"
"strconv"
"strings"
"github.com/drakkan/sftpgo/v2/vfs"
)
const (
selectUserFields = "id,username,password,public_keys,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions,used_quota_size," +
"used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth,expiration_date,last_login,status,filters,filesystem," +
"additional_info,description,email,created_at,updated_at,upload_data_transfer,download_data_transfer,total_data_transfer," +
"used_upload_data_transfer,used_download_data_transfer"
selectFolderFields = "id,path,used_quota_size,used_quota_files,last_quota_update,name,description,filesystem"
selectAdminFields = "id,username,password,status,email,permissions,filters,additional_info,description,created_at,updated_at,last_login"
selectAPIKeyFields = "key_id,name,api_key,scope,created_at,updated_at,last_use_at,expires_at,description,user_id,admin_id"
selectShareFields = "s.share_id,s.name,s.description,s.scope,s.paths,u.username,s.created_at,s.updated_at,s.last_use_at," +
"s.expires_at,s.password,s.max_tokens,s.used_tokens,s.allow_from"
selectGroupFields = "id,name,description,created_at,updated_at,user_settings"
)
func getSQLPlaceholders() []string {
var placeholders []string
for i := 1; i <= 50; i++ {
if config.Driver == PGSQLDataProviderName || config.Driver == CockroachDataProviderName {
placeholders = append(placeholders, fmt.Sprintf("$%v", i))
} else {
placeholders = append(placeholders, "?")
}
}
return placeholders
}
func getAddSessionQuery() string {
if config.Driver == MySQLDataProviderName {
return fmt.Sprintf("INSERT INTO %s (`key`,`data`,`type`,`timestamp`) VALUES (%s,%s,%s,%s) "+
"ON DUPLICATE KEY UPDATE `data`=VALUES(`data`), `timestamp`=VALUES(`timestamp`)",
sqlTableSharedSessions, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3])
}
return fmt.Sprintf(`INSERT INTO %s (key,data,type,timestamp) VALUES (%s,%s,%s,%s) ON CONFLICT(key) DO UPDATE SET data=
EXCLUDED.data, timestamp=EXCLUDED.timestamp`,
sqlTableSharedSessions, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3])
}
func getDeleteSessionQuery() string {
if config.Driver == MySQLDataProviderName {
return fmt.Sprintf("DELETE FROM %s WHERE `key` = %s", sqlTableSharedSessions, sqlPlaceholders[0])
}
return fmt.Sprintf(`DELETE FROM %s WHERE key = %s`, sqlTableSharedSessions, sqlPlaceholders[0])
}
func getSessionQuery() string {
if config.Driver == MySQLDataProviderName {
return fmt.Sprintf("SELECT `key`,`data`,`type`,`timestamp` FROM %s WHERE `key` = %s", sqlTableSharedSessions,
sqlPlaceholders[0])
}
return fmt.Sprintf(`SELECT key,data,type,timestamp FROM %s WHERE key = %s`, sqlTableSharedSessions,
sqlPlaceholders[0])
}
func getCleanupSessionsQuery() string {
return fmt.Sprintf(`DELETE from %s WHERE type = %s AND timestamp < %s`,
sqlTableSharedSessions, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getAddDefenderHostQuery() string {
if config.Driver == MySQLDataProviderName {
return fmt.Sprintf("INSERT INTO %v (`ip`,`updated_at`,`ban_time`) VALUES (%v,%v,0) ON DUPLICATE KEY UPDATE `updated_at`=VALUES(`updated_at`)",
sqlTableDefenderHosts, sqlPlaceholders[0], sqlPlaceholders[1])
}
return fmt.Sprintf(`INSERT INTO %v (ip,updated_at,ban_time) VALUES (%v,%v,0) ON CONFLICT (ip) DO UPDATE SET updated_at = EXCLUDED.updated_at RETURNING id`,
sqlTableDefenderHosts, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getAddDefenderEventQuery() string {
return fmt.Sprintf(`INSERT INTO %v (date_time,score,host_id) VALUES (%v,%v,(SELECT id from %v WHERE ip = %v))`,
sqlTableDefenderEvents, sqlPlaceholders[0], sqlPlaceholders[1], sqlTableDefenderHosts, sqlPlaceholders[2])
}
func getDefenderHostsQuery() string {
return fmt.Sprintf(`SELECT id,ip,ban_time FROM %v WHERE updated_at >= %v OR ban_time > 0 ORDER BY updated_at DESC LIMIT %v`,
sqlTableDefenderHosts, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getDefenderHostQuery() string {
return fmt.Sprintf(`SELECT id,ip,ban_time FROM %v WHERE ip = %v AND (updated_at >= %v OR ban_time > 0)`,
sqlTableDefenderHosts, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getDefenderEventsQuery(hostIDS []int64) string {
var sb strings.Builder
for _, hID := range hostIDS {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(strconv.FormatInt(hID, 10))
}
if sb.Len() > 0 {
sb.WriteString(")")
} else {
sb.WriteString("(0)")
}
return fmt.Sprintf(`SELECT host_id,SUM(score) FROM %v WHERE date_time >= %v AND host_id IN %v GROUP BY host_id`,
sqlTableDefenderEvents, sqlPlaceholders[0], sb.String())
}
func getDefenderIsHostBannedQuery() string {
return fmt.Sprintf(`SELECT id FROM %v WHERE ip = %v AND ban_time >= %v`,
sqlTableDefenderHosts, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getDefenderIncrementBanTimeQuery() string {
return fmt.Sprintf(`UPDATE %v SET ban_time = ban_time + %v WHERE ip = %v`,
sqlTableDefenderHosts, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getDefenderSetBanTimeQuery() string {
return fmt.Sprintf(`UPDATE %v SET ban_time = %v WHERE ip = %v`,
sqlTableDefenderHosts, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getDeleteDefenderHostQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE ip = %v`, sqlTableDefenderHosts, sqlPlaceholders[0])
}
func getDefenderHostsCleanupQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE ban_time < %v AND NOT EXISTS (
SELECT id FROM %v WHERE %v.host_id = %v.id AND %v.date_time > %v)`,
sqlTableDefenderHosts, sqlPlaceholders[0], sqlTableDefenderEvents, sqlTableDefenderEvents, sqlTableDefenderHosts,
sqlTableDefenderEvents, sqlPlaceholders[1])
}
func getDefenderEventsCleanupQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE date_time < %v`, sqlTableDefenderEvents, sqlPlaceholders[0])
}
func getGroupByNameQuery() string {
return fmt.Sprintf(`SELECT %s FROM %s WHERE name = %s`, selectGroupFields, sqlTableGroups, sqlPlaceholders[0])
}
func getGroupsQuery(order string, minimal bool) string {
var fieldSelection string
if minimal {
fieldSelection = "id,name"
} else {
fieldSelection = selectGroupFields
}
return fmt.Sprintf(`SELECT %s FROM %s ORDER BY name %s LIMIT %v OFFSET %v`, fieldSelection, sqlTableGroups,
order, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getGroupsWithNamesQuery(numArgs int) string {
var sb strings.Builder
for idx := 0; idx < numArgs; idx++ {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(sqlPlaceholders[idx])
}
if sb.Len() > 0 {
sb.WriteString(")")
} else {
sb.WriteString("('')")
}
return fmt.Sprintf(`SELECT %s FROM %s WHERE name in %s`, selectGroupFields, sqlTableGroups, sb.String())
}
func getUsersInGroupsQuery(numArgs int) string {
var sb strings.Builder
for idx := 0; idx < numArgs; idx++ {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(sqlPlaceholders[idx])
}
if sb.Len() > 0 {
sb.WriteString(")")
} else {
sb.WriteString("('')")
}
return fmt.Sprintf(`SELECT username FROM %s WHERE id IN (SELECT user_id from %s WHERE group_id IN (SELECT id FROM %s WHERE name IN (%s)))`,
sqlTableUsers, sqlTableUsersGroupsMapping, sqlTableGroups, sb.String())
}
func getDumpGroupsQuery() string {
return fmt.Sprintf(`SELECT %s FROM %s`, selectGroupFields, sqlTableGroups)
}
func getAddGroupQuery() string {
return fmt.Sprintf(`INSERT INTO %s (name,description,created_at,updated_at,user_settings)
VALUES (%v,%v,%v,%v,%v)`, sqlTableGroups, sqlPlaceholders[0], sqlPlaceholders[1],
sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4])
}
func getUpdateGroupQuery() string {
return fmt.Sprintf(`UPDATE %s SET description=%v,user_settings=%v,updated_at=%v
WHERE name = %s`, sqlTableGroups, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2],
sqlPlaceholders[3])
}
func getDeleteGroupQuery() string {
return fmt.Sprintf(`DELETE FROM %s WHERE name = %s`, sqlTableGroups, sqlPlaceholders[0])
}
func getAdminByUsernameQuery() string {
return fmt.Sprintf(`SELECT %v FROM %v WHERE username = %v`, selectAdminFields, sqlTableAdmins, sqlPlaceholders[0])
}
func getAdminsQuery(order string) string {
return fmt.Sprintf(`SELECT %v FROM %v ORDER BY username %v LIMIT %v OFFSET %v`, selectAdminFields, sqlTableAdmins,
order, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getDumpAdminsQuery() string {
return fmt.Sprintf(`SELECT %v FROM %v`, selectAdminFields, sqlTableAdmins)
}
func getAddAdminQuery() string {
return fmt.Sprintf(`INSERT INTO %v (username,password,status,email,permissions,filters,additional_info,description,created_at,updated_at,last_login)
VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0)`, sqlTableAdmins, sqlPlaceholders[0], sqlPlaceholders[1],
sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7],
sqlPlaceholders[8], sqlPlaceholders[9])
}
func getUpdateAdminQuery() string {
return fmt.Sprintf(`UPDATE %v SET password=%v,status=%v,email=%v,permissions=%v,filters=%v,additional_info=%v,description=%v,updated_at=%v
WHERE username = %v`, sqlTableAdmins, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2],
sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8])
}
func getDeleteAdminQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE username = %v`, sqlTableAdmins, sqlPlaceholders[0])
}
func getShareByIDQuery(filterUser bool) string {
if filterUser {
return fmt.Sprintf(`SELECT %v FROM %v s INNER JOIN %v u ON s.user_id = u.id WHERE s.share_id = %v AND u.username = %v`,
selectShareFields, sqlTableShares, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1])
}
return fmt.Sprintf(`SELECT %v FROM %v s INNER JOIN %v u ON s.user_id = u.id WHERE s.share_id = %v`,
selectShareFields, sqlTableShares, sqlTableUsers, sqlPlaceholders[0])
}
func getSharesQuery(order string) string {
return fmt.Sprintf(`SELECT %v FROM %v s INNER JOIN %v u ON s.user_id = u.id WHERE u.username = %v ORDER BY s.share_id %v LIMIT %v OFFSET %v`,
selectShareFields, sqlTableShares, sqlTableUsers, sqlPlaceholders[0], order, sqlPlaceholders[1], sqlPlaceholders[2])
}
func getDumpSharesQuery() string {
return fmt.Sprintf(`SELECT %v FROM %v s INNER JOIN %v u ON s.user_id = u.id`,
selectShareFields, sqlTableShares, sqlTableUsers)
}
func getAddShareQuery() string {
return fmt.Sprintf(`INSERT INTO %v (share_id,name,description,scope,paths,created_at,updated_at,last_use_at,
expires_at,password,max_tokens,used_tokens,allow_from,user_id) VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,%v)`,
sqlTableShares, sqlPlaceholders[0], sqlPlaceholders[1],
sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6],
sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9], sqlPlaceholders[10], sqlPlaceholders[11],
sqlPlaceholders[12], sqlPlaceholders[13])
}
func getUpdateShareRestoreQuery() string {
return fmt.Sprintf(`UPDATE %v SET name=%v,description=%v,scope=%v,paths=%v,created_at=%v,updated_at=%v,
last_use_at=%v,expires_at=%v,password=%v,max_tokens=%v,used_tokens=%v,allow_from=%v,user_id=%v WHERE share_id = %v`, sqlTableShares,
sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4],
sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9],
sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13])
}
func getUpdateShareQuery() string {
return fmt.Sprintf(`UPDATE %v SET name=%v,description=%v,scope=%v,paths=%v,updated_at=%v,expires_at=%v,
password=%v,max_tokens=%v,allow_from=%v,user_id=%v WHERE share_id = %v`, sqlTableShares,
sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4],
sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9],
sqlPlaceholders[10])
}
func getDeleteShareQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE share_id = %v`, sqlTableShares, sqlPlaceholders[0])
}
func getAPIKeyByIDQuery() string {
return fmt.Sprintf(`SELECT %v FROM %v WHERE key_id = %v`, selectAPIKeyFields, sqlTableAPIKeys, sqlPlaceholders[0])
}
func getAPIKeysQuery(order string) string {
return fmt.Sprintf(`SELECT %v FROM %v ORDER BY key_id %v LIMIT %v OFFSET %v`, selectAPIKeyFields, sqlTableAPIKeys,
order, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getDumpAPIKeysQuery() string {
return fmt.Sprintf(`SELECT %v FROM %v`, selectAPIKeyFields, sqlTableAPIKeys)
}
func getAddAPIKeyQuery() string {
return fmt.Sprintf(`INSERT INTO %v (key_id,name,api_key,scope,created_at,updated_at,last_use_at,expires_at,description,user_id,admin_id)
VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,%v)`, sqlTableAPIKeys, sqlPlaceholders[0], sqlPlaceholders[1],
sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6],
sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9], sqlPlaceholders[10])
}
func getUpdateAPIKeyQuery() string {
return fmt.Sprintf(`UPDATE %v SET name=%v,scope=%v,expires_at=%v,user_id=%v,admin_id=%v,description=%v,updated_at=%v
WHERE key_id = %v`, sqlTableAPIKeys, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2],
sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7])
}
func getDeleteAPIKeyQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE key_id = %v`, sqlTableAPIKeys, sqlPlaceholders[0])
}
func getRelatedUsersForAPIKeysQuery(apiKeys []APIKey) string {
var sb strings.Builder
for _, k := range apiKeys {
if k.userID == 0 {
continue
}
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(strconv.FormatInt(k.userID, 10))
}
if sb.Len() > 0 {
sb.WriteString(")")
} else {
sb.WriteString("(0)")
}
return fmt.Sprintf(`SELECT id,username FROM %v WHERE id IN %v`, sqlTableUsers, sb.String())
}
func getRelatedAdminsForAPIKeysQuery(apiKeys []APIKey) string {
var sb strings.Builder
for _, k := range apiKeys {
if k.adminID == 0 {
continue
}
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(strconv.FormatInt(k.adminID, 10))
}
if sb.Len() > 0 {
sb.WriteString(")")
} else {
sb.WriteString("(0)")
}
return fmt.Sprintf(`SELECT id,username FROM %v WHERE id IN %v`, sqlTableAdmins, sb.String())
}
func getUserByUsernameQuery() string {
return fmt.Sprintf(`SELECT %v FROM %v WHERE username = %v`, selectUserFields, sqlTableUsers, sqlPlaceholders[0])
}
func getUsersQuery(order string) string {
return fmt.Sprintf(`SELECT %v FROM %v ORDER BY username %v LIMIT %v OFFSET %v`, selectUserFields, sqlTableUsers,
order, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getUsersForQuotaCheckQuery(numArgs int) string {
var sb strings.Builder
for idx := 0; idx < numArgs; idx++ {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(sqlPlaceholders[idx])
}
if sb.Len() > 0 {
sb.WriteString(")")
}
return fmt.Sprintf(`SELECT id,username,quota_size,used_quota_size,total_data_transfer,upload_data_transfer,
download_data_transfer,used_upload_data_transfer,used_download_data_transfer,filters FROM %v WHERE username IN %v`,
sqlTableUsers, sb.String())
}
func getRecentlyUpdatedUsersQuery() string {
return fmt.Sprintf(`SELECT %v FROM %v WHERE updated_at >= %v`, selectUserFields, sqlTableUsers, sqlPlaceholders[0])
}
func getDumpUsersQuery() string {
return fmt.Sprintf(`SELECT %v FROM %v`, selectUserFields, sqlTableUsers)
}
func getDumpFoldersQuery() string {
return fmt.Sprintf(`SELECT %v FROM %v`, selectFolderFields, sqlTableFolders)
}
func getUpdateTransferQuotaQuery(reset bool) string {
if reset {
return fmt.Sprintf(`UPDATE %v SET used_upload_data_transfer = %v,used_download_data_transfer = %v,last_quota_update = %v
WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3])
}
return fmt.Sprintf(`UPDATE %v SET used_upload_data_transfer = used_upload_data_transfer + %v,
used_download_data_transfer = used_download_data_transfer + %v,last_quota_update = %v
WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3])
}
func getUpdateQuotaQuery(reset bool) string {
if reset {
return fmt.Sprintf(`UPDATE %v SET used_quota_size = %v,used_quota_files = %v,last_quota_update = %v
WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3])
}
return fmt.Sprintf(`UPDATE %v SET used_quota_size = used_quota_size + %v,used_quota_files = used_quota_files + %v,last_quota_update = %v
WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3])
}
func getSetUpdateAtQuery() string {
return fmt.Sprintf(`UPDATE %v SET updated_at = %v WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getUpdateLastLoginQuery() string {
return fmt.Sprintf(`UPDATE %v SET last_login = %v WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getUpdateAdminLastLoginQuery() string {
return fmt.Sprintf(`UPDATE %v SET last_login = %v WHERE username = %v`, sqlTableAdmins, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getUpdateAPIKeyLastUseQuery() string {
return fmt.Sprintf(`UPDATE %v SET last_use_at = %v WHERE key_id = %v`, sqlTableAPIKeys, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getUpdateShareLastUseQuery() string {
return fmt.Sprintf(`UPDATE %v SET last_use_at = %v, used_tokens = used_tokens +%v WHERE share_id = %v`,
sqlTableShares, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2])
}
func getQuotaQuery() string {
return fmt.Sprintf(`SELECT used_quota_size,used_quota_files,used_upload_data_transfer,
used_download_data_transfer FROM %v WHERE username = %v`,
sqlTableUsers, sqlPlaceholders[0])
}
func getAddUserQuery() string {
return fmt.Sprintf(`INSERT INTO %v (username,password,public_keys,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions,
used_quota_size,used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth,status,last_login,expiration_date,filters,
filesystem,additional_info,description,email,created_at,updated_at,upload_data_transfer,download_data_transfer,total_data_transfer,
used_upload_data_transfer,used_download_data_transfer)
VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0,0,0,%v,%v,%v,0,%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0,0)`,
sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4],
sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9],
sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13], sqlPlaceholders[14],
sqlPlaceholders[15], sqlPlaceholders[16], sqlPlaceholders[17], sqlPlaceholders[18], sqlPlaceholders[19],
sqlPlaceholders[20], sqlPlaceholders[21], sqlPlaceholders[22], sqlPlaceholders[23])
}
func getUpdateUserQuery() string {
return fmt.Sprintf(`UPDATE %v SET password=%v,public_keys=%v,home_dir=%v,uid=%v,gid=%v,max_sessions=%v,quota_size=%v,
quota_files=%v,permissions=%v,upload_bandwidth=%v,download_bandwidth=%v,status=%v,expiration_date=%v,filters=%v,filesystem=%v,
additional_info=%v,description=%v,email=%v,updated_at=%v,upload_data_transfer=%v,download_data_transfer=%v,
total_data_transfer=%v WHERE id = %v`,
sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4],
sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9],
sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13], sqlPlaceholders[14],
sqlPlaceholders[15], sqlPlaceholders[16], sqlPlaceholders[17], sqlPlaceholders[18], sqlPlaceholders[19],
sqlPlaceholders[20], sqlPlaceholders[21], sqlPlaceholders[22])
}
func getUpdateUserPasswordQuery() string {
return fmt.Sprintf(`UPDATE %v SET password=%v WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getDeleteUserQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE id = %v`, sqlTableUsers, sqlPlaceholders[0])
}
func getFolderByNameQuery() string {
return fmt.Sprintf(`SELECT %v FROM %v WHERE name = %v`, selectFolderFields, sqlTableFolders, sqlPlaceholders[0])
}
func getAddFolderQuery() string {
return fmt.Sprintf(`INSERT INTO %v (path,used_quota_size,used_quota_files,last_quota_update,name,description,filesystem)
VALUES (%v,%v,%v,%v,%v,%v,%v)`, sqlTableFolders, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2],
sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6])
}
func getUpdateFolderQuery() string {
return fmt.Sprintf(`UPDATE %v SET path=%v,description=%v,filesystem=%v WHERE name = %v`, sqlTableFolders, sqlPlaceholders[0],
sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3])
}
func getDeleteFolderQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE id = %v`, sqlTableFolders, sqlPlaceholders[0])
}
func getUpsertFolderQuery() string {
if config.Driver == MySQLDataProviderName {
return fmt.Sprintf("INSERT INTO %v (`path`,`used_quota_size`,`used_quota_files`,`last_quota_update`,`name`,"+
"`description`,`filesystem`) VALUES (%v,%v,%v,%v,%v,%v,%v) ON DUPLICATE KEY UPDATE "+
"`path`=VALUES(`path`),`description`=VALUES(`description`),`filesystem`=VALUES(`filesystem`)",
sqlTableFolders, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4],
sqlPlaceholders[5], sqlPlaceholders[6])
}
return fmt.Sprintf(`INSERT INTO %v (path,used_quota_size,used_quota_files,last_quota_update,name,description,filesystem)
VALUES (%v,%v,%v,%v,%v,%v,%v) ON CONFLICT (name) DO UPDATE SET path = EXCLUDED.path,description=EXCLUDED.description,
filesystem=EXCLUDED.filesystem`, sqlTableFolders, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2],
sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6])
}
func getClearUserGroupMappingQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE user_id = (SELECT id FROM %v WHERE username = %v)`, sqlTableUsersGroupsMapping,
sqlTableUsers, sqlPlaceholders[0])
}
func getAddUserGroupMappingQuery() string {
return fmt.Sprintf(`INSERT INTO %v (user_id,group_id,group_type) VALUES ((SELECT id FROM %v WHERE username = %v),
(SELECT id FROM %v WHERE name = %v),%v)`,
sqlTableUsersGroupsMapping, sqlTableUsers, sqlPlaceholders[0], sqlTableGroups, sqlPlaceholders[1], sqlPlaceholders[2])
}
func getClearGroupFolderMappingQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE group_id = (SELECT id FROM %v WHERE name = %v)`, sqlTableGroupsFoldersMapping,
sqlTableGroups, sqlPlaceholders[0])
}
func getAddGroupFolderMappingQuery() string {
return fmt.Sprintf(`INSERT INTO %v (virtual_path,quota_size,quota_files,folder_id,group_id)
VALUES (%v,%v,%v,(SELECT id FROM %v WHERE name = %v),(SELECT id FROM %v WHERE name = %v))`,
sqlTableGroupsFoldersMapping, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlTableFolders,
sqlPlaceholders[3], sqlTableGroups, sqlPlaceholders[4])
}
func getClearUserFolderMappingQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE user_id = (SELECT id FROM %v WHERE username = %v)`, sqlTableUsersFoldersMapping,
sqlTableUsers, sqlPlaceholders[0])
}
func getAddUserFolderMappingQuery() string {
return fmt.Sprintf(`INSERT INTO %v (virtual_path,quota_size,quota_files,folder_id,user_id)
VALUES (%v,%v,%v,(SELECT id FROM %v WHERE name = %v),(SELECT id FROM %v WHERE username = %v))`,
sqlTableUsersFoldersMapping, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlTableFolders,
sqlPlaceholders[3], sqlTableUsers, sqlPlaceholders[4])
}
func getFoldersQuery(order string, minimal bool) string {
var fieldSelection string
if minimal {
fieldSelection = "id,name"
} else {
fieldSelection = selectFolderFields
}
return fmt.Sprintf(`SELECT %v FROM %v ORDER BY name %v LIMIT %v OFFSET %v`, fieldSelection, sqlTableFolders,
order, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getUpdateFolderQuotaQuery(reset bool) string {
if reset {
return fmt.Sprintf(`UPDATE %v SET used_quota_size = %v,used_quota_files = %v,last_quota_update = %v
WHERE name = %v`, sqlTableFolders, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3])
}
return fmt.Sprintf(`UPDATE %v SET used_quota_size = used_quota_size + %v,used_quota_files = used_quota_files + %v,last_quota_update = %v
WHERE name = %v`, sqlTableFolders, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3])
}
func getQuotaFolderQuery() string {
return fmt.Sprintf(`SELECT used_quota_size,used_quota_files FROM %v WHERE name = %v`, sqlTableFolders,
sqlPlaceholders[0])
}
func getRelatedGroupsForUsersQuery(users []User) string {
var sb strings.Builder
for _, u := range users {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(strconv.FormatInt(u.ID, 10))
}
if sb.Len() > 0 {
sb.WriteString(")")
}
return fmt.Sprintf(`SELECT g.name,ug.group_type,ug.user_id FROM %v g INNER JOIN %v ug ON g.id = ug.group_id WHERE
ug.user_id IN %v ORDER BY ug.user_id`, sqlTableGroups, sqlTableUsersGroupsMapping, sb.String())
}
func getRelatedFoldersForUsersQuery(users []User) string {
var sb strings.Builder
for _, u := range users {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(strconv.FormatInt(u.ID, 10))
}
if sb.Len() > 0 {
sb.WriteString(")")
}
return fmt.Sprintf(`SELECT f.id,f.name,f.path,f.used_quota_size,f.used_quota_files,f.last_quota_update,fm.virtual_path,
fm.quota_size,fm.quota_files,fm.user_id,f.filesystem,f.description FROM %v f INNER JOIN %v fm ON f.id = fm.folder_id WHERE
fm.user_id IN %v ORDER BY fm.user_id`, sqlTableFolders, sqlTableUsersFoldersMapping, sb.String())
}
func getRelatedUsersForFoldersQuery(folders []vfs.BaseVirtualFolder) string {
var sb strings.Builder
for _, f := range folders {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(strconv.FormatInt(f.ID, 10))
}
if sb.Len() > 0 {
sb.WriteString(")")
}
return fmt.Sprintf(`SELECT fm.folder_id,u.username FROM %v fm INNER JOIN %v u ON fm.user_id = u.id
WHERE fm.folder_id IN %v ORDER BY fm.folder_id`, sqlTableUsersFoldersMapping, sqlTableUsers, sb.String())
}
func getRelatedGroupsForFoldersQuery(folders []vfs.BaseVirtualFolder) string {
var sb strings.Builder
for _, f := range folders {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(strconv.FormatInt(f.ID, 10))
}
if sb.Len() > 0 {
sb.WriteString(")")
}
return fmt.Sprintf(`SELECT fm.folder_id,g.name FROM %v fm INNER JOIN %v g ON fm.group_id = g.id
WHERE fm.folder_id IN %v ORDER BY fm.folder_id`, sqlTableGroupsFoldersMapping, sqlTableGroups, sb.String())
}
func getRelatedUsersForGroupsQuery(groups []Group) string {
var sb strings.Builder
for _, g := range groups {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(strconv.FormatInt(g.ID, 10))
}
if sb.Len() > 0 {
sb.WriteString(")")
}
return fmt.Sprintf(`SELECT um.group_id,u.username FROM %v um INNER JOIN %v u ON um.user_id = u.id
WHERE um.group_id IN %v ORDER BY um.group_id`, sqlTableUsersGroupsMapping, sqlTableUsers, sb.String())
}
func getRelatedFoldersForGroupsQuery(groups []Group) string {
var sb strings.Builder
for _, g := range groups {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(",")
}
sb.WriteString(strconv.FormatInt(g.ID, 10))
}
if sb.Len() > 0 {
sb.WriteString(")")
}
return fmt.Sprintf(`SELECT f.id,f.name,f.path,f.used_quota_size,f.used_quota_files,f.last_quota_update,fm.virtual_path,
fm.quota_size,fm.quota_files,fm.group_id,f.filesystem,f.description FROM %s f INNER JOIN %s fm ON f.id = fm.folder_id WHERE
fm.group_id IN %v ORDER BY fm.group_id`, sqlTableFolders, sqlTableGroupsFoldersMapping, sb.String())
}
func getActiveTransfersQuery() string {
return fmt.Sprintf(`SELECT transfer_id,connection_id,transfer_type,username,folder_name,ip,truncated_size,
current_ul_size,current_dl_size,created_at,updated_at FROM %v WHERE updated_at > %v`,
sqlTableActiveTransfers, sqlPlaceholders[0])
}
func getAddActiveTransferQuery() string {
return fmt.Sprintf(`INSERT INTO %v (transfer_id,connection_id,transfer_type,username,folder_name,ip,truncated_size,
current_ul_size,current_dl_size,created_at,updated_at) VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,%v)`,
sqlTableActiveTransfers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3],
sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8],
sqlPlaceholders[9], sqlPlaceholders[10])
}
func getUpdateActiveTransferSizesQuery() string {
return fmt.Sprintf(`UPDATE %v SET current_ul_size=%v,current_dl_size=%v,updated_at=%v WHERE connection_id = %v AND transfer_id = %v`,
sqlTableActiveTransfers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4])
}
func getRemoveActiveTransferQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE connection_id = %v AND transfer_id = %v`,
sqlTableActiveTransfers, sqlPlaceholders[0], sqlPlaceholders[1])
}
func getCleanupActiveTransfersQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE updated_at < %v`, sqlTableActiveTransfers, sqlPlaceholders[0])
}
func getDatabaseVersionQuery() string {
return fmt.Sprintf("SELECT version from %v LIMIT 1", sqlTableSchemaVersion)
}
func getUpdateDBVersionQuery() string {
return fmt.Sprintf(`UPDATE %v SET version=%v`, sqlTableSchemaVersion, sqlPlaceholders[0])
}

View file

@ -1,204 +0,0 @@
# Official Docker image
SFTPGo provides an official Docker image, it is available on both [Docker Hub](https://hub.docker.com/r/drakkan/sftpgo) and on [GitHub Container Registry](https://github.com/users/drakkan/packages/container/package/sftpgo).
## Supported tags and respective Dockerfile links
- [v2.3.0, v2.3, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.3.0/Dockerfile)
- [v2.3.0-alpine, v2.3-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.3.0/Dockerfile.alpine)
- [v2.3.0-slim, v2.3-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.3.0/Dockerfile)
- [v2.3.0-alpine-slim, v2.3-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.3.0/Dockerfile.alpine)
- [v2.3.0-distroless-slim, v2.3-distroless-slim, v2-distroless-slim, distroless-slim](https://github.com/drakkan/sftpgo/blob/v2.3.0/Dockerfile.distroless)
- [edge](../Dockerfile)
- [edge-alpine](../Dockerfile.alpine)
- [edge-slim](../Dockerfile)
- [edge-alpine-slim](../Dockerfile.alpine)
- [edge-distroless-slim](../Dockerfile.distroless)
## How to use the SFTPGo image
### Start a `sftpgo` server instance
Starting a SFTPGo instance is simple:
```shell
docker run --name some-sftpgo -p 8080:8080 -p 2022:2022 -d "drakkan/sftpgo:tag"
```
... where `some-sftpgo` is the name you want to assign to your container, and `tag` is the tag specifying the SFTPGo version you want. See the list above for relevant tags.
Now visit [http://localhost:8080/web/admin](http://localhost:8080/web/admin), replacing `localhost` with the appropriate IP address if SFTPGo is not reachable on localhost, create the first admin and a new SFTPGo user. The SFTP service is available on port 2022.
If you don't want to persist any files, for example for testing purposes, you can run an SFTPGo instance like this:
```shell
docker run --rm --name some-sftpgo -p 8080:8080 -p 2022:2022 -d "drakkan/sftpgo:tag"
```
If you prefer GitHub Container Registry to Docker Hub replace `drakkan/sftpgo:tag` with `ghcr.io/drakkan/sftpgo:tag`.
### Enable FTP service
FTP is disabled by default, you can enable the FTP service by starting the SFTPGo instance in this way:
```shell
docker run --name some-sftpgo \
-p 8080:8080 \
-p 2022:2022 \
-p 2121:2121 \
-p 50000-50100:50000-50100 \
-e SFTPGO_FTPD__BINDINGS__0__PORT=2121 \
-e SFTPGO_FTPD__BINDINGS__0__FORCE_PASSIVE_IP=<your external ip here> \
-d "drakkan/sftpgo:tag"
```
The FTP service is now available on port 2121 and SFTP on port 2022.
You can change the passive ports range (`50000-50100` by default) by setting the environment variables `SFTPGO_FTPD__PASSIVE_PORT_RANGE__START` and `SFTPGO_FTPD__PASSIVE_PORT_RANGE__END`.
It is recommended that you provide a certificate and key file to expose FTP over TLS. You should prefer SFTP to FTP even if you configure TLS, please don't blindly enable the old FTP protocol.
### Enable WebDAV service
WebDAV is disabled by default, you can enable the WebDAV service by starting the SFTPGo instance in this way:
```shell
docker run --name some-sftpgo \
-p 8080:8080 \
-p 2022:2022 \
-p 10080:10080 \
-e SFTPGO_WEBDAVD__BINDINGS__0__PORT=10080 \
-d "drakkan/sftpgo:tag"
```
The WebDAV service is now available on port 10080 and SFTP on port 2022.
It is recommended that you provide a certificate and key file to expose WebDAV over https.
### Container shell access and viewing SFTPGo logs
The docker exec command allows you to run commands inside a Docker container. The following command line will give you a shell inside your `sftpgo` container:
```shell
docker exec -it some-sftpgo sh
```
The logs are available through Docker's container log:
```shell
docker logs some-sftpgo
```
**Note:** [distroless](../Dockerfile.distroless) image contains only a statically linked sftpgo binary and its minimal runtime dependencies. Shell is not available on this image.
### Where to Store Data
Important note: There are several ways to store data used by applications that run in Docker containers. We encourage users of the SFTPGo images to familiarize themselves with the options available, including:
- Let Docker manage the storage for SFTPGo data by [writing them to disk on the host system using its own internal volume management](https://docs.docker.com/engine/tutorials/dockervolumes/#adding-a-data-volume). This is the default and is easy and fairly transparent to the user. The downside is that the files may be hard to locate for tools and applications that run directly on the host system, i.e. outside containers.
- Create a data directory on the host system (outside the container) and [mount this to a directory visible from inside the container]((https://docs.docker.com/engine/tutorials/dockervolumes/#mount-a-host-directory-as-a-data-volume)). This places the SFTPGo files in a known location on the host system, and makes it easy for tools and applications on the host system to access the files. The downside is that the user needs to make sure that the directory exists, and that e.g. directory permissions and other security mechanisms on the host system are set up correctly. The SFTPGo image runs using `1000` as UID/GID by default.
The Docker documentation is a good starting point for understanding the different storage options and variations, and there are multiple blogs and forum postings that discuss and give advice in this area. We will simply show the basic procedure here for the latter option above:
1. Create a data directory on a suitable volume on your host system, e.g. `/my/own/sftpgodata`. The user with ID `1000` must be able to write to this directory. Please note that you don't need an actual user with ID `1000` on your host system: `chown -R 1000:1000 /my/own/sftpgodata` is enough even if there is no user/group with UID/GID `1000`.
2. Create a home directory for the sftpgo container user on your host system e.g. `/my/own/sftpgohome`. As with the data directory above, make sure that the user with ID `1000` can write to this directory: `chown -R 1000:1000 /my/own/sftpgohome`
3. Start your SFTPGo container like this:
```shell
docker run --name some-sftpgo \
-p 8080:8090 \
-p 2022:2022 \
--mount type=bind,source=/my/own/sftpgodata,target=/srv/sftpgo \
--mount type=bind,source=/my/own/sftpgohome,target=/var/lib/sftpgo \
-e SFTPGO_HTTPD__BINDINGS__0__PORT=8090 \
-d "drakkan/sftpgo:tag"
```
As you can see SFTPGo uses two main volumes:
- `/srv/sftpgo` to handle persistent data. The default home directory for SFTP/FTP/WebDAV users is `/srv/sftpgo/data/<username>`. Backups are stored in `/srv/sftpgo/backups`
- `/var/lib/sftpgo` is the home directory for the sftpgo system user defined inside the container. This is the container working directory too, host keys will be created here when using the default configuration.
If you want to get fine grained control, you can also mount `/srv/sftpgo/data` and `/srv/sftpgo/backups` as separate volumes instead of mounting `/srv/sftpgo`.
### Configuration
The runtime configuration can be customized via environment variables that you can set passing the `-e` option to the `docker run` command or inside the `environment` section if you are using [docker stack deploy](https://docs.docker.com/engine/reference/commandline/stack_deploy/) or [docker-compose](https://github.com/docker/compose).
Please take a look [here](../docs/full-configuration.md) to learn how to configure SFTPGo via environment variables.
Alternately you can mount your custom configuration file to `/var/lib/sftpgo` or `/var/lib/sftpgo/.config/sftpgo`.
### Loading initial data
Initial data can be loaded in the following ways:
- via the `--loaddata-from` flag or the `SFTPGO_LOADDATA_FROM` environment variable. This flag is supported for both the `serve` command (load initial data and start the service) and the `initprovider` command (initialize the provider, load initial data and exit)
- by providing a dump file to the memory provider
Please take a look [here](../docs/full-configuration.md) for more details.
### Running as an arbitrary user
The SFTPGo image runs using `1000` as UID/GID by default. If you know the permissions of your data and/or configuration directory are already set appropriately or you have need of running SFTPGo with a specific UID/GID, it is possible to invoke this image with `--user` set to any value (other than `root/0`) in order to achieve the desired access/configuration:
```shell
$ ls -lnd data
drwxr-xr-x 2 1100 1100 6 7 nov 09.09 data
$ ls -lnd config
drwxr-xr-x 2 1100 1100 6 7 nov 09.19 config
```
With the above directory permissions, you can start a SFTPGo instance like this:
```shell
docker run --name some-sftpgo \
--user 1100:1100 \
-p 8080:8080 \
-p 2022:2022 \
--mount type=bind,source="${PWD}/data",target=/srv/sftpgo \
--mount type=bind,source="${PWD}/config",target=/var/lib/sftpgo \
-d "drakkan/sftpgo:tag"
```
Alternately build your own image using the official one as a base, here is a sample Dockerfile:
```shell
FROM drakkan/sftpgo:tag
USER root
RUN chown -R 1100:1100 /etc/sftpgo && chown 1100:1100 /var/lib/sftpgo /srv/sftpgo
USER 1100:1100
```
**Note:** the above Dockerfile will not work if you use the [distroless](../Dockerfile.distroless) image as base since the `chown` command is not available there.
## Image Variants
The `sftpgo` images comes in many flavors, each designed for a specific use case. The `edge`, `edge-slim`, `edge-alpine`, `edge-alpine-slim` and `edge-distroless-slim` tags are updated after each new commit.
### `sftpgo:<version>`
This is the defacto image, it is based on [Debian](https://www.debian.org/), available in [the `debian` official image](https://hub.docker.com/_/debian). If you are unsure about what your needs are, you probably want to use this one.
### `sftpgo:<version>-alpine`
This image is based on the popular [Alpine Linux project](https://alpinelinux.org/), available in [the `alpine` official image](https://hub.docker.com/_/alpine). Alpine Linux is much smaller than most distribution base images (~5MB), and thus leads to much slimmer images in general.
This variant is highly recommended when final image size being as small as possible is desired. The main caveat to note is that it does use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), so certain software might run into issues depending on the depth of their libc requirements. However, most software doesn't have an issue with this, so this variant is usually a very safe choice. See [this Hacker News comment thread](https://news.ycombinator.com/item?id=10782897) for more discussion of the issues that might arise and some pro/con comparisons of using Alpine-based images.
### `sftpgo:<version>-distroless`
This image is based on the popular [Distroless project](https://github.com/GoogleContainerTools/distroless). We use the latest Debian based distroless image as base.
Distroless variant contains only a statically linked sftpgo binary and its minimal runtime dependencies and so it doesn't allow shell access (no shell is installed).
SQLite support is disabled since it requires CGO and so a C runtime which is not installed.
The default data provider is `bolt`, all the supported data providers except `sqlite` work.
We only provide the slim variant and so the optional `git` dependency is not available.
### `sftpgo:<suite>-slim`
These tags provide a slimmer image that does not include the optional `git` dependency.
## Helm Chart
An helm chart is [available](https://artifacthub.io/packages/helm/sagikazarmark/sftpgo). You can find the source code [here](https://github.com/sagikazarmark/helm-charts/tree/master/charts/sftpgo).

View file

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -e
ARCH=`uname -m`
case ${ARCH} in
"x86_64")
SUFFIX=amd64
;;
"aarch64")
SUFFIX=arm64
;;
*)
SUFFIX=ppc64le
;;
esac
echo "download plugins for arch ${SUFFIX}"
for PLUGIN in geoipfilter kms pubsub eventstore eventsearch auth
do
echo "download plugin from https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/latest/download/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}"
curl -L "https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/latest/download/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}" --output "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
chmod 755 "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
done

View file

@ -1,28 +0,0 @@
#!/usr/bin/env bash
SFTPGO_PUID=${SFTPGO_PUID:-1000}
SFTPGO_PGID=${SFTPGO_PGID:-1000}
if [ "$1" = 'sftpgo' ]; then
if [ "$(id -u)" = '0' ]; then
for DIR in "/etc/sftpgo" "/var/lib/sftpgo" "/srv/sftpgo"
do
DIR_UID=$(stat -c %u ${DIR})
DIR_GID=$(stat -c %g ${DIR})
if [ ${DIR_UID} != ${SFTPGO_PUID} ] || [ ${DIR_GID} != ${SFTPGO_PGID} ]; then
echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.000`'","sender":"entrypoint","message":"change owner for \"'${DIR}'\" UID: '${SFTPGO_PUID}' GID: '${SFTPGO_PGID}'"}'
if [ ${DIR} = "/etc/sftpgo" ]; then
chown -R ${SFTPGO_PUID}:${SFTPGO_PGID} ${DIR}
else
chown ${SFTPGO_PUID}:${SFTPGO_PGID} ${DIR}
fi
fi
done
echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.000`'","sender":"entrypoint","message":"run as UID: '${SFTPGO_PUID}' GID: '${SFTPGO_PGID}'"}'
exec su-exec ${SFTPGO_PUID}:${SFTPGO_PGID} "$@"
fi
exec "$@"
fi
exec "$@"

View file

@ -1,32 +0,0 @@
#!/usr/bin/env bash
SFTPGO_PUID=${SFTPGO_PUID:-1000}
SFTPGO_PGID=${SFTPGO_PGID:-1000}
if [ "$1" = 'sftpgo' ]; then
if [ "$(id -u)" = '0' ]; then
getent passwd ${SFTPGO_PUID} > /dev/null
HAS_PUID=$?
getent group ${SFTPGO_PGID} > /dev/null
HAS_PGID=$?
if [ ${HAS_PUID} -ne 0 ] || [ ${HAS_PGID} -ne 0 ]; then
echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.%3N`'","sender":"entrypoint","message":"prepare to run as UID: '${SFTPGO_PUID}' GID: '${SFTPGO_PGID}'"}'
if [ ${HAS_PGID} -ne 0 ]; then
echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.%3N`'","sender":"entrypoint","message":"set GID to: '${SFTPGO_PGID}'"}'
groupmod -g ${SFTPGO_PGID} sftpgo
fi
if [ ${HAS_PUID} -ne 0 ]; then
echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.%3N`'","sender":"entrypoint","message":"set UID to: '${SFTPGO_PUID}'"}'
usermod -u ${SFTPGO_PUID} sftpgo
fi
chown -R ${SFTPGO_PUID}:${SFTPGO_PGID} /etc/sftpgo
chown ${SFTPGO_PUID}:${SFTPGO_PGID} /var/lib/sftpgo /srv/sftpgo
fi
echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.%3N`'","sender":"entrypoint","message":"run as UID: '${SFTPGO_PUID}' GID: '${SFTPGO_PGID}'"}'
exec gosu ${SFTPGO_PUID}:${SFTPGO_PGID} "$@"
fi
exec "$@"
fi
exec "$@"

View file

@ -1,50 +0,0 @@
FROM golang:alpine as builder
RUN apk add --no-cache git gcc g++ ca-certificates \
&& go get -v -d github.com/drakkan/sftpgo
WORKDIR /go/src/github.com/drakkan/sftpgo
ARG TAG
ARG FEATURES
# Use --build-arg TAG=LATEST for latest tag. Use e.g. --build-arg TAG=v1.0.0 for a specific tag/commit. Otherwise HEAD (master) is built.
RUN git checkout $(if [ "${TAG}" = LATEST ]; then echo `git rev-list --tags --max-count=1`; elif [ -n "${TAG}" ]; then echo "${TAG}"; else echo HEAD; fi)
RUN go build $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -ldflags "-s -w -X github.com/drakkan/sftpgo/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/version.date=`date -u +%FT%TZ`" -v -o /go/bin/sftpgo
FROM alpine:latest
RUN apk add --no-cache ca-certificates su-exec \
&& mkdir -p /data /etc/sftpgo /srv/sftpgo/config /srv/sftpgo/web /srv/sftpgo/backups
# git and rsync are optional, uncomment the next line to add support for them if needed.
#RUN apk add --no-cache git rsync
COPY --from=builder /go/bin/sftpgo /bin/
COPY --from=builder /go/src/github.com/drakkan/sftpgo/sftpgo.json /etc/sftpgo/sftpgo.json
COPY --from=builder /go/src/github.com/drakkan/sftpgo/templates /srv/sftpgo/web/templates
COPY --from=builder /go/src/github.com/drakkan/sftpgo/static /srv/sftpgo/web/static
COPY docker-entrypoint.sh /bin/entrypoint.sh
RUN chmod +x /bin/entrypoint.sh
VOLUME [ "/data", "/srv/sftpgo/config", "/srv/sftpgo/backups" ]
EXPOSE 2022 8080
# uncomment the following settings to enable FTP support
#ENV SFTPGO_FTPD__BIND_PORT=2121
#ENV SFTPGO_FTPD__FORCE_PASSIVE_IP=<your FTP visibile IP here>
#EXPOSE 2121
# we need to expose the passive ports range too
#EXPOSE 50000-50100
# it is a good idea to provide certificates to enable FTPS too
#ENV SFTPGO_FTPD__CERTIFICATE_FILE=/srv/sftpgo/config/mycert.crt
#ENV SFTPGO_FTPD__CERTIFICATE_KEY_FILE=/srv/sftpgo/config/mycert.key
# uncomment the following setting to enable WebDAV support
#ENV SFTPGO_WEBDAVD__BIND_PORT=8090
# it is a good idea to provide certificates to enable WebDAV over HTTPS
#ENV SFTPGO_WEBDAVD__CERTIFICATE_FILE=${CONFIG_DIR}/mycert.crt
#ENV SFTPGO_WEBDAVD__CERTIFICATE_KEY_FILE=${CONFIG_DIR}/mycert.key
ENTRYPOINT ["/bin/entrypoint.sh"]
CMD ["serve"]

View file

@ -1,61 +0,0 @@
# SFTPGo with Docker and Alpine
:warning: The recommended way to run SFTPGo on Docker is to use the official [images](https://hub.docker.com/r/drakkan/sftpgo). The documentation here is now obsolete.
This DockerFile is made to build image to host multiple instances of SFTPGo started with different users.
## Example
> 1003 is a custom uid:gid for this instance of SFTPGo
```bash
# Prereq on docker host
sudo groupadd -g 1003 sftpgrp && \
sudo useradd -u 1003 -g 1003 sftpuser -d /home/sftpuser/ && \
sudo -u sftpuser mkdir /home/sftpuser/{conf,data} && \
curl https://raw.githubusercontent.com/drakkan/sftpgo/master/sftpgo.json -o /home/sftpuser/conf/sftpgo.json
# Edit sftpgo.json as you need
# Get and build SFTPGo image.
# Add --build-arg TAG=LATEST to build the latest tag or e.g. TAG=v1.0.0 for a specific tag/commit.
# Add --build-arg FEATURES=<build features comma separated> to specify the features to build.
git clone https://github.com/drakkan/sftpgo.git && \
cd sftpgo && \
sudo docker build -t sftpgo docker/sftpgo/alpine/
# Initialize the configured provider. For PostgreSQL and MySQL providers you need to create the configured database and the "initprovider" command will create the required tables.
sudo docker run --name sftpgo \
-e PUID=1003 \
-e GUID=1003 \
-v /home/sftpuser/conf/:/srv/sftpgo/config \
sftpgo initprovider -c /srv/sftpgo/config
# Start the image
sudo docker rm sftpgo && sudo docker run --name sftpgo \
-e SFTPGO_LOG_FILE_PATH= \
-e SFTPGO_CONFIG_DIR=/srv/sftpgo/config \
-e SFTPGO_HTTPD__TEMPLATES_PATH=/srv/sftpgo/web/templates \
-e SFTPGO_HTTPD__STATIC_FILES_PATH=/srv/sftpgo/web/static \
-e SFTPGO_HTTPD__BACKUPS_PATH=/srv/sftpgo/backups \
-p 8080:8080 \
-p 2022:2022 \
-e PUID=1003 \
-e GUID=1003 \
-v /home/sftpuser/conf/:/srv/sftpgo/config \
-v /home/sftpuser/data:/data \
-v /home/sftpuser/backups:/srv/sftpgo/backups \
sftpgo
```
If you want to enable FTP/S you also need the publish the FTP port and the FTP passive port range, defined in your `Dockerfile`, by adding, for example, the following options to the `docker run` command `-p 2121:2121 -p 50000-50100:50000-50100`. The same goes for WebDAV, you need to publish the configured port.
The script `entrypoint.sh` makes sure to correct the permissions of directories and start the process with the right user.
Several images can be run with different parameters.
## Custom systemd script
An example of systemd script is present [here](sftpgo.service), with `Environment` parameter to set `PUID` and `GUID`
`WorkingDirectory` parameter must be exist with one file in this directory like `sftpgo-${PUID}.env` corresponding to the variable file for SFTPGo instance.

View file

@ -1,7 +0,0 @@
#!/bin/sh
set -eu
chown -R "${PUID}:${GUID}" /data /etc/sftpgo /srv/sftpgo/config /srv/sftpgo/backups \
&& exec su-exec "${PUID}:${GUID}" \
/bin/sftpgo "$@"

View file

@ -1,35 +0,0 @@
[Unit]
Description=SFTPGo server
After=docker.service
[Service]
User=root
Group=root
WorkingDirectory=/etc/sftpgo
Environment=PUID=1003
Environment=GUID=1003
EnvironmentFile=-/etc/sysconfig/sftpgo.env
ExecStartPre=-docker kill sftpgo
ExecStartPre=-docker rm sftpgo
ExecStart=docker run --name sftpgo \
--env-file sftpgo-${PUID}.env \
-e PUID=${PUID} \
-e GUID=${GUID} \
-e SFTPGO_LOG_FILE_PATH= \
-e SFTPGO_CONFIG_DIR=/srv/sftpgo/config \
-e SFTPGO_HTTPD__TEMPLATES_PATH=/srv/sftpgo/web/templates \
-e SFTPGO_HTTPD__STATIC_FILES_PATH=/srv/sftpgo/web/static \
-e SFTPGO_HTTPD__BACKUPS_PATH=/srv/sftpgo/backups \
-p 8080:8080 \
-p 2022:2022 \
-v /home/sftpuser/conf/:/srv/sftpgo/config \
-v /home/sftpuser/data:/data \
-v /home/sftpuser/backups:/srv/sftpgo/backups \
sftpgo
ExecStop=docker stop sftpgo
SyslogIdentifier=sftpgo
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.target

View file

@ -1,93 +0,0 @@
# we use a multi stage build to have a separate build and run env
FROM golang:latest as buildenv
LABEL maintainer="nicola.murino@gmail.com"
RUN go get -v -d github.com/drakkan/sftpgo
WORKDIR /go/src/github.com/drakkan/sftpgo
ARG TAG
ARG FEATURES
# Use --build-arg TAG=LATEST for latest tag. Use e.g. --build-arg TAG=v1.0.0 for a specific tag/commit. Otherwise HEAD (master) is built.
RUN git checkout $(if [ "${TAG}" = LATEST ]; then echo `git rev-list --tags --max-count=1`; elif [ -n "${TAG}" ]; then echo "${TAG}"; else echo HEAD; fi)
RUN go build $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -ldflags "-s -w -X github.com/drakkan/sftpgo/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/version.date=`date -u +%FT%TZ`" -v -o sftpgo
# now define the run environment
FROM debian:latest
# ca-certificates is needed for Cloud Storage Support and for HTTPS/FTPS.
RUN apt-get update && apt-get install -y ca-certificates && apt-get clean
# git and rsync are optional, uncomment the next line to add support for them if needed.
#RUN apt-get update && apt-get install -y git rsync && apt-get clean
ARG BASE_DIR=/app
ARG DATA_REL_DIR=data
ARG CONFIG_REL_DIR=config
ARG BACKUP_REL_DIR=backups
ARG USERNAME=sftpgo
ARG GROUPNAME=sftpgo
ARG UID=515
ARG GID=515
ARG WEB_REL_PATH=web
# HOME_DIR for sftpgo itself
ENV HOME_DIR=${BASE_DIR}/${USERNAME}
# DATA_DIR, this is a volume that you can use hold user's home dirs
ENV DATA_DIR=${BASE_DIR}/${DATA_REL_DIR}
# CONFIG_DIR, this is a volume to persist the daemon private keys, configuration file ecc..
ENV CONFIG_DIR=${BASE_DIR}/${CONFIG_REL_DIR}
# BACKUPS_DIR, this is a volume to store backups done using "dumpdata" REST API
ENV BACKUPS_DIR=${BASE_DIR}/${BACKUP_REL_DIR}
ENV WEB_DIR=${BASE_DIR}/${WEB_REL_PATH}
RUN mkdir -p ${DATA_DIR} ${CONFIG_DIR} ${WEB_DIR} ${BACKUPS_DIR}
RUN groupadd --system -g ${GID} ${GROUPNAME}
RUN useradd --system --create-home --no-log-init --home-dir ${HOME_DIR} --comment "SFTPGo user" --shell /usr/sbin/nologin --gid ${GID} --uid ${UID} ${USERNAME}
WORKDIR ${HOME_DIR}
RUN mkdir -p bin .config/sftpgo
ENV PATH ${HOME_DIR}/bin:$PATH
COPY --from=buildenv /go/src/github.com/drakkan/sftpgo/sftpgo bin/sftpgo
# default config file to use if no config file is found inside the CONFIG_DIR volume.
# You can override each configuration options via env vars too
COPY --from=buildenv /go/src/github.com/drakkan/sftpgo/sftpgo.json .config/sftpgo/
COPY --from=buildenv /go/src/github.com/drakkan/sftpgo/templates ${WEB_DIR}/templates
COPY --from=buildenv /go/src/github.com/drakkan/sftpgo/static ${WEB_DIR}/static
RUN chown -R ${UID}:${GID} ${DATA_DIR} ${BACKUPS_DIR}
# run as non root user
USER ${USERNAME}
EXPOSE 2022 8080
# the defined volumes must have write access for the UID and GID defined above
VOLUME [ "$DATA_DIR", "$CONFIG_DIR", "$BACKUPS_DIR" ]
# override some default configuration options using env vars
ENV SFTPGO_CONFIG_DIR=${CONFIG_DIR}
# setting SFTPGO_LOG_FILE_PATH to an empty string will log to stdout
ENV SFTPGO_LOG_FILE_PATH=""
ENV SFTPGO_HTTPD__BIND_ADDRESS=""
ENV SFTPGO_HTTPD__TEMPLATES_PATH=${WEB_DIR}/templates
ENV SFTPGO_HTTPD__STATIC_FILES_PATH=${WEB_DIR}/static
ENV SFTPGO_DATA_PROVIDER__USERS_BASE_DIR=${DATA_DIR}
ENV SFTPGO_HTTPD__BACKUPS_PATH=${BACKUPS_DIR}
# uncomment the following settings to enable FTP support
#ENV SFTPGO_FTPD__BIND_PORT=2121
#ENV SFTPGO_FTPD__FORCE_PASSIVE_IP=<your FTP visibile IP here>
#EXPOSE 2121
# we need to expose the passive ports range too
#EXPOSE 50000-50100
# it is a good idea to provide certificates to enable FTPS too
#ENV SFTPGO_FTPD__CERTIFICATE_FILE=${CONFIG_DIR}/mycert.crt
#ENV SFTPGO_FTPD__CERTIFICATE_KEY_FILE=${CONFIG_DIR}/mycert.key
# uncomment the following setting to enable WebDAV support
#ENV SFTPGO_WEBDAVD__BIND_PORT=8090
# it is a good idea to provide certificates to enable WebDAV over HTTPS
#ENV SFTPGO_WEBDAVD__CERTIFICATE_FILE=${CONFIG_DIR}/mycert.crt
#ENV SFTPGO_WEBDAVD__CERTIFICATE_KEY_FILE=${CONFIG_DIR}/mycert.key
ENTRYPOINT ["sftpgo"]
CMD ["serve"]

View file

@ -1,59 +0,0 @@
# Dockerfile based on Debian stable
:warning: The recommended way to run SFTPGo on Docker is to use the official [images](https://hub.docker.com/r/drakkan/sftpgo). The documentation here is now obsolete.
Please read the comments inside the `Dockerfile` to learn how to customize things for your setup.
You can build the container image using `docker build`, for example:
```bash
docker build -t="drakkan/sftpgo" .
```
This will build master of github.com/drakkan/sftpgo.
To build the latest tag you can add `--build-arg TAG=LATEST` and to build a specific tag/commit you can use for example `TAG=v1.0.0`, like this:
```bash
docker build -t="drakkan/sftpgo" --build-arg TAG=v1.0.0 .
```
To specify the features to build you can add `--build-arg FEATURES=<build features comma separated>`. For example you can disable SQLite and S3 support like this:
```bash
docker build -t="drakkan/sftpgo" --build-arg FEATURES=nosqlite,nos3 .
```
Please take a look at the [build from source](./../../../docs/build-from-source.md) documentation for the complete list of the features that can be disabled.
Now create the required folders on the host system, for example:
```bash
sudo mkdir -p /srv/sftpgo/data /srv/sftpgo/config /srv/sftpgo/backups
```
and give write access to them to the UID/GID defined inside the `Dockerfile`. You can choose to create a new user, on the host system, with a matching UID/GID pair, or simply do something like this:
```bash
sudo chown -R <UID>:<GID> /srv/sftpgo/data /srv/sftpgo/config /srv/sftpgo/backups
```
Download the default configuration file and edit it as you need:
```bash
sudo curl https://raw.githubusercontent.com/drakkan/sftpgo/master/sftpgo.json -o /srv/sftpgo/config/sftpgo.json
```
Initialize the configured provider. For PostgreSQL and MySQL providers you need to create the configured database and the `initprovider` command will create the required tables:
```bash
docker run --name sftpgo --mount type=bind,source=/srv/sftpgo/config,target=/app/config drakkan/sftpgo initprovider -c /app/config
```
and finally you can run the image using something like this:
```bash
docker rm sftpgo && docker run --name sftpgo -p 8080:8080 -p 2022:2022 --mount type=bind,source=/srv/sftpgo/data,target=/app/data --mount type=bind,source=/srv/sftpgo/config,target=/app/config --mount type=bind,source=/srv/sftpgo/backups,target=/app/backups drakkan/sftpgo
```
If you want to enable FTP/S you also need the publish the FTP port and the FTP passive port range, defined in your `Dockerfile`, by adding, for example, the following options to the `docker run` command `-p 2121:2121 -p 50000-50100:50000-50100`. The same goes for WebDAV, you need to publish the configured port.

View file

@ -1,22 +0,0 @@
# Account's configuration properties
Please take a look at the [OpenAPI schema](../openapi/openapi.yaml) for the exact definitions of user, folder and admin fields.
If you need an example you can export a dump using the Web Admin or by invoking the `dumpdata` endpoint directly, you need to obtain an access token first, for example:
```shell
$ curl "http://admin:password@127.0.0.1:8080/api/v2/token"
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiQVBJIl0sImV4cCI6MTYxMzMzNTI2MSwianRpIjoiYzBrb2gxZmNkcnBjaHNzMGZwZmciLCJuYmYiOjE2MTMzMzQ2MzEsInBlcm1pc3Npb25zIjpbIioiXSwic3ViIjoiYUJ0SHUwMHNBUmxzZ29yeEtLQ1pZZWVqSTRKVTlXbThHSGNiVWtWVmc1TT0iLCJ1c2VybmFtZSI6ImFkbWluIn0.WiyqvUF-92zCr--y4Q_sxn-tPnISFzGZd_exsG-K7ME","expires_at":"2021-02-14T20:41:01Z"}
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiQVBJIl0sImV4cCI6MTYxMzMzNTI2MSwianRpIjoiYzBrb2gxZmNkcnBjaHNzMGZwZmciLCJuYmYiOjE2MTMzMzQ2MzEsInBlcm1pc3Npb25zIjpbIioiXSwic3ViIjoiYUJ0SHUwMHNBUmxzZ29yeEtLQ1pZZWVqSTRKVTlXbThHSGNiVWtWVmc1TT0iLCJ1c2VybmFtZSI6ImFkbWluIn0.WiyqvUF-92zCr--y4Q_sxn-tPnISFzGZd_exsG-K7ME" "http://127.0.0.1:8080/api/v2/dumpdata?output-data=1"
```
the dump is a JSON with all SFTPGo data including users, folders, admins.
These properties are stored inside the configured data provider.
SFTPGo supports checking passwords stored with bcrypt, pbkdf2, md5crypt and sha512crypt too. For pbkdf2 the supported format is `$<algo>$<iterations>$<salt>$<hashed pwd base64 encoded>`, where algo is `pbkdf2-sha1` or `pbkdf2-sha256` or `pbkdf2-sha512` or `$pbkdf2-b64salt-sha256$`. For example the pbkdf2-sha256 of the word password using 150000 iterations and E86a9YMX3zC7 as salt must be stored as `$pbkdf2-sha256$150000$E86a9YMX3zC7$R5J62hsSq+pYw00hLLPKBbcGXmq7fj5+/M0IFoYtZbo=`. In pbkdf2 variant with b64salt the salt is base64 encoded. For bcrypt the format must be the one supported by golang's crypto/bcrypt package, for example the password secret with cost 14 must be stored as `$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK`. For md5crypt and sha512crypt we support the format used in `/etc/shadow` with the `$1$` and `$6$` prefix, this is useful if you are migrating from Unix system user accounts. We support Apache md5crypt (`$apr1$` prefix) too. Using the REST API you can send a password hashed as bcrypt, pbkdf2, md5crypt or sha512crypt and it will be stored as is.
If you want to use your existing accounts, you have these options:
- you can import your users inside SFTPGo. Take a look at [convert users](.../examples/convertusers) script, it can convert and import users from Linux system users and Pure-FTPd/ProFTPD virtual users
- you can use an external authentication program

View file

@ -1,20 +0,0 @@
# Azure Blob Storage backend
To connect SFTPGo to Azure Blob Storage, you need to specify the access credentials. Azure Blob Storage has different options for credentials, we support:
1. Providing an account name and account key.
2. Providing a shared access signature (SAS).
If you authenticate using account and key you also need to specify a container. The endpoint can generally be left blank, the default is `blob.core.windows.net`.
If you provide a SAS URL the container is optional and if given it must match the one inside the shared access signature.
If you want to connect to an emulator such as [Azurite](https://github.com/Azure/Azurite) you need to provide the account name/key pair and an endpoint prefixed with the protocol, for example `http://127.0.0.1:10000`.
Specifying a different `key_prefix`, you can assign different "folders" of the same container to different users. This is similar to a chroot directory for local filesystem. Each SFTPGo user can only access the assigned folder and its contents. The folder identified by `key_prefix` does not need to be pre-created.
For multipart uploads you can customize the parts size and the upload concurrency. Please note that if the upload bandwidth between the client and SFTPGo is greater than the upload bandwidth between SFTPGo and the Azure Blob service then the client should wait for the last parts to be uploaded to Azure after finishing uploading the file to SFTPGo, and it may time out. Keep this in mind if you customize these parameters.
The configured container must exist.
This backend is very similar to the [S3](./s3.md) backend, and it has the same limitations. As with S3 `chtime` will fail with the default configuration, you can install the [metadata plugin](https://github.com/sftpgo/sftpgo-plugin-metadata) to make it work and thus be able to preserve/change file modification times.

View file

@ -1,40 +0,0 @@
# Build SFTPGo from source
Download the sources and use `go build`.
The following build tags are available:
- `nogcs`, disable Google Cloud Storage backend, default enabled
- `nos3`, disable S3 Compabible Object Storage backends, default enabled
- `noazblob`, disable Azure Blob Storage backend, default enabled
- `nobolt`, disable Bolt data provider, default enabled
- `nomysql`, disable MySQL data provider, default enabled
- `nopgsql`, disable PostgreSQL data provider, default enabled
- `nosqlite`, disable SQLite data provider, default enabled
- `noportable`, disable portable mode, default enabled
- `nometrics`, disable Prometheus metrics, default enabled
If no build tag is specified the build will include the default features.
The optional [SQLite driver](https://github.com/mattn/go-sqlite3 "go-sqlite3") is a `CGO` package and so it requires a `C` compiler at build time.
On Linux and macOS, a compiler is easy to install or already installed. On Windows, you need to download [MinGW-w64](https://sourceforge.net/projects/mingw-w64/files/) and build SFTPGo from its command prompt.
The compiler is a build time only dependency. It is not required at runtime.
Version info, such as git commit and build date, can be embedded setting the following string variables at build time:
- `github.com/drakkan/sftpgo/v2/version.commit`
- `github.com/drakkan/sftpgo/v2/version.date`
For example, you can build using the following command:
```bash
go build -tags nogcs,nos3,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
```
You should get a version that includes git commit, build date and available features like this one:
```bash
$ ./sftpgo -v
SFTPGo 0.9.6-dev-b30614e-dirty-2020-06-19T11:04:56Z +metrics -gcs -s3 +bolt +mysql +pgsql -sqlite +portable
```

View file

@ -1,47 +0,0 @@
# Check password hook
This hook allows you to externally check the provided password, its main use case is to allow to easily support things like password+OTP for protocols without keyboard interactive support such as FTP and WebDAV. You can ask your users to login using a string consisting of a fixed password and a One Time Token, you can verify the token inside the hook and ask to SFTPGo to verify the fixed part.
The same thing can be achieved using [External authentication](./external-auth.md) but using this hook is simpler in some use cases.
The `check password hook` can be defined as the absolute path of your program or an HTTP URL.
The expected response is a JSON serialized struct containing the following keys:
- `status` integer. 0 means KO, 1 means OK, 2 means partial success
- `to_verify` string. For `status` = 2 SFTPGo will check this password against the one stored inside SFTPGo data provider
If the hook defines an external program it can read the following environment variables:
- `SFTPGO_AUTHD_USERNAME`
- `SFTPGO_AUTHD_PASSWORD`
- `SFTPGO_AUTHD_IP`
- `SFTPGO_AUTHD_PROTOCOL`, possible values are `SSH`, `FTP`, `DAV`, `HTTP`
Previous global environment variables aren't cleared when the script is called. The content of these variables is _not_ quoted. They may contain special characters. They are under the control of a possibly malicious remote user.
The program must write, on its standard output, the expected JSON serialized response described above.
If the hook is an HTTP URL then it will be invoked as HTTP POST. The request body will contain a JSON serialized struct with the following fields:
- `username`
- `password`
- `ip`
- `protocol`, possible values are `SSH`, `FTP`, `DAV`
If authentication succeeds the HTTP response code must be 200 and the response body must contain the expected JSON serialized response described above.
The program hook must finish within 30 seconds, the HTTP hook timeout will use the global configuration for HTTP clients.
You can also restrict the hook scope using the `check_password_scope` configuration key:
- `0` means all supported protocols.
- `1` means SSH only
- `2` means FTP only
- `4` means WebDAV only
You can combine the scopes. For example, 6 means FTP and WebDAV.
You can disable the hook on a per-user basis.
An example check password program allowing 2FA using password + one time token can be found inside the source tree [checkpwd](../examples/OTP/authy/checkpwd) directory.

View file

@ -1,116 +0,0 @@
# Custom Actions
SFTPGo can notify filesystem and provider events using custom actions. A custom action can be an external program or an HTTP URL.
## Filesystem events
The `actions` struct inside the `common` configuration section allows to configure the actions for file operations and SSH commands.
The `hook` can be defined as the absolute path of your program or an HTTP URL.
The following `actions` are supported:
- `download`
- `pre-download`
- `upload`
- `pre-upload`
- `delete`
- `pre-delete`
- `rename`
- `mkdir`
- `rmdir`
- `ssh_cmd`
The `upload` condition includes both uploads to new files and overwrite of existing ones. If an upload is aborted for quota limits SFTPGo tries to remove the partial file, so if the notification reports a zero size file and a quota exceeded error the file has been deleted. The `ssh_cmd` condition will be triggered after a command is successfully executed via SSH. `scp` will trigger the `download` and `upload` conditions and not `ssh_cmd`.
For cloud backends directories are virtual, they are created implicitly when you upload a file and are implicitly removed when the last file within a directory is removed. The `mkdir` and `rmdir` notifications are sent only when a directory is explicitly created or removed.
The notification will indicate if an error is detected and so, for example, a partial file is uploaded.
The `pre-delete` action, if defined, will be called just before files deletion. If the external command completes with a zero exit status or the HTTP notification response code is `200` then SFTPGo will assume that the file was already deleted/moved and so it will not try to remove the file and it will not execute the hook defined for the `delete` action.
The `pre-download` and `pre-upload` actions, will be called before downloads and uploads. If the external command completes with a zero exit status or the HTTP notification response code is `200` then SFTPGo allows the operation, otherwise the client will get a permission denied error.
If the `hook` defines a path to an external program, then this program can read the following environment variables:
- `SFTPGO_ACTION`, supported action
- `SFTPGO_ACTION_USERNAME`
- `SFTPGO_ACTION_PATH`, is the full filesystem path, can be empty for some ssh commands
- `SFTPGO_ACTION_TARGET`, full filesystem path, non-empty for `rename` `SFTPGO_ACTION` and for some SSH commands
- `SFTPGO_ACTION_VIRTUAL_PATH`, virtual path, seen by SFTPGo users
- `SFTPGO_ACTION_VIRTUAL_TARGET`, virtual target path, seen by SFTPGo users
- `SFTPGO_ACTION_SSH_CMD`, non-empty for `ssh_cmd` `SFTPGO_ACTION`
- `SFTPGO_ACTION_FILE_SIZE`, non-zero for `pre-upload`,`upload`, `download` and `delete` actions if the file size is greater than `0`
- `SFTPGO_ACTION_FS_PROVIDER`, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
- `SFTPGO_ACTION_BUCKET`, non-empty for S3, GCS and Azure backends
- `SFTPGO_ACTION_ENDPOINT`, non-empty for S3, SFTP and Azure backend if configured
- `SFTPGO_ACTION_STATUS`, integer. Status for `upload`, `download` and `ssh_cmd` actions. 1 means no error, 2 means a generic error occurred, 3 means quota exceeded error
- `SFTPGO_ACTION_PROTOCOL`, string. Possible values are `SSH`, `SFTP`, `SCP`, `FTP`, `DAV`, `HTTP`, `HTTPShare`, `OIDC`, `DataRetention`
- `SFTPGO_ACTION_IP`, the action was executed from this IP address
- `SFTPGO_ACTION_SESSION_ID`, string. Unique protocol session identifier. For stateless protocols such as HTTP the session id will change for each request
- `SFTPGO_ACTION_OPEN_FLAGS`, integer. File open flags, can be non-zero for `pre-upload` action. If `SFTPGO_ACTION_FILE_SIZE` is greater than zero and `SFTPGO_ACTION_OPEN_FLAGS&512 == 0` the target file will not be truncated
- `SFTPGO_ACTION_TIMESTAMP`, int64. Event timestamp as nanoseconds since epoch
Previous global environment variables aren't cleared when the script is called.
The program must finish within 30 seconds.
If the `hook` defines an HTTP URL then this URL will be invoked as HTTP POST. The request body will contain a JSON serialized struct with the following fields:
- `action`, string
- `username`, string
- `path`, string
- `target_path`, string, included for `rename` action and `sftpgo-copy` SSH command
- `virtual_path`, string, virtual path, seen by SFTPGo users
- `virtual_target_path`, string, virtual target path, seen by SFTPGo users
- `ssh_cmd`, string, included for `ssh_cmd` action
- `file_size`, int64, included for `pre-upload`, `upload`, `download`, `delete` actions if the file size is greater than `0`
- `fs_provider`, integer, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
- `bucket`, string, included for S3, GCS and Azure backends
- `endpoint`, string, included for S3, SFTP and Azure backend if configured
- `status`, integer. Status for `upload`, `download` and `ssh_cmd` actions. 1 means no error, 2 means a generic error occurred, 3 means quota exceeded error
- `protocol`, string. Possible values are `SSH`, `SFTP`, `SCP`, `FTP`, `DAV`, `HTTP`, `HTTPShare`, `OIDC`, `DataRetention`
- `ip`, string. The action was executed from this IP address
- `session_id`, string. Unique protocol session identifier. For stateless protocols such as HTTP the session id will change for each request
- `open_flags`, integer. File open flags, can be non-zero for `pre-upload` action. If `file_size` is greater than zero and `file_size&512 == 0` the target file will not be truncated
- `timestamp`, int64. Event timestamp as nanoseconds since epoch
The HTTP hook will use the global configuration for HTTP clients and will respect the retry configurations.
The `pre-*` actions are always executed synchronously while the other ones are asynchronous. You can specify the actions to run synchronously via the `execute_sync` configuration key. Executing an action synchronously means that SFTPGo will not return a result code to the client (which is waiting for it) until your hook have completed its execution. If your hook takes a long time to complete this could cause a timeout on the client side, which wouldn't receive the server response in a timely manner and eventually drop the connection.
## Provider events
The `actions` struct inside the `data_provider` configuration section allows you to configure actions on data provider objects add, update, delete.
The supported object types are:
- `user`
- `admin`
- `api_key`
Actions will not be fired for internal updates, such as the last login or the user quota fields, or after external authentication.
If the `hook` defines a path to an external program, then this program can read the following environment variables:
- `SFTPGO_PROVIDER_ACTION`, supported values are `add`, `update`, `delete`
- `SFTPGO_PROVIDER_OBJECT_TYPE`, affected object type
- `SFTPGO_PROVIDER_OBJECT_NAME`, unique identifier for the affected object, for example username or key id
- `SFTPGO_PROVIDER_USERNAME`, the username that executed the action. There are two special usernames: `__self__` identifies a user/admin that updates itself and `__system__` identifies an action that does not have an explicit executor associated with it, for example users/admins can be added/updated by loading them from initial data
- `SFTPGO_PROVIDER_IP`, the action was executed from this IP address
- `SFTPGO_PROVIDER_TIMESTAMP`, event timestamp as nanoseconds since epoch
- `SFTPGO_PROVIDER_OBJECT`, object serialized as JSON with sensitive fields removed
Previous global environment variables aren't cleared when the script is called.
The program must finish within 15 seconds.
If the `hook` defines an HTTP URL then this URL will be invoked as HTTP POST. The action, username, ip, object_type and object_name and timestamp are added to the query string, for example `<hook>?action=update&username=admin&ip=127.0.0.1&object_type=user&object_name=user1&timestamp=1633860803249`, and the full object is sent serialized as JSON inside the POST body with sensitive fields removed.
The HTTP hook will use the global configuration for HTTP clients and will respect the retry configurations.
The structure for SFTPGo objects can be found within the [OpenAPI schema](../openapi/openapi.yaml).
## Pub/Sub services
You can forward SFTPGo events to several publish/subscribe systems using the [sftpgo-plugin-pubsub](https://github.com/sftpgo/sftpgo-plugin-pubsub). The notifiers SFTPGo plugins are not suitable for interactive actions such as `pre-*` events. Their scope is to simply forward events to external services. A custom hook is a better choice if you need to react to `pre-*` events.
## Database services
You can store SFTPGo events in database systems using the [sftpgo-plugin-eventstore](https://github.com/sftpgo/sftpgo-plugin-eventstore) and you can search the stored events using the [sftpgo-plugin-eventsearch](https://github.com/sftpgo/sftpgo-plugin-eventsearch).

View file

@ -1,20 +0,0 @@
# Data At Rest Encryption (DARE)
SFTPGo supports data at-rest encryption via its `cryptfs` virtual file system, in this mode SFTPGo transparently encrypts and decrypts data (to/from the local disk) on-the-fly during uploads and/or downloads, making sure that the files at-rest on the server-side are always encrypted.
Data At Rest Encryption is supported for local filesystem, for cloud storage backends you can use their server side encryption feature.
So, because of the way it works, as described here above, when you set up an encrypted filesystem for a user you need to make sure it points to an empty path/directory (that has no files in it). Otherwise, it would try to decrypt existing files that are not encrypted in the first place and fail.
The SFTPGo's `cryptfs` is a tiny wrapper around [sio](https://github.com/minio/sio) therefore data is encrypted and authenticated using `AES-256-GCM` or `ChaCha20-Poly1305`. AES-GCM will be used if the CPU provides hardware support for it.
The only required configuration parameter is a `passphrase`, each file will be encrypted using an unique, randomly generated secret key derived from the given passphrase using the HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as defined in [RFC 5869](http://tools.ietf.org/html/rfc5869). It is important to note that the per-object encryption key is never stored anywhere: it is derived from your `passphrase` and a randomly generated initialization vector just before encryption/decryption. The initialization vector is stored with the file.
The passphrase is stored encrypted itself according to your [KMS configuration](./kms.md) and is required to decrypt any file encrypted using an encryption key derived from it.
The encrypted filesystem has some limitations compared to the local, unencrypted, one:
- Resuming uploads is not supported.
- Opening a file for both reading and writing at the same time is not supported and so clients that require advanced filesystem-like features such as `sshfs` are not supported too.
- Truncate is not supported.
- System commands such as `git` or `rsync` are not supported: they will store data unencrypted.

View file

@ -1,32 +0,0 @@
# Data retention hook
This hook runs after a data retention check completes if you specify `Hook` between notifications methods when you start the check.
The `data_retention_hook` can be defined as the absolute path of your program or an HTTP URL.
If the hook defines an external program it can read the following environment variable:
- `SFTPGO_DATA_RETENTION_RESULT`, it contains the data retention check result JSON serialized.
Previous global environment variables aren't cleared when the script is called.
The program must finish within 20 seconds.
If the hook defines an HTTP URL then this URL will be invoked as HTTP POST and the POST body contains the data retention check result JSON serialized.
The HTTP hook will use the global configuration for HTTP clients and will respect the retry configurations.
Here is the schema for the data retention check result:
- `username`, string
- `status`, int. 1 means success, 0 error
- `start_time`, int64. Start time as UNIX timestamp in milliseconds
- `total_deleted_files`, int. Total number of files deleted
- `total_deleted_size`, int64. Total size deleted in bytes
- `elapsed`, int64. Elapsed time in milliseconds
- `details`, list of struct with details for each checked path, each struct contains the following fields:
- `path`, string
- `retention`, int. Retention time in hours
- `deleted_files`, int. Number of files deleted
- `deleted_size`, int64. Size deleted in bytes
- `info`, string. Informative, non fatal, message if any. For example it can indicates that the check was skipped because the user doesn't have the required permissions on this path
- `error`, string. Error message if any

View file

@ -1,69 +0,0 @@
# Defender
The built-in `defender` allows you to configure an auto-blocking policy for SFTPGo and thus helps to prevent DoS (Denial of Service) and brute force password guessing.
If enabled it will protect SFTP, HTTP, FTP and WebDAV services and it will automatically block hosts (IP addresses) that continually fail to log in or attempt to connect.
You can configure a score for the following events:
- `score_valid`, defines the score for valid login attempts, eg. user accounts that exist. Default `1`.
- `score_invalid`, defines the score for invalid login attempts, eg. non-existent user accounts or client disconnected for inactivity without authentication attempts. Default `2`.
- `score_limit_exceeded`, defines the score for hosts that exceeded the configured rate limits or the configured max connections per host. Default `3`.
And then you can configure:
- `observation_time`, defines the time window, in minutes, for tracking client errors.
- `threshold`, defines the threshold value before banning a host.
- `ban_time`, defines the time to ban a client, as minutes
So a host is banned, for `ban_time` minutes, if the sum of the scores has exceeded the defined threshold during the last observation time minutes.
By defining the scores, each type of event can be weighted. Let's see an example: if `score_invalid` is 3 and `threshold` is 8, a host will be banned after 3 login attempts with an non-existent user within the configured `observation_time`.
A banned IP has no score, it makes no sense to accumulate host events in memory for an already banned IP address.
If an already banned client tries to log in again, its ban time will be incremented according the `ban_time_increment` configuration.
The `ban_time_increment` is calculated as percentage of `ban_time`, so if `ban_time` is 30 minutes and `ban_time_increment` is 50 the host will be banned for additionally 15 minutes. You can also specify values greater than 100 for `ban_time_increment` if you want to increase the penalty for already banned hosts.
SFTPGo can store host scores and banned hosts in memory or within the configured data provider according to the `driver` set in the `defender` configuration section. The available drivers are `memory` and `provider`.
The `provider` driver is useful if you want to share the defender data across multiple SFTPGo instances and it requires a shared or distributed data provider: `MySQL`, `PostgreSQL` and `CockroachDB` are supported.
If you set the `provider` driver, the defender implementation may do many database queries (at least one query every time a new client connects to check if it is banned), if you have a single SFTPGo instance the `memory` driver is recommended.
For the `memory` driver, you can limit the memory usage using the `entries_soft_limit` and `entries_hard_limit` configuration keys.
The `provider` driver will periodically clean up expired hosts and events.
Using the REST API you can:
- list hosts within the defender's lists
- remove hosts from the defender's lists
The `defender` can also load a permanent block list and/or a safe list of ip addresses/networks from a file:
- `safelist_file`, defines the path to a file containing a list of ip addresses and/or networks to never ban.
- `blocklist_file`, defines the path to a file containing a list of ip addresses and/or networks to always ban.
These list must be stored as JSON conforming to the following schema:
- `addresses`, list of strings. Each string must be a valid IPv4/IPv6 address.
- `networks`, list of strings. Each string must be a valid IPv4/IPv6 CIDR address.
Here is a small example:
```json
{
"addresses":[
"192.0.2.1",
"2001:db8::68"
],
"networks":[
"192.0.3.0/24",
"2001:db8:1234::/48"
]
}
```
Small lists can also be set using the `safelist`/`blocklist` configuration parameters and or using environment variables. These lists will be merged with the ones specified via files, if any, so that you can set both.
These list will be always loaded in memory (even if you use the `provider` driver) for faster lookups. The REST API queries "live" data and not these lists.

View file

@ -1,61 +0,0 @@
# Dynamic user creation or modification
Dynamic user creation or modification is supported via an external program or an HTTP URL that can be invoked just before the user login.
To enable dynamic user modification, you must set the absolute path of your program or an HTTP URL using the `pre_login_hook` key in your configuration file.
The external program can read the following environment variables to get info about the user trying to login:
- `SFTPGO_LOGIND_USER`, it contains the user trying to login serialized as JSON. A JSON serialized user id equal to zero means the user does not exist inside SFTPGo
- `SFTPGO_LOGIND_METHOD`, possible values are: `password`, `publickey`, `keyboard-interactive`, `TLSCertificate`, `IDP` (external identity provider)
- `SFTPGO_LOGIND_IP`, ip address of the user trying to login
- `SFTPGO_LOGIND_PROTOCOL`, possible values are `SSH`, `FTP`, `DAV`, `HTTP`, `OIDC` (OpenID Connect)
The program must write, on its standard output:
- an empty string (or no response at all) if the user should not be created/updated
- or the SFTPGo user, JSON serialized, if you want to create or update the given user
If the hook is an HTTP URL then it will be invoked as HTTP POST. The login method, the used protocol and the ip address of the user trying to login are added to the query string, for example `<http_url>?login_method=password&ip=1.2.3.4&protocol=SSH`.
The request body will contain the user trying to login serialized as JSON. If no modification is needed the HTTP response code must be 204, otherwise the response code must be 200 and the response body a valid SFTPGo user serialized as JSON.
Actions defined for user's updates will not be executed in this case and an already logged in user with the same username will not be disconnected, you have to handle these things yourself.
The JSON response can include only the fields to update instead of the full user. For example, if you want to disable the user, you can return a response like this:
```json
{"status": 0}
```
Please note that if you want to create a new user, the pre-login hook response must include all the mandatory user fields.
The program hook must finish within 30 seconds, the HTTP hook will use the global configuration for HTTP clients.
If an error happens while executing the hook then login will be denied.
"Dynamic user creation or modification" and "External Authentication" are mutually exclusive, they are quite similar, the difference is that "External Authentication" returns an already authenticated user while using "Dynamic users modification" you simply create or update a user. The authentication will be checked inside SFTPGo.
In other words while using "External Authentication" the external program receives the credentials of the user trying to login (for example the cleartext password) and it needs to validate them. While using "Dynamic users modification" the pre-login program receives the user stored inside the dataprovider (it includes the hashed password if any) and it can modify it, after the modification SFTPGo will check the credentials of the user trying to login.
For SFTPGo users (not admins) authenticating using an external identity provider such as OpenID Connect, the pre-login hook will be executed after a successful authentication against the external IDP so that you can create/update the SFTPGo user matching the one authenticated against the identity provider. This is the only case where the pre-login hook is executed even if an external authentication hook is defined.
You can disable the hook on a per-user basis.
Let's see a very basic example. Our sample program will grant access to the existing user `test_user` only in the time range 10:00-18:00. Other users will not be modified since the program will terminate with no output.
```shell
#!/bin/bash
CURRENT_TIME=`date +%H:%M`
if [[ "$SFTPGO_LOGIND_USER" =~ "\"test_user\"" ]]
then
if [[ $CURRENT_TIME > "18:00" || $CURRENT_TIME < "10:00" ]]
then
echo '{"status":0}'
else
echo '{"status":1}'
fi
fi
```
Please note that this is a demo program and it might not work in all cases. For example, the username should be obtained by parsing the JSON serialized user and not by searching the username inside the JSON as shown here.
The structure for SFTPGo users can be found within the [OpenAPI schema](../openapi/openapi.yaml).

View file

@ -1,81 +0,0 @@
# External Authentication
To enable external authentication, you must set the absolute path of your authentication program or an HTTP URL using the `external_auth_hook` key in your configuration file.
The external program can read the following environment variables to get info about the user trying to authenticate:
- `SFTPGO_AUTHD_USERNAME`
- `SFTPGO_AUTHD_USER`, STPGo user serialized as JSON, empty if the user does not exist within the data provider
- `SFTPGO_AUTHD_IP`
- `SFTPGO_AUTHD_PROTOCOL`, possible values are `SSH`, `FTP`, `DAV`, `HTTP`
- `SFTPGO_AUTHD_PASSWORD`, not empty for password authentication
- `SFTPGO_AUTHD_PUBLIC_KEY`, not empty for public key authentication
- `SFTPGO_AUTHD_KEYBOARD_INTERACTIVE`, not empty for keyboard interactive authentication
- `SFTPGO_AUTHD_TLS_CERT`, TLS client certificate PEM encoded. Not empty for TLS certificate authentication
Previous global environment variables aren't cleared when the script is called. The content of these variables is _not_ quoted. They may contain special characters. They are under the control of a possibly malicious remote user.
The program can inspect the SFTPGo user, if it exists, using the `SFTPGO_AUTHD_USER` environment variable.
The program must write, on its standard output:
- a valid SFTPGo user serialized as JSON if the authentication succeeds. The user will be added/updated within the defined data provider
- an empty string, or no response at all, if authentication succeeds and the existing SFTPGo user does not need to be updated. Please note that in versions 2.0.x and earlier an empty response was interpreted as an authentication error
- a user with an empty username if the authentication fails
If the hook is an HTTP URL then it will be invoked as HTTP POST. The request body will contain a JSON serialized struct with the following fields:
- `username`
- `ip`
- `user`, STPGo user, omitted if the user does not exist within the data provider
- `protocol`, possible values are `SSH`, `FTP`, `DAV`, `HTTP`
- `password`, not empty for password authentication
- `public_key`, not empty for public key authentication
- `keyboard_interactive`, not empty for keyboard interactive authentication
- `tls_cert`, TLS client certificate PEM encoded. Not empty for TLS certificate authentication
If authentication succeeds the HTTP response code must be 200 and the response body can be:
- a valid SFTPGo user serialized as JSON. The user will be added/updated within the defined data provider
- empty, the existing SFTPGo user does not need to be updated. Please note that in versions 2.0.x and earlier an empty response was interpreted as an authentication error
If the authentication fails the HTTP response code must be != 200 or the returned SFTPGo user must have an empty username.
If the hook returns a user who is only allowed to authenticate using public key + password (multi step authentication), your hook will be invoked for each authentication step, so it must validate the public key and password separately. SFTPGo will take care that the client uses the allowed sequence.
Actions defined for users added/updated will not be executed in this case and an already logged in user with the same username will not be disconnected.
The program hook must finish within 30 seconds, the HTTP hook timeout will use the global configuration for HTTP clients.
This method is slower than built-in authentication, but it's very flexible as anyone can easily write his own authentication hooks.
You can also restrict the authentication scope for the hook using the `external_auth_scope` configuration key:
- `0` means all supported authentication scopes. The external hook will be used for password, public key, keyboard interactive and TLS certificate authentication
- `1` means passwords only
- `2` means public keys only
- `4` means keyboard interactive only
- `8` means TLS certificate only
You can combine the scopes. For example, 3 means password and public key, 5 means password and keyboard interactive, and so on.
Let's see a very basic example. Our sample authentication program will only accept user `test_user` with any password or public key.
```shell
#!/bin/sh
if test "$SFTPGO_AUTHD_USERNAME" = "test_user"; then
echo '{"status":1,"username":"test_user","expiration_date":0,"home_dir":"/tmp/test_user","uid":0,"gid":0,"max_sessions":0,"quota_size":0,"quota_files":100000,"permissions":{"/":["*"],"/somedir":["list","download"]},"upload_bandwidth":0,"download_bandwidth":0,"filters":{"allowed_ip":[],"denied_ip":[]},"public_keys":[]}'
else
echo '{"username":""}'
fi
```
The structure for SFTPGo users can be found within the [OpenAPI schema](../openapi/openapi.yaml).
You can instruct SFTPGo to cache the external user by setting an `external_auth_cache_time` in user object returned by your hook. The `external_auth_cache_time` defines the cache time in seconds.
You can disable the hook on a per-user basis so that you can mix external and internal users.
An example authentication program allowing to authenticate against an LDAP server can be found inside the source tree [ldapauth](../examples/ldapauth) directory.
An example server, to use as HTTP authentication hook, allowing to authenticate against an LDAP server can be found inside the source tree [ldapauthserver](../examples/ldapauthserver) directory.
If you have an external authentication hook that could be useful to others too, please let us know and/or please send a pull request.

View file

@ -1,504 +0,0 @@
# Configuring SFTPGo
<details><summary><font size=5> Command line option</font></summary>
The SFTPGo executable can be used this way:
```console
Usage:
sftpgo [command]
Available Commands:
acme Obtain TLS certificates from ACME-based CAs like Let's Encrypt
gen A collection of useful generators
help Help about any command
initprovider Initialize and/or updates the configured data provider
portable Serve a single directory/account
resetprovider Reset the configured provider, any data will be lost
revertprovider Revert the configured data provider to a previous version
serve Start the SFTPGo service
smtptest Test the SMTP configuration
startsubsys Use sftpgo as SFTP file transfer subsystem
Flags:
-h, --help help for sftpgo
-v, --version
Use "sftpgo [command] --help" for more information about a command
```
The `serve` command supports the following flags:
- `--config-dir` string. Location of the config dir. This directory is used as the base for files with a relative path, eg. the private keys for the SFTP server or the SQLite database if you use SQLite as data provider. The configuration file, if not explicitly set, is looked for in this dir. We support reading from JSON, TOML, YAML, HCL, envfile and Java properties config files. The default config file name is `sftpgo` and therefore `sftpgo.json`, `sftpgo.yaml` and so on are searched. The default value is the working directory (".") or the value of `SFTPGO_CONFIG_DIR` environment variable.
- `--config-file` string. This flag explicitly defines the path, name and extension of the config file. If must be an absolute path or a path relative to the configuration directory. The specified file name must have a supported extension (JSON, YAML, TOML, HCL or Java properties). The default value is empty or the value of `SFTPGO_CONFIG_FILE` environment variable.
- `--loaddata-from` string. Load users and folders from this file. The file must be specified as absolute path and it must contain a backup obtained using the `dumpdata` REST API or compatible content. The default value is empty or the value of `SFTPGO_LOADDATA_FROM` environment variable.
- `--loaddata-clean` boolean. Determine if the loaddata-from file should be removed after a successful load. Default `false` or the value of `SFTPGO_LOADDATA_CLEAN` environment variable (1 or `true`, 0 or `false`).
- `--loaddata-mode`, integer. Restore mode for data to load. 0 means new users are added, existing users are updated. 1 means new users are added, existing users are not modified. Default 1 or the value of `SFTPGO_LOADDATA_MODE` environment variable.
- `--loaddata-scan`, integer. Quota scan mode after data load. 0 means no quota scan. 1 means quota scan. 2 means scan quota if the user has quota restrictions. Default 0 or the value of `SFTPGO_LOADDATA_QUOTA_SCAN` environment variable.
- `--log-compress` boolean. Determine if the rotated log files should be compressed using gzip. Default `false` or the value of `SFTPGO_LOG_COMPRESS` environment variable (1 or `true`, 0 or `false`). It is unused if `log-file-path` is empty.
- `--log-file-path` string. Location for the log file, default "sftpgo.log" or the value of `SFTPGO_LOG_FILE_PATH` environment variable. Leave empty to write logs to the standard error.
- `--log-max-age` int. Maximum number of days to retain old log files. Default 28 or the value of `SFTPGO_LOG_MAX_AGE` environment variable. It is unused if `log-file-path` is empty.
- `--log-max-backups` int. Maximum number of old log files to retain. Default 5 or the value of `SFTPGO_LOG_MAX_BACKUPS` environment variable. It is unused if `log-file-path` is empty.
- `--log-max-size` int. Maximum size in megabytes of the log file before it gets rotated. Default 10 or the value of `SFTPGO_LOG_MAX_SIZE` environment variable. It is unused if `log-file-path` is empty.
- `--log-verbose` boolean. Enable verbose logs. Default `true` or the value of `SFTPGO_LOG_VERBOSE` environment variable (1 or `true`, 0 or `false`).
- `--log-utc-time` boolean. Enable UTC time for logging. Default `false` or the value of `SFTPGO_LOG_UTC_TIME` environment variable (1 or `true`, 0 or `false`)
Log file can be rotated on demand sending a `SIGUSR1` signal on Unix based systems and using the command `sftpgo service rotatelogs` on Windows.
If you don't configure any private host key, the daemon will use `id_rsa`, `id_ecdsa` and `id_ed25519` in the configuration directory. If these files don't exist, the daemon will attempt to autogenerate them. The server supports any private key format supported by [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/keys.go#L33).
The `gen` command allows to generate completion scripts for your shell and man pages.
</details>
<details><summary><font size=5> Configuration file</font></summary>
The configuration file contains the following sections:
- **"common"**, configuration parameters shared among all the supported protocols
- `idle_timeout`, integer. Time in minutes after which an idle client will be disconnected. 0 means disabled. Default: 15
- `upload_mode` integer. 0 means standard: the files are uploaded directly to the requested path. 1 means atomic: files are uploaded to a temporary path and renamed to the requested path when the client ends the upload. Atomic mode avoids problems such as a web server that serves partial files when the files are being uploaded. In atomic mode, if there is an upload error, the temporary file is deleted and so the requested upload path will not contain a partial file. 2 means atomic with resume support: same as atomic but if there is an upload error, the temporary file is renamed to the requested path and not deleted. This way, a client can reconnect and resume the upload. Default: 0
- `actions`, struct. It contains the command to execute and/or the HTTP URL to notify and the trigger conditions. See [Custom Actions](./custom-actions.md) for more details
- `execute_on`, list of strings. Valid values are `pre-download`, `download`, `pre-upload`, `upload`, `pre-delete`, `delete`, `rename`, `mkdir`, `rmdir`, `ssh_cmd`. Leave empty to disable actions.
- `execute_sync`, list of strings. Actions, defined in the `execute_on` list above, to be performed synchronously. The `pre-*` actions are always executed synchronously while the other ones are asynchronous. Executing an action synchronously means that SFTPGo will not return a result code to the client (which is waiting for it) until your hook have completed its execution. Leave empty to execute only the defined `pre-*` hook synchronously
- `hook`, string. Absolute path to the command to execute or HTTP URL to notify.
- `setstat_mode`, integer. 0 means "normal mode": requests for changing permissions, owner/group and access/modification times are executed. 1 means "ignore mode": requests for changing permissions, owner/group and access/modification times are silently ignored. 2 means "ignore mode if not supported": requests for changing permissions and owner/group are silently ignored for cloud filesystems and executed for local/SFTP filesystem. Requests for changing modification times are always executed for local/SFTP filesystems and are executed for cloud based filesystems if the target is a file and there is a metadata plugin available. A metadata plugin can be found [here](https://github.com/sftpgo/sftpgo-plugin-metadata).
- `temp_path`, string. Defines the path for temporary files such as those used for atomic uploads or file pipes. If you set this option you must make sure that the defined path exists, is accessible for writing by the user running SFTPGo, and is on the same filesystem as the users home directories otherwise the renaming for atomic uploads will become a copy and therefore may take a long time. The temporary files are not namespaced. The default is generally fine. Leave empty for the default.
- `proxy_protocol`, integer. Support for [HAProxy PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGINX, you can enable the proxy protocol. It provides a convenient way to safely transport connection information such as a client's address across multiple layers of NAT or TCP proxies to get the real client IP address instead of the proxy IP. Both protocol versions 1 and 2 are supported. If the proxy protocol is enabled in SFTPGo then you have to enable the protocol in your proxy configuration too. For example, for HAProxy, add `send-proxy` or `send-proxy-v2` to each server configuration line. The following modes are supported:
- 0, disabled
- 1, enabled. If the upstream IP is not allowed to send a proxy header the header be ignored. Using this mode does not mean that we can accept connections with and without the proxy header. We always try to read the proxy header and we ignore it if the upstream IP is not allowed to send a proxy header
- 2, required. If the upstream IP is not allowed to send a proxy header the connection will be rejected
- `proxy_allowed`, List of IP addresses and IP ranges allowed to send the proxy header:
- If `proxy_protocol` is set to 1 and we receive a proxy header from an IP that is not in the list then the connection will be accepted and the header will be ignored
- If `proxy_protocol` is set to 2 and we receive a proxy header from an IP that is not in the list then the connection will be rejected
- `startup_hook`, string. Absolute path to an external program or an HTTP URL to invoke as soon as SFTPGo starts. If you define an HTTP URL it will be invoked using a `GET` request. Please note that SFTPGo services may not yet be available when this hook is run. Leave empty do disable
- `post_connect_hook`, string. Absolute path to the command to execute or HTTP URL to notify. See [Post-connect hook](./post-connect-hook.md) for more details. Leave empty to disable
- `post_disconnect_hook`, string. Absolute path to the command to execute or HTTP URL to notify. See [Post-disconnect hook](./post-disconnect-hook.md) for more details. Leave empty to disable
- `data_retention_hook`, string. Absolute path to the command to execute or HTTP URL to notify. See [Data retention hook](./data-retention-hook.md) for more details. Leave empty to disable
- `max_total_connections`, integer. Maximum number of concurrent client connections. 0 means unlimited. Default: 0.
- `max_per_host_connections`, integer. Maximum number of concurrent client connections from the same host (IP). If the defender is enabled, exceeding this limit will generate `score_limit_exceeded` events and thus hosts that repeatedly exceed the max allowed connections can be automatically blocked. 0 means unlimited. Default: 20.
- `whitelist_file`, string. Path to a file containing a list of IP addresses and/or networks to allow. Only the listed IPs/networks can access the configured services, all other client connections will be dropped before they even try to authenticate. The whitelist must be a JSON file with the same structure documented for the [defenders's list](./defender.md). The whitelist can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows. Default: "".
- `defender`, struct containing the defender configuration. See [Defender](./defender.md) for more details.
- `enabled`, boolean. Default `false`.
- `driver`, string. Supported drivers are `memory` and `provider`. The `provider` driver will use the configured data provider to store defender events and it is supported for `MySQL`, `PostgreSQL` and `CockroachDB` data providers. Using the `provider` driver you can share the defender events among multiple SFTPGO instances. For a single instance the `memory` driver will be much faster. Default: `memory`.
- `ban_time`, integer. Ban time in minutes.
- `ban_time_increment`, integer. Ban time increment, as a percentage, if a banned host tries to connect again.
- `threshold`, integer. Threshold value for banning a client.
- `score_invalid`, integer. Score for invalid login attempts, eg. non-existent user accounts or client disconnected for inactivity without authentication attempts.
- `score_valid`, integer. Score for valid login attempts, eg. user accounts that exist.
- `score_limit_exceeded`, integer. Score for hosts that exceeded the configured rate limits or the maximum, per-host, allowed connections.
- `observation_time`, integer. Defines the time window, in minutes, for tracking client errors. A host is banned if it has exceeded the defined threshold during the last observation time minutes.
- `entries_soft_limit`, integer. Ignored for `provider` driver. Default: 100.
- `entries_hard_limit`, integer. The number of banned IPs and host scores kept in memory will vary between the soft and hard limit for `memory` driver. If you use the `provider` driver, this setting will limit the number of entries to return when you ask for the entire host list from the defender. Default: 150.
- `safelist_file`, string. Path to a file containing a list of ip addresses and/or networks to never ban.
- `blocklist_file`, string. Path to a file containing a list of ip addresses and/or networks to always ban. The lists can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows. An host that is already banned will not be automatically unbanned if you put it inside the safe list, you have to unban it using the REST API.
- `safelist`, list of IP addresses and/or IP ranges and/or networks to never ban. Invalid entries will be silently ignored. For large lists prefer `safelist_file`. `safelist` and `safelist_file` will be merged so that you can set both.
- `blocklist`, list of IP addresses and/or IP ranges and/or networks to always ban. Invalid entries will be silently ignored.. For large lists prefer `blocklist_file`. `blocklist` and `blocklist_file` will be merged so that you can set both.
- `rate_limiters`, list of structs containing the rate limiters configuration. Take a look [here](./rate-limiting.md) for more details. Each struct has the following fields:
- `average`, integer. Average defines the maximum rate allowed. 0 means disabled. Default: 0
- `period`, integer. Period defines the period as milliseconds. The rate is actually defined by dividing average by period Default: 1000 (1 second).
- `burst`, integer. Burst defines the maximum number of requests allowed to go through in the same arbitrarily small period of time. Default: 1
- `type`, integer. 1 means a global rate limiter, independent from the source host. 2 means a per-ip rate limiter. Default: 2
- `protocols`, list of strings. Available protocols are `SSH`, `FTP`, `DAV`, `HTTP`. By default all supported protocols are enabled
- `allow_list`, list of IP addresses and IP ranges excluded from rate limiting. Default: empty
- `generate_defender_events`, boolean. If `true`, the defender is enabled, and this is not a global rate limiter, a new defender event will be generated each time the configured limit is exceeded. Default `false`
- `entries_soft_limit`, integer.
- `entries_hard_limit`, integer. The number of per-ip rate limiters kept in memory will vary between the soft and hard limit
- **"acme"**, Automatic Certificate Management Environment (ACME) protocol configuration. To obtain the certificates the first time you have to configure the ACME protocol and execute the `sftpgo acme run` command. The SFTPGo service will take care of the automatic renewal of certificates for the configured domains.
- `domains`, list of domains for which to obtain certificates. If a single certificate is to be valid for multiple domains specify the names separated by commas, for example: `example.com,www.example.com`. An empty list means that ACME protocol is disabled. Default: empty.
- `email`, string. Email used for registration and recovery contact. Default: empty.
- `key_type`, string. Key type to use for private keys. Supported values: `2048` (RSA 2048), `4096` (RSA 4096), `8192` (RSA 8192), `P256` (EC 256), `P384` (EC 384). Default: `4096`
- `certs_path`, string. Directory, absolute or relative to the configuration directory, to use for storing certificates and related data.
- `ca_endpoint`, string. Default: `https://acme-v02.api.letsencrypt.org/directory`.
- `renew_days`, integer. The number of days left on a certificate to renew it. Default: `30`.
- `http01_challenge`, configuration for `HTTP-01` challenge type, the following fields are supported:
- `port`, integer. This challenge is expected to run on port `80`. If you set a port other than `80` you have to proxy the path `/.well-known/acme-challenge` from the port `80` to the configured port. Default: `80`.
- `proxy_header`, string. Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy. Empty means `Host`. Default: empty.
- `webroot`, string. Set the absolute path to the webroot folder to use for HTTP based challenges to write directly in a file in `.well-known/acme-challenge`. Setting a `webroot` disables the built-in server (the `port` setting is ignored) and expects the given directory to be publicly served, on port `80`, with access to `.well-known/acme-challenge`. If `webroot` is empty and `port` is `0` the `HTTP-01` challenge is disabled. Default: empty.
- `tls_alpn01_challenge`, configuration for `TLS-ALPN-01` challenge type, the following fields are supported:
- `port`, integer. This challenge is expected to run on port `443`. `0` means `TLS-ALPN-01` is disabled. Default: `0`.
- **"sftpd"**, the configuration for the SFTP server
- `bindings`, list of structs. Each struct has the following fields:
- `port`, integer. The port used for serving SFTP requests. 0 means disabled. Default: 2022
- `address`, string. Leave blank to listen on all available network interfaces. Default: ""
- `apply_proxy_config`, boolean. If enabled the common proxy configuration, if any, will be applied. Default `true`
- `max_auth_tries` integer. Maximum number of authentication attempts permitted per connection. If set to a negative number, the number of attempts is unlimited. If set to zero, the number of attempts is limited to 6.
- `banner`, string. Identification string used by the server. Leave empty to use the default banner. Default `SFTPGo_<version>`, for example `SSH-2.0-SFTPGo_0.9.5`
- `host_keys`, list of strings. It contains the daemon's private host keys. Each host key can be defined as a path relative to the configuration directory or an absolute one. If empty, the daemon will search or try to generate `id_rsa`, `id_ecdsa` and `id_ed25519` keys inside the configuration directory. If you configure absolute paths to files named `id_rsa`, `id_ecdsa` and/or `id_ed25519` then SFTPGo will try to generate these keys using the default settings.
- `host_certificates`, list of strings. Public host certificates. Each certificate can be defined as a path relative to the configuration directory or an absolute one. Certificate's public key must match a private host key otherwise it will be silently ignored. Default: empty.
- `host_key_algorithms`, list of strings. Public key algorithms that the server will accept for host key authentication. The supported values are: `rsa-sha2-512-cert-v01@openssh.com`, `rsa-sha2-256-cert-v01@openssh.com`, `ssh-rsa-cert-v01@openssh.com`, `ssh-dss-cert-v01@openssh.com`, `ecdsa-sha2-nistp256-cert-v01@openssh.com`, `ecdsa-sha2-nistp384-cert-v01@openssh.com`, `ecdsa-sha2-nistp521-cert-v01@openssh.com`, `ssh-ed25519-cert-v01@openssh.com`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `rsa-sha2-512`, `rsa-sha2-256`, `ssh-rsa`, `ssh-dss`, `ssh-ed25519`. Default values: `rsa-sha2-512-cert-v01@openssh.com`, `rsa-sha2-256-cert-v01@openssh.com`, `ecdsa-sha2-nistp256-cert-v01@openssh.com`, `ecdsa-sha2-nistp384-cert-v01@openssh.com`, `ecdsa-sha2-nistp521-cert-v01@openssh.com`, `ssh-ed25519-cert-v01@openssh.com`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `rsa-sha2-512`, `rsa-sha2-256`, `ssh-ed25519`.
- `kex_algorithms`, list of strings. Available KEX (Key Exchange) algorithms in preference order. Leave empty to use default values. The supported values are: `curve25519-sha256`, `curve25519-sha256@libssh.org`, `ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `diffie-hellman-group14-sha256`, `diffie-hellman-group16-sha512`, `diffie-hellman-group18-sha512`, `diffie-hellman-group14-sha1`, `diffie-hellman-group1-sha1`. Default values: `curve25519-sha256`, `curve25519-sha256@libssh.org`, `ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `diffie-hellman-group14-sha256`. SHA512 based KEXs are disabled by default because they are slow.
- `ciphers`, list of strings. Allowed ciphers in preference order. Leave empty to use default values. The supported values are: `aes128-gcm@openssh.com`, `aes256-gcm@openssh.com`, `chacha20-poly1305@openssh.com`, `aes128-ctr`, `aes192-ctr`, `aes256-ctr`, `aes128-cbc`, `aes192-cbc`, `aes256-cbc`, `3des-cbc`, `arcfour256`, `arcfour128`, `arcfour`. Default values: `aes128-gcm@openssh.com`, `aes256-gcm@openssh.com`, `chacha20-poly1305@openssh.com`, `aes128-ctr`, `aes192-ctr`, `aes256-ctr`. Please note that the ciphers disabled by default are insecure, you should expect that an active attacker can recover plaintext if you enable them.
- `macs`, list of strings. Available MAC (message authentication code) algorithms in preference order. Leave empty to use default values. The supported values are: `hmac-sha2-256-etm@openssh.com`, `hmac-sha2-256`, `hmac-sha2-512-etm@openssh.com`, `hmac-sha2-512`, `hmac-sha1`, `hmac-sha1-96`. Default values: `hmac-sha2-256-etm@openssh.com`, `hmac-sha2-256`.
- `trusted_user_ca_keys`, list of public keys paths of certificate authorities that are trusted to sign user certificates for authentication. The paths can be absolute or relative to the configuration directory.
- `revoked_user_certs_file`, path to a file containing the revoked user certificates. The path can be absolute or relative to the configuration directory. It must contain a JSON list with the public key fingerprints of the revoked certificates. Example content: `["SHA256:bsBRHC/xgiqBJdSuvSTNpJNLTISP/G356jNMCRYC5Es","SHA256:119+8cL/HH+NLMawRsJx6CzPF1I3xC+jpM60bQHXGE8"]`. The revocation list can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows. Default: "".
- `login_banner_file`, path to the login banner file. The contents of the specified file, if any, are sent to the remote user before authentication is allowed. It can be a path relative to the config dir or an absolute one. Leave empty to disable login banner.
- `enabled_ssh_commands`, list of enabled SSH commands. `*` enables all supported commands. More information can be found [here](./ssh-commands.md).
- `keyboard_interactive_authentication`, boolean. This setting specifies whether keyboard interactive authentication is allowed. If no keyboard interactive hook or auth plugin is defined the default is to prompt for the user password and then the one time authentication code, if defined. Default: `false`.
- `keyboard_interactive_auth_hook`, string. Absolute path to an external program or an HTTP URL to invoke for keyboard interactive authentication. See [Keyboard Interactive Authentication](./keyboard-interactive.md) for more details.
- `password_authentication`, boolean. Set to false to disable password authentication. This setting will disable multi-step authentication method using public key + password too. It is useful for public key only configurations if you need to manage old clients that will not attempt to authenticate with public keys if the password login method is advertised. Default: `true`.
- `folder_prefix`, string. Virtual root folder prefix to include in all file operations (ex: `/files`). The virtual paths used for per-directory permissions, file patterns etc. must not include the folder prefix. The prefix is only applied to SFTP requests (in SFTP server mode), SCP and other SSH commands will be automatically disabled if you configure a prefix. The prefix is ignored while running as OpenSSH's SFTP subsystem. This setting can help some specific migrations from SFTP servers based on OpenSSH and it is not recommended for general usage. Default: blank.
- **"ftpd"**, the configuration for the FTP server
- `bindings`, list of structs. Each struct has the following fields:
- `port`, integer. The port used for serving FTP requests. 0 means disabled. Default: 0.
- `address`, string. Leave blank to listen on all available network interfaces. Default: "".
- `apply_proxy_config`, boolean. If enabled the common proxy configuration, if any, will be applied. Please note that we expect the proxy header on control and data connections. Default `true`.
- `tls_mode`, integer. 0 means accept both cleartext and encrypted sessions. 1 means TLS is required for both control and data connection. 2 means implicit TLS. Do not enable this blindly, please check that a proper TLS config is in place if you set `tls_mode` is different from 0.
- `certificate_file`, string. Binding specific TLS certificate. This can be an absolute path or a path relative to the config dir.
- `certificate_key_file`, string. Binding specific private key matching the above certificate. This can be an absolute path or a path relative to the config dir. If not set the global ones will be used, if any.
- `min_tls_version`, integer. Defines the minimum version of TLS to be enabled. `12` means TLS 1.2 (and therefore TLS 1.2 and TLS 1.3 will be enabled),`13` means TLS 1.3. Default: `12`.
- `force_passive_ip`, ip address. External IP address to expose for passive connections. Leave empty to autodetect. If not empty, it must be a valid IPv4 address. Default: "".
- `passive_ip_overrides`, list of struct that allows to return a different passive ip based on the client IP address. Each struct has the following fields:
- `networks`, list of strings. Each string must define a network in CIDR notation, for example 192.168.1.0/24.
- `ip`, string. Passive IP to return if the client IP address belongs to the defined networks. Empty means autodetect.
- `client_auth_type`, integer. Set to `1` to require a client certificate and verify it. Set to `2` to request a client certificate during the TLS handshake and verify it if given, in this mode the client is allowed not to send a certificate. At least one certification authority must be defined in order to verify client certificates. If no certification authority is defined, this setting is ignored. Default: 0.
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
- `passive_connections_security`, integer. Defines the security checks for passive data connections. Set to `0` to require matching peer IP addresses of control and data connection. Set to `1` to disable any checks. Please note that if you run the FTP service behind a proxy you must enable the proxy protocol for control and data connections. Default: `0`.
- `active_connections_security`, integer. Defines the security checks for active data connections. The supported values are the same as described for `passive_connections_security`. Please note that disabling the security checks you will make the FTP service vulnerable to bounce attacks on active data connections, so change the default value only if you are on a trusted/internal network. Default: `0`.
- `debug`, boolean. If enabled any FTP command will be logged. This will generate a lot of logs. Enable only if you are investigating a client compatibility issue or something similar. You shouldn't leave this setting enabled for production servers. Default `false`.
- `banner`, string. Greeting banner displayed when a connection first comes in. Leave empty to use the default banner. Default `SFTPGo <version> ready`, for example `SFTPGo 1.0.0-dev ready`.
- `banner_file`, path to the banner file. The contents of the specified file, if any, are displayed when someone connects to the server. It can be a path relative to the config dir or an absolute one. If set, it overrides the banner string provided by the `banner` option. Leave empty to disable.
- `active_transfers_port_non_20`, boolean. Do not impose the port 20 for active data transfers. Enabling this option allows to run SFTPGo with less privilege. Default: `true`.
- `passive_port_range`, struct containing the key `start` and `end`. Port Range for data connections. Random if not specified. Default range is 50000-50100.
- `disable_active_mode`, boolean. Set to `true` to disable active FTP, default `false`.
- `enable_site`, boolean. Set to true to enable the FTP SITE command. We support `chmod` and `symlink` if SITE support is enabled. Default `false`
- `hash_support`, integer. Set to `1` to enable FTP commands that allow to calculate the hash value of files. These FTP commands will be enabled: `HASH`, `XCRC`, `MD5/XMD5`, `XSHA/XSHA1`, `XSHA256`, `XSHA512`. Please keep in mind that to calculate the hash we need to read the whole file, for remote backends this means downloading the file, for the encrypted backend this means decrypting the file. Default `0`.
- `combine_support`, integer. Set to 1 to enable support for the non standard `COMB` FTP command. Combine is only supported for local filesystem, for cloud backends it has no advantage as it will download the partial files and will upload the combined one. Cloud backends natively support multipart uploads. Default `0`.
- `certificate_file`, string. Certificate for FTPS. This can be an absolute path or a path relative to the config dir.
- `certificate_key_file`, string. Private key matching the above certificate. This can be an absolute path or a path relative to the config dir. A certificate and the private key are required to enable explicit and implicit TLS. Certificate and key files can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
- `ca_certificates`, list of strings. Set of root certificate authorities to be used to verify client certificates.
- `ca_revocation_lists`, list of strings. Set a revocation lists, one for each root CA, to be used to check if a client certificate has been revoked. The revocation lists can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
- **"webdavd"**, the configuration for the WebDAV server, more info [here](./webdav.md)
- `bindings`, list of structs. Each struct has the following fields:
- `port`, integer. The port used for serving WebDAV requests. 0 means disabled. Default: 0.
- `address`, string. Leave blank to listen on all available network interfaces. Default: "".
- `enable_https`, boolean. Set to `true` and provide both a certificate and a key file to enable HTTPS connection for this binding. Default `false`.
- `certificate_file`, string. Binding specific TLS certificate. This can be an absolute path or a path relative to the config dir.
- `certificate_key_file`, string. Binding specific private key matching the above certificate. This can be an absolute path or a path relative to the config dir. If not set the global ones will be used, if any.
- `min_tls_version`, integer. Defines the minimum version of TLS to be enabled. `12` means TLS 1.2 (and therefore TLS 1.2 and TLS 1.3 will be enabled),`13` means TLS 1.3. Default: `12`.
- `client_auth_type`, integer. Set to `1` to require a client certificate and verify it. Set to `2` to request a client certificate during the TLS handshake and verify it if given, in this mode the client is allowed not to send a certificate. At least one certification authority must be defined in order to verify client certificates. If no certification authority is defined, this setting is ignored. Default: 0.
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
- `prefix`, string. Prefix for WebDAV resources, if empty WebDAV resources will be available at the `/` URI. If defined it must be an absolute URI, for example `/dav`. Default: "".
- `proxy_allowed`, list of IP addresses and IP ranges allowed to set client IP proxy header such as `X-Forwarded-For`. Any client IP proxy headers, if set on requests from a connection address not in this list, will be silently ignored. Default: empty.
- `client_ip_proxy_header`, string. Defines the allowed client IP proxy header such as `X-Forwarded-For`, `X-Real-IP` etc. Default: empty
- `client_ip_header_depth`, integer. Some client IP headers such as `X-Forwarded-For` can contain multiple IP address, this setting define the position to trust starting from the right. For example if we have: `10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1` and the depth is `0`, SFTPGo will use `13.0.0.1` as client IP, if depth is `1`, `12.0.0.1` will be used and so on. Default: `0`.
- `certificate_file`, string. Certificate for WebDAV over HTTPS. This can be an absolute path or a path relative to the config dir.
- `certificate_key_file`, string. Private key matching the above certificate. This can be an absolute path or a path relative to the config dir. A certificate and a private key are required to enable HTTPS connections. Certificate and key files can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
- `ca_certificates`, list of strings. Set of root certificate authorities to be used to verify client certificates.
- `ca_revocation_lists`, list of strings. Set a revocation lists, one for each root CA, to be used to check if a client certificate has been revoked. The revocation lists can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
- `cors` struct containing CORS configuration. SFTPGo uses [Go CORS handler](https://github.com/rs/cors), please refer to upstream documentation for fields meaning and their default values.
- `enabled`, boolean, set to true to enable CORS.
- `allowed_origins`, list of strings.
- `allowed_methods`, list of strings.
- `allowed_headers`, list of strings.
- `exposed_headers`, list of strings.
- `allow_credentials` boolean.
- `max_age`, integer.
- `cache` struct containing cache configuration for the authenticated users.
- `enabled`, boolean, set to true to enable user caching. Default: true.
- `expiration_time`, integer. Expiration time, in minutes, for the cached users. 0 means unlimited. Default: 0.
- `max_size`, integer. Maximum number of users to cache. 0 means unlimited. Default: 50.
- **"data_provider"**, the configuration for the data provider
- `driver`, string. Supported drivers are `sqlite`, `mysql`, `postgresql`, `cockroachdb`, `bolt`, `memory`
- `name`, string. Database name. For driver `sqlite` this can be the database name relative to the config dir or the absolute path to the SQLite database. For driver `memory` this is the (optional) path relative to the config dir or the absolute path to the provider dump, obtained using the `dumpdata` REST API, to load. This dump will be loaded at startup and can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows. The `memory` provider will not modify the provided file so quota usage and last login will not be persisted. If you plan to use a SQLite database over a `cifs` network share (this is not recommended in general) you must use the `nobrl` mount option otherwise you will get the `database is locked` error. Some users reported that the `bolt` provider works fine over `cifs` shares.
- `host`, string. Database host. Leave empty for drivers `sqlite`, `bolt` and `memory`
- `port`, integer. Database port. Leave empty for drivers `sqlite`, `bolt` and `memory`
- `username`, string. Database user. Leave empty for drivers `sqlite`, `bolt` and `memory`
- `password`, string. Database password. Leave empty for drivers `sqlite`, `bolt` and `memory`
- `sslmode`, integer. Used for drivers `mysql` and `postgresql`. 0 disable TLS connections, 1 require TLS, 2 set TLS mode to `verify-ca` for driver `postgresql` and `skip-verify` for driver `mysql`, 3 set TLS mode to `verify-full` for driver `postgresql` and `preferred` for driver `mysql`
- `root_cert`, string. Path to the root certificate authority used to verify that the server certificate was signed by a trusted CA
- `client_cert`, string. Path to the client certificate for two-way TLS authentication
- `client_key`,string. Path to the client key for two-way TLS authentication
- `connection_string`, string. Provide a custom database connection string. If not empty, this connection string will be used instead of building one using the previous parameters. Leave empty for drivers `bolt` and `memory`
- `sql_tables_prefix`, string. Prefix for SQL tables
- `track_quota`, integer. Set the preferred mode to track users quota between the following choices:
- 0, disable quota tracking. REST API to scan users home directories/virtual folders and update quota will do nothing
- 1, quota is updated each time a user uploads or deletes a file, even if the user has no quota restrictions
- 2, quota is updated each time a user uploads or deletes a file, but only for users with quota restrictions and for virtual folders. With this configuration, the `quota scan` and `folder_quota_scan` REST API can still be used to periodically update space usage for users without quota restrictions and for folders
- `delayed_quota_update`, integer. This configuration parameter defines the number of seconds to accumulate quota updates. If there are a lot of close uploads, accumulating quota updates can save you many queries to the data provider. If you want to track quotas, a scheduled quota update is recommended in any case, the stored quota may be incorrect for several reasons, such as an unexpected shutdown while uploading files, temporary provider failures, files copied outside of SFTPGo, and so on. You could use the [quotascan example](../examples/quotascan) as a starting point. 0 means immediate quota update.
- `pool_size`, integer. Sets the maximum number of open connections for `mysql` and `postgresql` driver. Default 0 (unlimited)
- `users_base_dir`, string. Users default base directory. If no home dir is defined while adding a new user, and this value is a valid absolute path, then the user home dir will be automatically defined as the path obtained joining the base dir and the username
- `actions`, struct. It contains the command to execute and/or the HTTP URL to notify and the trigger conditions. See [Custom Actions](./custom-actions.md) for more details
- `execute_on`, list of strings. Valid values are `add`, `update`, `delete`. `update` action will not be fired for internal updates such as the last login or the user quota fields.
- `execute_for`, list of strings. Defines the provider objects that trigger the action. Valid values are `user`, `admin`, `api_key`.
- `hook`, string. Absolute path to the command to execute or HTTP URL to notify.
- `external_auth_hook`, string. Absolute path to an external program or an HTTP URL to invoke for users authentication. See [External Authentication](./external-auth.md) for more details. Leave empty to disable.
- `external_auth_scope`, integer. 0 means all supported authentication scopes (passwords, public keys and keyboard interactive). 1 means passwords only. 2 means public keys only. 4 means key keyboard interactive only. 8 means TLS certificate. The flags can be combined, for example 6 means public keys and keyboard interactive
- `credentials_path`, string. It defines the directory for storing user provided credential files such as Google Cloud Storage credentials. This can be an absolute path or a path relative to the config dir
- `pre_login_hook`, string. Absolute path to an external program or an HTTP URL to invoke to modify user details just before the login. See [Dynamic user modification](./dynamic-user-mod.md) for more details. Leave empty to disable.
- `post_login_hook`, string. Absolute path to an external program or an HTTP URL to invoke to notify a successful or failed login. See [Post-login hook](./post-login-hook.md) for more details. Leave empty to disable.
- `post_login_scope`, defines the scope for the post-login hook. 0 means notify both failed and successful logins. 1 means notify failed logins. 2 means notify successful logins.
- `check_password_hook`, string. Absolute path to an external program or an HTTP URL to invoke to check the user provided password. See [Check password hook](./check-password-hook.md) for more details. Leave empty to disable.
- `check_password_scope`, defines the scope for the check password hook. 0 means all protocols, 1 means SSH, 2 means FTP, 4 means WebDAV. You can combine the scopes, for example 6 means FTP and WebDAV.
- `password_hashing`, struct. It contains the configuration parameters to be used to generate the password hash. SFTPGo can verify passwords in several formats and uses, by default, the `bcrypt` algorithm to hash passwords in plain-text before storing them inside the data provider. These options allow you to customize how the hash is generated.
- `argon2_options`, struct containing the options for argon2id hashing algorithm. The `memory` and `iterations` parameters control the computational cost of hashing the password. The higher these figures are, the greater the cost of generating the hash and the longer the runtime. It also follows that the greater the cost will be for any attacker trying to guess the password. If the code is running on a machine with multiple cores, then you can decrease the runtime without reducing the cost by increasing the `parallelism` parameter. This controls the number of threads that the work is spread across.
- `memory`, unsigned integer. The amount of memory used by the algorithm (in kibibytes). Default: 65536.
- `iterations`, unsigned integer. The number of iterations over the memory. Default: 1.
- `parallelism`. unsigned 8 bit integer. The number of threads (or lanes) used by the algorithm. Default: 2.
- `bcrypt_options`, struct containing the options for bcrypt hashing algorithm
- `cost`, integer between 4 and 31. Default: 10
- `algo`, string. Algorithm to use for hashing passwords. Available algorithms: `argon2id`, `bcrypt`. For bcrypt hashing we use the `$2a$` prefix. Default: `bcrypt`
- `password_validation` struct. It defines the password validation rules for admins and protocol users.
- `admins`, struct. It defines the password validation rules for SFTPGo admins.
- `min_entropy`, float. Defines the minimum password entropy. Take a looke [here](https://github.com/wagslane/go-password-validator#what-entropy-value-should-i-use) for more details. `0` means disabled, any password will be accepted. Default: `0`.
- `users`, struct. It defines the password validation rules for SFTPGo protocol users.
- `min_entropy`, float. Default: `0`.
- `password_caching`, boolean. Verifying argon2id passwords has a high memory and computational cost, verifying bcrypt passwords has a high computational cost, by enabling, in memory, password caching you reduce these costs. Default: `true`
- `update_mode`, integer. Defines how the database will be initialized/updated. 0 means automatically. 1 means manually using the initprovider sub-command.
- `create_default_admin`, boolean. Before you can use SFTPGo you need to create an admin account. If you open the admin web UI, a setup screen will guide you in creating the first admin account. You can automatically create the first admin account by enabling this setting and setting the environment variables `SFTPGO_DEFAULT_ADMIN_USERNAME` and `SFTPGO_DEFAULT_ADMIN_PASSWORD`. You can also create the first admin by loading initial data. This setting has no effect if an admin account is already found within the data provider. Default `false`.
- `naming_rules`, integer. Naming rules for usernames, folder and group names. `0` means no rules. `1` means you can use any UTF-8 character. The names are used in URIs for REST API and Web admin. If not set only unreserved URI characters are allowed: ALPHA / DIGIT / "-" / "." / "_" / "~". `2` means names are converted to lowercase before saving/matching and so case insensitive matching is possible. `3` means trimming trailing and leading white spaces before saving/matching. Rules can be combined, for example `3` means both converting to lowercase and allowing any UTF-8 character. Enabling these options for existing installations could be backward incompatible, some users could be unable to login, for example existing users with mixed cases in their usernames. You have to ensure that all existing users respect the defined rules. Default: `1`.
- `is_shared`, integer. If the data provider is shared across multiple SFTPGo instances, set this parameter to `1`. `MySQL`, `PostgreSQL` and `CockroachDB` can be shared, this setting is ignored for other data providers. For shared data providers, active transfers are persisted in the database and thus quota checks between ongoing transfers will work cross multiple instances. Password reset requests and OIDC tokens/states are also persisted in the database if the provider is shared. The database table `shared_sessions` is used only to store temporary sessions. In performance critical installations, you might consider using a database-specific optimization, for example you might use an `UNLOGGED` table for PostgreSQL. This optimization in only required in very limited use cases. Default: `0`.
- `backups_path`, string. Path to the backup directory. This can be an absolute path or a path relative to the config dir. We don't allow backups in arbitrary paths for security reasons.
- `auto_backup`, struct. Defines the configuration for automatic data provider backups. Example: hour `0` and day_of_week `*` means a backup every day at midnight. The backup file name is in the format `backup_<day_of_week>_<hour>.json`, files with the same name will be overwritten. Note, this process will only backup provider data (users, folders, shares, admins, api keys) and will not backup the configuration file and users files.
- `enabled`, boolean. Set to `true` to enable automatic backups. Default: `true`.
- `hour`, string. Hour as standard cron expression. Allowed values: 0-23. Allowed special characters: asterisk (`*`), slash (`/`), comma (`,`), hyphen (`-`). More info about special characters [here](https://pkg.go.dev/github.com/robfig/cron#hdr-Special_Characters). Default: `0`.
- `day_of_week`, string. Day of week as standard cron expression. Allowed values: 0-6 (Sunday to Saturday). Allowed special characters: asterisk (`*`), slash (`/`), comma (`,`), hyphen (`-`), question mark (`?`). More info about special characters [here](https://pkg.go.dev/github.com/robfig/cron#hdr-Special_Characters). Default: `*`.
- **"httpd"**, the configuration for the HTTP server used to serve REST API and to expose the built-in web interface
- `bindings`, list of structs. Each struct has the following fields:
- `port`, integer. The port used for serving HTTP requests. Default: 8080.
- `address`, string. Leave blank to listen on all available network interfaces. On *NIX you can specify an absolute path to listen on a Unix-domain socket Default: blank.
- `enable_web_admin`, boolean. Set to `false` to disable the built-in web admin for this binding. You also need to define `templates_path` and `static_files_path` to use the built-in web admin interface. Default `true`.
- `enable_web_client`, boolean. Set to `false` to disable the built-in web client for this binding. You also need to define `templates_path` and `static_files_path` to use the built-in web client interface. Default `true`.
- `enable_https`, boolean. Set to `true` and provide both a certificate and a key file to enable HTTPS connection for this binding. Default `false`.
- `certificate_file`, string. Binding specific TLS certificate. This can be an absolute path or a path relative to the config dir.
- `certificate_key_file`, string. Binding specific private key matching the above certificate. This can be an absolute path or a path relative to the config dir. If not set the global ones will be used, if any.
- `min_tls_version`, integer. Defines the minimum version of TLS to be enabled. `12` means TLS 1.2 (and therefore TLS 1.2 and TLS 1.3 will be enabled),`13` means TLS 1.3. Default: `12`.
- `client_auth_type`, integer. Set to `1` to require client certificate authentication in addition to JWT/Web authentication. You need to define at least a certificate authority for this to work. Default: 0.
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
- `proxy_allowed`, list of IP addresses and IP ranges allowed to set client IP proxy header such as `X-Forwarded-For`, `X-Real-IP` and any other headers defined in the `security` section. Any of the indicated headers, if set on requests from a connection address not in this list, will be silently ignored. Default: empty.
- `client_ip_proxy_header`, string. Defines the allowed client IP proxy header such as `X-Forwarded-For`, `X-Real-IP` etc. Default: empty
- `client_ip_header_depth`, integer. Some client IP headers such as `X-Forwarded-For` can contain multiple IP address, this setting define the position to trust starting from the right. For example if we have: `10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1` and the depth is `0`, SFTPGo will use `13.0.0.1` as client IP, if depth is `1`, `12.0.0.1` will be used and so on. Default: `0`.
- `hide_login_url`, integer. If both web admin and web client are enabled each login page will show a link to the other one. This setting allows to hide this link. 0 means that the login links are displayed on both admin and client login page. This is the default. 1 means that the login link to the web client login page is hidden on admin login page. 2 means that the login link to the web admin login page is hidden on client login page. The flags can be combined, for example 3 will disable both login links.
- `render_openapi`, boolean. Set to `false` to disable serving of the OpenAPI schema and renderer. Default `true`.
- `web_client_integrations`, list of struct. The SFTPGo web client allows to send the files with the specified extensions to the configured URL using the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). This way you can integrate your own file viewer or editor. Take a look at the commentented example [here](../examples/webclient-integrations/test.html) to understand how to use this feature. Each struct has the following fields:
- `file_extensions`, list of strings. File extensions must be specified with the leading dot, for example `.pdf`.
- `url`, string. URL to open for the configured file extensions. The url will open in a new tab.
- `oidc`, struct. Defines the OpenID connect configuration. OpenID integration allows you to map your identity provider users to SFTPGo users and so you can login to SFTPGo Web Client and Web Admin user interfaces using your identity provider. The following fields are supported:
- `config_url`, string. Identifier for the service. If defined, SFTPGo will add `/.well-known/openid-configuration` to this url and attempt to retrieve the provider configuration on startup. SFTPGo will refuse to start if it fails to connect to the specified URL. Default: blank.
- `client_id`, string. Defines the application's ID. Default: blank.
- `client_secret`, string. Defines the application's secret. Default: blank.
- `redirect_base_url`, string. Defines the base URL to redirect to after OpenID authentication. The suffix `/web/oidc/redirect` will be added to this base URL, adding also the `web_root` if configured. Default: blank.
- `username_field`, string. Defines the ID token claims field to map to the SFTPGo username. Default: blank.
- `role_field`, string. Defines the optional ID token claims field to map to a SFTPGo role. If the defined ID token claims field is set to `admin` the authenticated user is mapped to an SFTPGo admin. You don't need to specify this field if you want to use OpenID only for the Web Client UI. Default: blank.
- `implicit_roles`, boolean. If set, the `role_field` is ignored and the SFTPGo role is assumed based on the login link used. Default: `false`.
- `custom_fields`, list of strings. Custom token claims fields to pass to the pre-login hook. Default: empty.
- `security`, struct. Defines security headers to add to HTTP responses and allows to restrict allowed hosts. The following parameters are supported:
- `enabled`, boolean. Set to `true` to enable security configurations. Default: `false`.
- `allowed_hosts`, list of strings. Fully qualified domain names that are allowed. An empty list allows any and all host names. Default: empty.
- `allowed_hosts_are_regex`, boolean. Determines if the provided allowed hosts contains valid regular expressions. Default: `false`.
- `hosts_proxy_headers`, list of string. Defines a set of header keys that may hold a proxied hostname value for the request, for example `X-Forwarded-Host`. Default: empty.
- `https_redirect`, boolean. Set to `true` to redirect HTTP requests to HTTPS. Default: `false`.
- `https_host`, string. Defines the host name that is used to redirect HTTP requests to HTTPS. Default is blank, which indicates to use the same host. For example, if `https_redirect` is enabled and `https_host` is blank, a request for `http://127.0.0.1/web/client/login` will be redirected to `https://127.0.0.1/web/client/login`, if `https_host` is set to `www.example.com` the same request will be redirected to `https://www.example.com/web/client/login`.
- `https_proxy_headers`, list of struct, each struct contains the fields `key` and `value`. Defines a a list of header keys with associated values that would indicate a valid https request. For example `key` could be `X-Forwarded-Proto` and `value` `https`. Default: empty.
- `sts_seconds`, integer. Defines the max-age of the `Strict-Transport-Security` header. This header will be included for `https` responses or for HTTP request if the request includes a defined HTTPS proxy header. Default: `0`, which would NOT include the header.
- `sts_include_subdomains`, boolean. Set to `true`, the `includeSubdomains` will be appended to the `Strict-Transport-Security` header. Default: `false`.
- `sts_preload`, boolean. Set to true, the `preload` flag will be appended to the `Strict-Transport-Security` header. Default: `false`.
- `content_type_nosniff`, boolean. Set to `true` to add the `X-Content-Type-Options` header with the value `nosniff`. Default: `false`.
- `content_security_policy`, string. Allows to set the `Content-Security-Policy` header value. Default: blank.
- `permissions_policy`, string. Allows to set the `Permissions-Policy` header value. Default: blank.
- `cross_origin_opener_policy`, string. Allows to set the `Cross-Origin-Opener-Policy` header value. Default: blank.
- `expect_ct_header`, string. Allows to set the `Expect-CT` header value. Default: blank.
- `branding`, struct. Defines the supported customizations to suit your brand. It contains the `web_admin` and `web_client` structs that define customizations for the WebAdmin and the WebClient UIs. Each customization struct contains the following fields:
- `name`, string. Defines the UI name
- `short_name`, string. Defines the short name to show next to the logo image and on the login page
- `favicon_path`, string. Path to the favicon relative to `static_files_path`. For example, if you create a directory named `branding` inside the static dir and put the `favicon.ico` file in it, you must set `/branding/favicon.ico` as path.
- `logo_path`, string. Path to your logo relative to `static_files_path`. The preferred image size is 256x256 pixel
- `login_image_path`, string. Path to a custom image to show on the login screen relative to `static_files_path`. The preferred image size is 900x900 pixel
- `disclaimer_name`, string. Name for your optional disclaimer
- `disclaimer_path`, string. Path to the HTML page with the disclaimer relative to `static_files_path`
- `default_css`, string. Optional path to a custom CSS file, relative to `static_files_path`, which replaces the SB Admin2 default CSS
- `extra_css`, list of strings. Defines the paths, relative to `static_files_path`, to additional CSS files
- `templates_path`, string. Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
- `static_files_path`, string. Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir. If both `templates_path` and `static_files_path` are empty the built-in web interface will be disabled
- `openapi_path`, string. Path to the directory that contains the OpenAPI schema and the default renderer. This can be an absolute path or a path relative to the config dir. If empty the OpenAPI schema and the renderer will not be served regardless of the `render_openapi` directive
- `web_root`, string. Defines a base URL for the web admin and client interfaces. If empty web admin and client resources will be available at the root ("/") URI. If defined it must be an absolute URI or it will be ignored
- `certificate_file`, string. Certificate for HTTPS. This can be an absolute path or a path relative to the config dir.
- `certificate_key_file`, string. Private key matching the above certificate. This can be an absolute path or a path relative to the config dir. If both the certificate and the private key are provided, you can enable HTTPS for the configured bindings. Certificate and key files can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
- `ca_certificates`, list of strings. Set of root certificate authorities to be used to verify client certificates.
- `ca_revocation_lists`, list of strings. Set a revocation lists, one for each root CA, to be used to check if a client certificate has been revoked. The revocation lists can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
- `signing_passphrase`, string. Passphrase to use to derive the signing key for JWT and CSRF tokens. If empty a random signing key will be generated each time SFTPGo starts. If you set a signing passphrase you should consider rotating it periodically for added security.
- `token_validation`, integer. Define how to validate JWT tokens, cookies and CSRF tokens. By default all the available security checks are enabled. Set to 1 to disable the requirement that a token must be used by the same IP for which it was issued. Default: `0`.
- `max_upload_file_size`, integer. Defines the maximum request body size, in bytes, for Web Client/API HTTP upload requests. 0 means no limit. Default: 1048576000.
- `cors` struct containing CORS configuration. SFTPGo uses [Go CORS handler](https://github.com/rs/cors), please refer to upstream documentation for fields meaning and their default values.
- `enabled`, boolean, set to `true` to enable CORS.
- `allowed_origins`, list of strings.
- `allowed_methods`, list of strings.
- `allowed_headers`, list of strings.
- `exposed_headers`, list of strings.
- `allow_credentials` boolean.
- `max_age`, integer.
- `setup` struct containing configurations for the initial setup screen
- `installation_code`, string. If set, this installation code will be required when creating the first admin account. Please note that even if set using an environment variable this field is read at SFTPGo startup and not at runtime. This is not a license key or similar, the purpose here is to prevent anyone who can access to the initial setup screen from creating an admin user. Default: blank.
- `installation_code_hint`, string. Description for the installation code input field. Default: `Installation code`.
- **"telemetry"**, the configuration for the telemetry server, more details [below](#telemetry-server)
- `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 0
- `bind_address`, string. Leave blank to listen on all available network interfaces. On \*NIX you can specify an absolute path to listen on a Unix-domain socket. Default: `127.0.0.1`
- `enable_profiler`, boolean. Enable the built-in profiler. Default `false`
- `auth_user_file`, string. Path to a file used to store usernames and passwords for basic authentication. This can be an absolute path or a path relative to the config dir. We support HTTP basic authentication, and the file format must conform to the one generated using the Apache `htpasswd` tool. The supported password formats are bcrypt (`$2y$` prefix) and md5 crypt (`$apr1$` prefix). If empty, HTTP authentication is disabled. Authentication will be always disabled for the `/healthz` endpoint.
- `certificate_file`, string. Certificate for HTTPS. This can be an absolute path or a path relative to the config dir.
- `certificate_key_file`, string. Private key matching the above certificate. This can be an absolute path or a path relative to the config dir. If both the certificate and the private key are provided, the server will expect HTTPS connections. Certificate and key files can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
- `min_tls_version`, integer. Defines the minimum version of TLS to be enabled. `12` means TLS 1.2 (and therefore TLS 1.2 and TLS 1.3 will be enabled),`13` means TLS 1.3. Default: `12`.
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
- **"http"**, the configuration for HTTP clients. HTTP clients are used for executing hooks. Some hooks use a retryable HTTP client, for these hooks you can configure the time between retries and the number of retries. Please check the hook specific documentation to understand which hooks use a retryable HTTP client.
- `timeout`, float. Timeout specifies a time limit, in seconds, for requests. For requests with retries this is the timeout for a single request
- `retry_wait_min`, integer. Defines the minimum waiting time between attempts in seconds.
- `retry_wait_max`, integer. Defines the maximum waiting time between attempts in seconds. The backoff algorithm will perform exponential backoff based on the attempt number and limited by the provided minimum and maximum durations.
- `retry_max`, integer. Defines the maximum number of retries if the first request fails.
- `ca_certificates`, list of strings. List of paths to extra CA certificates to trust. The paths can be absolute or relative to the config dir. Adding trusted CA certificates is a convenient way to use self-signed certificates without defeating the purpose of using TLS.
- `certificates`, list of certificate for mutual TLS. Each certificate is a struct with the following fields:
- `cert`, string. Path to the certificate file. The path can be absolute or relative to the config dir.
- `key`, string. Path to the key file. The path can be absolute or relative to the config dir.
- `skip_tls_verify`, boolean. if enabled the HTTP client accepts any TLS certificate presented by the server and any host name in that certificate. In this mode, TLS is susceptible to man-in-the-middle attacks. This should be used only for testing.
- `headers`, list of structs. You can define a list of http headers to add to each hook. Each struct has the following fields:
- `key`, string
- `value`, string. The header is silently ignored if `key` or `value` are empty
- `url`, string, optional. If not empty, the header will be added only if the request URL starts with the one specified here
- **command**, configuration for external commands such as program based hooks
- `timeout`, integer. Timeout specifies a time limit, in seconds, to execute external commands. Valid range: `1-300`. Default: `30`
- `env`, list of strings. Additional environment variable to pass to all the external commands. Each entry is of the form `key=value`. Do not use environment variables prefixed with `SFTPGO_` to avoid conflicts with environment variables that SFTPGo hooks can set. Default: empty
- `commands`, list of structs. Allow to customize configuration per-command. Each struct has the following fields:
- `path`, string. Define the command path as defined in the hook configuration
- `timeout`, integer. This value overrides the global timeout if set
- `env`, list of strings. These values are added to the environment variables defined for all commands, if any
- **kms**, configuration for the Key Management Service, more details can be found [here](./kms.md)
- `secrets`
- `url`, string. Defines the URI to the KMS service. Default: blank.
- `master_key`, string. Defines the master encryption key as string. If not empty, it takes precedence over `master_key_path`. Default: blank.
- `master_key_path`, string. Defines the absolute path to a file containing the master encryption key. Default: blank.
- **mfa**, multi-factor authentication settings
- `totp`, list of struct that define settings for time-based one time passwords (RFC 6238). Each struct has the following fields:
- `name`, string. Unique configuration name. This name should not be changed if there are users or admins using the configuration. The name is not exposed to the authentication apps. Default: `Default`.
- `issuer`, string. Name of the issuing Organization/Company. Default: `SFTPGo`.
- `algo`, string. Algorithm to use for HMAC. The supported algorithms are: `sha1`, `sha256`, `sha512`. Currently Google Authenticator app on iPhone seems to only support `sha1`, please check the compatibility with your target apps/device before setting a different algorithm. You can also define multiple configurations, for example one that uses `sha256` or `sha512` and another one that uses `sha1` and instruct your users to use the appropriate configuration for their devices/apps. The algorithm should not be changed if there are users or admins using the configuration. Default: `sha1`.
- **smtp**, SMTP configuration enables SFTPGo email sending capabilities
- `host`, string. Location of SMTP email server. Leave empty to disable email sending capabilities. Default: blank.
- `port`, integer. Port of SMTP email server.
- `from`, string. From address, for example `SFTPGo <sftpgo@example.com>`. Many SMTP servers reject emails without a `From` header so, if not set, SFTPGo will try to use the username as fallback, this may or may not be appropriate. Default: blank
- `user`, string. SMTP username. Default: blank
- `password`, string. SMTP password. Leaving both username and password empty the SMTP authentication will be disabled. Default: blank
- `auth_type`, integer. 0 means `Plain`, 1 means `Login`, 2 means `CRAM-MD5`. Default: `0`.
- `encryption`, integer. 0 means no encryption, 1 means `TLS`, 2 means `STARTTLS`. Default: `0`.
- `domain`, string. Domain to use for `HELO` command, if empty `localhost` will be used. Default: blank.
- `templates_path`, string. Path to the email templates. This can be an absolute path or a path relative to the config dir. Templates are searched within a subdirectory named "email" in the specified path. You can customize the email templates by simply specifying an alternate path and putting your custom templates there.
- **plugins**, list of external plugins. Each plugin is configured using a struct with the following fields:
- `type`, string. Defines the plugin type. Supported types: `notifier`, `kms`, `auth`, `metadata`.
- `notifier_options`, struct. Defines the options for notifier plugins.
- `fs_events`, list of strings. Defines the filesystem events that will be notified to this plugin.
- `provider_events`, list of strings. Defines the provider events that will be notified to this plugin.
- `provider_objects`, list if strings. Defines the provider objects that will be notified to this plugin.
- `retry_max_time`, integer. Defines the maximum number of seconds an event can be late. SFTPGo adds a timestamp to each event and add to an internal queue any events that a the plugin fails to handle (the plugin returns an error or it is not running). If a plugin fails to handle an event that is too late, based on this configuration, it will be discarded. SFTPGo will try to resend queued events every 30 seconds. 0 means no retry.
- `retry_queue_max_size`, integer. Defines the maximum number of events that the internal queue can hold. Once the queue is full, the events that cannot be sent to the plugin will be discarded. 0 means no limit.
- `kms_options`, struct. Defines the options for kms plugins.
- `scheme`, string. KMS scheme. Supported schemes are: `awskms`, `gcpkms`, `hashivault`, `azurekeyvault`.
- `encrypted_status`, string. Encrypted status for a KMS secret. Supported statuses are: `AWS`, `GCP`, `VaultTransit`, `AzureKeyVault`.
- `auth_options`, struct. Defines the options for auth plugins.
- `scope`, integer. 1 means passwords only. 2 means public keys only. 4 means key keyboard interactive only. 8 means TLS certificate. The flags can be combined, for example 6 means public keys and keyboard interactive. The scope must be explicit, `0` is not a valid option.
- `cmd`, string. Path to the plugin executable.
- `args`, list of strings. Optional arguments to pass to the plugin executable.
- `sha256sum`, string. SHA256 checksum for the plugin executable. If not empty it will be used to verify the integrity of the executable.
- `auto_mtls`, boolean. If enabled the client and the server automatically negotiate mutual TLS for transport authentication. This ensures that only the original client will be allowed to connect to the server, and all other connections will be rejected. The client will also refuse to connect to any server that isn't the original instance started by the client.
:warning: Please note that the plugin system is experimental, the exposed configuration parameters and interfaces may change in a backward incompatible way in future.
A full example showing the default config (in JSON format) can be found [here](../sftpgo.json).
If you want to use a private host key that uses an algorithm/setting different from the auto generated RSA/ECDSA keys, or more than two private keys, you can generate your own keys and replace the empty `keys` array with something like this:
```json
"host_keys": [
"id_rsa",
"id_ecdsa",
"id_ed25519"
]
```
where `id_rsa`, `id_ecdsa` and `id_ed25519`, in this example, are files containing your generated keys. You can use absolute paths or paths relative to the configuration directory specified via the `--config-dir` serve flag. By default the configuration directory is the working directory.
If you want the default host keys generation in a directory different from the config dir, please specify absolute paths to files named `id_rsa`, `id_ecdsa` or `id_ed25519` like this:
```json
"host_keys": [
"/etc/sftpgo/keys/id_rsa",
"/etc/sftpgo/keys/id_ecdsa",
"/etc/sftpgo/keys/id_ed25519"
]
```
then SFTPGo will try to create `id_rsa`, `id_ecdsa` and `id_ed25519`, if they are missing, inside the directory `/etc/sftpgo/keys`.
The configuration can be read from JSON, TOML, YAML, HCL, envfile and Java properties config files. If your `config-file` flag is set to `sftpgo` (default value), you need to create a configuration file called `sftpgo.json` or `sftpgo.yaml` and so on inside `config-dir`.
</details>
<details><summary><font size=5> Environment variables</font></summary>
You can also override all the available configuration options using environment variables. SFTPGo will check for environment variables with a name matching the key uppercased and prefixed with the `SFTPGO_`. You need to use `__` to traverse a struct.
Let's see some examples:
- To set the `port` for the first sftpd binding, you need to define the env var `SFTPGO_SFTPD__BINDINGS__0__PORT`
- To set the `execute_on` actions, you need to define the env var `SFTPGO_COMMON__ACTIONS__EXECUTE_ON`. For example `SFTPGO_COMMON__ACTIONS__EXECUTE_ON=upload,download`
On some hardware you can get faster SFTP performance by replacing the Go `crypto/sha256` implementation with [sha256-simd](https://github.com/minio/sha256-simd).
The performances of SHA256 is relevant for clients using AES CTR ciphers and `hmac-sha2-256` as Message Authentication Code (MAC).
Up to 2.0.x versions SFTPGo automatically used `sha256-simd` but over the time the standard Go implementation improved a lot and now is faster than `sha256-simd` on some CPUs.
You can select `sha256-simd` setting the environment variable `SFTPGO_MINIO_SHA256_SIMD` to `1`.
`sha256-simd` is particularly useful if you have an Intel CPU with SHA extensions or an ARM CPU with Cryptography Extensions.
</details>
<details><summary><font size=5>Binding to privileged ports</font></summary>
On Linux, if you want to use Internet domain privileged ports (port numbers less than 1024) instead of running the SFTPGo service as root user you can set the `cap_net_bind_service` capability on the `sftpgo` binary. To set the capability you can use the following command:
```shell
$ sudo setcap cap_net_bind_service=+ep /usr/bin/sftpgo
# Check that the capability is added
$ getcap /usr/bin/sftpgo
/usr/bin/sftpgo cap_net_bind_service=ep
```
Now you can use privileged ports such as 21, 22, 443 etc.. without running the SFTPGo service as root user. You have to set the `cap_net_bind_service` capability each time you update the `sftpgo` binary.
The "official" deb/rpm packages attempt to set the `cap_net_bind_service` capability in their `postinstall` scripts.
An alternative method is to use `iptables`, for example you run the SFTP service on port `2022` and redirect traffic from port `22` to port `2022`:
```shell
sudo iptables -t nat -A PREROUTING -d <ip> -p tcp --dport 22 -m addrtype --dst-type LOCAL -j DNAT --to-destination <ip>:2022
sudo iptables -t nat -A OUTPUT -d <ip> -p tcp --dport 22 -m addrtype --dst-type LOCAL -j DNAT --to-destination <ip>:2022
```
</details>
<details><summary><font size=5>Supported Password Hashing Algorithms</font></summary>
SFTPGo can verify passwords in several formats and uses, by default, the `bcrypt` algorithm to hash passwords in plain-text before storing them inside the data provider. Each hashing algorithm is identified by a prefix.
Supported hash algorithms:
- bcrypt, prefix `$2a$`
- argon2id, prefix `$argon2id$`
- PBKDF2 sha1, prefix `$pbkdf2-sha1$`
- PBKDF2 sha256, prefix `$pbkdf2-sha256$`
- PBKDF2 sha512, prefix `$pbkdf2-sha512$`
- PBKDF2 sha256 with base64 salt, prefix `$pbkdf2-b64salt-sha256$`
- MD5 crypt, prefix `$1$`
- MD5 crypt APR1, prefix `$apr1$`
- SHA512 crypt, prefix `$6$`
- LDAP MD5, prefix `{MD5}`
If you set a password with one of these prefixes it will not be hashed.
When users log in, if their passwords are stored with anything other than the preferred algorithm, SFTPGo will automatically upgrade the algorithm to the preferred one.
</details>
## Telemetry Server
The telemetry server exposes the following endpoints:
- `/healthz`, health information (for health checks)
- `/metrics`, Prometheus metrics
- `/debug/pprof`, if enabled via the `enable_profiler` configuration key, for profiling, more details [here](./profiling.md)

View file

@ -1,11 +0,0 @@
# Google Cloud Storage backend
To connect SFTPGo to Google Cloud Storage you can use use the Application Default Credentials (ADC) strategy to try to find your application's credentials automatically or you can explicitly provide a JSON credentials file that you can obtain from the Google Cloud Console. Take a look [here](https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application) for details.
Specifying a different `key_prefix`, you can assign different "folders" of the same bucket to different users. This is similar to a chroot directory for local filesystem. Each SFTP/SCP user can only access the assigned folder and its contents. The folder identified by `key_prefix` does not need to be pre-created.
You can optionally specify a [storage class](https://cloud.google.com/storage/docs/storage-classes) too. Leave it blank to use the default storage class.
The configured bucket must exist.
This backend is very similar to the [S3](./s3.md) backend, and it has the same limitations. As with S3 `chtime` will fail with the default configuration, you can install the [metadata plugin](https://github.com/sftpgo/sftpgo-plugin-metadata) to make it work and thus be able to preserve/change file modification times.

View file

@ -1,41 +0,0 @@
# Groups
Using groups simplifies the administration of multiple accounts by letting you assign settings once to a group, instead of multiple times to each individual user.
SFTPGo supports two types of groups:
- primary groups
- secondary groups
A user can be a member of a primary group and many secondary groups. Depending on the group type, the settings are inherited differently.
The following settings are inherited from the primary group:
- home dir, if set for the group will replace the one defined for the user. The `%username%` placeholder is replaced with the username
- filesystem config, if the provider set for the group is different from the "local provider" will replace the one defined for the user. The `%username%` placeholder is replaced with the username within the defined "prefix", for any vfs, and the "username" for the SFTP filesystem config
- max sessions, quota size/files, upload/download bandwidth, upload/download/total data transfer, max upload size, external auth cache time: if they are set to `0` for the user they are replaced with the value set for the group, if different from `0`
- TLS username, check password hook disabled, pre-login hook disabled, external auth hook disabled, filesystem checks disabled, allow API key authentication: if they are not set for the user they are replaced with the value set for the group
- starting directory, if the user does not have a starting directory set, the value set for the group is used, if any. The `%username%` placeholder is replaced with the username
The following settings are inherited from the primary and secondary groups:
- virtual folders, file patterns, permissions: they are added to the user configuration if the user does not already have a setting for the configured path. The `/` path is ignored for secondary groups. The `%username%` placeholder is replaced with the username within the virtual path, the defined "prefix", for any vfs, and the "username" for the SFTP filesystem config
- per-source bandwidth limits
- per-source data transfer limits
- allowed/denied IPs
- denied login methods and protocols
- two factor auth protocols
- web client/REST API permissions
The settings from the primary group are always merged first.
The final settings are a combination of the user settings and the group ones.
For example you can define the following groups:
- "group1", it has a virtual directory to mount on `/vdir1`
- "group2", it has a virtual directory to mount on `/vdir2`
- "group3", it has a virtual directory to mount on `/vdir3`
If you define users with a virtual directory to mount on `/vdir` and make them member of all the above groups, they will have virtual directories mounted on `/vdir`, `/vdir1`, `/vdir2`, `/vdir3`. If users already have a virtual directory to mount on `/vdir1`, the group's one will be ignored.
Please note that if the same virtual path is set in more than one secondary group the behavior is undefined. For example if a user is a member of two secondary groups and each secondary group defines a virtual folder to mount on the `/vdir2` path, the virtual folder mounted on `/vdir2` may change with every login.

View file

@ -1,11 +0,0 @@
# Tutorials
Here we collect step-to-step tutorials. SFTPGo users are encouraged to contribute!
- [Getting Started](./getting-started.md)
- [Securing SFTPGo with a free Let's Encrypt TLS Certificate](./lets-encrypt-certificate.md)
- [Two-factor Authentication](./two-factor-authentication.md)
- [SFTPGo as OpenSSH's SFTP subsystem](./openssh-sftp-subsystem.md)
- [SFTPGo with PostgreSQL data provider and S3 backend](./postgresql-s3.md)
- [SFTPGo on Windows with Active Directory Integration + Caddy Static File Server](https://www.youtube.com/watch?v=M5UcJI8t4AI)
- [File Traefik: Serve files securely via SFTP, HTTPS, and WebDAV with SFTPGo proxied behind Traefik](https://thad.getterman.org/articles/file-traefik/)

View file

@ -1,510 +0,0 @@
# Getting Started
SFTPGo allows to securely share your files over SFTP and optionally FTP/S and WebDAV too.
Several storage backends are supported and they are configurable per user, so you can serve a local directory for a user and an S3 bucket (or part of it) for another one.
SFTPGo also supports virtual folders, a virtual folder can use any of the supported storage backends. So you can have, for example, an S3 user that exposes a GCS bucket (or part of it) on a specified path and an encrypted local filesystem on another one.
Virtual folders can be private or shared among multiple users, for shared virtual folders you can define different quota limits for each user.
In this tutorial we explore the main features and concepts using the built-in web admin interface. Advanced users can also use the SFTPGo [REST API](https://sftpgo.stoplight.io/docs/sftpgo/openapi.yaml)
- [Installation](#Installation)
- [Initial configuration](#Initial-configuration)
- [Creating users](#Creating-users)
- [Creating users with a Cloud Storage backend](#Creating-users-with-a-Cloud-Storage-backend)
- [Creating users with a local encrypted backend (Data At Rest Encryption)](#Creating-users-with-a-local-encrypted-backend-Data-At-Rest-Encryption))
- [Virtual permissions](#Virtual-permissions)
- [Virtual folders](#Virtual-folders)
- [Configuration parameters](#Configuration-parameters)
- [Use PostgreSQL data provider](#Use-PostgreSQL-data-provider)
- [Use MySQL/MariaDB data provider](#Use-MySQLMariaDB-data-provider)
- [Use CockroachDB data provider](#Use-CockroachDB-data-provider)
- [Enable FTP service](#Enable-FTP-service)
- [Enable WebDAV service](#Enable-WebDAV-service)
## Installation
You can easily install SFTPGo by downloading the appropriate package for your operating system and architecture. Please visit the [releases](https://github.com/drakkan/sftpgo/releases "releases") page.
An official Docker image is available. Documentation is [here](./../../docker/README.md).
In this guide, we assume that SFTPGo is already installed and running using the default configuration.
## Initial configuration
Before you can use SFTPGo you need to create an admin account, so open [http://127.0.0.1:8080/web/admin](http://127.0.0.1:8080/web) in your web browser, replacing `127.0.0.1` with the appropriate IP address if SFTPGo is not running on localhost.
![Setup](./img/setup.png)
After creating the admin account you will be automatically logged in.
![Users list](./img/initial-screen.png)
The web admin is now available at the following URL:
[http://127.0.0.1:8080/web/admin](http://127.0.0.1:8080/web/admin)
From the `Status` page you see the active services.
![Status](./img/status.png)
The default configuration enables the SFTP service on port `2022` and uses an embedded data provider (`SQLite` or `bolt` based on the target OS and architecture).
## Creating users
Let's create our first local user:
- from the `Users` page click the `+` icon to open the `Add user page`
- the only required fields are the `Username` and a `Password` or a `Public key`
- if you are on Windows or you installed SFTPGo manually and no `users_base_dir` is defined in your configuration file you also have to set a `Home Dir`. It must be an absolute path, for example `/srv/sftpgo/data/username` on Linux or `C:\sftpgo\data\username` on Windows. SFTPGo will try to automatically create the home directory, if missing, when the user logs in. Each user can only access files and folders inside its home directory.
- click `Submit`
![Add user](./img/add-user.png)
Now test the new user, we use the `sftp` CLI here, you can use any SFTP client.
```shell
$ sftp -P 2022 nicola@127.0.0.1
nicola@127.0.0.1's password:
Connected to 127.0.0.1.
sftp> ls
sftp> put file.txt
Uploading file.txt to /file.txt
file.txt 100% 4034 3.9MB/s 00:00
sftp> ls
file.txt
sftp> mkdir adir
sftp> cd adir/
sftp> put file.txt
Uploading file.txt to /adir/file.txt
file.txt 100% 4034 4.0MB/s 00:00
sftp> ls
file.txt
sftp> get file.txt
Fetching /adir/file.txt to file.txt
/adir/file.txt 100% 4034 1.9MB/s 00:00
```
It worked! We can upload/download files and create directories.
Each user can browse and download their files, share files with external users, change their credentials and configure two-factor authentication using the WebClient interface available at the following URL:
[http://127.0.0.1:8080/web/client](http://127.0.0.1:8080/web/client)
![WebClient files](./img/web-client-files.png)
![WebClient two-factor authentication](./img/web-client-two-factor-auth.png)
### Creating users with a Cloud Storage backend
The procedure is similar to the one described for local users, you have only specify the Cloud Storage backend and its credentials.
The screenshot below shows an example configuration for an S3 backend.
![S3 user](./img/s3-user-1.png)
![S3 user](./img/s3-user-2.png)
The screenshot below shows an example configuration for an Azure Blob Storage backend.
![Azure Blob user](./img/az-user-1.png)
![Azure Blob user](./img/az-user-2.png)
The screenshot below shows an example configuration for a Google Cloud Storage backend.
![Google Cloud user](./img/gcs-user.png)
The screenshot below shows an example configuration for an SFTP server as storage backend.
![User using another SFTP server as storage backend](./img/sftp-user.png)
Setting a `Key Prefix` you restrict the user to a specific "sub-folder" in the bucket, so that the same bucket can be shared among different users.
### Creating users with a local encrypted backend (Data At Rest Encryption)
The procedure is similar to the one described for local users, you have only specify the encryption passphrase.
The screenshot below shows an example configuration.
![User with cryptfs backend](./img/local-encrypted.png)
You can find more details about Data At Rest Encryption [here](../dare.md).
## Virtual permissions
SFTPGo supports per directory virtual permissions. For each user you have to specify global permissions and then override them on a per-directory basis.
Take a look at the following screens.
![Permissions](./img/virtual-permissions.png)
This user has full access as default (`*`), can only list and download from `/read-only` path and has no permissions at all for the `/subdir` path.
Let's test it. We use the `sftp` CLI here, you can use any SFTP client.
```shell
$ sftp -P 2022 nicola@127.0.0.1
Connected to 127.0.0.1.
sftp> ls
adir file.txt read-only subdir
sftp> put file.txt
Uploading file.txt to /file.txt
file.txt 100% 4034 19.4MB/s 00:00
sftp> rm file.txt
Removing /file.txt
sftp> ls
adir read-only subdir
sftp> cd read-only/
sftp> ls
file.txt
sftp> put file1.txt
Uploading file1.txt to /read-only/file1.txt
remote open("/read-only/file1.txt"): Permission denied
sftp> get file.txt
Fetching /read-only/file.txt to file.txt
/read-only/file.txt 100% 4034 2.2MB/s 00:00
sftp> cd ..
sftp> ls
adir read-only subdir
sftp> cd /subdir
sftp> ls
remote readdir("/subdir"): Permission denied
```
as you can see it worked as expected.
## Virtual folders
From the web admin interface click `Folders` and then the `+` icon.
![Add folder](./img/add-folder.png)
To create a local folder you need to specify a `Name` and an `Absolute path`. For other backends you have to specify the backend type and its credentials, this is the same procedure already detailed for creating users with cloud backends.
Suppose we created two virtual folders name `localfolder` and `minio` as you can see in the following screen.
![Folders](./img/folders.png)
- `localfolder` uses the local filesystem as storage backend
- `minio` uses MinIO (S3 compatible) as storage backend
Now, click `Users`, on the left menu, select a user and click the `Edit` icon, to update the user and associate the virtual folders.
Virtual folders must be referenced using their unique name and you can expose them on a configurable virtual path. Take a look at the following screenshot.
![Virtual Folders](./img/virtual-folders.png)
We exposed the folder named `localfolder` on the path `/vdirlocal` (this must be an absolute UNIX path on Windows too) and the folder named `minio` on the path `/vdirminio`. For `localfolder` the quota usage is included within the user quota, while for the `minio` folder we defined separate quota limits: at most 2 files and at most 100MB, whichever is reached first.
The folder `minio` can be shared with other users and we can define different quota limits on a per-user basis. The folder `localfolder` is considered private since we have included its quota limits within those of the user, if we share them with other users we will break quota calculation.
Let's test these virtual folders. We use the `sftp` CLI here, you can use any SFTP client.
```shell
$ sftp -P 2022 nicola@127.0.0.1
nicola@127.0.0.1's password:
Connected to 127.0.0.1.
sftp> ls
adir read-only subdir vdirlocal vdirminio
sftp> cd vdirlocal
sftp> put file.txt
Uploading file.txt to /vdirlocal/file.txt
file.txt 100% 4034 17.3MB/s 00:00
sftp> ls
file.txt
sftp> cd ..
sftp> cd vdirminio/
sftp> put file.txt
Uploading file.txt to /vdirminio/file.txt
file.txt 100% 4034 4.8MB/s 00:00
sftp> ls
file.txt
sftp> put file.txt file1.txt
Uploading file.txt to /vdirminio/file1.txt
file.txt 100% 4034 2.8MB/s 00:00
sftp> put file.txt file2.txt
Uploading file.txt to /vdirminio/file2.txt
remote open("/vdirminio/file2.txt"): Failure
sftp> quit
```
The last upload failed since we exceeded the number of files quota limit.
## Configuration parameters
Until now we used the default configuration, to change the global service parameters you have to edit the configuration file, or set appropriate environment variables, and restart SFTPGo to apply the changes.
A full explanation of all configuration methods can be found [here](./../full-configuration.md), we explore some common use cases. Please keep in mind that SFTPGo can also be configured via environment variables, this is very convenient if you are using Docker.
The default configuration file is `sftpgo.json` and it can be found within the `/etc/sftpgo` directory if you installed from Linux distro packages. On Windows the configuration file can be found within the `{commonappdata}\SFTPGo` directory where `{commonappdata}` is typically `C:\ProgramData`. SFTPGo also supports reading from TOML and YAML configuration files.
The following snippets assume your are running SFTPGo on Linux but they can be easily adapted for other operating systems.
### Use PostgreSQL data provider
Create a PostgreSQL database named `sftpgo` and a PostgreSQL user with the correct permissions, for example using the `psql` CLI.
```shell
sudo -i -u postgres psql
CREATE DATABASE "sftpgo" WITH ENCODING='UTF8' CONNECTION LIMIT=-1;
create user "sftpgo" with encrypted password 'your password here';
grant all privileges on database "sftpgo" to "sftpgo";
\q
```
Open the SFTPGo configuration file, search for the `data_provider` section and change it as follow.
```json
"data_provider": {
"driver": "postgresql",
"name": "sftpgo",
"host": "127.0.0.1",
"port": 5432,
"username": "sftpgo",
"password": "your password here",
...
}
```
Confirm that the database connection works by initializing the data provider.
```shell
$ sudo su - sftpgo -s /bin/bash -c 'sftpgo initprovider -c /etc/sftpgo'
2021-05-19T22:21:54.000 INF Initializing provider: "postgresql" config file: "/etc/sftpgo/sftpgo.json"
2021-05-19T22:21:54.000 INF updating database version: 8 -> 9
2021-05-19T22:21:54.000 INF Data provider successfully initialized/updated
```
Ensure that SFTPGo starts after the database service.
```shell
sudo systemctl edit sftpgo.service
```
And override the unit definition with the following snippet.
```shell
[Unit]
After=postgresql.service
```
Restart SFTPGo to apply the changes.
### Use MySQL/MariaDB data provider
Create a MySQL database named `sftpgo` and a MySQL user with the correct permissions, for example using the `mysql` CLI.
```shell
$ mysql -u root
MariaDB [(none)]> CREATE DATABASE sftpgo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Query OK, 1 row affected (0.000 sec)
MariaDB [(none)]> grant all privileges on sftpgo.* to sftpgo@localhost identified by 'your password here';
Query OK, 0 rows affected (0.027 sec)
MariaDB [(none)]> quit
Bye
```
Open the SFTPGo configuration file, search for the `data_provider` section and change it as follow.
```json
"data_provider": {
"driver": "mysql",
"name": "sftpgo",
"host": "127.0.0.1",
"port": 3306,
"username": "sftpgo",
"password": "your password here",
...
}
```
Confirm that the database connection works by initializing the data provider.
```shell
$ sudo su - sftpgo -s /bin/bash -c 'sftpgo initprovider -c /etc/sftpgo'
2021-05-19T22:29:30.000 INF Initializing provider: "mysql" config file: "/etc/sftpgo/sftpgo.json"
2021-05-19T22:29:30.000 INF updating database version: 8 -> 9
2021-05-19T22:29:30.000 INF Data provider successfully initialized/updated
```
Ensure that SFTPGo starts after the database service.
```shell
sudo systemctl edit sftpgo.service
```
And override the unit definition with the following snippet.
```shell
[Unit]
After=mariadb.service
```
Restart SFTPGo to apply the changes.
### Use CockroachDB data provider
We suppose you have installed CockroachDB this way:
```shell
sudo su
export CRDB_VERSION=22.1.0 # set the latest available version here
wget -qO- https://binaries.cockroachdb.com/cockroach-v${CRDB_VERSION}.linux-amd64.tgz | tar xvz
cp -i cockroach-v${CRDB_VERSION}.linux-amd64/cockroach /usr/local/bin/
mkdir -p /usr/local/lib/cockroach
cp -i cockroach-v${CRDB_VERSION}.linux-amd64/lib/libgeos.so /usr/local/lib/cockroach/
cp -i cockroach-v${CRDB_VERSION}.linux-amd64/lib/libgeos_c.so /usr/local/lib/cockroach/
mkdir /var/lib/cockroach
chown sftpgo:sftpgo /var/lib/cockroach
mkdir -p /etc/cockroach/{certs,ca}
chmod 700 /etc/cockroach/ca
/usr/local/bin/cockroach cert create-ca --certs-dir=/etc/cockroach/certs --ca-key=/etc/cockroach/ca/ca.key
/usr/local/bin/cockroach cert create-node localhost $(hostname) --certs-dir=/etc/cockroach/certs --ca-key=/etc/cockroach/ca/ca.key
/usr/local/bin/cockroach cert create-client root --certs-dir=/etc/cockroach/certs --ca-key=/etc/cockroach/ca/ca.key
chown -R sftpgo:sftpgo /etc/cockroach/certs
exit
```
and you are running it using a systemd unit like this one:
```shell
[Unit]
Description=Cockroach Database single node
Requires=network.target
[Service]
Type=notify
WorkingDirectory=/var/lib/cockroach
ExecStart=/usr/local/bin/cockroach start-single-node --certs-dir=/etc/cockroach/certs --http-addr 127.0.0.1:8888 --listen-addr 127.0.0.1:26257 --cache=.25 --max-sql-memory=.25 --store=path=/var/lib/cockroach
TimeoutStopSec=60
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=cockroach
User=sftpgo
[Install]
WantedBy=default.target
```
Create a CockroachDB database named `sftpgo`.
```shell
$ sudo /usr/local/bin/cockroach sql --certs-dir=/etc/cockroach/certs -e 'create database "sftpgo"'
CREATE DATABASE
Time: 13ms
```
Open the SFTPGo configuration file, search for the `data_provider` section and change it as follow.
```json
"data_provider": {
"driver": "cockroachdb",
"name": "sftpgo",
"host": "localhost",
"port": 26257,
"username": "root",
"password": "",
"sslmode": 3,
"root_cert": "/etc/cockroach/certs/ca.crt",
"client_cert": "/etc/cockroach/certs/client.root.crt",
"client_key": "/etc/cockroach/certs/client.root.key",
...
}
```
Confirm that the database connection works by initializing the data provider.
```shell
$ sudo su - sftpgo -s /bin/bash -c 'sftpgo initprovider -c /etc/sftpgo'
2022-06-02T14:54:04.510 INF Initializing provider: "cockroachdb" config file: "/etc/sftpgo/sftpgo.json"
2022-06-02T14:54:04.554 INF creating initial database schema, version 15
2022-06-02T14:54:04.698 INF updating database version: 15 -> 16
2022-06-02T14:54:07.093 INF updating database version: 16 -> 17
2022-06-02T14:54:07.672 INF updating database version: 17 -> 18
2022-06-02T14:54:07.699 INF updating database version: 18 -> 19
2022-06-02T14:54:07.721 INF Data provider successfully initialized/updated
```
Ensure that SFTPGo starts after the database service.
```shell
sudo systemctl edit sftpgo.service
```
And override the unit definition with the following snippet.
```shell
[Unit]
After=cockroachdb.service
```
Restart SFTPGo to apply the changes.
### Enable FTP service
Open the SFTPGo configuration file, search for the `ftpd` section and change it as follow.
```json
"ftpd": {
"bindings": [
{
"port": 2121,
"address": "",
"apply_proxy_config": true,
"tls_mode": 0,
"certificate_file": "",
"certificate_key_file": "",
"min_tls_version": 12,
"force_passive_ip": "",
"passive_ip_overrides": [],
"client_auth_type": 0,
"tls_cipher_suites": [],
"passive_connections_security": 0,
"active_connections_security": 0,
"debug": false
}
],
"banner": "",
"banner_file": "",
"active_transfers_port_non_20": true,
"passive_port_range": {
"start": 50000,
"end": 50100
},
...
}
```
Restart SFTPGo to apply the changes. The FTP service is now available on port `2121`.
You can also configure the passive ports range (`50000-50100` by default), these ports must be reachable for passive FTP to work. If your FTP server is on the private network side of a NAT configuration you have to set `force_passive_ip` to your external IP address. You may also need to open the passive port range on your firewall.
It is recommended that you provide a certificate and key file to expose FTP over TLS. You should prefer SFTP to FTP even if you configure TLS, please don't blindly enable the old FTP protocol.
### Enable WebDAV service
Open the SFTPGo configuration file, search for the `webdavd` section and change it as follow.
```json
"webdavd": {
"bindings": [
{
"port": 10080,
"address": "",
"enable_https": false,
"certificate_file": "",
"certificate_key_file": "",
"min_tls_version": 12,
"client_auth_type": 0,
"tls_cipher_suites": [],
"prefix": "",
"proxy_allowed": [],
"client_ip_proxy_header": "",
"client_ip_header_depth": 0
}
],
...
}
```
Restart SFTPGo to apply the changes. The WebDAV service is now available on port `10080`. It is recommended that you provide a certificate and key file to expose WebDAV over https.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Some files were not shown because too many files have changed in this diff Show more