Skip to content

Commit

Permalink
Add support for pam authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
iychoi committed Sep 6, 2023
1 parent f98ace7 commit b7ec3ed
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 29 deletions.
2 changes: 0 additions & 2 deletions docs/irods.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

To connect SFTPGo to iRODS, you need to specify credentials and a `collection path`. For example, if your collection `some_collection` is under your account's home directory `/home/irods_user` in a zone `example_zone`, you have to set the `/example_zone/home/irods_user/some_collection`. If you want to specify a particular iRODS resource server to access, use `resource server`. You can set an empty string to `resource server` to use default resource server. An endpoint is host and port of an iRODS's catalog provider (also known as iCAT server). For example, `data.cyverse.org:1247` is the endpoint if you are connecting to [CyVerse Data Store](https://data.cyverse.org). Port can be omitted if the port is 1247.

Currently, we only support password authentication. SSL/PAM authentication is not supported.

Some SFTP commands don't work over iRODS:

- `chown` and `chmod` will fail. If you want to silently ignore these method set `setstat_mode` to `1` or `2` in your configuration file
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.18.8
github.com/cockroachdb/cockroach-go/v2 v2.3.3
github.com/coreos/go-oidc/v3 v3.5.0
github.com/cyverse/go-irodsclient v0.11.4
github.com/cyverse/go-irodsclient v0.12.8
github.com/drakkan/webdav v0.0.0-20230227175313-32996838bcd8
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
github.com/fclairamb/ftpserverlib v0.21.0
Expand Down Expand Up @@ -71,7 +71,7 @@ require (
golang.org/x/crypto v0.7.0
golang.org/x/net v0.9.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sys v0.7.0
golang.org/x/sys v0.8.0
golang.org/x/time v0.3.0
google.golang.org/api v0.116.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
Expand Down Expand Up @@ -174,6 +174,6 @@ require (
replace (
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
github.com/sftpgo/sdk => github.com/cyverse/sftpgo-sdk v0.1.3-0.20230410232438-7190b52214b0
github.com/sftpgo/sdk => github.com/cyverse/sftpgo-sdk v0.1.3-0.20230906214213-bdb8dbbe543f
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20230106095953-5417b4dfde62
)
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -830,10 +830,10 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/cyverse/go-irodsclient v0.11.4 h1:1jrF2sqvC3rb0pe4Y7koq8wOYNuHcbzmQ+BSxg7D91A=
github.com/cyverse/go-irodsclient v0.11.4/go.mod h1:Qs1cjnDN1RaBaUcaZCsRGPFqCffg/cExSBIm466nvTw=
github.com/cyverse/sftpgo-sdk v0.1.3-0.20230410232438-7190b52214b0 h1:t08LiL1z6ND1pBBfXS35HufxUJl0W+oT4DZZxFJaCQY=
github.com/cyverse/sftpgo-sdk v0.1.3-0.20230410232438-7190b52214b0/go.mod h1:Giy5vj7Gmju0nGlmBNd28DwPo0G0o1nr9XkE+vu3i+o=
github.com/cyverse/go-irodsclient v0.12.8 h1:sUaNCQ7nDxUiD+HI/hBSnUiY11P8Ph+IujJt/M8Eh48=
github.com/cyverse/go-irodsclient v0.12.8/go.mod h1:SOMr0JtAmbtYp06ZdYhxBYi47GYpV9ImW7sqKVypQhU=
github.com/cyverse/sftpgo-sdk v0.1.3-0.20230906214213-bdb8dbbe543f h1:479l82wotcLnomdIiaypZ6yUNRQ34BGf+mUiM5xVT8Q=
github.com/cyverse/sftpgo-sdk v0.1.3-0.20230906214213-bdb8dbbe543f/go.mod h1:Giy5vj7Gmju0nGlmBNd28DwPo0G0o1nr9XkE+vu3i+o=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
Expand Down Expand Up @@ -2462,8 +2462,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand Down
28 changes: 23 additions & 5 deletions internal/cmd/portable.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ var (
portableIRODSUsername string
portableIRODSProxyUsername string
portableIRODSResourceServer string
portableIRODSAuthScheme string
portableIRODSSSLCACertificatePath string
portableIRODSSSLKeySize int
portableIRODSSSLAlgorithm string
portableIRODSSSLSaltSize int
protableIRODSSSLHashRounds int
portableIRODSPassword string
portableCmd = &cobra.Command{
Use: "portable",
Expand Down Expand Up @@ -263,11 +269,17 @@ Please take a look at the usage below to customize the serving parameters`,
},
IRODSConfig: vfs.IRODSFsConfig{
BaseIRODSFsConfig: sdk.BaseIRODSFsConfig{
Endpoint: portableIRODSEndpoint,
CollectionPath: portableIRODSCollectionPath,
Username: portableIRODSUsername,
ProxyUsername: portableIRODSProxyUsername,
ResourceServer: portableIRODSResourceServer,
Endpoint: portableIRODSEndpoint,
CollectionPath: portableIRODSCollectionPath,
Username: portableIRODSUsername,
ProxyUsername: portableIRODSProxyUsername,
ResourceServer: portableIRODSResourceServer,
AuthScheme: portableIRODSAuthScheme,
SSLCACertificatePath: portableIRODSSSLCACertificatePath,
SSLKeySize: portableIRODSSSLKeySize,
SSLAlgorithm: portableIRODSSSLAlgorithm,
SSLSaltSize: portableIRODSSSLSaltSize,
SSLHashRounds: protableIRODSSSLHashRounds,
},
Password: kms.NewPlainSecret(portableIRODSPassword),
},
Expand Down Expand Up @@ -435,6 +447,12 @@ by overlapping round-trip times`)
portableCmd.Flags().StringVar(&portableIRODSUsername, "irods-username", "", `iRODS user for iRODS provider`)
portableCmd.Flags().StringVar(&portableIRODSProxyUsername, "irods-proxyusername", "", `iRODS proxy user for iRODS provider`)
portableCmd.Flags().StringVar(&portableIRODSResourceServer, "irods-resource", "", `iRODS resource server for iRODS provider`)
portableCmd.Flags().StringVar(&portableIRODSAuthScheme, "irods-auth-scheme", "", `iRODS authentication scheme for iRODS provider`)
portableCmd.Flags().StringVar(&portableIRODSSSLCACertificatePath, "irods-ssl-ca-cert", "", `iRODS SSL CA Certificate file path for iRODS provider`)
portableCmd.Flags().StringVar(&portableIRODSSSLAlgorithm, "irods-ssl-algorithm", "", `iRODS SSL encryption algorithm for iRODS provider`)
portableCmd.Flags().IntVar(&portableIRODSSSLKeySize, "irods-ssl-key-size", 0, `iRODS SSL encryption key size for iRODS provider`)
portableCmd.Flags().IntVar(&portableIRODSSSLSaltSize, "irods-ssl-salt-size", 0, `iRODS SSL encryption salt size for iRODS provider`)
portableCmd.Flags().IntVar(&protableIRODSSSLHashRounds, "irods-ssl-hash-rounds", 0, `iRODS SSL encryption hash rounds for iRODS provider`)
portableCmd.Flags().StringVar(&portableIRODSPassword, "irods-password", "", `iRODS password for iRODS provider`)
rootCmd.AddCommand(portableCmd)
}
Expand Down
18 changes: 18 additions & 0 deletions internal/httpd/webadmin.go
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,24 @@ func getIRODSConfig(r *http.Request) (vfs.IRODSFsConfig, error) {
config.ProxyUsername = r.Form.Get("irods_proxyusername")
config.CollectionPath = r.Form.Get("irods_collection")
config.ResourceServer = r.Form.Get("irods_resource")
config.AuthScheme = r.Form.Get("irods_auth_scheme")
config.SSLCACertificatePath = r.Form.Get("irods_ssl_ca_cert_path")
encryptionKeySize, err := strconv.ParseInt(r.Form.Get("irods_ssl_key_size"), 10, 32)
if err != nil {
return config, fmt.Errorf("invalid irods ssl key size: %w", err)
}
config.SSLKeySize = int(encryptionKeySize)
config.SSLAlgorithm = r.Form.Get("irods_ssl_algorithm")
saltSize, err := strconv.ParseInt(r.Form.Get("irods_ssl_salt_size"), 10, 32)
if err != nil {
return config, fmt.Errorf("invalid irods ssl salt size: %w", err)
}
config.SSLSaltSize = int(saltSize)
hashRounds, err := strconv.ParseInt(r.Form.Get("irods_ssl_hash_rounds"), 10, 32)
if err != nil {
return config, fmt.Errorf("invalid irods ssl hash rounds: %w", err)
}
config.SSLHashRounds = int(hashRounds)
config.Password = getSecretFromFormField(r, "irods_password")
return config, err
}
Expand Down
18 changes: 18 additions & 0 deletions internal/httpdtest/httpdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -2013,6 +2013,24 @@ func compareIRODSFsConfig(expected *vfs.Filesystem, actual *vfs.Filesystem) erro
if expected.IRODSConfig.ResourceServer != actual.IRODSConfig.ResourceServer {
return errors.New("IRODSFs resource server mismatch")
}
if expected.IRODSConfig.AuthScheme != actual.IRODSConfig.AuthScheme {
return errors.New("IRODSFs auth scheme mismatch")
}
if expected.IRODSConfig.SSLCACertificatePath != actual.IRODSConfig.SSLCACertificatePath {
return errors.New("IRODSFs SSL CA certificate path scheme mismatch")
}
if expected.IRODSConfig.SSLKeySize != actual.IRODSConfig.SSLKeySize {
return errors.New("IRODSFs SSL encryption key size mismatch")
}
if expected.IRODSConfig.SSLAlgorithm != actual.IRODSConfig.SSLAlgorithm {
return errors.New("IRODSFs SSL encryption algorithm mismatch")
}
if expected.IRODSConfig.SSLSaltSize != actual.IRODSConfig.SSLSaltSize {
return errors.New("IRODSFs SSL salt size mismatch")
}
if expected.IRODSConfig.SSLHashRounds != actual.IRODSConfig.SSLHashRounds {
return errors.New("IRODSFs SSL hash rounds mismatch")
}
if err := checkEncryptedSecret(expected.IRODSConfig.Password, actual.IRODSConfig.Password); err != nil {
return fmt.Errorf("IRODSFs password mismatch: %v", err)
}
Expand Down
16 changes: 11 additions & 5 deletions internal/vfs/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,11 +385,17 @@ func (f *Filesystem) GetACopy() Filesystem {
},
IRODSConfig: IRODSFsConfig{
BaseIRODSFsConfig: sdk.BaseIRODSFsConfig{
Endpoint: f.IRODSConfig.Endpoint,
CollectionPath: f.IRODSConfig.CollectionPath,
Username: f.IRODSConfig.Username,
ProxyUsername: f.IRODSConfig.ProxyUsername,
ResourceServer: f.IRODSConfig.ResourceServer,
Endpoint: f.IRODSConfig.Endpoint,
CollectionPath: f.IRODSConfig.CollectionPath,
Username: f.IRODSConfig.Username,
ProxyUsername: f.IRODSConfig.ProxyUsername,
ResourceServer: f.IRODSConfig.ResourceServer,
AuthScheme: f.IRODSConfig.AuthScheme,
SSLCACertificatePath: f.IRODSConfig.SSLCACertificatePath,
SSLKeySize: f.IRODSConfig.SSLKeySize,
SSLAlgorithm: f.IRODSConfig.SSLAlgorithm,
SSLSaltSize: f.IRODSConfig.SSLSaltSize,
SSLHashRounds: f.IRODSConfig.SSLHashRounds,
},
Password: f.IRODSConfig.Password.Clone(),
},
Expand Down
67 changes: 62 additions & 5 deletions internal/vfs/irodsfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,24 @@ func (c *IRODSFsConfig) isEqual(other *IRODSFsConfig) bool {
if c.ResourceServer != other.ResourceServer {
return false
}
if c.AuthScheme != other.AuthScheme {
return false
}
if c.SSLCACertificatePath != other.SSLCACertificatePath {
return false
}
if c.SSLKeySize != other.SSLKeySize {
return false
}
if c.SSLAlgorithm != other.SSLAlgorithm {
return false
}
if c.SSLSaltSize != other.SSLSaltSize {
return false
}
if c.SSLHashRounds != other.SSLHashRounds {
return false
}
c.setEmptyCredentialsIfNil()
other.setEmptyCredentialsIfNil()
return c.Password.IsEqual(other.Password)
Expand Down Expand Up @@ -105,6 +123,26 @@ func (c *IRODSFsConfig) validate() error {
if c.Username == "" {
return errors.New("username cannot be empty")
}
if strings.ToLower(c.AuthScheme) != "" && strings.ToLower(c.AuthScheme) != "native" && strings.ToLower(c.AuthScheme) != "pam" {
return errors.New("unknown authentication scheme")
}
if strings.ToLower(c.AuthScheme) == "pam" {
if c.SSLCACertificatePath == "" {
return errors.New("SSL CA certificate path cannot be empty when PAM authentication is used")
}
if c.SSLKeySize == 0 {
return errors.New("SSL encryption key size cannot be 0 when PAM authentication is used")
}
if c.SSLAlgorithm == "" {
return errors.New("SSL encryption algorithm cannot be 0 when PAM authentication is used")
}
if c.SSLSaltSize == 0 {
return errors.New("SSL encryption salt size cannot be 0 when PAM authentication is used")
}
if c.SSLHashRounds == 0 {
return errors.New("SSL encryption has rounds cannot be 0 when PAM authentication is used")
}
}
if err := c.validateCredentials(); err != nil {
return err
}
Expand Down Expand Up @@ -203,7 +241,7 @@ func NewIRODSFs(connectionID, localTempDir, mountPath string, irodsConfig IRODSF
config: &irodsConfig,
}

fsLog(fs, logger.LevelDebug, "creating a new iRODS Fs connID: %s, localTempDir: %s, mountPath: %s\n iRODS Host: %s, Collection Path: %s", fs.connectionID, fs.localTempDir, fs.mountPath, fs.config.Endpoint, fs.config.CollectionPath)
fsLog(fs, logger.LevelDebug, "creating a new iRODS Fs connID: %s, localTempDir: %s, mountPath: %s\n iRODS Host: %s, Auth: %s, Collection Path: %s", fs.connectionID, fs.localTempDir, fs.mountPath, fs.config.Endpoint, fs.config.AuthScheme, fs.config.CollectionPath)

if err := fs.config.validate(); err != nil {
return fs, err
Expand Down Expand Up @@ -764,12 +802,31 @@ func (fs *IRODSFs) createConnection() error {
fs.config.ProxyUsername = fs.config.Username
}

irodsAccount, err := irodstypes.CreateIRODSProxyAccount(host, port, fs.config.Username, zone, fs.config.ProxyUsername, zone, irodstypes.AuthSchemeNative, fs.config.Password.GetPayload(), fs.config.ResourceServer)
if err != nil {
return err
var irodsAccount *irodstypes.IRODSAccount

switch strings.ToLower(fs.config.AuthScheme) {
case "", "native":
irodsAccount, err = irodstypes.CreateIRODSProxyAccount(host, port, fs.config.Username, zone, fs.config.ProxyUsername, zone, irodstypes.AuthSchemeNative, fs.config.Password.GetPayload(), fs.config.ResourceServer)
if err != nil {
return err
}
case "pam":
irodsAccount, err = irodstypes.CreateIRODSProxyAccount(host, port, fs.config.Username, zone, fs.config.ProxyUsername, zone, irodstypes.AuthSchemePAM, fs.config.Password.GetPayload(), fs.config.ResourceServer)
if err != nil {
return err
}

sslConf, err := irodstypes.CreateIRODSSSLConfig(fs.config.SSLCACertificatePath, fs.config.SSLKeySize, fs.config.SSLAlgorithm, fs.config.SSLSaltSize, fs.config.SSLHashRounds)
if err != nil {
return err
}

irodsAccount.SetSSLConfiguration(sslConf)
default:
return fmt.Errorf("unknown authentication scheme %s", fs.config.AuthScheme)
}

fsLog(fs, logger.LevelDebug, "connecting to iRODS %s:%d", irodsAccount.Host, irodsAccount.Port)
fsLog(fs, logger.LevelDebug, "connecting to iRODS %s:%d using %s auth", irodsAccount.Host, irodsAccount.Port, irodsAccount.AuthenticationScheme)

irodsClient, err := irodsfs.NewFileSystemWithDefault(irodsAccount, "sftpgo")
if err != nil {
Expand Down
Loading

0 comments on commit b7ec3ed

Please sign in to comment.