mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-22 07:30:25 +00:00
sdk: add a logger interface
we are now ready to make the sdk a separate module Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
0e1d8fc4d9
commit
6a20e7411b
30 changed files with 225 additions and 134 deletions
|
@ -34,7 +34,6 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/dataprovider"
|
"github.com/drakkan/sftpgo/v2/dataprovider"
|
||||||
"github.com/drakkan/sftpgo/v2/httpclient"
|
"github.com/drakkan/sftpgo/v2/httpclient"
|
||||||
"github.com/drakkan/sftpgo/v2/httpdtest"
|
"github.com/drakkan/sftpgo/v2/httpdtest"
|
||||||
_ "github.com/drakkan/sftpgo/v2/kms"
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/mfa"
|
"github.com/drakkan/sftpgo/v2/mfa"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk"
|
"github.com/drakkan/sftpgo/v2/sdk"
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/ftpd"
|
"github.com/drakkan/sftpgo/v2/ftpd"
|
||||||
"github.com/drakkan/sftpgo/v2/httpclient"
|
"github.com/drakkan/sftpgo/v2/httpclient"
|
||||||
"github.com/drakkan/sftpgo/v2/httpd"
|
"github.com/drakkan/sftpgo/v2/httpd"
|
||||||
_ "github.com/drakkan/sftpgo/v2/kms"
|
|
||||||
"github.com/drakkan/sftpgo/v2/mfa"
|
"github.com/drakkan/sftpgo/v2/mfa"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/plugin"
|
"github.com/drakkan/sftpgo/v2/sdk/plugin"
|
||||||
|
|
|
@ -52,6 +52,7 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/sdk"
|
"github.com/drakkan/sftpgo/v2/sdk"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/plugin"
|
"github.com/drakkan/sftpgo/v2/sdk/plugin"
|
||||||
|
sdkutil "github.com/drakkan/sftpgo/v2/sdk/util"
|
||||||
"github.com/drakkan/sftpgo/v2/util"
|
"github.com/drakkan/sftpgo/v2/util"
|
||||||
"github.com/drakkan/sftpgo/v2/vfs"
|
"github.com/drakkan/sftpgo/v2/vfs"
|
||||||
)
|
)
|
||||||
|
@ -2889,7 +2890,7 @@ func getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip,
|
||||||
var tlsCert string
|
var tlsCert string
|
||||||
if cert != nil {
|
if cert != nil {
|
||||||
var err error
|
var err error
|
||||||
tlsCert, err = util.EncodeTLSCertToPem(cert)
|
tlsCert, err = sdkutil.EncodeTLSCertToPem(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/dataprovider"
|
"github.com/drakkan/sftpgo/v2/dataprovider"
|
||||||
"github.com/drakkan/sftpgo/v2/ftpd"
|
"github.com/drakkan/sftpgo/v2/ftpd"
|
||||||
"github.com/drakkan/sftpgo/v2/httpdtest"
|
"github.com/drakkan/sftpgo/v2/httpdtest"
|
||||||
_ "github.com/drakkan/sftpgo/v2/kms"
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/mfa"
|
"github.com/drakkan/sftpgo/v2/mfa"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk"
|
"github.com/drakkan/sftpgo/v2/sdk"
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -140,8 +140,6 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
|
||||||
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||||
github.com/aws/aws-sdk-go v1.42.25 h1:BbdvHAi+t9LRiaYUyd53noq9jcaAcfzOhSVbKfr6Avs=
|
|
||||||
github.com/aws/aws-sdk-go v1.42.25/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
|
|
||||||
github.com/aws/aws-sdk-go v1.42.26 h1:3+GcxzyI+kvqoASDNeeLZfqGkvyNMrE9IDuErxPRtCA=
|
github.com/aws/aws-sdk-go v1.42.26 h1:3+GcxzyI+kvqoASDNeeLZfqGkvyNMrE9IDuErxPRtCA=
|
||||||
github.com/aws/aws-sdk-go v1.42.26/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
|
github.com/aws/aws-sdk-go v1.42.26/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
|
github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
|
||||||
|
@ -271,8 +269,6 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
|
||||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
|
||||||
github.com/go-chi/chi/v5 v5.0.8-0.20220103230436-7dbe9a0bd10f h1:6kLofhLkWj7lgCc+mvcVLnwhTzQYgL/yW/Y0e/JYwjg=
|
github.com/go-chi/chi/v5 v5.0.8-0.20220103230436-7dbe9a0bd10f h1:6kLofhLkWj7lgCc+mvcVLnwhTzQYgL/yW/Y0e/JYwjg=
|
||||||
github.com/go-chi/chi/v5 v5.0.8-0.20220103230436-7dbe9a0bd10f/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.8-0.20220103230436-7dbe9a0bd10f/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/jwtauth/v5 v5.0.2 h1:CSKtr+b6Jnfy5T27sMaiBPxaVE/bjnjS3ramFQ0526w=
|
github.com/go-chi/jwtauth/v5 v5.0.2 h1:CSKtr+b6Jnfy5T27sMaiBPxaVE/bjnjS3ramFQ0526w=
|
||||||
|
|
|
@ -45,7 +45,6 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/httpclient"
|
"github.com/drakkan/sftpgo/v2/httpclient"
|
||||||
"github.com/drakkan/sftpgo/v2/httpd"
|
"github.com/drakkan/sftpgo/v2/httpd"
|
||||||
"github.com/drakkan/sftpgo/v2/httpdtest"
|
"github.com/drakkan/sftpgo/v2/httpdtest"
|
||||||
_ "github.com/drakkan/sftpgo/v2/kms"
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/mfa"
|
"github.com/drakkan/sftpgo/v2/mfa"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk"
|
"github.com/drakkan/sftpgo/v2/sdk"
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/mfa"
|
"github.com/drakkan/sftpgo/v2/mfa"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk"
|
"github.com/drakkan/sftpgo/v2/sdk"
|
||||||
|
sdklogger "github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/smtp"
|
"github.com/drakkan/sftpgo/v2/smtp"
|
||||||
"github.com/drakkan/sftpgo/v2/util"
|
"github.com/drakkan/sftpgo/v2/util"
|
||||||
"github.com/drakkan/sftpgo/v2/version"
|
"github.com/drakkan/sftpgo/v2/version"
|
||||||
|
@ -75,7 +76,7 @@ func (s *httpdServer) listenAndServe() error {
|
||||||
WriteTimeout: 60 * time.Second,
|
WriteTimeout: 60 * time.Second,
|
||||||
IdleTimeout: 60 * time.Second,
|
IdleTimeout: 60 * time.Second,
|
||||||
MaxHeaderBytes: 1 << 16, // 64KB
|
MaxHeaderBytes: 1 << 16, // 64KB
|
||||||
ErrorLog: log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
|
ErrorLog: log.New(&sdklogger.StdLoggerWrapper{Sender: logSender}, "", 0),
|
||||||
}
|
}
|
||||||
if certMgr != nil && s.binding.EnableHTTPS {
|
if certMgr != nil && s.binding.EnableHTTPS {
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
// Package kms provides built-in Key Management Services support
|
|
||||||
package kms
|
|
|
@ -16,8 +16,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ftpserverlog "github.com/fclairamb/go-log"
|
ftpserverlog "github.com/fclairamb/go-log"
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
|
||||||
|
sdklogger "github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -41,22 +44,25 @@ var (
|
||||||
rollingLogger *lumberjack.Logger
|
rollingLogger *lumberjack.Logger
|
||||||
)
|
)
|
||||||
|
|
||||||
// StdLoggerWrapper is a wrapper for standard logger compatibility
|
type logWrapper struct{}
|
||||||
type StdLoggerWrapper struct {
|
|
||||||
Sender string
|
// LogWithKeyVals logs at the specified level for the specified sender adding the specified key vals
|
||||||
|
func (l *logWrapper) LogWithKeyVals(level hclog.Level, sender, msg string, args ...interface{}) {
|
||||||
|
LogWithKeyVals(level, sender, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements the io.Writer interface. This is useful to set as a writer
|
// Log logs at the specified level for the specified sender
|
||||||
// for the standard library log.
|
func (l *logWrapper) Log(level hclog.Level, sender, format string, v ...interface{}) {
|
||||||
func (l *StdLoggerWrapper) Write(p []byte) (n int, err error) {
|
switch level {
|
||||||
n = len(p)
|
case hclog.Info:
|
||||||
if n > 0 && p[n-1] == '\n' {
|
Log(LevelInfo, sender, "", format, v...)
|
||||||
// Trim CR added by stdlog.
|
case hclog.Warn:
|
||||||
p = p[0 : n-1]
|
Log(LevelWarn, sender, "", format, v...)
|
||||||
|
case hclog.Error:
|
||||||
|
Log(LevelError, sender, "", format, v...)
|
||||||
|
default:
|
||||||
|
Log(LevelDebug, sender, "", format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
Log(LevelError, l.Sender, "", string(p))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LeveledLogger is a logger that accepts a message string and a variadic number of key-value pairs
|
// LeveledLogger is a logger that accepts a message string and a variadic number of key-value pairs
|
||||||
|
@ -176,6 +182,7 @@ func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logMaxAge
|
||||||
consoleLogger = zerolog.Nop()
|
consoleLogger = zerolog.Nop()
|
||||||
}
|
}
|
||||||
logger = logger.Level(level)
|
logger = logger.Level(level)
|
||||||
|
sdklogger.SetLogger(&logWrapper{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitStdErrLogger configures the logger to write to stderr
|
// InitStdErrLogger configures the logger to write to stderr
|
||||||
|
@ -184,6 +191,7 @@ func InitStdErrLogger(level zerolog.Level) {
|
||||||
output: os.Stderr,
|
output: os.Stderr,
|
||||||
}).Level(level)
|
}).Level(level)
|
||||||
consoleLogger = zerolog.Nop()
|
consoleLogger = zerolog.Nop()
|
||||||
|
sdklogger.SetLogger(&logWrapper{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableLogger disable the main logger.
|
// DisableLogger disable the main logger.
|
||||||
|
@ -191,6 +199,7 @@ func InitStdErrLogger(level zerolog.Level) {
|
||||||
func DisableLogger() {
|
func DisableLogger() {
|
||||||
logger = zerolog.Nop()
|
logger = zerolog.Nop()
|
||||||
rollingLogger = nil
|
rollingLogger = nil
|
||||||
|
sdklogger.DisableLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableConsoleLogger enables the console logger
|
// EnableConsoleLogger enables the console logger
|
||||||
|
@ -210,6 +219,24 @@ func RotateLogFile() error {
|
||||||
return errors.New("logging to file is disabled")
|
return errors.New("logging to file is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogWithKeyVals logs at the specified level for the specified sender adding the specified key vals
|
||||||
|
func LogWithKeyVals(level hclog.Level, sender, msg string, args ...interface{}) {
|
||||||
|
var ev *zerolog.Event
|
||||||
|
switch level {
|
||||||
|
case hclog.Info:
|
||||||
|
ev = logger.Info()
|
||||||
|
case hclog.Warn:
|
||||||
|
ev = logger.Warn()
|
||||||
|
case hclog.Error:
|
||||||
|
ev = logger.Error()
|
||||||
|
default:
|
||||||
|
ev = logger.Debug()
|
||||||
|
}
|
||||||
|
ev.Timestamp().Str("sender", sender)
|
||||||
|
addKeysAndValues(ev, args...)
|
||||||
|
ev.Msg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
// Log logs at the specified level for the specified sender
|
// Log logs at the specified level for the specified sender
|
||||||
func Log(level LogLevel, sender string, connectionID string, format string, v ...interface{}) {
|
func Log(level LogLevel, sender string, connectionID string, format string, v ...interface{}) {
|
||||||
var ev *zerolog.Event
|
var ev *zerolog.Event
|
||||||
|
@ -231,22 +258,22 @@ func Log(level LogLevel, sender string, connectionID string, format string, v ..
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug logs at debug level for the specified sender
|
// Debug logs at debug level for the specified sender
|
||||||
func Debug(sender string, connectionID string, format string, v ...interface{}) {
|
func Debug(sender, connectionID, format string, v ...interface{}) {
|
||||||
Log(LevelDebug, sender, connectionID, format, v...)
|
Log(LevelDebug, sender, connectionID, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info logs at info level for the specified sender
|
// Info logs at info level for the specified sender
|
||||||
func Info(sender string, connectionID string, format string, v ...interface{}) {
|
func Info(sender, connectionID, format string, v ...interface{}) {
|
||||||
Log(LevelInfo, sender, connectionID, format, v...)
|
Log(LevelInfo, sender, connectionID, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn logs at warn level for the specified sender
|
// Warn logs at warn level for the specified sender
|
||||||
func Warn(sender string, connectionID string, format string, v ...interface{}) {
|
func Warn(sender, connectionID, format string, v ...interface{}) {
|
||||||
Log(LevelWarn, sender, connectionID, format, v...)
|
Log(LevelWarn, sender, connectionID, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error logs at error level for the specified sender
|
// Error logs at error level for the specified sender
|
||||||
func Error(sender string, connectionID string, format string, v ...interface{}) {
|
func Error(sender, connectionID, format string, v ...interface{}) {
|
||||||
Log(LevelError, sender, connectionID, format, v...)
|
Log(LevelError, sender, connectionID, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
main.go
1
main.go
|
@ -11,7 +11,6 @@ import (
|
||||||
"go.uber.org/automaxprocs/maxprocs"
|
"go.uber.org/automaxprocs/maxprocs"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/cmd"
|
"github.com/drakkan/sftpgo/v2/cmd"
|
||||||
_ "github.com/drakkan/sftpgo/v2/kms"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -8,8 +8,6 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
sdkkms "github.com/drakkan/sftpgo/v2/sdk/kms"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -17,14 +15,14 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type builtinSecret struct {
|
type builtinSecret struct {
|
||||||
sdkkms.BaseSecret
|
BaseSecret
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sdkkms.RegisterSecretProvider(sdkkms.SchemeBuiltin, sdkkms.SecretStatusAES256GCM, newBuiltinSecret)
|
RegisterSecretProvider(SchemeBuiltin, SecretStatusAES256GCM, newBuiltinSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBuiltinSecret(base sdkkms.BaseSecret, url, masterKey string) sdkkms.SecretProvider {
|
func newBuiltinSecret(base BaseSecret, url, masterKey string) SecretProvider {
|
||||||
return &builtinSecret{
|
return &builtinSecret{
|
||||||
BaseSecret: base,
|
BaseSecret: base,
|
||||||
}
|
}
|
||||||
|
@ -35,7 +33,7 @@ func (s *builtinSecret) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *builtinSecret) IsEncrypted() bool {
|
func (s *builtinSecret) IsEncrypted() bool {
|
||||||
return s.Status == sdkkms.SecretStatusAES256GCM
|
return s.Status == SecretStatusAES256GCM
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *builtinSecret) deriveKey(key []byte) []byte {
|
func (s *builtinSecret) deriveKey(key []byte) []byte {
|
||||||
|
@ -51,10 +49,10 @@ func (s *builtinSecret) deriveKey(key []byte) []byte {
|
||||||
|
|
||||||
func (s *builtinSecret) Encrypt() error {
|
func (s *builtinSecret) Encrypt() error {
|
||||||
if s.Payload == "" {
|
if s.Payload == "" {
|
||||||
return sdkkms.ErrInvalidSecret
|
return ErrInvalidSecret
|
||||||
}
|
}
|
||||||
switch s.Status {
|
switch s.Status {
|
||||||
case sdkkms.SecretStatusPlain:
|
case SecretStatusPlain:
|
||||||
key := make([]byte, 32)
|
key := make([]byte, 32)
|
||||||
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -78,16 +76,16 @@ func (s *builtinSecret) Encrypt() error {
|
||||||
ciphertext := gcm.Seal(nonce, nonce, []byte(s.Payload), aad)
|
ciphertext := gcm.Seal(nonce, nonce, []byte(s.Payload), aad)
|
||||||
s.Key = hex.EncodeToString(key)
|
s.Key = hex.EncodeToString(key)
|
||||||
s.Payload = hex.EncodeToString(ciphertext)
|
s.Payload = hex.EncodeToString(ciphertext)
|
||||||
s.Status = sdkkms.SecretStatusAES256GCM
|
s.Status = SecretStatusAES256GCM
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return sdkkms.ErrWrongSecretStatus
|
return ErrWrongSecretStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *builtinSecret) Decrypt() error {
|
func (s *builtinSecret) Decrypt() error {
|
||||||
switch s.Status {
|
switch s.Status {
|
||||||
case sdkkms.SecretStatusAES256GCM:
|
case SecretStatusAES256GCM:
|
||||||
encrypted, err := hex.DecodeString(s.Payload)
|
encrypted, err := hex.DecodeString(s.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -117,18 +115,18 @@ func (s *builtinSecret) Decrypt() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.Status = sdkkms.SecretStatusPlain
|
s.Status = SecretStatusPlain
|
||||||
s.Payload = string(plaintext)
|
s.Payload = string(plaintext)
|
||||||
s.Key = ""
|
s.Key = ""
|
||||||
s.AdditionalData = ""
|
s.AdditionalData = ""
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return sdkkms.ErrWrongSecretStatus
|
return ErrWrongSecretStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *builtinSecret) Clone() sdkkms.SecretProvider {
|
func (s *builtinSecret) Clone() SecretProvider {
|
||||||
baseSecret := sdkkms.BaseSecret{
|
baseSecret := BaseSecret{
|
||||||
Status: s.Status,
|
Status: s.Status,
|
||||||
Payload: s.Payload,
|
Payload: s.Payload,
|
||||||
Key: s.Key,
|
Key: s.Key,
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/util"
|
"github.com/drakkan/sftpgo/v2/sdk/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SecretProvider defines the interface for a KMS secrets provider
|
// SecretProvider defines the interface for a KMS secrets provider
|
||||||
|
@ -141,7 +141,7 @@ func (c *Configuration) Initialize() error {
|
||||||
config.Secrets.URL = SchemeLocal + "://"
|
config.Secrets.URL = SchemeLocal + "://"
|
||||||
}
|
}
|
||||||
for k, v := range secretProviders {
|
for k, v := range secretProviders {
|
||||||
logger.Debug(logSender, "", "secret provider registered for scheme: %#v, encrypted status: %#v",
|
logger.Debug(logSender, "secret provider registered for scheme: %#v, encrypted status: %#v",
|
||||||
k, v.encryptedStatus)
|
k, v.encryptedStatus)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -166,8 +166,8 @@ func (c *Configuration) getSecretProvider(base BaseSecret) SecretProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we assume that SchemeLocal is always registered
|
// we assume that SchemeLocal is always registered
|
||||||
logger.Warn(logSender, "", "no secret provider registered for URL %v, fallback to local provider", c.Secrets.URL)
|
logger.Warn(logSender, "no secret provider registered for URL %v, fallback to local provider", c.Secrets.URL)
|
||||||
return secretProviders[SchemeLocal].newFn(base, c.Secrets.URL, c.Secrets.masterKey)
|
return NewLocalSecret(base, c.Secrets.URL, c.Secrets.masterKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret defines the struct used to store confidential data
|
// Secret defines the struct used to store confidential data
|
||||||
|
@ -217,7 +217,7 @@ func (s *Secret) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Debug(logSender, "", "no provider registered for status %#v", baseSecret.Status)
|
logger.Debug(logSender, "no provider registered for status %#v", baseSecret.Status)
|
||||||
|
|
||||||
return ErrInvalidSecret
|
return ErrInvalidSecret
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,21 +9,19 @@ import (
|
||||||
|
|
||||||
"gocloud.dev/secrets/localsecrets"
|
"gocloud.dev/secrets/localsecrets"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
|
|
||||||
sdkkms "github.com/drakkan/sftpgo/v2/sdk/kms"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sdkkms.RegisterSecretProvider(sdkkms.SchemeLocal, sdkkms.SecretStatusSecretBox, NewLocalSecret)
|
RegisterSecretProvider(SchemeLocal, SecretStatusSecretBox, NewLocalSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
type localSecret struct {
|
type localSecret struct {
|
||||||
sdkkms.BaseSecret
|
BaseSecret
|
||||||
masterKey string
|
masterKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLocalSecret returns a SecretProvider that use a locally provided symmetric key
|
// NewLocalSecret returns a SecretProvider that use a locally provided symmetric key
|
||||||
func NewLocalSecret(base sdkkms.BaseSecret, url, masterKey string) sdkkms.SecretProvider {
|
func NewLocalSecret(base BaseSecret, url, masterKey string) SecretProvider {
|
||||||
return &localSecret{
|
return &localSecret{
|
||||||
BaseSecret: base,
|
BaseSecret: base,
|
||||||
masterKey: masterKey,
|
masterKey: masterKey,
|
||||||
|
@ -35,15 +33,15 @@ func (s *localSecret) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *localSecret) IsEncrypted() bool {
|
func (s *localSecret) IsEncrypted() bool {
|
||||||
return s.Status == sdkkms.SecretStatusSecretBox
|
return s.Status == SecretStatusSecretBox
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *localSecret) Encrypt() error {
|
func (s *localSecret) Encrypt() error {
|
||||||
if s.Status != sdkkms.SecretStatusPlain {
|
if s.Status != SecretStatusPlain {
|
||||||
return sdkkms.ErrWrongSecretStatus
|
return ErrWrongSecretStatus
|
||||||
}
|
}
|
||||||
if s.Payload == "" {
|
if s.Payload == "" {
|
||||||
return sdkkms.ErrInvalidSecret
|
return ErrInvalidSecret
|
||||||
}
|
}
|
||||||
secretKey, err := localsecrets.NewRandomKey()
|
secretKey, err := localsecrets.NewRandomKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -62,14 +60,14 @@ func (s *localSecret) Encrypt() error {
|
||||||
}
|
}
|
||||||
s.Key = hex.EncodeToString(secretKey[:])
|
s.Key = hex.EncodeToString(secretKey[:])
|
||||||
s.Payload = base64.StdEncoding.EncodeToString(ciphertext)
|
s.Payload = base64.StdEncoding.EncodeToString(ciphertext)
|
||||||
s.Status = sdkkms.SecretStatusSecretBox
|
s.Status = SecretStatusSecretBox
|
||||||
s.Mode = s.getEncryptionMode()
|
s.Mode = s.getEncryptionMode()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *localSecret) Decrypt() error {
|
func (s *localSecret) Decrypt() error {
|
||||||
if !s.IsEncrypted() {
|
if !s.IsEncrypted() {
|
||||||
return sdkkms.ErrWrongSecretStatus
|
return ErrWrongSecretStatus
|
||||||
}
|
}
|
||||||
encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
|
encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -90,7 +88,7 @@ func (s *localSecret) Decrypt() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.Status = sdkkms.SecretStatusPlain
|
s.Status = SecretStatusPlain
|
||||||
s.Payload = string(plaintext)
|
s.Payload = string(plaintext)
|
||||||
s.Key = ""
|
s.Key = ""
|
||||||
s.AdditionalData = ""
|
s.AdditionalData = ""
|
||||||
|
@ -131,8 +129,8 @@ func (s *localSecret) getEncryptionMode() int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *localSecret) Clone() sdkkms.SecretProvider {
|
func (s *localSecret) Clone() SecretProvider {
|
||||||
baseSecret := sdkkms.BaseSecret{
|
baseSecret := BaseSecret{
|
||||||
Status: s.Status,
|
Status: s.Status,
|
||||||
Payload: s.Payload,
|
Payload: s.Payload,
|
||||||
Key: s.Key,
|
Key: s.Key,
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HCLogAdapter is an adapter for hclog.Logger
|
// HCLogAdapter is an adapter for hclog.Logger
|
||||||
|
@ -15,20 +14,7 @@ type HCLogAdapter struct {
|
||||||
|
|
||||||
// Log emits a message and key/value pairs at a provided log level
|
// Log emits a message and key/value pairs at a provided log level
|
||||||
func (l *HCLogAdapter) Log(level hclog.Level, msg string, args ...interface{}) {
|
func (l *HCLogAdapter) Log(level hclog.Level, msg string, args ...interface{}) {
|
||||||
var ev *zerolog.Event
|
logger.LogWithKeyVals(level, l.Name(), msg, args...)
|
||||||
switch level {
|
|
||||||
case hclog.Info:
|
|
||||||
ev = logger.Info()
|
|
||||||
case hclog.Warn:
|
|
||||||
ev = logger.Warn()
|
|
||||||
case hclog.Error:
|
|
||||||
ev = logger.Error()
|
|
||||||
default:
|
|
||||||
ev = logger.Debug()
|
|
||||||
}
|
|
||||||
ev.Timestamp().Str("sender", l.Name())
|
|
||||||
addKeysAndValues(ev, args...)
|
|
||||||
ev.Msg(msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trace emits a message and key/value pairs at the TRACE level
|
// Trace emits a message and key/value pairs at the TRACE level
|
||||||
|
@ -75,3 +61,21 @@ func (l *HCLogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Lo
|
||||||
func (l *HCLogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
|
func (l *HCLogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
|
||||||
return &StdLoggerWrapper{Sender: l.Name()}
|
return &StdLoggerWrapper{Sender: l.Name()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StdLoggerWrapper is a wrapper for standard logger compatibility
|
||||||
|
type StdLoggerWrapper struct {
|
||||||
|
Sender string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements the io.Writer interface. This is useful to set as a writer
|
||||||
|
// for the standard library log.
|
||||||
|
func (l *StdLoggerWrapper) Write(p []byte) (n int, err error) {
|
||||||
|
n = len(p)
|
||||||
|
if n > 0 && p[n-1] == '\n' {
|
||||||
|
// Trim CR added by stdlog.
|
||||||
|
p = p[0 : n-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log(hclog.Error, l.Sender, "", string(p))
|
||||||
|
return
|
||||||
|
}
|
56
sdk/logger/logger.go
Normal file
56
sdk/logger/logger.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// Package logger provides logging capabilities.
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import "github.com/hashicorp/go-hclog"
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger Logger
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
DisableLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger interface
|
||||||
|
type Logger interface {
|
||||||
|
// LogWithKeyVals logs at the specified level for the specified sender adding the specified key vals
|
||||||
|
LogWithKeyVals(level hclog.Level, sender, msg string, args ...interface{})
|
||||||
|
// Log logs at the specified level for the specified sender
|
||||||
|
Log(level hclog.Level, sender, format string, v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger sets the specified logger
|
||||||
|
func SetLogger(l Logger) {
|
||||||
|
logger = l
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableLogger disables logging
|
||||||
|
func DisableLogger() {
|
||||||
|
logger = &noLogger{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type noLogger struct{}
|
||||||
|
|
||||||
|
func (*noLogger) LogWithKeyVals(level hclog.Level, sender, msg string, args ...interface{}) {}
|
||||||
|
|
||||||
|
func (*noLogger) Log(level hclog.Level, sender, format string, v ...interface{}) {}
|
||||||
|
|
||||||
|
// Debug logs at debug level for the specified sender
|
||||||
|
func Debug(sender, format string, v ...interface{}) {
|
||||||
|
logger.Log(hclog.Debug, sender, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs at info level for the specified sender
|
||||||
|
func Info(sender, format string, v ...interface{}) {
|
||||||
|
logger.Log(hclog.Info, sender, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs at warn level for the specified sender
|
||||||
|
func Warn(sender, format string, v ...interface{}) {
|
||||||
|
logger.Log(hclog.Warn, sender, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs at error level for the specified sender
|
||||||
|
func Error(sender, format string, v ...interface{}) {
|
||||||
|
logger.Log(hclog.Error, sender, format, v...)
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/plugin/auth"
|
"github.com/drakkan/sftpgo/v2/sdk/plugin/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,10 @@ import (
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
||||||
|
"github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms"
|
kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms"
|
||||||
"github.com/drakkan/sftpgo/v2/util"
|
"github.com/drakkan/sftpgo/v2/sdk/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/plugin/metadata"
|
"github.com/drakkan/sftpgo/v2/sdk/plugin/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/plugin/notifier"
|
"github.com/drakkan/sftpgo/v2/sdk/plugin/notifier"
|
||||||
"github.com/drakkan/sftpgo/v2/util"
|
"github.com/drakkan/sftpgo/v2/sdk/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NotifierConfig defines configuration parameters for notifiers plugins
|
// NotifierConfig defines configuration parameters for notifiers plugins
|
||||||
|
|
|
@ -11,14 +11,14 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
||||||
|
"github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/plugin/auth"
|
"github.com/drakkan/sftpgo/v2/sdk/plugin/auth"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher"
|
"github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher"
|
||||||
kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms"
|
kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/plugin/metadata"
|
"github.com/drakkan/sftpgo/v2/sdk/plugin/metadata"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/plugin/notifier"
|
"github.com/drakkan/sftpgo/v2/sdk/plugin/notifier"
|
||||||
"github.com/drakkan/sftpgo/v2/util"
|
"github.com/drakkan/sftpgo/v2/sdk/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -100,7 +100,7 @@ type Manager struct {
|
||||||
|
|
||||||
// Initialize initializes the configured plugins
|
// Initialize initializes the configured plugins
|
||||||
func Initialize(configs []Config, logVerbose bool) error {
|
func Initialize(configs []Config, logVerbose bool) error {
|
||||||
logger.Debug(logSender, "", "initialize")
|
logger.Debug(logSender, "initialize")
|
||||||
Handler = Manager{
|
Handler = Manager{
|
||||||
Configs: configs,
|
Configs: configs,
|
||||||
done: make(chan bool),
|
done: make(chan bool),
|
||||||
|
@ -135,7 +135,7 @@ func Initialize(configs []Config, logVerbose bool) error {
|
||||||
kmsID++
|
kmsID++
|
||||||
kms.RegisterSecretProvider(config.KMSOptions.Scheme, config.KMSOptions.EncryptedStatus,
|
kms.RegisterSecretProvider(config.KMSOptions.Scheme, config.KMSOptions.EncryptedStatus,
|
||||||
Handler.Configs[idx].newKMSPluginSecretProvider)
|
Handler.Configs[idx].newKMSPluginSecretProvider)
|
||||||
logger.Debug(logSender, "", "registered secret provider for scheme: %v, encrypted status: %v",
|
logger.Debug(logSender, "registered secret provider for scheme: %v, encrypted status: %v",
|
||||||
config.KMSOptions.Scheme, config.KMSOptions.EncryptedStatus)
|
config.KMSOptions.Scheme, config.KMSOptions.EncryptedStatus)
|
||||||
case auth.PluginName:
|
case auth.PluginName:
|
||||||
plugin, err := newAuthPlugin(config)
|
plugin, err := newAuthPlugin(config)
|
||||||
|
@ -357,7 +357,7 @@ func (m *Manager) Authenticate(username, password, ip, protocol string, pkey str
|
||||||
case AuthScopeTLSCertificate:
|
case AuthScopeTLSCertificate:
|
||||||
cert, err := util.EncodeTLSCertToPem(tlsCert)
|
cert, err := util.EncodeTLSCertToPem(tlsCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(logSender, "", "unable to encode tls certificate to pem: %v", err)
|
logger.Error(logSender, "unable to encode tls certificate to pem: %v", err)
|
||||||
return nil, fmt.Errorf("unable to encode tls cert to pem: %w", err)
|
return nil, fmt.Errorf("unable to encode tls cert to pem: %w", err)
|
||||||
}
|
}
|
||||||
return m.checkUserAndTLSCert(username, cert, ip, protocol, userAsJSON)
|
return m.checkUserAndTLSCert(username, cert, ip, protocol, userAsJSON)
|
||||||
|
@ -520,10 +520,10 @@ func (m *Manager) restartNotifierPlugin(config Config, idx int) {
|
||||||
if atomic.LoadInt32(&m.closed) == 1 {
|
if atomic.LoadInt32(&m.closed) == 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info(logSender, "", "try to restart crashed notifier plugin %#v, idx: %v", config.Cmd, idx)
|
logger.Info(logSender, "try to restart crashed notifier plugin %#v, idx: %v", config.Cmd, idx)
|
||||||
plugin, err := newNotifierPlugin(config)
|
plugin, err := newNotifierPlugin(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(logSender, "", "unable to restart notifier plugin %#v, err: %v", config.Cmd, err)
|
logger.Error(logSender, "unable to restart notifier plugin %#v, err: %v", config.Cmd, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,10 +538,10 @@ func (m *Manager) restartKMSPlugin(config Config, idx int) {
|
||||||
if atomic.LoadInt32(&m.closed) == 1 {
|
if atomic.LoadInt32(&m.closed) == 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info(logSender, "", "try to restart crashed kms plugin %#v, idx: %v", config.Cmd, idx)
|
logger.Info(logSender, "try to restart crashed kms plugin %#v, idx: %v", config.Cmd, idx)
|
||||||
plugin, err := newKMSPlugin(config)
|
plugin, err := newKMSPlugin(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(logSender, "", "unable to restart kms plugin %#v, err: %v", config.Cmd, err)
|
logger.Error(logSender, "unable to restart kms plugin %#v, err: %v", config.Cmd, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,10 +554,10 @@ func (m *Manager) restartAuthPlugin(config Config, idx int) {
|
||||||
if atomic.LoadInt32(&m.closed) == 1 {
|
if atomic.LoadInt32(&m.closed) == 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info(logSender, "", "try to restart crashed auth plugin %#v, idx: %v", config.Cmd, idx)
|
logger.Info(logSender, "try to restart crashed auth plugin %#v, idx: %v", config.Cmd, idx)
|
||||||
plugin, err := newAuthPlugin(config)
|
plugin, err := newAuthPlugin(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(logSender, "", "unable to restart auth plugin %#v, err: %v", config.Cmd, err)
|
logger.Error(logSender, "unable to restart auth plugin %#v, err: %v", config.Cmd, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,10 +570,10 @@ func (m *Manager) restartSearcherPlugin(config Config) {
|
||||||
if atomic.LoadInt32(&m.closed) == 1 {
|
if atomic.LoadInt32(&m.closed) == 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info(logSender, "", "try to restart crashed searcher plugin %#v", config.Cmd)
|
logger.Info(logSender, "try to restart crashed searcher plugin %#v", config.Cmd)
|
||||||
plugin, err := newSearcherPlugin(config)
|
plugin, err := newSearcherPlugin(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(logSender, "", "unable to restart searcher plugin %#v, err: %v", config.Cmd, err)
|
logger.Error(logSender, "unable to restart searcher plugin %#v, err: %v", config.Cmd, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,10 +586,10 @@ func (m *Manager) restartMetadaterPlugin(config Config) {
|
||||||
if atomic.LoadInt32(&m.closed) == 1 {
|
if atomic.LoadInt32(&m.closed) == 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info(logSender, "", "try to restart crashed metadater plugin %#v", config.Cmd)
|
logger.Info(logSender, "try to restart crashed metadater plugin %#v", config.Cmd)
|
||||||
plugin, err := newMetadaterPlugin(config)
|
plugin, err := newMetadaterPlugin(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(logSender, "", "unable to restart metadater plugin %#v, err: %v", config.Cmd, err)
|
logger.Error(logSender, "unable to restart metadater plugin %#v, err: %v", config.Cmd, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,40 +600,40 @@ func (m *Manager) restartMetadaterPlugin(config Config) {
|
||||||
|
|
||||||
// Cleanup releases all the active plugins
|
// Cleanup releases all the active plugins
|
||||||
func (m *Manager) Cleanup() {
|
func (m *Manager) Cleanup() {
|
||||||
logger.Debug(logSender, "", "cleanup")
|
logger.Debug(logSender, "cleanup")
|
||||||
atomic.StoreInt32(&m.closed, 1)
|
atomic.StoreInt32(&m.closed, 1)
|
||||||
close(m.done)
|
close(m.done)
|
||||||
m.notifLock.Lock()
|
m.notifLock.Lock()
|
||||||
for _, n := range m.notifiers {
|
for _, n := range m.notifiers {
|
||||||
logger.Debug(logSender, "", "cleanup notifier plugin %v", n.config.Cmd)
|
logger.Debug(logSender, "cleanup notifier plugin %v", n.config.Cmd)
|
||||||
n.cleanup()
|
n.cleanup()
|
||||||
}
|
}
|
||||||
m.notifLock.Unlock()
|
m.notifLock.Unlock()
|
||||||
|
|
||||||
m.kmsLock.Lock()
|
m.kmsLock.Lock()
|
||||||
for _, k := range m.kms {
|
for _, k := range m.kms {
|
||||||
logger.Debug(logSender, "", "cleanup kms plugin %v", k.config.Cmd)
|
logger.Debug(logSender, "cleanup kms plugin %v", k.config.Cmd)
|
||||||
k.cleanup()
|
k.cleanup()
|
||||||
}
|
}
|
||||||
m.kmsLock.Unlock()
|
m.kmsLock.Unlock()
|
||||||
|
|
||||||
m.authLock.Lock()
|
m.authLock.Lock()
|
||||||
for _, a := range m.auths {
|
for _, a := range m.auths {
|
||||||
logger.Debug(logSender, "", "cleanup auth plugin %v", a.config.Cmd)
|
logger.Debug(logSender, "cleanup auth plugin %v", a.config.Cmd)
|
||||||
a.cleanup()
|
a.cleanup()
|
||||||
}
|
}
|
||||||
m.authLock.Unlock()
|
m.authLock.Unlock()
|
||||||
|
|
||||||
if m.hasSearcher {
|
if m.hasSearcher {
|
||||||
m.searcherLock.Lock()
|
m.searcherLock.Lock()
|
||||||
logger.Debug(logSender, "", "cleanup searcher plugin %v", m.searcher.config.Cmd)
|
logger.Debug(logSender, "cleanup searcher plugin %v", m.searcher.config.Cmd)
|
||||||
m.searcher.cleanup()
|
m.searcher.cleanup()
|
||||||
m.searcherLock.Unlock()
|
m.searcherLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.hasMetadater {
|
if m.hasMetadater {
|
||||||
m.metadaterLock.Lock()
|
m.metadaterLock.Lock()
|
||||||
logger.Debug(logSender, "", "cleanup metadater plugin %v", m.metadater.config.Cmd)
|
logger.Debug(logSender, "cleanup metadater plugin %v", m.metadater.config.Cmd)
|
||||||
m.metadater.cleanup()
|
m.metadater.cleanup()
|
||||||
m.metadaterLock.Unlock()
|
m.metadaterLock.Unlock()
|
||||||
}
|
}
|
||||||
|
@ -648,14 +648,14 @@ func setLogLevel(logVerbose bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func startCheckTicker() {
|
func startCheckTicker() {
|
||||||
logger.Debug(logSender, "", "start plugins checker")
|
logger.Debug(logSender, "start plugins checker")
|
||||||
checker := time.NewTicker(30 * time.Second)
|
checker := time.NewTicker(30 * time.Second)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-Handler.done:
|
case <-Handler.done:
|
||||||
logger.Debug(logSender, "", "handler done, stop plugins checker")
|
logger.Debug(logSender, "handler done, stop plugins checker")
|
||||||
checker.Stop()
|
checker.Stop()
|
||||||
return
|
return
|
||||||
case <-checker.C:
|
case <-checker.C:
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher"
|
"github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package plugin
|
||||||
import (
|
import (
|
||||||
"github.com/shirou/gopsutil/v3/process"
|
"github.com/shirou/gopsutil/v3/process"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func killProcess(processPath string) {
|
func killProcess(processPath string) {
|
||||||
|
@ -16,10 +16,10 @@ func killProcess(processPath string) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if cmdLine == processPath {
|
if cmdLine == processPath {
|
||||||
err = p.Kill()
|
err = p.Kill()
|
||||||
logger.Debug(logSender, "", "killed process %v, pid %v, err %v", cmdLine, p.Pid, err)
|
logger.Debug(logSender, "killed process %v, pid %v, err %v", cmdLine, p.Pid, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Debug(logSender, "", "no match for plugin process %v", processPath)
|
logger.Debug(logSender, "no match for plugin process %v", processPath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
||||||
"github.com/drakkan/sftpgo/v2/util"
|
"github.com/drakkan/sftpgo/v2/sdk/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Web Client/user REST API restrictions
|
// Web Client/user REST API restrictions
|
||||||
|
|
31
sdk/util/util.go
Normal file
31
sdk/util/util.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Package util provides some common utility methods
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsStringInSlice searches a string in a slice and returns true if the string is found
|
||||||
|
func IsStringInSlice(obj string, list []string) bool {
|
||||||
|
for i := 0; i < len(list); i++ {
|
||||||
|
if list[i] == obj {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeTLSCertToPem returns the specified certificate PEM encoded.
|
||||||
|
// This can be verified using openssl x509 -in cert.crt -text -noout
|
||||||
|
func EncodeTLSCertToPem(tlsCert *x509.Certificate) (string, error) {
|
||||||
|
if len(tlsCert.Raw) == 0 {
|
||||||
|
return "", errors.New("invalid x509 certificate, no der contents")
|
||||||
|
}
|
||||||
|
publicKeyBlock := pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: tlsCert.Raw,
|
||||||
|
}
|
||||||
|
return string(pem.EncodeToMemory(&publicKeyBlock)), nil
|
||||||
|
}
|
|
@ -42,7 +42,6 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/config"
|
"github.com/drakkan/sftpgo/v2/config"
|
||||||
"github.com/drakkan/sftpgo/v2/dataprovider"
|
"github.com/drakkan/sftpgo/v2/dataprovider"
|
||||||
"github.com/drakkan/sftpgo/v2/httpdtest"
|
"github.com/drakkan/sftpgo/v2/httpdtest"
|
||||||
_ "github.com/drakkan/sftpgo/v2/kms"
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/mfa"
|
"github.com/drakkan/sftpgo/v2/mfa"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk"
|
"github.com/drakkan/sftpgo/v2/sdk"
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/common"
|
"github.com/drakkan/sftpgo/v2/common"
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/logger"
|
||||||
|
sdklogger "github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/util"
|
"github.com/drakkan/sftpgo/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ func (c Conf) Initialize(configDir string) error {
|
||||||
WriteTimeout: 60 * time.Second,
|
WriteTimeout: 60 * time.Second,
|
||||||
IdleTimeout: 60 * time.Second,
|
IdleTimeout: 60 * time.Second,
|
||||||
MaxHeaderBytes: 1 << 14, // 16KB
|
MaxHeaderBytes: 1 << 14, // 16KB
|
||||||
ErrorLog: log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
|
ErrorLog: log.New(&sdklogger.StdLoggerWrapper{Sender: logSender}, "", 0),
|
||||||
}
|
}
|
||||||
if certificateFile != "" && certificateKeyFile != "" {
|
if certificateFile != "" && certificateKeyFile != "" {
|
||||||
certMgr, err = common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
|
certMgr, err = common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
|
||||||
|
|
|
@ -126,7 +126,7 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
|
||||||
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||||
github.com/aws/aws-sdk-go v1.42.25/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
|
github.com/aws/aws-sdk-go v1.42.26/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
|
github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
|
github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
|
||||||
|
@ -229,7 +229,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.8-0.20220103230436-7dbe9a0bd10f/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/jwtauth/v5 v5.0.2/go.mod h1:TeA7vmPe3uYThvHw8O8W13HOOpOd4MTgToxL41gZyjs=
|
github.com/go-chi/jwtauth/v5 v5.0.2/go.mod h1:TeA7vmPe3uYThvHw8O8W13HOOpOd4MTgToxL41gZyjs=
|
||||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
|
14
util/util.go
14
util/util.go
|
@ -11,7 +11,6 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
@ -423,19 +422,6 @@ func GetTLSCiphersFromNames(cipherNames []string) []uint16 {
|
||||||
return ciphers
|
return ciphers
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeTLSCertToPem returns the specified certificate PEM encoded.
|
|
||||||
// This can be verified using openssl x509 -in cert.crt -text -noout
|
|
||||||
func EncodeTLSCertToPem(tlsCert *x509.Certificate) (string, error) {
|
|
||||||
if len(tlsCert.Raw) == 0 {
|
|
||||||
return "", errors.New("invalid x509 certificate, no der contents")
|
|
||||||
}
|
|
||||||
publicKeyBlock := pem.Block{
|
|
||||||
Type: "CERTIFICATE",
|
|
||||||
Bytes: tlsCert.Raw,
|
|
||||||
}
|
|
||||||
return string(pem.EncodeToMemory(&publicKeyBlock)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckTCP4Port quits the app if bind on the given IPv4 port fails.
|
// CheckTCP4Port quits the app if bind on the given IPv4 port fails.
|
||||||
// This is a ugly hack to avoid to bind on an already used port.
|
// This is a ugly hack to avoid to bind on an already used port.
|
||||||
// It is required on Windows only. Upstream does not consider this
|
// It is required on Windows only. Upstream does not consider this
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/dataprovider"
|
"github.com/drakkan/sftpgo/v2/dataprovider"
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/metric"
|
"github.com/drakkan/sftpgo/v2/metric"
|
||||||
|
sdklogger "github.com/drakkan/sftpgo/v2/sdk/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/util"
|
"github.com/drakkan/sftpgo/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ func (s *webDavServer) listenAndServe(compressor *middleware.Compressor) error {
|
||||||
WriteTimeout: 60 * time.Second,
|
WriteTimeout: 60 * time.Second,
|
||||||
IdleTimeout: 60 * time.Second,
|
IdleTimeout: 60 * time.Second,
|
||||||
MaxHeaderBytes: 1 << 16, // 64KB
|
MaxHeaderBytes: 1 << 16, // 64KB
|
||||||
ErrorLog: log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
|
ErrorLog: log.New(&sdklogger.StdLoggerWrapper{Sender: logSender}, "", 0),
|
||||||
}
|
}
|
||||||
if s.config.Cors.Enabled {
|
if s.config.Cors.Enabled {
|
||||||
c := cors.New(cors.Options{
|
c := cors.New(cors.Options{
|
||||||
|
|
|
@ -31,7 +31,6 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/dataprovider"
|
"github.com/drakkan/sftpgo/v2/dataprovider"
|
||||||
"github.com/drakkan/sftpgo/v2/httpclient"
|
"github.com/drakkan/sftpgo/v2/httpclient"
|
||||||
"github.com/drakkan/sftpgo/v2/httpdtest"
|
"github.com/drakkan/sftpgo/v2/httpdtest"
|
||||||
_ "github.com/drakkan/sftpgo/v2/kms"
|
|
||||||
"github.com/drakkan/sftpgo/v2/logger"
|
"github.com/drakkan/sftpgo/v2/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk"
|
"github.com/drakkan/sftpgo/v2/sdk"
|
||||||
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
"github.com/drakkan/sftpgo/v2/sdk/kms"
|
||||||
|
|
Loading…
Reference in a new issue