kms: improve modularity
This commit is contained in:
parent
e1a2451c22
commit
776dffcf12
22 changed files with 394 additions and 357 deletions
6
go.mod
6
go.mod
|
@ -8,7 +8,7 @@ require (
|
||||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
||||||
github.com/StackExchange/wmi v1.2.0 // indirect
|
github.com/StackExchange/wmi v1.2.0 // indirect
|
||||||
github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
|
github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
|
||||||
github.com/aws/aws-sdk-go v1.39.4
|
github.com/aws/aws-sdk-go v1.39.5
|
||||||
github.com/cockroachdb/cockroach-go/v2 v2.1.1
|
github.com/cockroachdb/cockroach-go/v2 v2.1.1
|
||||||
github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b
|
github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b
|
||||||
github.com/fatih/color v1.12.0 // indirect
|
github.com/fatih/color v1.12.0 // indirect
|
||||||
|
@ -30,7 +30,7 @@ require (
|
||||||
github.com/klauspost/compress v1.13.1
|
github.com/klauspost/compress v1.13.1
|
||||||
github.com/klauspost/cpuid/v2 v2.0.8 // indirect
|
github.com/klauspost/cpuid/v2 v2.0.8 // indirect
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
||||||
github.com/lestrrat-go/jwx v1.2.1
|
github.com/lestrrat-go/jwx v1.2.2
|
||||||
github.com/lib/pq v1.10.2
|
github.com/lib/pq v1.10.2
|
||||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.7
|
github.com/mattn/go-sqlite3 v1.14.7
|
||||||
|
@ -63,7 +63,7 @@ require (
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
|
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
|
||||||
google.golang.org/api v0.50.0
|
google.golang.org/api v0.50.0
|
||||||
google.golang.org/genproto v0.0.0-20210708141623-e76da96a951f // indirect
|
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a // indirect
|
||||||
google.golang.org/grpc v1.39.0
|
google.golang.org/grpc v1.39.0
|
||||||
google.golang.org/protobuf v1.27.1
|
google.golang.org/protobuf v1.27.1
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -131,8 +131,8 @@ github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
|
||||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||||
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.39.4 h1:nXBChUaG5cinrl3yg4/rUyssOOLH/ohk4S9K03kJirE=
|
github.com/aws/aws-sdk-go v1.39.5 h1:yoJEE1NJxbpZ3CtPxvOSFJ9ByxiXmBTKk8J+XU5ldtg=
|
||||||
github.com/aws/aws-sdk-go v1.39.4/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
github.com/aws/aws-sdk-go v1.39.5/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
@ -589,8 +589,8 @@ github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++
|
||||||
github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
|
github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
|
||||||
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||||
github.com/lestrrat-go/jwx v1.1.6/go.mod h1:c+R8G7qsaFNmTzYjU98A+sMh8Bo/MJqO9GnpqR+X024=
|
github.com/lestrrat-go/jwx v1.1.6/go.mod h1:c+R8G7qsaFNmTzYjU98A+sMh8Bo/MJqO9GnpqR+X024=
|
||||||
github.com/lestrrat-go/jwx v1.2.1 h1:WJ/3tiPUz1wV24KiwMEanbENwHnYub9UqzCbQ82mv9c=
|
github.com/lestrrat-go/jwx v1.2.2 h1:sH9GeolQn9s3JyNbeEOXXPTko2fD4BwAMWXLYvcEl2k=
|
||||||
github.com/lestrrat-go/jwx v1.2.1/go.mod h1:Tg2uP7bpxEHUDtuWjap/PxroJ4okxGzkQznXiG+a5Dc=
|
github.com/lestrrat-go/jwx v1.2.2/go.mod h1:Tg2uP7bpxEHUDtuWjap/PxroJ4okxGzkQznXiG+a5Dc=
|
||||||
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
|
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
|
||||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
|
@ -1244,8 +1244,8 @@ google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxH
|
||||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||||
google.golang.org/genproto v0.0.0-20210624174822-c5cf32407d0a/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
google.golang.org/genproto v0.0.0-20210624174822-c5cf32407d0a/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||||
google.golang.org/genproto v0.0.0-20210708141623-e76da96a951f h1:khwpF3oSk7GIab/7DDMDyE8cPQEO6FAfOcWHIRAhO20=
|
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a h1:89EorDSnBRFywcvGsJvpxw2IsiDMI+DeM7iZOaunfHs=
|
||||||
google.golang.org/genproto v0.0.0-20210708141623-e76da96a951f/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
|
|
@ -302,7 +302,7 @@ func (c *Conf) getRedacted() Conf {
|
||||||
|
|
||||||
// Initialize configures and starts the HTTP server
|
// Initialize configures and starts the HTTP server
|
||||||
func (c *Conf) Initialize(configDir string) error {
|
func (c *Conf) Initialize(configDir string) error {
|
||||||
logger.Debug(logSender, "", "initializing HTTP server with config %v", c.getRedacted())
|
logger.Debug(logSender, "", "initializing HTTP server with config %+v", c.getRedacted())
|
||||||
backupsPath = getConfigPath(c.BackupsPath, configDir)
|
backupsPath = getConfigPath(c.BackupsPath, configDir)
|
||||||
staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
|
staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
|
||||||
templatesPath := getConfigPath(c.TemplatesPath, configDir)
|
templatesPath := getConfigPath(c.TemplatesPath, configDir)
|
||||||
|
|
51
kms/aws.go
51
kms/aws.go
|
@ -1,51 +0,0 @@
|
||||||
// +build !noawskms
|
|
||||||
|
|
||||||
package kms
|
|
||||||
|
|
||||||
import (
|
|
||||||
// we import awskms here to be able to disable AWS KMS support using a build tag
|
|
||||||
_ "gocloud.dev/secrets/awskms"
|
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
type awsSecret struct {
|
|
||||||
baseGCloudSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
version.AddFeature("+awskms")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAWSSecret(base baseSecret, url, masterKey string) SecretProvider {
|
|
||||||
return &awsSecret{
|
|
||||||
baseGCloudSecret{
|
|
||||||
baseSecret: base,
|
|
||||||
url: url,
|
|
||||||
masterKey: masterKey,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *awsSecret) Name() string {
|
|
||||||
return awsProviderName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *awsSecret) IsEncrypted() bool {
|
|
||||||
return s.Status == SecretStatusAWS
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *awsSecret) Encrypt() error {
|
|
||||||
if err := s.baseGCloudSecret.Encrypt(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Status = SecretStatusAWS
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *awsSecret) Decrypt() error {
|
|
||||||
if !s.IsEncrypted() {
|
|
||||||
return errWrongSecretStatus
|
|
||||||
}
|
|
||||||
return s.baseGCloudSecret.Decrypt()
|
|
||||||
}
|
|
67
kms/aws/aws.go
Normal file
67
kms/aws/aws.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// +build !noawskms
|
||||||
|
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
// we import awskms here to be able to disable AWS KMS support using a build tag
|
||||||
|
_ "gocloud.dev/secrets/awskms"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/v2/kms"
|
||||||
|
"github.com/drakkan/sftpgo/v2/kms/gocloud"
|
||||||
|
"github.com/drakkan/sftpgo/v2/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const encryptedStatus = kms.SecretStatusAWS
|
||||||
|
|
||||||
|
type awsSecret struct {
|
||||||
|
gocloud.Secret
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
version.AddFeature("+awskms")
|
||||||
|
kms.RegisterSecretProvider(kms.SchemeAWS, encryptedStatus, newAWSSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAWSSecret(base kms.BaseSecret, url, masterKey string) kms.SecretProvider {
|
||||||
|
return &awsSecret{
|
||||||
|
gocloud.Secret{
|
||||||
|
BaseSecret: base,
|
||||||
|
URL: url,
|
||||||
|
MasterKey: masterKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *awsSecret) Name() string {
|
||||||
|
return "AWS"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *awsSecret) IsEncrypted() bool {
|
||||||
|
return s.Status == encryptedStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *awsSecret) Encrypt() error {
|
||||||
|
if err := s.Secret.Encrypt(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Status = encryptedStatus
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *awsSecret) Decrypt() error {
|
||||||
|
if !s.IsEncrypted() {
|
||||||
|
return kms.ErrWrongSecretStatus
|
||||||
|
}
|
||||||
|
return s.Secret.Decrypt()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *awsSecret) Clone() kms.SecretProvider {
|
||||||
|
baseSecret := kms.BaseSecret{
|
||||||
|
Status: s.Status,
|
||||||
|
Payload: s.Payload,
|
||||||
|
Key: s.Key,
|
||||||
|
AdditionalData: s.AdditionalData,
|
||||||
|
Mode: s.Mode,
|
||||||
|
}
|
||||||
|
return newAWSSecret(baseSecret, s.URL, s.MasterKey)
|
||||||
|
}
|
11
kms/aws/aws_disabled.go
Normal file
11
kms/aws/aws_disabled.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// +build noawskms
|
||||||
|
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drakkan/sftpgo/v2/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
version.AddFeature("-awskms")
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
// +build noawskms
|
|
||||||
|
|
||||||
package kms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
version.AddFeature("-awskms")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAWSSecret(base baseSecret, url, masterKey string) SecretProvider {
|
|
||||||
return newDisabledSecret(errors.New("AWS KMS disabled at build time"))
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
package kms
|
package kms
|
||||||
|
|
||||||
// baseSecret defines the base struct shared among all the secret providers
|
// BaseSecret defines the base struct shared among all the secret providers
|
||||||
type baseSecret struct {
|
type BaseSecret struct {
|
||||||
Status SecretStatus `json:"status,omitempty"`
|
Status SecretStatus `json:"status,omitempty"`
|
||||||
Payload string `json:"payload,omitempty"`
|
Payload string `json:"payload,omitempty"`
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
|
@ -10,39 +10,39 @@ type baseSecret struct {
|
||||||
Mode int `json:"mode,omitempty"`
|
Mode int `json:"mode,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseSecret) GetStatus() SecretStatus {
|
func (s *BaseSecret) GetStatus() SecretStatus {
|
||||||
return s.Status
|
return s.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseSecret) GetPayload() string {
|
func (s *BaseSecret) GetPayload() string {
|
||||||
return s.Payload
|
return s.Payload
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseSecret) GetKey() string {
|
func (s *BaseSecret) GetKey() string {
|
||||||
return s.Key
|
return s.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseSecret) GetMode() int {
|
func (s *BaseSecret) GetMode() int {
|
||||||
return s.Mode
|
return s.Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseSecret) GetAdditionalData() string {
|
func (s *BaseSecret) GetAdditionalData() string {
|
||||||
return s.AdditionalData
|
return s.AdditionalData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseSecret) SetKey(value string) {
|
func (s *BaseSecret) SetKey(value string) {
|
||||||
s.Key = value
|
s.Key = value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseSecret) SetAdditionalData(value string) {
|
func (s *BaseSecret) SetAdditionalData(value string) {
|
||||||
s.AdditionalData = value
|
s.AdditionalData = value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseSecret) SetStatus(value SecretStatus) {
|
func (s *BaseSecret) SetStatus(value SecretStatus) {
|
||||||
s.Status = value
|
s.Status = value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseSecret) isEmpty() bool {
|
func (s *BaseSecret) isEmpty() bool {
|
||||||
if s.Status != "" {
|
if s.Status != "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,17 +10,21 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type builtinSecret struct {
|
type builtinSecret struct {
|
||||||
baseSecret
|
BaseSecret
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBuiltinSecret(base baseSecret) SecretProvider {
|
func init() {
|
||||||
|
RegisterSecretProvider(SchemeBuiltin, SecretStatusAES256GCM, newBuiltinSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuiltinSecret(base BaseSecret, url, masterKey string) SecretProvider {
|
||||||
return &builtinSecret{
|
return &builtinSecret{
|
||||||
baseSecret: base,
|
BaseSecret: base,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *builtinSecret) Name() string {
|
func (s *builtinSecret) Name() string {
|
||||||
return builtinProviderName
|
return "Builtin"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *builtinSecret) IsEncrypted() bool {
|
func (s *builtinSecret) IsEncrypted() bool {
|
||||||
|
@ -40,7 +44,7 @@ func (s *builtinSecret) deriveKey(key []byte) []byte {
|
||||||
|
|
||||||
func (s *builtinSecret) Encrypt() error {
|
func (s *builtinSecret) Encrypt() error {
|
||||||
if s.Payload == "" {
|
if s.Payload == "" {
|
||||||
return errInvalidSecret
|
return ErrInvalidSecret
|
||||||
}
|
}
|
||||||
switch s.Status {
|
switch s.Status {
|
||||||
case SecretStatusPlain:
|
case SecretStatusPlain:
|
||||||
|
@ -70,7 +74,7 @@ func (s *builtinSecret) Encrypt() error {
|
||||||
s.Status = SecretStatusAES256GCM
|
s.Status = SecretStatusAES256GCM
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return errWrongSecretStatus
|
return ErrWrongSecretStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +116,17 @@ func (s *builtinSecret) Decrypt() error {
|
||||||
s.AdditionalData = ""
|
s.AdditionalData = ""
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return errWrongSecretStatus
|
return ErrWrongSecretStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *builtinSecret) Clone() SecretProvider {
|
||||||
|
baseSecret := BaseSecret{
|
||||||
|
Status: s.Status,
|
||||||
|
Payload: s.Payload,
|
||||||
|
Key: s.Key,
|
||||||
|
AdditionalData: s.AdditionalData,
|
||||||
|
Mode: s.Mode,
|
||||||
|
}
|
||||||
|
return newBuiltinSecret(baseSecret, "", "")
|
||||||
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
// +build noawskms nogcpkms novaultkms
|
|
||||||
|
|
||||||
package kms
|
|
||||||
|
|
||||||
const disabledProviderName = "Disabled"
|
|
||||||
|
|
||||||
type disabledSecret struct {
|
|
||||||
baseSecret
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDisabledSecret(err error) SecretProvider {
|
|
||||||
return &disabledSecret{
|
|
||||||
baseSecret: baseSecret{},
|
|
||||||
err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *disabledSecret) Name() string {
|
|
||||||
return disabledProviderName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *disabledSecret) IsEncrypted() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *disabledSecret) Encrypt() error {
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *disabledSecret) Decrypt() error {
|
|
||||||
return s.err
|
|
||||||
}
|
|
51
kms/gcp.go
51
kms/gcp.go
|
@ -1,51 +0,0 @@
|
||||||
// +build !nogcpkms
|
|
||||||
|
|
||||||
package kms
|
|
||||||
|
|
||||||
import (
|
|
||||||
// we import gcpkms here to be able to disable GCP KMS support using a build tag
|
|
||||||
_ "gocloud.dev/secrets/gcpkms"
|
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
type gcpSecret struct {
|
|
||||||
baseGCloudSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
version.AddFeature("+gcpkms")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGCPSecret(base baseSecret, url, masterKey string) SecretProvider {
|
|
||||||
return &gcpSecret{
|
|
||||||
baseGCloudSecret{
|
|
||||||
baseSecret: base,
|
|
||||||
url: url,
|
|
||||||
masterKey: masterKey,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *gcpSecret) Name() string {
|
|
||||||
return gcpProviderName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *gcpSecret) IsEncrypted() bool {
|
|
||||||
return s.Status == SecretStatusGCP
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *gcpSecret) Encrypt() error {
|
|
||||||
if err := s.baseGCloudSecret.Encrypt(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Status = SecretStatusGCP
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *gcpSecret) Decrypt() error {
|
|
||||||
if !s.IsEncrypted() {
|
|
||||||
return errWrongSecretStatus
|
|
||||||
}
|
|
||||||
return s.baseGCloudSecret.Decrypt()
|
|
||||||
}
|
|
67
kms/gcp/gcp.go
Normal file
67
kms/gcp/gcp.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// +build !nogcpkms
|
||||||
|
|
||||||
|
package gcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
// we import gcpkms here to be able to disable GCP KMS support using a build tag
|
||||||
|
_ "gocloud.dev/secrets/gcpkms"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/v2/kms"
|
||||||
|
"github.com/drakkan/sftpgo/v2/kms/gocloud"
|
||||||
|
"github.com/drakkan/sftpgo/v2/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const encryptedStatus = kms.SecretStatusGCP
|
||||||
|
|
||||||
|
type gcpSecret struct {
|
||||||
|
gocloud.Secret
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
version.AddFeature("+gcpkms")
|
||||||
|
kms.RegisterSecretProvider(kms.SchemeGCP, encryptedStatus, newGCPSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGCPSecret(base kms.BaseSecret, url, masterKey string) kms.SecretProvider {
|
||||||
|
return &gcpSecret{
|
||||||
|
gocloud.Secret{
|
||||||
|
BaseSecret: base,
|
||||||
|
URL: url,
|
||||||
|
MasterKey: masterKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *gcpSecret) Name() string {
|
||||||
|
return "GCP"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *gcpSecret) IsEncrypted() bool {
|
||||||
|
return s.Status == encryptedStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *gcpSecret) Encrypt() error {
|
||||||
|
if err := s.Secret.Encrypt(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Status = encryptedStatus
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *gcpSecret) Decrypt() error {
|
||||||
|
if !s.IsEncrypted() {
|
||||||
|
return kms.ErrWrongSecretStatus
|
||||||
|
}
|
||||||
|
return s.Secret.Decrypt()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *gcpSecret) Clone() kms.SecretProvider {
|
||||||
|
baseSecret := kms.BaseSecret{
|
||||||
|
Status: s.Status,
|
||||||
|
Payload: s.Payload,
|
||||||
|
Key: s.Key,
|
||||||
|
AdditionalData: s.AdditionalData,
|
||||||
|
Mode: s.Mode,
|
||||||
|
}
|
||||||
|
return newGCPSecret(baseSecret, s.URL, s.MasterKey)
|
||||||
|
}
|
11
kms/gcp/gcp_disabled.go
Normal file
11
kms/gcp/gcp_disabled.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// +build nogcpkms
|
||||||
|
|
||||||
|
package gcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drakkan/sftpgo/v2/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
version.AddFeature("-gcpkms")
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
// +build nogcpkms
|
|
||||||
|
|
||||||
package kms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
version.AddFeature("-gcpkms")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGCPSecret(base baseSecret, url, masterKey string) SecretProvider {
|
|
||||||
return newDisabledSecret(errors.New("GCP KMS disabled at build time"))
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package kms
|
package gocloud
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -6,27 +6,34 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gocloud.dev/secrets"
|
"gocloud.dev/secrets"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/v2/kms"
|
||||||
)
|
)
|
||||||
|
|
||||||
type baseGCloudSecret struct {
|
const (
|
||||||
baseSecret
|
defaultTimeout = 10 * time.Second
|
||||||
masterKey string
|
)
|
||||||
url string
|
|
||||||
|
// Secret defines common methods for go-cloud based kms
|
||||||
|
type Secret struct {
|
||||||
|
kms.BaseSecret
|
||||||
|
MasterKey string
|
||||||
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseGCloudSecret) Encrypt() error {
|
func (s *Secret) Encrypt() error {
|
||||||
if s.Status != SecretStatusPlain {
|
if s.Status != kms.SecretStatusPlain {
|
||||||
return errWrongSecretStatus
|
return kms.ErrWrongSecretStatus
|
||||||
}
|
}
|
||||||
if s.Payload == "" {
|
if s.Payload == "" {
|
||||||
return errInvalidSecret
|
return kms.ErrInvalidSecret
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := s.Payload
|
payload := s.Payload
|
||||||
key := ""
|
key := ""
|
||||||
mode := 0
|
mode := 0
|
||||||
if s.masterKey != "" {
|
if s.MasterKey != "" {
|
||||||
localSecret := newLocalSecret(s.baseSecret, s.masterKey)
|
localSecret := kms.NewLocalSecret(s.BaseSecret, "", s.MasterKey)
|
||||||
err := localSecret.Encrypt()
|
err := localSecret.Encrypt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -39,7 +46,7 @@ func (s *baseGCloudSecret) Encrypt() error {
|
||||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(defaultTimeout))
|
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(defaultTimeout))
|
||||||
defer cancelFn()
|
defer cancelFn()
|
||||||
|
|
||||||
keeper, err := secrets.OpenKeeper(ctx, s.url)
|
keeper, err := secrets.OpenKeeper(ctx, s.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -55,7 +62,7 @@ func (s *baseGCloudSecret) Encrypt() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *baseGCloudSecret) Decrypt() error {
|
func (s *Secret) Decrypt() error {
|
||||||
encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
|
encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -63,7 +70,7 @@ func (s *baseGCloudSecret) Decrypt() error {
|
||||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(defaultTimeout))
|
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(defaultTimeout))
|
||||||
defer cancelFn()
|
defer cancelFn()
|
||||||
|
|
||||||
keeper, err := secrets.OpenKeeper(ctx, s.url)
|
keeper, err := secrets.OpenKeeper(ctx, s.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -75,21 +82,21 @@ func (s *baseGCloudSecret) Decrypt() error {
|
||||||
}
|
}
|
||||||
payload := string(plaintext)
|
payload := string(plaintext)
|
||||||
if s.Key != "" {
|
if s.Key != "" {
|
||||||
baseSecret := baseSecret{
|
baseSecret := kms.BaseSecret{
|
||||||
Status: SecretStatusSecretBox,
|
Status: kms.SecretStatusSecretBox,
|
||||||
Payload: string(plaintext),
|
Payload: string(plaintext),
|
||||||
Key: s.Key,
|
Key: s.Key,
|
||||||
AdditionalData: s.AdditionalData,
|
AdditionalData: s.AdditionalData,
|
||||||
Mode: s.Mode,
|
Mode: s.Mode,
|
||||||
}
|
}
|
||||||
localSecret := newLocalSecret(baseSecret, s.masterKey)
|
localSecret := kms.NewLocalSecret(baseSecret, "", s.MasterKey)
|
||||||
err = localSecret.Decrypt()
|
err = localSecret.Decrypt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
payload = localSecret.GetPayload()
|
payload = localSecret.GetPayload()
|
||||||
}
|
}
|
||||||
s.Status = SecretStatusPlain
|
s.Status = kms.SecretStatusPlain
|
||||||
s.Payload = payload
|
s.Payload = payload
|
||||||
s.Key = ""
|
s.Key = ""
|
||||||
s.AdditionalData = ""
|
s.AdditionalData = ""
|
127
kms/kms.go
127
kms/kms.go
|
@ -7,8 +7,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/v2/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/util"
|
"github.com/drakkan/sftpgo/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,8 +26,13 @@ type SecretProvider interface {
|
||||||
SetKey(string)
|
SetKey(string)
|
||||||
SetAdditionalData(string)
|
SetAdditionalData(string)
|
||||||
SetStatus(SecretStatus)
|
SetStatus(SecretStatus)
|
||||||
|
Clone() SecretProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
logSender = "kms"
|
||||||
|
)
|
||||||
|
|
||||||
// SecretStatus defines the statuses of a Secret object
|
// SecretStatus defines the statuses of a Secret object
|
||||||
type SecretStatus = string
|
type SecretStatus = string
|
||||||
|
|
||||||
|
@ -51,12 +56,16 @@ const (
|
||||||
SecretStatusRedacted SecretStatus = "Redacted"
|
SecretStatusRedacted SecretStatus = "Redacted"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Scheme defines the supported URL scheme
|
||||||
|
type Scheme = string
|
||||||
|
|
||||||
|
// supported URL schemes
|
||||||
const (
|
const (
|
||||||
localProviderName = "Local"
|
SchemeLocal Scheme = "local://"
|
||||||
builtinProviderName = "Builtin"
|
SchemeBuiltin Scheme = "builtin://"
|
||||||
awsProviderName = "AWS"
|
SchemeAWS Scheme = "awskms://"
|
||||||
gcpProviderName = "GCP"
|
SchemeGCP Scheme = "gcpkms://"
|
||||||
vaultProviderName = "VaultTransit"
|
SchemeVaultTransit Scheme = "hashivault://"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configuration defines the KMS configuration
|
// Configuration defines the KMS configuration
|
||||||
|
@ -71,16 +80,32 @@ type Secrets struct {
|
||||||
masterKey string
|
masterKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type registeredSecretProvider struct {
|
||||||
|
encryptedStatus SecretStatus
|
||||||
|
newFn func(base BaseSecret, url, masterKey string) SecretProvider
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errWrongSecretStatus = errors.New("wrong secret status")
|
// ErrWrongSecretStatus defines the error to return if the secret status is not appropriate
|
||||||
|
// for the request operation
|
||||||
|
ErrWrongSecretStatus = errors.New("wrong secret status")
|
||||||
|
// ErrInvalidSecret defines the error to return if a secret is not valid
|
||||||
|
ErrInvalidSecret = errors.New("invalid secret")
|
||||||
errMalformedCiphertext = errors.New("malformed ciphertext")
|
errMalformedCiphertext = errors.New("malformed ciphertext")
|
||||||
errInvalidSecret = errors.New("invalid secret")
|
|
||||||
validSecretStatuses = []string{SecretStatusPlain, SecretStatusAES256GCM, SecretStatusSecretBox,
|
validSecretStatuses = []string{SecretStatusPlain, SecretStatusAES256GCM, SecretStatusSecretBox,
|
||||||
SecretStatusVaultTransit, SecretStatusAWS, SecretStatusGCP, SecretStatusRedacted}
|
SecretStatusVaultTransit, SecretStatusAWS, SecretStatusGCP, SecretStatusRedacted}
|
||||||
config Configuration
|
config Configuration
|
||||||
defaultTimeout = 10 * time.Second
|
secretProviders = make(map[string]registeredSecretProvider)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RegisterSecretProvider register a new secret provider
|
||||||
|
func RegisterSecretProvider(scheme string, encryptedStatus SecretStatus, fn func(base BaseSecret, url, masterKey string) SecretProvider) {
|
||||||
|
secretProviders[scheme] = registeredSecretProvider{
|
||||||
|
encryptedStatus: encryptedStatus,
|
||||||
|
newFn: fn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewSecret builds a new Secret using the provided arguments
|
// NewSecret builds a new Secret using the provided arguments
|
||||||
func NewSecret(status SecretStatus, payload, key, data string) *Secret {
|
func NewSecret(status SecretStatus, payload, key, data string) *Secret {
|
||||||
return config.newSecret(status, payload, key, data)
|
return config.newSecret(status, payload, key, data)
|
||||||
|
@ -115,11 +140,18 @@ func (c *Configuration) Initialize() error {
|
||||||
c.Secrets.masterKey = strings.TrimSpace(string(mKey))
|
c.Secrets.masterKey = strings.TrimSpace(string(mKey))
|
||||||
}
|
}
|
||||||
config = *c
|
config = *c
|
||||||
|
if config.Secrets.URL == "" {
|
||||||
|
config.Secrets.URL = "local://"
|
||||||
|
}
|
||||||
|
for k, v := range secretProviders {
|
||||||
|
logger.Debug(logSender, "", "secret provider registered for scheme: %#v, encrypted status: %#v",
|
||||||
|
k, v.encryptedStatus)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Configuration) newSecret(status SecretStatus, payload, key, data string) *Secret {
|
func (c *Configuration) newSecret(status SecretStatus, payload, key, data string) *Secret {
|
||||||
base := baseSecret{
|
base := BaseSecret{
|
||||||
Status: status,
|
Status: status,
|
||||||
Key: key,
|
Key: key,
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
|
@ -130,17 +162,13 @@ func (c *Configuration) newSecret(status SecretStatus, payload, key, data string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Configuration) getSecretProvider(base baseSecret) SecretProvider {
|
func (c *Configuration) getSecretProvider(base BaseSecret) SecretProvider {
|
||||||
if strings.HasPrefix(c.Secrets.URL, "hashivault://") {
|
for k, v := range secretProviders {
|
||||||
return newVaultSecret(base, c.Secrets.URL, c.Secrets.masterKey)
|
if strings.HasPrefix(c.Secrets.URL, k) {
|
||||||
|
return v.newFn(base, c.Secrets.URL, c.Secrets.masterKey)
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(c.Secrets.URL, "awskms://") {
|
|
||||||
return newAWSSecret(base, c.Secrets.URL, c.Secrets.masterKey)
|
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(c.Secrets.URL, "gcpkms://") {
|
return NewLocalSecret(base, c.Secrets.URL, c.Secrets.masterKey)
|
||||||
return newGCPSecret(base, c.Secrets.URL, c.Secrets.masterKey)
|
|
||||||
}
|
|
||||||
return newLocalSecret(base, c.Secrets.masterKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret defines the struct used to store confidential data
|
// Secret defines the struct used to store confidential data
|
||||||
|
@ -154,7 +182,7 @@ func (s *Secret) MarshalJSON() ([]byte, error) {
|
||||||
s.RLock()
|
s.RLock()
|
||||||
defer s.RUnlock()
|
defer s.RUnlock()
|
||||||
|
|
||||||
return json.Marshal(&baseSecret{
|
return json.Marshal(&BaseSecret{
|
||||||
Status: s.provider.GetStatus(),
|
Status: s.provider.GetStatus(),
|
||||||
Payload: s.provider.GetPayload(),
|
Payload: s.provider.GetPayload(),
|
||||||
Key: s.provider.GetKey(),
|
Key: s.provider.GetKey(),
|
||||||
|
@ -169,7 +197,7 @@ func (s *Secret) UnmarshalJSON(data []byte) error {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
|
|
||||||
baseSecret := baseSecret{}
|
baseSecret := BaseSecret{}
|
||||||
err := json.Unmarshal(data, &baseSecret)
|
err := json.Unmarshal(data, &baseSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -178,23 +206,21 @@ func (s *Secret) UnmarshalJSON(data []byte) error {
|
||||||
s.provider = config.getSecretProvider(baseSecret)
|
s.provider = config.getSecretProvider(baseSecret)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
switch baseSecret.Status {
|
|
||||||
case SecretStatusAES256GCM:
|
if baseSecret.Status == SecretStatusPlain || baseSecret.Status == SecretStatusRedacted {
|
||||||
s.provider = newBuiltinSecret(baseSecret)
|
|
||||||
case SecretStatusSecretBox:
|
|
||||||
s.provider = newLocalSecret(baseSecret, config.Secrets.masterKey)
|
|
||||||
case SecretStatusVaultTransit:
|
|
||||||
s.provider = newVaultSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey)
|
|
||||||
case SecretStatusAWS:
|
|
||||||
s.provider = newAWSSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey)
|
|
||||||
case SecretStatusGCP:
|
|
||||||
s.provider = newGCPSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey)
|
|
||||||
case SecretStatusPlain, SecretStatusRedacted:
|
|
||||||
s.provider = config.getSecretProvider(baseSecret)
|
s.provider = config.getSecretProvider(baseSecret)
|
||||||
default:
|
|
||||||
return errInvalidSecret
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range secretProviders {
|
||||||
|
if v.encryptedStatus == baseSecret.Status {
|
||||||
|
s.provider = v.newFn(baseSecret, config.Secrets.URL, config.Secrets.masterKey)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Debug(logSender, "", "no provider registered for status %#v", baseSecret.Status)
|
||||||
|
|
||||||
|
return ErrInvalidSecret
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEqual returns true if all the secrets fields are equal
|
// IsEqual returns true if all the secrets fields are equal
|
||||||
|
@ -222,36 +248,9 @@ func (s *Secret) Clone() *Secret {
|
||||||
s.RLock()
|
s.RLock()
|
||||||
defer s.RUnlock()
|
defer s.RUnlock()
|
||||||
|
|
||||||
baseSecret := baseSecret{
|
|
||||||
Status: s.provider.GetStatus(),
|
|
||||||
Payload: s.provider.GetPayload(),
|
|
||||||
Key: s.provider.GetKey(),
|
|
||||||
AdditionalData: s.provider.GetAdditionalData(),
|
|
||||||
Mode: s.provider.GetMode(),
|
|
||||||
}
|
|
||||||
switch s.provider.Name() {
|
|
||||||
case builtinProviderName:
|
|
||||||
return &Secret{
|
return &Secret{
|
||||||
provider: newBuiltinSecret(baseSecret),
|
provider: s.provider.Clone(),
|
||||||
}
|
}
|
||||||
case awsProviderName:
|
|
||||||
return &Secret{
|
|
||||||
provider: newAWSSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey),
|
|
||||||
}
|
|
||||||
case gcpProviderName:
|
|
||||||
return &Secret{
|
|
||||||
provider: newGCPSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey),
|
|
||||||
}
|
|
||||||
case localProviderName:
|
|
||||||
return &Secret{
|
|
||||||
provider: newLocalSecret(baseSecret, config.Secrets.masterKey),
|
|
||||||
}
|
|
||||||
case vaultProviderName:
|
|
||||||
return &Secret{
|
|
||||||
provider: newVaultSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NewSecret(s.GetStatus(), s.GetPayload(), s.GetKey(), s.GetAdditionalData())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEncrypted returns true if the secret is encrypted
|
// IsEncrypted returns true if the secret is encrypted
|
||||||
|
|
30
kms/local.go
30
kms/local.go
|
@ -11,20 +11,25 @@ import (
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterSecretProvider(SchemeLocal, SecretStatusSecretBox, NewLocalSecret)
|
||||||
|
}
|
||||||
|
|
||||||
type localSecret struct {
|
type localSecret struct {
|
||||||
baseSecret
|
BaseSecret
|
||||||
masterKey string
|
masterKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLocalSecret(base baseSecret, masterKey string) SecretProvider {
|
// NewLocalSecret returns a SecretProvider that use a locally provided symmetric key
|
||||||
|
func NewLocalSecret(base BaseSecret, url, masterKey string) SecretProvider {
|
||||||
return &localSecret{
|
return &localSecret{
|
||||||
baseSecret: base,
|
BaseSecret: base,
|
||||||
masterKey: masterKey,
|
masterKey: masterKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *localSecret) Name() string {
|
func (s *localSecret) Name() string {
|
||||||
return localProviderName
|
return "Local"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *localSecret) IsEncrypted() bool {
|
func (s *localSecret) IsEncrypted() bool {
|
||||||
|
@ -33,10 +38,10 @@ func (s *localSecret) IsEncrypted() bool {
|
||||||
|
|
||||||
func (s *localSecret) Encrypt() error {
|
func (s *localSecret) Encrypt() error {
|
||||||
if s.Status != SecretStatusPlain {
|
if s.Status != SecretStatusPlain {
|
||||||
return errWrongSecretStatus
|
return ErrWrongSecretStatus
|
||||||
}
|
}
|
||||||
if s.Payload == "" {
|
if s.Payload == "" {
|
||||||
return errInvalidSecret
|
return ErrInvalidSecret
|
||||||
}
|
}
|
||||||
secretKey, err := localsecrets.NewRandomKey()
|
secretKey, err := localsecrets.NewRandomKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -62,7 +67,7 @@ func (s *localSecret) Encrypt() error {
|
||||||
|
|
||||||
func (s *localSecret) Decrypt() error {
|
func (s *localSecret) Decrypt() error {
|
||||||
if !s.IsEncrypted() {
|
if !s.IsEncrypted() {
|
||||||
return errWrongSecretStatus
|
return ErrWrongSecretStatus
|
||||||
}
|
}
|
||||||
encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
|
encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -123,3 +128,14 @@ func (s *localSecret) getEncryptionMode() int {
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *localSecret) Clone() SecretProvider {
|
||||||
|
baseSecret := BaseSecret{
|
||||||
|
Status: s.Status,
|
||||||
|
Payload: s.Payload,
|
||||||
|
Key: s.Key,
|
||||||
|
AdditionalData: s.AdditionalData,
|
||||||
|
Mode: s.Mode,
|
||||||
|
}
|
||||||
|
return NewLocalSecret(baseSecret, "", s.masterKey)
|
||||||
|
}
|
||||||
|
|
51
kms/vault.go
51
kms/vault.go
|
@ -1,51 +0,0 @@
|
||||||
// +build !novaultkms
|
|
||||||
|
|
||||||
package kms
|
|
||||||
|
|
||||||
import (
|
|
||||||
// we import hashivault here to be able to disable Vault support using a build tag
|
|
||||||
_ "gocloud.dev/secrets/hashivault"
|
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
type vaultSecret struct {
|
|
||||||
baseGCloudSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
version.AddFeature("+vaultkms")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newVaultSecret(base baseSecret, url, masterKey string) SecretProvider {
|
|
||||||
return &vaultSecret{
|
|
||||||
baseGCloudSecret{
|
|
||||||
baseSecret: base,
|
|
||||||
url: url,
|
|
||||||
masterKey: masterKey,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *vaultSecret) Name() string {
|
|
||||||
return vaultProviderName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *vaultSecret) IsEncrypted() bool {
|
|
||||||
return s.Status == SecretStatusVaultTransit
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *vaultSecret) Encrypt() error {
|
|
||||||
if err := s.baseGCloudSecret.Encrypt(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Status = SecretStatusVaultTransit
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *vaultSecret) Decrypt() error {
|
|
||||||
if !s.IsEncrypted() {
|
|
||||||
return errWrongSecretStatus
|
|
||||||
}
|
|
||||||
return s.baseGCloudSecret.Decrypt()
|
|
||||||
}
|
|
67
kms/vault/vault.go
Normal file
67
kms/vault/vault.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// +build !novaultkms
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
// we import hashivault here to be able to disable Vault support using a build tag
|
||||||
|
_ "gocloud.dev/secrets/hashivault"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/v2/kms"
|
||||||
|
"github.com/drakkan/sftpgo/v2/kms/gocloud"
|
||||||
|
"github.com/drakkan/sftpgo/v2/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const encryptedStatus = kms.SecretStatusVaultTransit
|
||||||
|
|
||||||
|
type vaultSecret struct {
|
||||||
|
gocloud.Secret
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
version.AddFeature("+vaultkms")
|
||||||
|
kms.RegisterSecretProvider(kms.SchemeVaultTransit, encryptedStatus, newVaultSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVaultSecret(base kms.BaseSecret, url, masterKey string) kms.SecretProvider {
|
||||||
|
return &vaultSecret{
|
||||||
|
gocloud.Secret{
|
||||||
|
BaseSecret: base,
|
||||||
|
URL: url,
|
||||||
|
MasterKey: masterKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vaultSecret) Name() string {
|
||||||
|
return "VaultTransit"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vaultSecret) IsEncrypted() bool {
|
||||||
|
return s.Status == encryptedStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vaultSecret) Encrypt() error {
|
||||||
|
if err := s.Secret.Encrypt(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Status = encryptedStatus
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vaultSecret) Decrypt() error {
|
||||||
|
if !s.IsEncrypted() {
|
||||||
|
return kms.ErrWrongSecretStatus
|
||||||
|
}
|
||||||
|
return s.Secret.Decrypt()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vaultSecret) Clone() kms.SecretProvider {
|
||||||
|
baseSecret := kms.BaseSecret{
|
||||||
|
Status: s.Status,
|
||||||
|
Payload: s.Payload,
|
||||||
|
Key: s.Key,
|
||||||
|
AdditionalData: s.AdditionalData,
|
||||||
|
Mode: s.Mode,
|
||||||
|
}
|
||||||
|
return newVaultSecret(baseSecret, s.URL, s.MasterKey)
|
||||||
|
}
|
11
kms/vault/vault_disabled.go
Normal file
11
kms/vault/vault_disabled.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// +build novaultkms
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drakkan/sftpgo/v2/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
version.AddFeature("-vaultkms")
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
// +build novaultkms
|
|
||||||
|
|
||||||
package kms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
version.AddFeature("-vaultkms")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newVaultSecret(base baseSecret, url, masterKey string) SecretProvider {
|
|
||||||
return newDisabledSecret(errors.New("Vault KMS disabled at build time"))
|
|
||||||
}
|
|
3
main.go
3
main.go
|
@ -11,6 +11,9 @@ 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/aws"
|
||||||
|
_ "github.com/drakkan/sftpgo/v2/kms/gcp"
|
||||||
|
_ "github.com/drakkan/sftpgo/v2/kms/vault"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
Loading…
Reference in a new issue