From 62b87083bb905963919d03abd153aff187e71d91 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sun, 20 Aug 2023 16:00:36 +0200 Subject: [PATCH] ftpd: add support for TLS session reuse Signed-off-by: Nicola Murino --- docs/full-configuration.md | 1 + go.mod | 44 +++++----- go.sum | 88 ++++++++++---------- internal/config/config.go | 6 ++ internal/config/config_test.go | 4 + internal/ftpd/ftpd.go | 10 +++ internal/ftpd/ftpd_test.go | 145 ++++++++++++++++++++++++++++++--- internal/ftpd/internal_test.go | 5 ++ internal/ftpd/server.go | 23 +++++- sftpgo.json | 1 + 10 files changed, 247 insertions(+), 80 deletions(-) diff --git a/docs/full-configuration.md b/docs/full-configuration.md index 14771e7e..8e1de798 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -164,6 +164,7 @@ The configuration file contains the following sections: - `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. + - `tls_session_reuse`, integer. 0 means session reuse is not checked, clients may or may not resume TLS sessions. 1 means TLS session reuse is required for explicit FTPS. Not supported for implicit TLS. Default: `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`. diff --git a/go.mod b/go.mod index dc5fb0d0..2176f1f0 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,15 @@ require ( github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 - github.com/aws/aws-sdk-go-v2 v1.20.2 - github.com/aws/aws-sdk-go-v2/config v1.18.34 - github.com/aws/aws-sdk-go-v2/credentials v1.13.33 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.9 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.78 - github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.15.3 - github.com/aws/aws-sdk-go-v2/service/s3 v1.38.3 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.21.3 + github.com/aws/aws-sdk-go-v2 v1.20.3 + github.com/aws/aws-sdk-go-v2/config v1.18.35 + github.com/aws/aws-sdk-go-v2/credentials v1.13.34 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.10 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.79 + github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.15.4 + github.com/aws/aws-sdk-go-v2/service/s3 v1.38.4 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.2 + github.com/aws/aws-sdk-go-v2/service/sts v1.21.4 github.com/bmatcuk/doublestar/v4 v4.6.0 github.com/cockroachdb/cockroach-go/v2 v2.3.5 github.com/coreos/go-oidc/v3 v3.6.0 @@ -85,18 +85,18 @@ require ( cloud.google.com/go/iam v1.1.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/ajg/form v1.5.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.39 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.40 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.34 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.33 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.13.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.3 // indirect - github.com/aws/smithy-go v1.14.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.40 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.41 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.35 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.34 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.4 // indirect + github.com/aws/smithy-go v1.14.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect @@ -172,7 +172,7 @@ require ( ) replace ( - github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20230818123055-c8426c7a1b8d + github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20230820134433-b2a9de7897b3 github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 github.com/robfig/cron/v3 => github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0 golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20230804183749-f40d052136b8 diff --git a/go.sum b/go.sum index 57104b70..3b270ff8 100644 --- a/go.sum +++ b/go.sum @@ -72,48 +72,48 @@ github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736/go.mod h1:mTe github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 h1:I9YN9WMo3SUh7p/4wKeNvD/IQla3U3SUa61U7ul+xM4= github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964/go.mod h1:eFiR01PwTcpbzXtdMces7zxg6utvFM5puiWHpWB8D/k= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aws/aws-sdk-go-v2 v1.20.2 h1:0Aok9u/HVTk7RtY6M1KDcthbaMKGhhS0eLPxIdSIzRI= -github.com/aws/aws-sdk-go-v2 v1.20.2/go.mod h1:NU06lETsFm8fUC6ZjhgDpVBcGZTFQ6XM+LZWZxMI4ac= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 h1:lN6L3LrYHeZ6xCxaIYtoWCx4GMLk4nRknsh29OMSqHY= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12/go.mod h1:TDCkEAkMTXxTs0oLBGBKpBZbk3NLh8EvAfF0Q3x8/0c= -github.com/aws/aws-sdk-go-v2/config v1.18.34 h1:bFf7CtSgwz/vE4tl0cNbWbf6EDQ2TZR5VrsrO9ardoY= -github.com/aws/aws-sdk-go-v2/config v1.18.34/go.mod h1:uJ/keVhwR8vsSaErMu2Vb3dArUZZKLVTcOsKXIFfvjs= -github.com/aws/aws-sdk-go-v2/credentials v1.13.33 h1:esA1X5Eti1xSGCF0W0LYpHH/r6p+MqT0DiKXsfDEPxs= -github.com/aws/aws-sdk-go-v2/credentials v1.13.33/go.mod h1:jNC10ZEYuLlt9IOowix60yNiO6vGA14RVK3oUfX5KgI= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.9 h1:DnNHcClgyFV5suHJ4axqhmG3YeRGgIu6yv29IEWR9aE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.9/go.mod h1:kz0hzQXlc/5Y5mkbwTKX8A+aTRA45t8Aavly60bQzAQ= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.78 h1:yKlVjl84XK9IshIDplZCUaqwK6jvpQ/h1dQwrzMwZj0= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.78/go.mod h1:IIZC114y2/TZCDCczXrq2bL2nLDUtLqjEFT7zurX8kA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.39 h1:OBokd2jreL7ItwqRRcN5QiSt24/i2r742aRsd2qMyeg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.39/go.mod h1:OLmjwglQh90dCcFJDGD+T44G0ToLH+696kRwRhS1KOU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.33 h1:gcRN6PXAo8w3HYFp2wFyr+WYEP4n/a25/IOhzJl36Yw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.33/go.mod h1:S/zgOphghZAIvrbtvsVycoOncfqh1Hc4uGDIHqDLwTU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.40 h1:glWaI8WyeYqQN4zh4zqogzSpNPj8rf11Nj+oE3ghQPw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.40/go.mod h1:OCnFHzgaBY2PuGiHSzLlfqV4j5rJrky7YMfBXcx2Uk0= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.2 h1:9Np6KOCKYnjMwJd1/17ReLdN21gnloI80LNP3uCKk44= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.2/go.mod h1:0YZJZKZCSSbQYQrXpqv0DpIaOMcZ27+OHFaSJTmN+8o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13 h1:iV/W5OMBys+66OeXJi/7xIRrKZNsu0ylsLGu+6nbmQE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13/go.mod h1:ReJb6xYmtGyu9KoFtRreWegbN9dZqvZIIv4vWnhcsyI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.34 h1:gQE3p36iC+wwf/hDaCw+tNVXmNxDUehqv5nAvnoG+yc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.34/go.mod h1:swEfojiNWdgJaOTNT65+XsMclEx4k/tyzBAVEi0Y6vM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.33 h1:cr70Hw6Lq9cqRst1y4YOHLiaVWaWtBPiqdloinNkfis= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.33/go.mod h1:kcNtzCcEoflp+6e2CDTmm2h3xQGZOBZqYA/8DhYx/S8= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.2 h1:M5vGdcDO+jUGWu7d4BXwcLRXp3UikWXAiCfQI20rqFQ= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.2/go.mod h1:bC2B9AS4ygwMNrefck3XeD6YwXeplWhY6Z2UtlGjv1s= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.15.3 h1:NnDCBIRlYtreJIuK0PfqpvgPc3aYHpp2dFnxWxAfzUE= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.15.3/go.mod h1:h2345n5Sc82zNIm7XItORbqv44cBx+di6gS7gpZG9OU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.38.3 h1:yWclTL4cyiqLBWSjxDJ1tjiIzP4x4Kp85aAUtKSbtwA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.38.3/go.mod h1:yER+u7+gwH6dXy5xRTC2OfoHpYY1BFRiS0SF5iamO6M= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.1 h1:JBrOoTb1gfm4EhlwbMigvLRgOHgouSyQFRbOVQWn3wU= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.1/go.mod h1:fYrcZAwlCzBXN7+5RiJlokZbdIbEsEjwonLyPZQGVGg= -github.com/aws/aws-sdk-go-v2/service/sso v1.13.3 h1:nceOkYE0jmaG9CoyXHJJm00FAQ8JE+/LCKJJ06hH/Nc= -github.com/aws/aws-sdk-go-v2/service/sso v1.13.3/go.mod h1:DApEBnZzexe+LDLaNrGOJA8xtRMCpikLW1gX7jZhHxc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.3 h1:90qW9puxI7LgmiYKSPhx6wz4XqgVauTxCyS3185+JpA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.3/go.mod h1:kKpyLjToIS7E3z0672lBhxIPD+uoQ9V0MYRYCVGIkO0= -github.com/aws/aws-sdk-go-v2/service/sts v1.21.3 h1:s3wBkMxfA/u2EJJl6KRsPcWv858lDHkhinqXyN6fkZI= -github.com/aws/aws-sdk-go-v2/service/sts v1.21.3/go.mod h1:b+y9zL57mwCRy6ftp9Nc7CONGHX3sZ50ZCLTrI5xpCc= -github.com/aws/smithy-go v1.14.1 h1:EFKMUmH/iHMqLiwoEDx2rRjRQpI1YCn5jTysoaDujFs= -github.com/aws/smithy-go v1.14.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/aws-sdk-go-v2 v1.20.3 h1:lgeKmAZhlj1JqN43bogrM75spIvYnRxqTAh1iupu1yE= +github.com/aws/aws-sdk-go-v2 v1.20.3/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 h1:OPLEkmhXf6xFPiz0bLeDArZIDx1NNS4oJyG4nv3Gct0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13/go.mod h1:gpAbvyDGQFozTEmlTFO8XcQKHzubdq0LzRyJpG6MiXM= +github.com/aws/aws-sdk-go-v2/config v1.18.35 h1:uU9rgCzrW/pVRUUlRULiwKQe8RoEDst1NQu4Qo8kOtk= +github.com/aws/aws-sdk-go-v2/config v1.18.35/go.mod h1:7xF1yr9GBMfYRQI4PLHO8iceqKLM6DpGVEvXI38HB/A= +github.com/aws/aws-sdk-go-v2/credentials v1.13.34 h1:/EYG4lzayDd5PY6HQQ2Qyj/cD6CR3kz96BjTZAO5tNo= +github.com/aws/aws-sdk-go-v2/credentials v1.13.34/go.mod h1:+wgdxCGNulHme6kTMZuDL9KOagLPloemoYkfjpQkSEU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.10 h1:mgOrtwYfJZ4e3QJe1TrliC/xIkauafGMdLLuCExOqcs= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.10/go.mod h1:wMsSLVM2hRpDVhd+3dtLUzqwm7/fjuhNN+b1aOLDt6g= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.79 h1:Lc2K7rBQlWnY+HB3cNrz/zpEF+ncyn//iJ6gpi5vfR4= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.79/go.mod h1:NpiOzmnVgrRfF/ZbqShAsisq5/6DWYEG2QqHnsdUnbE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.40 h1:CXceCS9BrDInRc74GDCQ8Qyk/Gp9VLdK+Rlve+zELSE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.40/go.mod h1:5kKmFhLeOVy6pwPDpDNA6/hK/d6URC98pqDDqHgdBx4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.34 h1:B+nZtd22cbko5+793hg7LEaTeLMiZwlgCLUrN5Y0uzg= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.34/go.mod h1:RZP0scceAyhMIQ9JvFp7HvkpcgqjL4l/4C+7RAeGbuM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.41 h1:EcSFdpLdkF3FWizimox0qYLuorn9e4PNMR27mvshGLs= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.41/go.mod h1:mKxUXW+TuwpCKKHVlmHGVVuBi9y9LKW8AiQodg23M5E= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.3 h1:uHhWcrNBgpm9gi3o8NSQcsAqha/U9OFYzi2k4+0UVz8= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.3/go.mod h1:jYLMm3Dh0wbeV3lxth5ryks/O2M/omVXWyYm3YcEVqQ= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 h1:m0QTSI6pZYJTk5WSKx3fm5cNW/DCicVzULBgU/6IyD0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14/go.mod h1:dDilntgHy9WnHXsh7dDtUPgHKEfTJIBUTHM8OWm0f/0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.35 h1:oCUrlTzh9GwhlYdyDGNAS6UgqJRzJp5rKoYCJWqLyZI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.35/go.mod h1:YVHrksq36j0sbXCT6rSuQafpfYkMYqy0QTk7JTCTBIU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.34 h1:JwvXk+1ePAD9xkFHprhHYqwsxLDcbNFsPI1IAT2sPS0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.34/go.mod h1:ytsF+t+FApY2lFnN51fJKPhH6ICKOPXKEcwwgmJEdWI= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.3 h1:rPDAISw3FjEhrJoaxmQjuD+GgBfv2p3AVhmAcnyqq3k= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.3/go.mod h1:TXBww3ANB+QRj+/dUoYDvI8d/u4F4WzTxD4mxtDoxrg= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.15.4 h1:/AyMci1EGQIRzUPgXHkLmxUTDEmyBV+kSk/RWi94UKk= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.15.4/go.mod h1:qQqwZdFSIOyDh/4I16LMDA/c5N4xBxQnynpIU6qzxpE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.38.4 h1:P4p346B+YMTTCH9D4I/FWYl+E7BjSLQxqk1e2KYDI5w= +github.com/aws/aws-sdk-go-v2/service/s3 v1.38.4/go.mod h1:uDxTlJiuPhbtRRPMHrPYRkn1Ck7Mtk3BEJiDut+gR5Y= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.2 h1:6N4VK/eLcMYonOqGgihkYlgjE2URxEMqjjS/1zErTKA= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.2/go.mod h1:aYWGu8cQcyRdfDi/V4agl6VDmDz2N42VhiHj0xMf77o= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.4 h1:WZPZ7Zf6Yo13lsfTetFrLU/7hZ9CXESDpdIHvmLxQFQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.4/go.mod h1:FP05hDXTLouXwAMQ1swqybHy7tHySblMkBMKSumaKg0= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.4 h1:pYFM2U/3/4RLrlMSYXwL1XPBCWvaePk2p+0+i/BgHOs= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.4/go.mod h1:4pdlNASc29u0j9bq2jIQcBghG5Lx2oQAIj91vo1u1t8= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.4 h1:zj4jxK3L54tGyqKleKDMK4vHolENxlq11dF0v1oBkJo= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.4/go.mod h1:CQRMCzYvl5eeAQW3AWkRLS+zGGXCucBnsiQlrs+tCeo= +github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= +github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc= @@ -160,8 +160,8 @@ github.com/drakkan/crypto v0.0.0-20230804183749-f40d052136b8 h1:TUieQf6mz4xlWJav github.com/drakkan/crypto v0.0.0-20230804183749-f40d052136b8/go.mod h1:jjOR8ZXZPvxgpYUhVmAtGUCuD1OFc5Hq984QRL686so= github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA= github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU= -github.com/drakkan/ftpserverlib v0.0.0-20230818123055-c8426c7a1b8d h1:WcXXDwXoVS85iFI6nCugkkMZqvU9Hb4GAa6MErVlaxY= -github.com/drakkan/ftpserverlib v0.0.0-20230818123055-c8426c7a1b8d/go.mod h1:dI9/yw/KfJ0g4wmRK8ZukUfqakLr6ZTf9VDydKoLy90= +github.com/drakkan/ftpserverlib v0.0.0-20230820134433-b2a9de7897b3 h1:z1XhTv2fAIkd0/T6NtYwW78iZWxwZclBPXcqiQnUELw= +github.com/drakkan/ftpserverlib v0.0.0-20230820134433-b2a9de7897b3/go.mod h1:dI9/yw/KfJ0g4wmRK8ZukUfqakLr6ZTf9VDydKoLy90= github.com/drakkan/webdav v0.0.0-20230227175313-32996838bcd8 h1:tdkLkSKtYd3WSDsZXGJDKsakiNstLQJPN5HjnqCkf2c= github.com/drakkan/webdav v0.0.0-20230227175313-32996838bcd8/go.mod h1:zOVb1QDhwwqWn2L2qZ0U3swMSO4GTSNyIwXCGO/UGWE= github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4= diff --git a/internal/config/config.go b/internal/config/config.go index 51f8002d..58048cd2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1163,6 +1163,12 @@ func getFTPDBindingSecurityFromEnv(idx int, binding *ftpd.Binding) bool { isSet = true } + tlsSessionReuse, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_FTPD__BINDINGS__%v__TLS_SESSION_REUSE", idx), 0) + if ok { + binding.TLSSessionReuse = int(tlsSessionReuse) + isSet = true + } + tlsVer, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_FTPD__BINDINGS__%v__MIN_TLS_VERSION", idx), 0) if ok { binding.MinTLSVersion = int(tlsVer) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index a16cf3ce..cfa838b6 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -962,6 +962,7 @@ func TestFTPDBindingsFromEnv(t *testing.T) { os.Setenv("SFTPGO_FTPD__BINDINGS__0__PORT", "2200") os.Setenv("SFTPGO_FTPD__BINDINGS__0__APPLY_PROXY_CONFIG", "f") os.Setenv("SFTPGO_FTPD__BINDINGS__0__TLS_MODE", "2") + os.Setenv("SFTPGO_FTPD__BINDINGS__0__TLS_SESSION_REUSE", "1") os.Setenv("SFTPGO_FTPD__BINDINGS__0__FORCE_PASSIVE_IP", "127.0.1.2") os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__0__IP", "172.16.1.1") os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_HOST", "127.0.1.3") @@ -985,6 +986,7 @@ func TestFTPDBindingsFromEnv(t *testing.T) { os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__PORT") os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__APPLY_PROXY_CONFIG") os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__TLS_MODE") + os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__TLS_SESSION_REUSE") os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__FORCE_PASSIVE_IP") os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__0__IP") os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_HOST") @@ -1012,6 +1014,7 @@ func TestFTPDBindingsFromEnv(t *testing.T) { require.Equal(t, "127.0.0.1", bindings[0].Address) require.False(t, bindings[0].ApplyProxyConfig) require.Equal(t, 2, bindings[0].TLSMode) + require.Equal(t, 1, bindings[0].TLSSessionReuse) require.Equal(t, 12, bindings[0].MinTLSVersion) require.Equal(t, "127.0.1.2", bindings[0].ForcePassiveIP) require.Len(t, bindings[0].PassiveIPOverrides, 0) @@ -1027,6 +1030,7 @@ func TestFTPDBindingsFromEnv(t *testing.T) { require.Equal(t, "127.0.1.1", bindings[1].Address) require.True(t, bindings[1].ApplyProxyConfig) // default value require.Equal(t, 1, bindings[1].TLSMode) + require.Equal(t, 0, bindings[1].TLSSessionReuse) require.Equal(t, 13, bindings[1].MinTLSVersion) require.Equal(t, "127.0.1.1", bindings[1].ForcePassiveIP) require.Empty(t, bindings[1].PassiveHost) diff --git a/internal/ftpd/ftpd.go b/internal/ftpd/ftpd.go index fc7a0c82..a0dd7edc 100644 --- a/internal/ftpd/ftpd.go +++ b/internal/ftpd/ftpd.go @@ -66,6 +66,8 @@ type Binding struct { // Set to 1 to require TLS for both data and control connection. // Set to 2 to enable implicit TLS TLSMode int `json:"tls_mode" mapstructure:"tls_mode"` + // 0 disabled, 1 required + TLSSessionReuse int `json:"tls_session_reuse" mapstructure:"tls_session_reuse"` // Certificate and matching private key for this specific binding, if empty the global // ones will be used, if any CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"` @@ -133,6 +135,14 @@ func (b *Binding) IsValid() bool { return b.Port > 0 } +func (b *Binding) isTLSModeValid() bool { + return b.TLSMode >= 0 && b.TLSMode <= 2 +} + +func (b *Binding) isTLSSessionReuseValid() bool { + return b.TLSSessionReuse >= 0 && b.TLSSessionReuse <= 1 +} + func (b *Binding) checkSecuritySettings() error { if b.PassiveConnectionsSecurity < 0 || b.PassiveConnectionsSecurity > 1 { return fmt.Errorf("invalid passive_connections_security: %v", b.PassiveConnectionsSecurity) diff --git a/internal/ftpd/ftpd_test.go b/internal/ftpd/ftpd_test.go index 6e6a3d07..2f9c7c70 100644 --- a/internal/ftpd/ftpd_test.go +++ b/internal/ftpd/ftpd_test.go @@ -58,14 +58,15 @@ import ( ) const ( - logSender = "ftpdTesting" - ftpServerAddr = "127.0.0.1:2121" - sftpServerAddr = "127.0.0.1:2122" - ftpSrvAddrTLS = "127.0.0.1:2124" // ftp server with implicit tls - defaultUsername = "test_user_ftp" - defaultPassword = "test_password" - osWindows = "windows" - ftpsCert = `-----BEGIN CERTIFICATE----- + logSender = "ftpdTesting" + ftpServerAddr = "127.0.0.1:2121" + sftpServerAddr = "127.0.0.1:2122" + ftpSrvAddrTLS = "127.0.0.1:2124" // ftp server with implicit tls + ftpSrvAddrTLSResumption = "127.0.0.1:2126" // ftp server with implicit tls + defaultUsername = "test_user_ftp" + defaultPassword = "test_password" + osWindows = "windows" + ftpsCert = `-----BEGIN CERTIFICATE----- MIICHTCCAaKgAwIBAgIUHnqw7QnB1Bj9oUsNpdb+ZkFPOxMwCgYIKoZIzj0EAwIw RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAyMDQwOTUzMDRaFw0zMDAyMDEw @@ -263,7 +264,7 @@ var ( caCRLPath string ) -func TestMain(m *testing.M) { +func TestMain(m *testing.M) { //nolint:gocyclo logFilePath = filepath.Join(configDir, "sftpgo_ftpd_test.log") bannerFileName := "banner_file" bannerFile := filepath.Join(configDir, bannerFileName) @@ -271,6 +272,7 @@ func TestMain(m *testing.M) { err := os.WriteFile(bannerFile, []byte("SFTPGo test ready\nsimple banner line\n"), os.ModePerm) if err != nil { logger.ErrorToConsole("error creating banner file: %v", err) + os.Exit(1) } // we run the test cases with UploadMode atomic and resume support. The non atomic code path // simply does not execute some code so if it works in atomic mode will @@ -431,6 +433,31 @@ func TestMain(m *testing.M) { }() waitTCPListening(ftpdConf.Bindings[0].GetAddress()) + + ftpdConf = config.GetFTPDConfig() + ftpdConf.Bindings = []ftpd.Binding{ + { + Port: 2126, + CertificateFile: certPath, + CertificateKeyFile: keyPath, + TLSMode: 1, + TLSSessionReuse: 1, + ClientAuthType: 2, + }, + } + ftpdConf.CACertificates = []string{caCrtPath} + ftpdConf.CARevocationLists = []string{caCRLPath} + + go func() { + logger.Debug(logSender, "", "initializing FTP server with config %+v", ftpdConf) + if err := ftpdConf.Initialize(configDir); err != nil { + logger.ErrorToConsole("could not start FTP server: %v", err) + os.Exit(1) + } + }() + + waitTCPListening(ftpdConf.Bindings[0].GetAddress()) + waitNoConnections() startHTTPFs() @@ -501,6 +528,16 @@ func TestInitializationFailure(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "the provided passive IP \"127001\" is not valid") ftpdConf.Bindings[1].ForcePassiveIP = "" + ftpdConf.Bindings[1].TLSMode = 2 + ftpdConf.Bindings[1].TLSSessionReuse = 1 + err = ftpdConf.Initialize(configDir) + require.Error(t, err, "TLS session resumption should not be supported with implicit FTPS") + ftpdConf.Bindings[1].TLSMode = 0 + ftpdConf.Bindings[1].TLSSessionReuse = 100 + err = ftpdConf.Initialize(configDir) + require.Error(t, err) + assert.Contains(t, err.Error(), "unsupported TLS reuse mode") + ftpdConf.Bindings[1].TLSSessionReuse = 0 err = ftpdConf.Initialize(configDir) require.Error(t, err) @@ -3359,6 +3396,61 @@ func TestCombine(t *testing.T) { assert.NoError(t, err) } +func TestTLSSessionReuse(t *testing.T) { + u := getTestUser() + user, _, err := httpdtest.AddUser(u, http.StatusCreated) + assert.NoError(t, err) + + client, err := getFTPClientWithSessionReuse(user, nil) + if assert.NoError(t, err) { + err = checkBasicFTP(client) + assert.NoError(t, err) + + testFilePath := filepath.Join(homeBasePath, testFileName) + testFileSize := int64(65535) + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + + err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0) + assert.NoError(t, err) + + localDownloadPath := filepath.Join(homeBasePath, testDLFileName) + err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0) + assert.NoError(t, err) + + entries, err := client.List("/") + assert.NoError(t, err) + assert.Len(t, entries, 1) + + err = client.Quit() + assert.NoError(t, err) + err = os.Remove(testFilePath) + assert.NoError(t, err) + err = os.Remove(localDownloadPath) + assert.NoError(t, err) + } + + // this TLS config does not support session resumption + tlsConfig := &tls.Config{ + ServerName: "localhost", + InsecureSkipVerify: true, // use this for tests only + MinVersion: tls.VersionTLS12, + } + client, err = getFTPClientWithSessionReuse(user, tlsConfig) + if assert.NoError(t, err) { + err = checkBasicFTP(client) + assert.Error(t, err) + + err = client.Quit() + assert.NoError(t, err) + } + + _, err = httpdtest.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) +} + func TestClientCertificateAuthRevokedCert(t *testing.T) { u := getTestUser() u.Username = tlsClient2Username @@ -3369,11 +3461,12 @@ func TestClientCertificateAuthRevokedCert(t *testing.T) { ServerName: "localhost", InsecureSkipVerify: true, // use this for tests only MinVersion: tls.VersionTLS12, + ClientSessionCache: tls.NewLRUClientSessionCache(0), } tlsCert, err := tls.X509KeyPair([]byte(client2Crt), []byte(client2Key)) assert.NoError(t, err) tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCert) - _, err = getFTPClient(user, true, tlsConfig) + _, err = getFTPClientWithSessionReuse(user, tlsConfig) if assert.Error(t, err) { assert.Contains(t, err.Error(), "bad certificate") } @@ -3869,6 +3962,38 @@ func getFTPClientImplicitTLS(user dataprovider.User) (*ftp.ServerConn, error) { return client, err } +func getFTPClientWithSessionReuse(user dataprovider.User, tlsConfig *tls.Config, dialOptions ...ftp.DialOption, +) (*ftp.ServerConn, error) { + ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)} + ftpOptions = append(ftpOptions, dialOptions...) + if tlsConfig == nil { + tlsConfig = &tls.Config{ + ServerName: "localhost", + InsecureSkipVerify: true, // use this for tests only + MinVersion: tls.VersionTLS12, + ClientSessionCache: tls.NewLRUClientSessionCache(0), + } + } + ftpOptions = append(ftpOptions, ftp.DialWithExplicitTLS(tlsConfig)) + client, err := ftp.Dial(ftpSrvAddrTLSResumption, ftpOptions...) + if err != nil { + return nil, err + } + pwd := defaultPassword + if user.Password != "" { + if user.Password == emptyPwdPlaceholder { + pwd = "" + } else { + pwd = user.Password + } + } + err = client.Login(user.Username, pwd) + if err != nil { + return nil, err + } + return client, err +} + func getFTPClient(user dataprovider.User, useTLS bool, tlsConfig *tls.Config, dialOptions ...ftp.DialOption, ) (*ftp.ServerConn, error) { ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)} diff --git a/internal/ftpd/internal_test.go b/internal/ftpd/internal_test.go index 1cc98b09..c22994f8 100644 --- a/internal/ftpd/internal_test.go +++ b/internal/ftpd/internal_test.go @@ -945,6 +945,11 @@ func TestVerifyTLSConnection(t *testing.T) { err = server.verifyTLSConnection(state) assert.Error(t, err) // no verified certification chain + err = server.VerifyTLSConnectionState(nil, state) + assert.NoError(t, err) + server.binding.ClientAuthType = 1 + err = server.VerifyTLSConnectionState(nil, state) + assert.Error(t, err) crt, err = tls.X509KeyPair([]byte(caCRT), []byte(caKey)) assert.NoError(t, err) diff --git a/internal/ftpd/server.go b/internal/ftpd/server.go index d9449c15..2e4c9d83 100644 --- a/internal/ftpd/server.go +++ b/internal/ftpd/server.go @@ -104,11 +104,15 @@ func (s *Server) GetSettings() (*ftpserver.Settings, error) { } } - if s.binding.TLSMode < 0 || s.binding.TLSMode > 2 { - return nil, errors.New("unsupported TLS mode") + if !s.binding.isTLSModeValid() { + return nil, fmt.Errorf("unsupported TLS mode: %d", s.binding.TLSMode) } - if s.binding.TLSMode > 0 && certMgr == nil { + if !s.binding.isTLSSessionReuseValid() { + return nil, fmt.Errorf("unsupported TLS reuse mode %d", s.binding.TLSSessionReuse) + } + + if (s.binding.TLSMode > 0 || s.binding.TLSSessionReuse > 0) && certMgr == nil { return nil, errors.New("to enable TLS you need to provide a certificate") } @@ -122,6 +126,7 @@ func (s *Server) GetSettings() (*ftpserver.Settings, error) { ConnectionTimeout: 20, Banner: s.statusBanner, TLSRequired: ftpserver.TLSRequirement(s.binding.TLSMode), + TLSSessionReuse: ftpserver.TLSSessionReuse(s.binding.TLSSessionReuse), DisableSite: !s.config.EnableSite, DisableActiveMode: s.config.DisableActiveMode, EnableHASH: s.config.HASHSupport > 0, @@ -284,7 +289,9 @@ func (s *Server) buildTLSConfig() { s.binding.GetAddress(), s.binding.ciphers, certID) if s.binding.isMutualTLSEnabled() { s.tlsConfig.ClientCAs = certMgr.GetRootCAs() - s.tlsConfig.VerifyConnection = s.verifyTLSConnection + if s.binding.TLSSessionReuse != int(ftpserver.TLSSessionReuseRequired) { + s.tlsConfig.VerifyConnection = s.verifyTLSConnection + } switch s.binding.ClientAuthType { case 1: s.tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert @@ -303,6 +310,14 @@ func (s *Server) GetTLSConfig() (*tls.Config, error) { return nil, errors.New("no TLS certificate configured") } +// VerifyTLSConnectionState implements the MainDriverExtensionTLSConnectionStateVerifier extension +func (s *Server) VerifyTLSConnectionState(_ ftpserver.ClientContext, cs tls.ConnectionState) error { + if !s.binding.isMutualTLSEnabled() { + return nil + } + return s.verifyTLSConnection(cs) +} + func (s *Server) verifyTLSConnection(state tls.ConnectionState) error { if certMgr != nil { var clientCrt *x509.Certificate diff --git a/sftpgo.json b/sftpgo.json index 5a0c2195..16a830d2 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -113,6 +113,7 @@ "address": "", "apply_proxy_config": true, "tls_mode": 0, + "tls_session_reuse": 0, "certificate_file": "", "certificate_key_file": "", "min_tls_version": 12,