Repository: C2FO/vfs
Pull request #205 (2024-10-15 21:11:45)

Add IncludeInsecureCiphers option to FTP backend.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 74af6d4..c85150b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 ## [Unreleased]
 
+## [6.20.0] - 2024-10-15
+### Added
+- Add IncludeInsecureCiphers option to FTP backend.  Fixes #204.
+
 ## [6.19.0] - 2024-09-13
 ### Added
 - Add ability to set file permissions after writing an SFTP file. Resolves #202.
diff --git a/backend/ftp/doc.go b/backend/ftp/doc.go
index 1aef9ea..c03ae68 100644
--- a/backend/ftp/doc.go
+++ b/backend/ftp/doc.go
@@ -55,6 +55,7 @@ These methods are chainable:
 				  Protocol: ftp.ProtocolFTPES,
 				  DialTimeout: 15 * time.Second,
 				  DebugWriter: os.Stdout,
+				  IncludeInsecureCiphers: true,
 			  },
 		  )
 
@@ -148,5 +149,7 @@ DebugWriter *io.Writer* - captures FTP command details to any writer.
 DialTimeout *time.Duration - sets timeout for connecting only.
 
 DisableEPSV bool - Extended Passive mode (EPSV) is attempted by default. Set to true to use regular Passive mode (PASV).
+
+IncludeInsecureCiphers bool - If set to true, includes insecure cipher suites in the TLS configuration.
 */
 package ftp
diff --git a/backend/ftp/options.go b/backend/ftp/options.go
index 947f7db..00ea455 100644
--- a/backend/ftp/options.go
+++ b/backend/ftp/options.go
@@ -17,12 +17,13 @@ import (
 
 // Options  struct implements the vfs.Options interface, providing optional parameters for creating and ftp filesystem.
 type Options struct {
-	Password    string // env var VFS_FTP_PASSWORD
-	Protocol    string // env var VFS_FTP_PROTOCOL
-	DisableEPSV *bool  // env var VFS_DISABLE_EPSV
-	DebugWriter io.Writer
-	TLSConfig   *tls.Config
-	DialTimeout time.Duration
+	Password               string // env var VFS_FTP_PASSWORD
+	Protocol               string // env var VFS_FTP_PROTOCOL
+	DisableEPSV            *bool  // env var VFS_DISABLE_EPSV
+	DebugWriter            io.Writer
+	TLSConfig              *tls.Config
+	DialTimeout            time.Duration
+	IncludeInsecureCiphers bool
 }
 
 const (
@@ -162,6 +163,22 @@ func fetchTLSConfig(auth utils.Authority, opts Options) *tls.Config {
 		ServerName:         auth.Host(),
 	}
 
+	if opts.IncludeInsecureCiphers {
+		var suites []uint16
+
+		// get default cipher suites
+		for _, suite := range tls.CipherSuites() {
+			suites = append(suites, suite.ID)
+		}
+
+		// add insecure cipher suites
+		for _, suite := range tls.InsecureCipherSuites() {
+			suites = append(suites, suite.ID)
+		}
+
+		tlsConfig.CipherSuites = suites
+	}
+
 	// override with Options, if any
 	if opts.TLSConfig != nil {
 		tlsConfig = opts.TLSConfig


Test patch:
diff --git a/backend/ftp/options_test.go b/backend/ftp/options_test.go
index 7fbb9de..77be3d0 100644
--- a/backend/ftp/options_test.go
+++ b/backend/ftp/options_test.go
@@ -215,10 +215,11 @@ func (s *optionsSuite) TestFetchTLSConfig() {
 	}
 
 	tests := []struct {
-		description string
-		authority   string
-		options     Options
-		expected    *tls.Config
+		description                string
+		authority                  string
+		options                    Options
+		expected                   *tls.Config
+		expectInsecureCipherSuites bool
 	}{
 		{
 			description: "check defaults",
@@ -239,6 +240,20 @@ func (s *optionsSuite) TestFetchTLSConfig() {
 			},
 			expected: cfg,
 		},
+		{
+			description: "include insecure cipher suites",
+			authority:   "user@host.com",
+			options: Options{
+				IncludeInsecureCiphers: true,
+			},
+			expected: &tls.Config{
+				MinVersion:         tls.VersionTLS12,
+				InsecureSkipVerify: true, //nolint:gosec
+				ClientSessionCache: tls.NewLRUClientSessionCache(0),
+				ServerName:         "host.com",
+			},
+			expectInsecureCipherSuites: true,
+		},
 	}
 
 	for i := range tests {
@@ -246,8 +261,29 @@ func (s *optionsSuite) TestFetchTLSConfig() {
 		s.NoError(err, tests[i].description)
 
 		tlsCfg := fetchTLSConfig(auth, tests[i].options)
-		s.Equal(tests[i].expected, tlsCfg, tests[i].description)
+		s.Equal(tests[i].expected.MinVersion, tlsCfg.MinVersion, tests[i].description)
+		s.Equal(tests[i].expected.InsecureSkipVerify, tlsCfg.InsecureSkipVerify, tests[i].description)
+		s.Equal(tests[i].expected.ClientSessionCache, tlsCfg.ClientSessionCache, tests[i].description)
+		s.Equal(tests[i].expected.ServerName, tlsCfg.ServerName, tests[i].description)
+		s.Equal(tests[i].expected.SessionTicketsDisabled, tlsCfg.SessionTicketsDisabled, tests[i].description)
+
+		if tests[i].expectInsecureCipherSuites {
+			s.NotEmpty(tlsCfg.CipherSuites, tests[i].description)
+			s.True(containsInsecureCipherSuites(tlsCfg.CipherSuites), tests[i].description)
+		}
+	}
+}
+
+func containsInsecureCipherSuites(suites []uint16) bool {
+	insecureSuites := tls.InsecureCipherSuites()
+	for _, s := range suites {
+		for _, insecureSuite := range insecureSuites {
+			if s == insecureSuite.ID {
+				return true
+			}
+		}
 	}
+	return false
 }
 
 func (s *optionsSuite) TestFetchProtocol() {