From b4aef60212089d01ce46694bc6507f23f0f47d89 Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Fri, 12 Jul 2024 14:44:20 -0600 Subject: [PATCH 1/9] Use memguard for S3 secrets --- component/s3storage/client.go | 20 +++++++++----- component/s3storage/client_test.go | 31 +++++++++++++++++++-- component/s3storage/config.go | 14 ++++++---- component/s3storage/config_test.go | 44 +++++++++++++++++++----------- component/s3storage/connection.go | 5 ++-- component/s3storage/s3storage.go | 30 ++++++++++++++++++-- go.mod | 2 ++ go.sum | 5 ++++ 8 files changed, 117 insertions(+), 34 deletions(-) diff --git a/component/s3storage/client.go b/component/s3storage/client.go index afb6f2860..90735243d 100644 --- a/component/s3storage/client.go +++ b/component/s3storage/client.go @@ -96,13 +96,19 @@ func (cl *Client) Configure(cfg Config) error { cl.Config = cfg var credentialsProvider aws.CredentialsProvider - credentialsInConfig := cl.Config.authConfig.KeyID != "" && cl.Config.authConfig.SecretKey != "" - if credentialsInConfig { - credentialsProvider = credentials.NewStaticCredentialsProvider( - cl.Config.authConfig.KeyID, - cl.Config.authConfig.SecretKey, - "", - ) + if cl.Config.authConfig.KeyID != nil && cl.Config.authConfig.SecretKey != nil { + keyID, _ := cl.Config.authConfig.KeyID.Open() + defer keyID.Destroy() + secretKey, _ := cl.Config.authConfig.SecretKey.Open() + defer secretKey.Destroy() + credentialsInConfig := keyID.String() != "" && secretKey.String() != "" + if credentialsInConfig { + credentialsProvider = credentials.NewStaticCredentialsProvider( + strings.Clone(keyID.String()), + strings.Clone(secretKey.String()), + "", + ) + } } var err error diff --git a/component/s3storage/client_test.go b/component/s3storage/client_test.go index 2ffc3be17..a8def06d8 100644 --- a/component/s3storage/client_test.go +++ b/component/s3storage/client_test.go @@ -47,6 +47,8 @@ import ( "github.com/Seagate/cloudfuse/common/log" "github.com/Seagate/cloudfuse/internal" "github.com/Seagate/cloudfuse/internal/handlemap" + "github.com/awnumar/memguard" + "github.com/spf13/viper" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" @@ -73,12 +75,37 @@ func newTestClient(configuration string) (*Client, error) { log.Err("ClientTest::newTestClient : config error [invalid config attributes]") return nil, fmt.Errorf("config error in %s. Here's why: %s", compName, err.Error()) } + + // Secure keyID in enclave + var encryptedKeyID *memguard.Enclave + if viper.GetString("s3storage.key-id") != "" { + dataBuf := memguard.NewBufferFromBytes([]byte(viper.GetString("s3storage.key-id"))) + memguard.ScrambleBytes([]byte(viper.GetString("s3storage.key-id"))) + + encryptedKeyID = dataBuf.Seal() + if encryptedKeyID == nil { + return nil, fmt.Errorf("config error in %s. Here's why: %s", compName, "Error storing key ID securely") + } + } + + // Secure secretKey in enclave + var encryptedSecretKey *memguard.Enclave + if viper.GetString("s3storage.secret-key") != "" { + dataBuf := memguard.NewBufferFromBytes([]byte(viper.GetString("s3storage.secret-key"))) + memguard.ScrambleBytes([]byte(viper.GetString("s3storage.secret-key"))) + + encryptedSecretKey = dataBuf.Seal() + if encryptedSecretKey == nil { + return nil, fmt.Errorf("config error in %s. Here's why: %s", compName, "Error storing secret key securely") + } + } + // now push Options data into an Config configForS3Client := Config{ authConfig: s3AuthConfig{ BucketName: conf.BucketName, - KeyID: conf.KeyID, - SecretKey: conf.SecretKey, + KeyID: encryptedKeyID, + SecretKey: encryptedSecretKey, Region: conf.Region, Profile: conf.Profile, Endpoint: conf.Endpoint, diff --git a/component/s3storage/config.go b/component/s3storage/config.go index 9e0adb1e0..ef0d6dc5e 100644 --- a/component/s3storage/config.go +++ b/component/s3storage/config.go @@ -32,6 +32,7 @@ import ( "github.com/Seagate/cloudfuse/common" "github.com/Seagate/cloudfuse/common/config" "github.com/Seagate/cloudfuse/common/log" + "github.com/awnumar/memguard" "github.com/aws/aws-sdk-go-v2/service/s3/types" ) @@ -40,8 +41,6 @@ var errInvalidConfigField = errors.New("config field is invalid") type Options struct { BucketName string `config:"bucket-name" yaml:"bucket-name,omitempty"` - KeyID string `config:"key-id" yaml:"key-id,omitempty"` - SecretKey string `config:"secret-key" yaml:"secret-key,omitempty"` Region string `config:"region" yaml:"region,omitempty"` Profile string `config:"profile" yaml:"region,omitempty"` Endpoint string `config:"endpoint" yaml:"endpoint,omitempty"` @@ -56,8 +55,13 @@ type Options struct { UsePathStyle bool `config:"use-path-style" yaml:"use-path-style,omitempty"` } +type ConfigSecrets struct { + KeyID *memguard.Enclave + SecretKey *memguard.Enclave +} + // ParseAndValidateConfig : Parse and validate config -func ParseAndValidateConfig(s3 *S3Storage, opt Options) error { +func ParseAndValidateConfig(s3 *S3Storage, opt Options, secrets ConfigSecrets) error { log.Trace("ParseAndValidateConfig : Parsing config") // Validate bucket name @@ -67,8 +71,8 @@ func ParseAndValidateConfig(s3 *S3Storage, opt Options) error { // Set authentication config s3.stConfig.authConfig.BucketName = opt.BucketName - s3.stConfig.authConfig.KeyID = opt.KeyID - s3.stConfig.authConfig.SecretKey = opt.SecretKey + s3.stConfig.authConfig.KeyID = secrets.KeyID + s3.stConfig.authConfig.SecretKey = secrets.SecretKey s3.stConfig.authConfig.Region = opt.Region s3.stConfig.authConfig.Profile = opt.Profile s3.stConfig.authConfig.Endpoint = opt.Endpoint diff --git a/component/s3storage/config_test.go b/component/s3storage/config_test.go index 713f5b8d8..9b46f85f4 100644 --- a/component/s3storage/config_test.go +++ b/component/s3storage/config_test.go @@ -31,6 +31,8 @@ import ( "github.com/Seagate/cloudfuse/common" "github.com/Seagate/cloudfuse/common/log" + "github.com/awnumar/memguard" + "github.com/spf13/viper" "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/stretchr/testify/assert" @@ -39,9 +41,10 @@ import ( type configTestSuite struct { suite.Suite - assert *assert.Assertions - s3 *S3Storage - opt Options + assert *assert.Assertions + s3 *S3Storage + opt Options + secrets ConfigSecrets } func (s *configTestSuite) SetupTest() { @@ -57,8 +60,6 @@ func (s *configTestSuite) SetupTest() { // Set Options s.opt = Options{ BucketName: "testBucketName", - KeyID: "testKeyId", - SecretKey: "testSecretKey", Region: "testRegion", Profile: "testProfile", Endpoint: "testEndpoint", @@ -66,6 +67,19 @@ func (s *configTestSuite) SetupTest() { PrefixPath: "testPrefixPath", } + dataBuf := memguard.NewBufferFromBytes([]byte("testKeyId")) + memguard.ScrambleBytes([]byte(viper.GetString("testKeyId"))) + encryptedKeyID := dataBuf.Seal() + + dataBuf = memguard.NewBufferFromBytes([]byte("testSecretKey")) + memguard.ScrambleBytes([]byte(viper.GetString("testSecretKey"))) + encryptedSecretKey := dataBuf.Seal() + + s.secrets = ConfigSecrets{ + KeyID: encryptedKeyID, + SecretKey: encryptedSecretKey, + } + // Create assertions s.assert = assert.New(s.T()) } @@ -75,7 +89,7 @@ func (s *configTestSuite) TestEmptyBucketName() { s.opt.BucketName = "" // Then - err := ParseAndValidateConfig(s.s3, s.opt) + err := ParseAndValidateConfig(s.s3, s.opt, s.secrets) s.assert.NoError(err) } @@ -83,13 +97,11 @@ func (s *configTestSuite) TestEmptyBucketName() { func (s *configTestSuite) TestConfigParse() { // When - err := ParseAndValidateConfig(s.s3, s.opt) + err := ParseAndValidateConfig(s.s3, s.opt, s.secrets) // Then s.assert.NoError(err) s.assert.Equal(s.opt.BucketName, s.s3.stConfig.authConfig.BucketName) - s.assert.Equal(s.opt.KeyID, s.s3.stConfig.authConfig.KeyID) - s.assert.Equal(s.opt.SecretKey, s.s3.stConfig.authConfig.SecretKey) s.assert.Equal(s.opt.Region, s.s3.stConfig.authConfig.Region) s.assert.Equal(s.opt.Profile, s.s3.stConfig.authConfig.Profile) s.assert.Equal(s.opt.Endpoint, s.s3.stConfig.authConfig.Endpoint) @@ -102,7 +114,7 @@ func (s *configTestSuite) TestPrefixPath() { s.opt.PrefixPath = "/testPrefixPath" // Then - err := ParseAndValidateConfig(s.s3, s.opt) + err := ParseAndValidateConfig(s.s3, s.opt, s.secrets) s.assert.NoError(err) s.assert.Equal("testPrefixPath", s.s3.stConfig.prefixPath) } @@ -113,7 +125,7 @@ func (s *configTestSuite) TestValidChecksum() { // Then // Default should be SHA1 if user does not provide checksum algorithm - err := ParseAndValidateConfig(s.s3, s.opt) + err := ParseAndValidateConfig(s.s3, s.opt, s.secrets) s.assert.NoError(err) s.assert.True(s.s3.stConfig.enableChecksum) s.assert.Equal(types.ChecksumAlgorithm("SHA1"), s.s3.stConfig.checksumAlgorithm) @@ -123,7 +135,7 @@ func (s *configTestSuite) TestValidChecksum() { s.opt.ChecksumAlgorithm = "SHA1" // Then - err = ParseAndValidateConfig(s.s3, s.opt) + err = ParseAndValidateConfig(s.s3, s.opt, s.secrets) s.assert.NoError(err) s.assert.True(s.s3.stConfig.enableChecksum) s.assert.Equal(types.ChecksumAlgorithm("SHA1"), s.s3.stConfig.checksumAlgorithm) @@ -132,7 +144,7 @@ func (s *configTestSuite) TestValidChecksum() { s.opt.ChecksumAlgorithm = "SHA256" // Then - err = ParseAndValidateConfig(s.s3, s.opt) + err = ParseAndValidateConfig(s.s3, s.opt, s.secrets) s.assert.NoError(err) s.assert.Equal(types.ChecksumAlgorithm("SHA256"), s.s3.stConfig.checksumAlgorithm) @@ -140,7 +152,7 @@ func (s *configTestSuite) TestValidChecksum() { s.opt.ChecksumAlgorithm = "CRC32" // Then - err = ParseAndValidateConfig(s.s3, s.opt) + err = ParseAndValidateConfig(s.s3, s.opt, s.secrets) s.assert.NoError(err) s.assert.Equal(types.ChecksumAlgorithm("CRC32"), s.s3.stConfig.checksumAlgorithm) @@ -148,7 +160,7 @@ func (s *configTestSuite) TestValidChecksum() { s.opt.ChecksumAlgorithm = "CRC32C" // Then - err = ParseAndValidateConfig(s.s3, s.opt) + err = ParseAndValidateConfig(s.s3, s.opt, s.secrets) s.assert.NoError(err) s.assert.Equal(types.ChecksumAlgorithm("CRC32C"), s.s3.stConfig.checksumAlgorithm) } @@ -159,7 +171,7 @@ func (s *configTestSuite) TestInvalidChecksum() { s.opt.ChecksumAlgorithm = "invalid" // Then - err := ParseAndValidateConfig(s.s3, s.opt) + err := ParseAndValidateConfig(s.s3, s.opt, s.secrets) s.assert.Error(err) s.assert.ErrorIs(err, errInvalidConfigField) } diff --git a/component/s3storage/connection.go b/component/s3storage/connection.go index f5b36ee06..2291e50e4 100644 --- a/component/s3storage/connection.go +++ b/component/s3storage/connection.go @@ -32,6 +32,7 @@ import ( "github.com/Seagate/cloudfuse/common" "github.com/Seagate/cloudfuse/internal" + "github.com/awnumar/memguard" "github.com/aws/aws-sdk-go-v2/service/s3/types" ) @@ -61,8 +62,8 @@ type Config struct { // s3AuthConfig : Config to authenticate to storage type s3AuthConfig struct { BucketName string - KeyID string - SecretKey string + KeyID *memguard.Enclave + SecretKey *memguard.Enclave Region string Profile string Endpoint string diff --git a/component/s3storage/s3storage.go b/component/s3storage/s3storage.go index 34c2b78cf..8b5cae70a 100644 --- a/component/s3storage/s3storage.go +++ b/component/s3storage/s3storage.go @@ -38,6 +38,8 @@ import ( "github.com/Seagate/cloudfuse/internal" "github.com/Seagate/cloudfuse/internal/handlemap" "github.com/Seagate/cloudfuse/internal/stats_manager" + "github.com/awnumar/memguard" + "github.com/spf13/viper" ) // S3Storage Wrapper type around aws-sdk-go-v2/service/s3 @@ -79,11 +81,35 @@ func (s3 *S3Storage) Configure(isParent bool) error { err = config.UnmarshalKey("restricted-characters-windows", &conf.RestrictedCharsWin) if err != nil { - log.Err("AzStorage::Configure : config error [unable to obtain restricted-characters-windows]") + log.Err("S3Storage::Configure : config error [unable to obtain restricted-characters-windows]") return err } - err = ParseAndValidateConfig(s3, conf) + secrets := ConfigSecrets{} + // Securely store key-id and secret-key in enclave + if viper.GetString("s3storage.key-id") != "" { + dataBuf := memguard.NewBufferFromBytes([]byte(viper.GetString("s3storage.key-id"))) + memguard.ScrambleBytes([]byte(viper.GetString("s3storage.key-id"))) + + encryptedKeyID := dataBuf.Seal() + if encryptedKeyID == nil { + return fmt.Errorf("S3Storage::Configure : unable to store key-id securely") + } + secrets.KeyID = encryptedKeyID + } + + if viper.GetString("s3storage.secret-key") != "" { + dataBuf := memguard.NewBufferFromBytes([]byte(viper.GetString("s3storage.secret-key"))) + memguard.ScrambleBytes([]byte(viper.GetString("s3storage.secret-key"))) + + encryptedSecretKey := dataBuf.Seal() + if encryptedSecretKey == nil { + return fmt.Errorf("S3Storage::Configure : unable to store secret-key securely") + } + secrets.SecretKey = encryptedSecretKey + } + + err = ParseAndValidateConfig(s3, conf, secrets) if err != nil { log.Err("S3Storage::Configure : Config validation failed [%s]", err.Error()) return fmt.Errorf("config error in %s [%s]", s3.Name(), err.Error()) diff --git a/go.mod b/go.mod index d0f8796c8..930a15b96 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 github.com/Azure/azure-sdk-for-go/sdk/storage/azdatalake v1.1.3 github.com/JeffreyRichter/enum v0.0.0-20180725232043-2567042f9cda + github.com/awnumar/memguard v0.22.5 github.com/aws/aws-sdk-go-v2 v1.30.1 github.com/aws/aws-sdk-go-v2/config v1.27.24 github.com/aws/aws-sdk-go-v2/credentials v1.17.24 @@ -38,6 +39,7 @@ require ( require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect + github.com/awnumar/memcall v0.2.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect diff --git a/go.sum b/go.sum index 32008f7df..df540f11c 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,10 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mx github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/JeffreyRichter/enum v0.0.0-20180725232043-2567042f9cda h1:NOo6+gM9NNPJ3W56nxOKb4164LEw094U0C8zYQM8mQU= github.com/JeffreyRichter/enum v0.0.0-20180725232043-2567042f9cda/go.mod h1:2CaSFTh2ph9ymS6goiOKIBdfhwWUVsX4nQ5QjIYFHHs= +github.com/awnumar/memcall v0.2.0 h1:sRaogqExTOOkkNwO9pzJsL8jrOV29UuUW7teRMfbqtI= +github.com/awnumar/memcall v0.2.0/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo= +github.com/awnumar/memguard v0.22.5 h1:PH7sbUVERS5DdXh3+mLo8FDcl1eIeVjJVYMnyuYpvuI= +github.com/awnumar/memguard v0.22.5/go.mod h1:+APmZGThMBWjnMlKiSM1X7MVpbIVewen2MTkqWkA/zE= github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o= github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= @@ -184,6 +188,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 06a4cecf386f7c8844e75333b5027ddeb58e60a2 Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:58:40 -0600 Subject: [PATCH 2/9] Use memguard for handling config passphrase --- .github/workflows/code-coverage.yml | 12 ++++---- cmd/config-gen.go | 12 ++++++-- cmd/config-gen_test.go | 9 ++++-- cmd/mount.go | 29 +++++++++++++------ cmd/mount_all.go | 29 +++++++++++++++---- cmd/secure.go | 30 ++++++++++++-------- cmd/secure_set.go | 2 +- cmd/secure_test.go | 43 ++++++++++++++++++----------- common/config/config_parser.go | 7 +++-- common/config/config_test.go | 8 ++++-- common/util.go | 13 ++++++--- common/util_test.go | 32 +++++++++++++++------ component/s3storage/s3storage.go | 2 ++ 13 files changed, 155 insertions(+), 73 deletions(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index e110f7433..9d1455435 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -382,11 +382,11 @@ jobs: - name: 'CLI : Mount all with secure config' timeout-minutes: 2 - run: "./cloudfuse.test unmount all\ncp ${{ env.cloudfuse_CFG }} /tmp/configMountall.yaml\necho \"mountall:\" >> /tmp/configMountall.yaml\necho \" container-allowlist:\" >> /tmp/configMountall.yaml\necho \" - abcd\" >> /tmp/configMountall.yaml\ncat /tmp/configMountall.yaml\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_encrypt_all.cov secure encrypt --config-file=/tmp/configMountall.yaml --output-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 \nif [ $? -ne 0 ]; then\n exit 1\nfi\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/mount_all_cmd_secure.cov mount all ${{ env.MOUNT_DIR }} --config-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 --log-level=log_debug --foreground=true &\nif [ $? -ne 0 ]; then\n exit 1\nfi\n\nsleep 5\n./cloudfuse.test unmount all" + run: "./cloudfuse.test unmount all\ncp ${{ env.cloudfuse_CFG }} /tmp/configMountall.yaml\necho \"mountall:\" >> /tmp/configMountall.yaml\necho \" container-allowlist:\" >> /tmp/configMountall.yaml\necho \" - abcd\" >> /tmp/configMountall.yaml\ncat /tmp/configMountall.yaml\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_encrypt_all.cov secure encrypt --config-file=/tmp/configMountall.yaml --output-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=MTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTI= \nif [ $? -ne 0 ]; then\n exit 1\nfi\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/mount_all_cmd_secure.cov mount all ${{ env.MOUNT_DIR }} --config-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=MTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTI= --log-level=log_debug --foreground=true &\nif [ $? -ne 0 ]; then\n exit 1\nfi\n\nsleep 5\n./cloudfuse.test unmount all" - name: 'CLI : Mount all with secure config 2' timeout-minutes: 2 - run: "./cloudfuse.test unmount all\ncp ${{ env.cloudfuse_CFG }} /tmp/configMountall.yaml\necho \"mountall:\" >> /tmp/configMountall.yaml\necho \" container-denylist:\" >> /tmp/configMountall.yaml\necho \" - abcd\" >> /tmp/configMountall.yaml\ncat /tmp/configMountall.yaml\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_encrypt_all2.cov secure encrypt --config-file=/tmp/configMountall.yaml --output-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 \nif [ $? -ne 0 ]; then\n exit 1\nfi\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/mount_all_cmd_secure2.cov mount all ${{ env.MOUNT_DIR }} --config-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 --log-level=log_debug --foreground=true &\nif [ $? -ne 0 ]; then\n exit 1\nfi\n\nsleep 5\n./cloudfuse.test unmount all" + run: "./cloudfuse.test unmount all\ncp ${{ env.cloudfuse_CFG }} /tmp/configMountall.yaml\necho \"mountall:\" >> /tmp/configMountall.yaml\necho \" container-denylist:\" >> /tmp/configMountall.yaml\necho \" - abcd\" >> /tmp/configMountall.yaml\ncat /tmp/configMountall.yaml\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_encrypt_all2.cov secure encrypt --config-file=/tmp/configMountall.yaml --output-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=MTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTI= \nif [ $? -ne 0 ]; then\n exit 1\nfi\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/mount_all_cmd_secure2.cov mount all ${{ env.MOUNT_DIR }} --config-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=MTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTI= --log-level=log_debug --foreground=true &\nif [ $? -ne 0 ]; then\n exit 1\nfi\n\nsleep 5\n./cloudfuse.test unmount all" - name: 'CLI : Remount test' timeout-minutes: 2 @@ -452,7 +452,7 @@ jobs: ACCOUNT_ENDPOINT: https://${{ secrets.NIGHTLY_STO_BLOB_ACC_NAME }}.blob.core.windows.net VERBOSE_LOG: false USE_HTTP: false - run: "set +x\nrm -rf ${{ env.MOUNT_DIR }}/*\nrm -rf ${{ env.TEMP_DIR }}/*\n./cloudfuse.test unmount all\n./cloudfuse.test gen-test-config --config-file=azure_key.yaml --container-name=${{ matrix.containerName }} --temp-path=${{ env.TEMP_DIR }} --output-file=${{ env.cloudfuse_CFG }}\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_encrypt.cov secure encrypt --config-file=${{ env.cloudfuse_CFG }} --output-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 \nif [ $? -ne 0 ]; then\n exit 1\nfi\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/mount_secure.cov mount ${{ env.MOUNT_DIR }} --config-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 &\nsleep 10\nps -aux | grep cloudfuse\nrm -rf ${{ env.MOUNT_DIR }}/*\ncd test/e2e_tests\ngo test -v -timeout=7200s ./... -args -mnt-path=${{ env.MOUNT_DIR }} -adls=false -tmp-path=${{ env.TEMP_DIR }}\ncd -\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_set.cov secure set --config-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 --key=logging.level --value=log_debug\n./cloudfuse.test unmount all\nsleep 5" + run: "set +x\nrm -rf ${{ env.MOUNT_DIR }}/*\nrm -rf ${{ env.TEMP_DIR }}/*\n./cloudfuse.test unmount all\n./cloudfuse.test gen-test-config --config-file=azure_key.yaml --container-name=${{ matrix.containerName }} --temp-path=${{ env.TEMP_DIR }} --output-file=${{ env.cloudfuse_CFG }}\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_encrypt.cov secure encrypt --config-file=${{ env.cloudfuse_CFG }} --output-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=MTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTI= \nif [ $? -ne 0 ]; then\n exit 1\nfi\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/mount_secure.cov mount ${{ env.MOUNT_DIR }} --config-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=MTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTI= &\nsleep 10\nps -aux | grep cloudfuse\nrm -rf ${{ env.MOUNT_DIR }}/*\ncd test/e2e_tests\ngo test -v -timeout=7200s ./... -args -mnt-path=${{ env.MOUNT_DIR }} -adls=false -tmp-path=${{ env.TEMP_DIR }}\ncd -\n\n./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_set.cov secure set --config-file=${{ runner.workspace }}/cloudfuse.azsec --passphrase=MTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTI= --key=logging.level --value=log_debug\n./cloudfuse.test unmount all\nsleep 5" - name: 'CLI : Health monitor stop pid' shell: bash {0} @@ -1025,18 +1025,18 @@ jobs: rm -rf ${{ env.TEMP_DIR }}/* ./cloudfuse.test unmount all ./cloudfuse.test gen-test-config --config-file=azure_key.yaml --container-name=${{ matrix.containerName }} --temp-path=${{ env.TEMP_DIR }} --output-file=${{ env.cloudfuse_CFG }} - ./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_encrypt.cov secure encrypt --config-file=${{ env.cloudfuse_CFG }} --output-file=${{ env.WORK_DIR }}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 + ./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_encrypt.cov secure encrypt --config-file=${{ env.cloudfuse_CFG }} --output-file=${{ env.WORK_DIR }}/cloudfuse.azsec --passphrase=MTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTI= if [ $? -ne 0 ]; then exit 1 fi - # ./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/mount_secure.cov mount ${{ env.MOUNT_DIR }} --config-file=${{ env.WORK_DIR }}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 --foreground=true & + # ./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/mount_secure.cov mount ${{ env.MOUNT_DIR }} --config-file=${{ env.WORK_DIR }}/cloudfuse.azsec --passphrase=MTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTI= --foreground=true & # sleep 10 # pid=`ps -a | grep cloudfuse | tr -s ' ' | cut -d ' ' -f2` # rm -rf ${{ env.MOUNT_DIR }}/* # cd test/e2e_tests # go test -v -timeout=7200s ./... -args -mnt-path=${{ env.MOUNT_DIR }} -adls=false -tmp-path=${{ env.TEMP_DIR }} # cd - - # ./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_set.cov secure set --config-file=${{ env.WORK_DIR }}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 --key=logging.level --value=log_debug + # ./cloudfuse.test -test.v -test.coverprofile=${{ env.WORK_DIR }}/secure_set.cov secure set --config-file=${{ env.WORK_DIR }}/cloudfuse.azsec --passphrase=MTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTIzMTI= --key=logging.level --value=log_debug # kill $pid # sleep 5 diff --git a/cmd/config-gen.go b/cmd/config-gen.go index 59f0d3c85..a2310f432 100644 --- a/cmd/config-gen.go +++ b/cmd/config-gen.go @@ -40,6 +40,7 @@ import ( "strings" "github.com/Seagate/cloudfuse/common" + "github.com/awnumar/memguard" "github.com/spf13/cobra" ) @@ -48,7 +49,7 @@ type configGenOptions struct { outputConfigPath string containerName string tempDirPath string - passphrase string + passphrase []byte } var opts configGenOptions @@ -120,6 +121,11 @@ var generateConfig = &cobra.Command{ var templateConfig []byte var err error + passphrase := memguard.NewBufferFromBytes([]byte(options.PassPhrase)) + memguard.ScrambleBytes(options.PassPhrase) + encryptedPassphrase = passphrase.Seal() + passphrase.Destroy() + templateConfig, err = os.ReadFile(opts.configFilePath) if err != nil { return fmt.Errorf("failed to read file [%s]", err.Error()) @@ -142,7 +148,7 @@ var generateConfig = &cobra.Command{ } } - cipherText, err := common.EncryptData([]byte(newConfig), opts.passphrase) + cipherText, err := common.EncryptData([]byte(newConfig), encryptedPassphrase) if err != nil { return err } @@ -168,6 +174,6 @@ func init() { generateConfig.Flags().StringVar(&opts.configFilePath, "config-file", "", "Input config file.") generateConfig.Flags().StringVar(&opts.outputConfigPath, "output-file", "", "Output config file path.") generateConfig.Flags().StringVar(&opts.tempDirPath, "temp-path", "", "Temporary file path.") - generateConfig.Flags().StringVar(&opts.passphrase, "passphrase", "", + generateConfig.Flags().BytesBase64Var(&opts.passphrase, "passphrase", nil, "Key to be used for encryption / decryption. Key length shall be 16 (AES-128), 24 (AES-192), or 32 (AES-256) bytes in length.") } diff --git a/cmd/config-gen_test.go b/cmd/config-gen_test.go index 65933235c..76c017be1 100644 --- a/cmd/config-gen_test.go +++ b/cmd/config-gen_test.go @@ -27,6 +27,7 @@ package cmd import ( "bytes" + "encoding/base64" "fmt" "io" "os" @@ -124,6 +125,7 @@ func (suite *genConfigTestSuite) TestGenConfig() { defer suite.cleanupTest() confFile, _ := os.CreateTemp("", "conf*.yaml") outFile := "config_encrypted.aes" + passphrase := base64.StdEncoding.EncodeToString([]byte("12312312312312312312312312312312")) defer os.Remove(confFile.Name()) defer os.Remove(outFile) @@ -133,7 +135,7 @@ func (suite *genConfigTestSuite) TestGenConfig() { confFile.Close() - _, err = executeCommandGen(rootCmd, "gen-config", fmt.Sprintf("--config-file=%s", confFile.Name()), "--passphrase=12312312312312312312312312312312", fmt.Sprintf("--output-file=%s", outFile), "--temp-path=/tmp") + _, err = executeCommandGen(rootCmd, "gen-config", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=%s", outFile), "--temp-path=/tmp") suite.assert.NoError(err) // Out file should exist @@ -145,6 +147,7 @@ func (suite *genConfigTestSuite) TestGenConfigGet() { defer suite.cleanupTest() confFile, _ := os.CreateTemp("", "conf*.yaml") outFile := "config_encrypted.aes" + passphrase := base64.StdEncoding.EncodeToString([]byte("12312312312312312312312312312312")) defer os.Remove(confFile.Name()) defer os.Remove(outFile) @@ -154,7 +157,7 @@ func (suite *genConfigTestSuite) TestGenConfigGet() { confFile.Close() - _, err = executeCommandGen(rootCmd, "gen-config", fmt.Sprintf("--config-file=%s", confFile.Name()), "--passphrase=12312312312312312312312312312312", fmt.Sprintf("--output-file=%s", outFile), "--temp-path=/tmp") + _, err = executeCommandGen(rootCmd, "gen-config", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=%s", outFile), "--temp-path=/tmp") suite.assert.NoError(err) // Out file should exist @@ -162,7 +165,7 @@ func (suite *genConfigTestSuite) TestGenConfigGet() { suite.assert.NoError(err) // Gen-config should correctly set the temp path for the file_cache - path, err := executeCommandGen(rootCmd, "secure", "get", fmt.Sprintf("--config-file=%s", outFile), "--passphrase=12312312312312312312312312312312", "--key=file_cache.path") + path, err := executeCommandGen(rootCmd, "secure", "get", fmt.Sprintf("--config-file=%s", outFile), fmt.Sprintf("--passphrase=%s", passphrase), "--key=file_cache.path") suite.assert.NoError(err) suite.assert.Equal("Fetching scalar configuration\nfile_cache.path = /tmp\n", path) } diff --git a/cmd/mount.go b/cmd/mount.go index eea4791a7..b57e0ce81 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -28,6 +28,7 @@ package cmd import ( "bytes" "context" + "encoding/base64" "errors" "fmt" "io/fs" @@ -46,6 +47,7 @@ import ( "github.com/Seagate/cloudfuse/common/config" "github.com/Seagate/cloudfuse/common/log" "github.com/Seagate/cloudfuse/internal" + "github.com/awnumar/memguard" "github.com/sevlyar/go-daemon" "github.com/spf13/cobra" @@ -72,7 +74,7 @@ type mountOptions struct { DefaultWorkingDir string `config:"default-working-dir"` CPUProfile string `config:"cpu-profile"` MemProfile string `config:"mem-profile"` - PassPhrase string `config:"passphrase"` + PassPhrase []byte `config:"passphrase"` SecureConfig bool `config:"secure-config"` DynamicProfiler bool `config:"dynamic-profile"` ProfilerPort int `config:"profiler-port"` @@ -200,26 +202,35 @@ func parseConfig() error { filepath.Ext(options.ConfigFile) == SecureConfigExtension { // Validate config is to be secured on write or not - if options.PassPhrase == "" { - options.PassPhrase = os.Getenv(SecureConfigEnvName) - } + if options.PassPhrase == nil || string(options.PassPhrase) == "" { + options.PassPhrase = []byte(os.Getenv(SecureConfigEnvName)) + if options.PassPhrase == nil || string(options.PassPhrase) == "" { + return errors.New("no passphrase provided to decrypt the config file.\n Either use --passphrase cli option or store passphrase in CLOUDFUSE_SECURE_CONFIG_PASSPHRASE environment variable") + } - if options.PassPhrase == "" { - return fmt.Errorf("no passphrase provided to decrypt the config file.\n Either use --passphrase cli option or store passphrase in CLOUDFUSE_SECURE_CONFIG_PASSPHRASE environment variable") + _, err := base64.StdEncoding.DecodeString(string(options.PassPhrase)) + if err != nil { + return fmt.Errorf("passphrase is not valid base64 encoded [%s]", err.Error()) + } } + passphrase := memguard.NewBufferFromBytes([]byte(options.PassPhrase)) + memguard.ScrambleBytes(options.PassPhrase) + encryptedPassphrase = passphrase.Seal() + passphrase.Destroy() + cipherText, err := os.ReadFile(options.ConfigFile) if err != nil { return fmt.Errorf("failed to read encrypted config file %s [%s]", options.ConfigFile, err.Error()) } - plainText, err := common.DecryptData(cipherText, options.PassPhrase) + plainText, err := common.DecryptData(cipherText, encryptedPassphrase) if err != nil { return fmt.Errorf("failed to decrypt config file %s [%s]", options.ConfigFile, err.Error()) } config.SetConfigFile(options.ConfigFile) - config.SetSecureConfigOptions(options.PassPhrase) + config.SetSecureConfigOptions(encryptedPassphrase) err = config.ReadFromConfigBuffer(plainText) if err != nil { return fmt.Errorf("invalid decrypted config file [%s]", err.Error()) @@ -651,7 +662,7 @@ func init() { mountCmd.PersistentFlags().BoolVar(&options.SecureConfig, "secure-config", false, "Encrypt auto generated config file for each container") - mountCmd.PersistentFlags().StringVar(&options.PassPhrase, "passphrase", "", + mountCmd.PersistentFlags().BytesBase64Var(&options.PassPhrase, "passphrase", []byte(""), "Base64 encoded key to decrypt config file. Can also be specified by env-variable CLOUDFUSE_SECURE_CONFIG_PASSPHRASE.\n Decoded key length shall be 16 (AES-128), 24 (AES-192), or 32 (AES-256) bytes in length.") mountCmd.PersistentFlags().String("log-type", "syslog", "Type of logger to be used by the system. Set to syslog by default. Allowed values are silent|syslog|base.") diff --git a/cmd/mount_all.go b/cmd/mount_all.go index 81dcf56c2..96b3a80cb 100644 --- a/cmd/mount_all.go +++ b/cmd/mount_all.go @@ -28,6 +28,8 @@ package cmd import ( "bytes" "context" + "encoding/base64" + "errors" "fmt" "os" "os/exec" @@ -38,6 +40,7 @@ import ( "github.com/Seagate/cloudfuse/common" "github.com/Seagate/cloudfuse/common/config" "github.com/Seagate/cloudfuse/common/log" + "github.com/awnumar/memguard" "github.com/Seagate/cloudfuse/component/azstorage" "github.com/Seagate/cloudfuse/component/s3storage" @@ -155,12 +158,26 @@ func processCommand() error { } // Validate config is to be secured on write or not - if options.PassPhrase == "" { - options.PassPhrase = os.Getenv(SecureConfigEnvName) - } + if options.SecureConfig || + filepath.Ext(options.ConfigFile) == SecureConfigExtension { + + // Validate config is to be secured on write or not + if options.PassPhrase == nil || string(options.PassPhrase) == "" { + options.PassPhrase = []byte(os.Getenv(SecureConfigEnvName)) + if options.PassPhrase == nil || string(options.PassPhrase) == "" { + return errors.New("no passphrase provided to decrypt the config file.\n Either use --passphrase cli option or store passphrase in CLOUDFUSE_SECURE_CONFIG_PASSPHRASE environment variable") + } + + _, err := base64.StdEncoding.DecodeString(string(options.PassPhrase)) + if err != nil { + return fmt.Errorf("passphrase is not valid base64 encoded [%s]", err.Error()) + } + } - if options.SecureConfig && options.PassPhrase == "" { - return fmt.Errorf("key not provided to decrypt config file") + passphrase := memguard.NewBufferFromBytes([]byte(options.PassPhrase)) + memguard.ScrambleBytes(options.PassPhrase) + encryptedPassphrase = passphrase.Seal() + passphrase.Destroy() } var containerList []string @@ -395,7 +412,7 @@ func writeConfigFile(contConfigFile string) error { return fmt.Errorf("failed to marshall yaml content") } - cipherText, err := common.EncryptData(confStream, opts.passphrase) + cipherText, err := common.EncryptData(confStream, encryptedPassphrase) if err != nil { return fmt.Errorf("failed to encrypt yaml content [%s]", err.Error()) } diff --git a/cmd/secure.go b/cmd/secure.go index 3d4f8396f..3907b8d6a 100644 --- a/cmd/secure.go +++ b/cmd/secure.go @@ -33,6 +33,7 @@ import ( "path/filepath" "github.com/Seagate/cloudfuse/common" + "github.com/awnumar/memguard" "github.com/spf13/cobra" ) @@ -40,7 +41,7 @@ import ( type secureOptions struct { Operation string ConfigFile string - PassPhrase string + PassPhrase []byte OutputFile string Key string Value string @@ -50,6 +51,7 @@ const SecureConfigEnvName string = "CLOUDFUSE_SECURE_CONFIG_PASSPHRASE" const SecureConfigExtension string = ".aes" var secOpts secureOptions +var encryptedPassphrase *memguard.Enclave // Section defining all the command that we have in secure feature var secureCmd = &cobra.Command{ @@ -116,15 +118,23 @@ var decryptCmd = &cobra.Command{ //--------------- command section ends func validateOptions() error { - if secOpts.PassPhrase == "" { - secOpts.PassPhrase = os.Getenv(SecureConfigEnvName) + if secOpts.PassPhrase == nil || string(secOpts.PassPhrase) == "" { + secOpts.PassPhrase = []byte(os.Getenv(SecureConfigEnvName)) + if secOpts.PassPhrase == nil || string(secOpts.PassPhrase) == "" { + return errors.New("provide the passphrase as a cli parameter or configure the CLOUDFUSE_SECURE_CONFIG_PASSPHRASE environment variable") + } } - _, err := base64.StdEncoding.DecodeString(secOpts.PassPhrase) + _, err := base64.StdEncoding.DecodeString(string(secOpts.PassPhrase)) if err != nil { - return fmt.Errorf("failed to base64 decode passphrase [%s]", err.Error()) + return fmt.Errorf("passphrase is not valid base64 encoded [%s]", err.Error()) } + passphrase := memguard.NewBufferFromBytes([]byte(secOpts.PassPhrase)) + memguard.ScrambleBytes(secOpts.PassPhrase) + encryptedPassphrase = passphrase.Seal() + defer passphrase.Destroy() + if secOpts.ConfigFile == "" { return errors.New("config file not provided, check usage") } @@ -133,10 +143,6 @@ func validateOptions() error { return errors.New("config file does not exist") } - if secOpts.PassPhrase == "" { - return errors.New("provide the passphrase as a cli parameter or configure the CLOUDFUSE_SECURE_CONFIG_PASSPHRASE environment variable") - } - return nil } @@ -147,7 +153,7 @@ func encryptConfigFile(saveConfig bool) ([]byte, error) { return nil, err } - cipherText, err := common.EncryptData(plaintext, secOpts.PassPhrase) + cipherText, err := common.EncryptData(plaintext, encryptedPassphrase) if err != nil { return nil, err } @@ -173,7 +179,7 @@ func decryptConfigFile(saveConfig bool) ([]byte, error) { return nil, err } - plainText, err := common.DecryptData(cipherText, secOpts.PassPhrase) + plainText, err := common.DecryptData(cipherText, encryptedPassphrase) if err != nil { return nil, err } @@ -231,7 +237,7 @@ func init() { secureCmd.PersistentFlags().StringVar(&secOpts.ConfigFile, "config-file", "", "Configuration file to be encrypted / decrypted") - secureCmd.PersistentFlags().StringVar(&secOpts.PassPhrase, "passphrase", "", + secureCmd.PersistentFlags().BytesBase64Var(&secOpts.PassPhrase, "passphrase", []byte(""), "Base64 encoded key to decrypt config file. Can also be specified by env-variable CLOUDFUSE_SECURE_CONFIG_PASSPHRASE.\n Decoded key length shall be 16 (AES-128), 24 (AES-192), or 32 (AES-256) bytes in length.") secureCmd.PersistentFlags().StringVar(&secOpts.OutputFile, "output-file", "", diff --git a/cmd/secure_set.go b/cmd/secure_set.go index 7453f7de6..e22607337 100644 --- a/cmd/secure_set.go +++ b/cmd/secure_set.go @@ -84,7 +84,7 @@ var setKeyCmd = &cobra.Command{ return fmt.Errorf("failed to marshal config [%s]", err.Error()) } - cipherText, err := common.EncryptData(confStream, secOpts.PassPhrase) + cipherText, err := common.EncryptData(confStream, encryptedPassphrase) if err != nil { return fmt.Errorf("failed to encrypt config [%s]", err.Error()) } diff --git a/cmd/secure_test.go b/cmd/secure_test.go index b1b7a81b3..63517cc81 100644 --- a/cmd/secure_test.go +++ b/cmd/secure_test.go @@ -27,6 +27,7 @@ package cmd import ( "bytes" + "encoding/base64" "fmt" "os" "testing" @@ -112,6 +113,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigEncrypt() { defer suite.cleanupTest() confFile, _ := os.CreateTemp("", "conf*.yaml") outFile, _ := os.CreateTemp("", "conf*.yaml") + passphrase := base64.StdEncoding.EncodeToString([]byte("12312312312312312312312312312312")) defer os.Remove(confFile.Name()) defer os.Remove(outFile.Name()) @@ -121,7 +123,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigEncrypt() { confFile.Close() - _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), "--passphrase=12312312312312312312312312312312", fmt.Sprintf("--output-file=%s", outFile.Name())) + _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=%s", outFile.Name())) suite.assert.NoError(err) // Config file should be deleted @@ -133,6 +135,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigEncryptNoOutfile() { defer suite.cleanupTest() confFile, _ := os.CreateTemp("", "conf*.yaml") outFile := confFile.Name() + SecureConfigExtension + passphrase := base64.StdEncoding.EncodeToString([]byte("12312312312312312312312312312312")) defer os.Remove(confFile.Name()) defer os.Remove(outFile) @@ -142,7 +145,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigEncryptNoOutfile() { confFile.Close() - _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), "--passphrase=12312312312312312312312312312312") + _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase)) suite.assert.NoError(err) // Config file should be deleted @@ -157,7 +160,8 @@ func (suite *secureConfigTestSuite) TestSecureConfigEncryptNoOutfile() { func (suite *secureConfigTestSuite) TestSecureConfigEncryptNotExistent() { defer suite.cleanupTest() confFile := "abcd.yaml" - _, err := executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile), "--passphrase=12312312312312312312312312312312") + passphrase := base64.StdEncoding.EncodeToString([]byte("12312312312312312312312312312312")) + _, err := executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile), fmt.Sprintf("--passphrase=%s", passphrase)) suite.assert.Error(err) } @@ -187,6 +191,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigEncryptInvalidKey() { defer suite.cleanupTest() confFile, _ := os.CreateTemp("", "conf*.yaml") outFile, _ := os.CreateTemp("", "conf*.yaml") + passphrase := base64.StdEncoding.EncodeToString([]byte("123")) defer os.Remove(confFile.Name()) defer os.Remove(outFile.Name()) @@ -197,7 +202,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigEncryptInvalidKey() { confFile.Close() outFile.Close() - _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), "--passphrase=123", fmt.Sprintf("--output-file=%s", outFile.Name())) + _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=%s", outFile.Name())) suite.assert.Error(err) } @@ -205,6 +210,8 @@ func (suite *secureConfigTestSuite) TestSecureConfigDecrypt() { defer suite.cleanupTest() confFile, _ := os.CreateTemp("", "conf*.yaml") outFile, _ := os.CreateTemp("", "conf*.yaml") + passphrase := base64.StdEncoding.EncodeToString([]byte("12312312312312312312312312312312")) + fmt.Println(passphrase) defer os.Remove(confFile.Name()) defer os.Remove(outFile.Name()) @@ -215,14 +222,14 @@ func (suite *secureConfigTestSuite) TestSecureConfigDecrypt() { confFile.Close() outFile.Close() - _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), "--passphrase=12312312312312312312312312312312", fmt.Sprintf("--output-file=%s", outFile.Name())) + _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=%s", outFile.Name())) suite.assert.NoError(err) // Config file should be deleted _, err = os.Stat(confFile.Name()) suite.assert.Error(err) - _, err = executeCommandSecure(rootCmd, "secure", "decrypt", fmt.Sprintf("--config-file=%s", outFile.Name()), "--passphrase=12312312312312312312312312312312", fmt.Sprintf("--output-file=./tmp.yaml")) + _, err = executeCommandSecure(rootCmd, "secure", "decrypt", fmt.Sprintf("--config-file=%s", outFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=./tmp.yaml")) suite.assert.NoError(err) data, err := os.ReadFile("./tmp.yaml") @@ -238,6 +245,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigDecryptNoOutputFile() { defer suite.cleanupTest() confFile, _ := os.CreateTemp("", "conf*.yaml") outFile := confFile.Name() + SecureConfigExtension + passphrase := base64.StdEncoding.EncodeToString([]byte("12312312312312312312312312312312")) defer os.Remove(confFile.Name()) @@ -246,7 +254,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigDecryptNoOutputFile() { confFile.Close() - _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), "--passphrase=12312312312312312312312312312312") + _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase)) suite.assert.NoError(err) // Config file should be deleted @@ -257,7 +265,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigDecryptNoOutputFile() { _, err = os.Stat(outFile) suite.assert.NoError(err) - _, err = executeCommandSecure(rootCmd, "secure", "decrypt", fmt.Sprintf("--config-file=%s", outFile), "--passphrase=12312312312312312312312312312312") + _, err = executeCommandSecure(rootCmd, "secure", "decrypt", fmt.Sprintf("--config-file=%s", outFile), fmt.Sprintf("--passphrase=%s", passphrase)) suite.assert.NoError(err) // Config file should exist @@ -298,6 +306,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigGet() { defer suite.cleanupTest() confFile, _ := os.CreateTemp("", "conf*.yaml") outFile, _ := os.CreateTemp("", "conf*.yaml") + passphrase := base64.StdEncoding.EncodeToString([]byte("12312312312312312312312312312312")) defer os.Remove(confFile.Name()) defer os.Remove(outFile.Name()) @@ -308,10 +317,10 @@ func (suite *secureConfigTestSuite) TestSecureConfigGet() { confFile.Close() outFile.Close() - _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), "--passphrase=12312312312312312312312312312312", fmt.Sprintf("--output-file=%s", outFile.Name())) + _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=%s", outFile.Name())) suite.assert.NoError(err) - _, err = executeCommandSecure(rootCmd, "secure", "get", fmt.Sprintf("--config-file=%s", outFile.Name()), "--passphrase=12312312312312312312312312312312", "--key=logging.level") + _, err = executeCommandSecure(rootCmd, "secure", "get", fmt.Sprintf("--config-file=%s", outFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), "--key=logging.level") suite.assert.NoError(err) } @@ -319,6 +328,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigGetInvalidKey() { defer suite.cleanupTest() confFile, _ := os.CreateTemp("", "conf*.yaml") outFile, _ := os.CreateTemp("", "conf*.yaml") + passphrase := base64.StdEncoding.EncodeToString([]byte("12312312312312312312312312312312")) defer os.Remove(confFile.Name()) defer os.Remove(outFile.Name()) @@ -329,10 +339,10 @@ func (suite *secureConfigTestSuite) TestSecureConfigGetInvalidKey() { confFile.Close() outFile.Close() - _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), "--passphrase=12312312312312312312312312312312", fmt.Sprintf("--output-file=%s", outFile.Name())) + _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=%s", outFile.Name())) suite.assert.NoError(err) - _, err = executeCommandSecure(rootCmd, "secure", "get", fmt.Sprintf("--config-file=%s", outFile.Name()), "--passphrase=12312312312312312312312312312312", "--key=abcd.efg") + _, err = executeCommandSecure(rootCmd, "secure", "get", fmt.Sprintf("--config-file=%s", outFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), "--key=abcd.efg") suite.assert.Error(err) } @@ -340,6 +350,7 @@ func (suite *secureConfigTestSuite) TestSecureConfigSet() { defer suite.cleanupTest() confFile, _ := os.CreateTemp("", "conf*.yaml") outFile, _ := os.CreateTemp("", "conf*.yaml") + passphrase := base64.StdEncoding.EncodeToString([]byte("12312312312312312312312312312312")) defer os.Remove(confFile.Name()) defer os.Remove(outFile.Name()) @@ -350,15 +361,15 @@ func (suite *secureConfigTestSuite) TestSecureConfigSet() { confFile.Close() outFile.Close() - _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), "--passphrase=12312312312312312312312312312312", fmt.Sprintf("--output-file=%s", outFile.Name())) + _, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=%s", outFile.Name())) suite.assert.NoError(err) - _, err = executeCommandSecure(rootCmd, "secure", "get", fmt.Sprintf("--config-file=%s", outFile.Name()), "--passphrase=12312312312312312312312312312312", "--key=logging.level") + _, err = executeCommandSecure(rootCmd, "secure", "get", fmt.Sprintf("--config-file=%s", outFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), "--key=logging.level") suite.assert.NoError(err) - _, err = executeCommandSecure(rootCmd, "secure", "set", fmt.Sprintf("--config-file=%s", outFile.Name()), "--passphrase=12312312312312312312312312312312", "--key=logging.level", "--value=log_err") + _, err = executeCommandSecure(rootCmd, "secure", "set", fmt.Sprintf("--config-file=%s", outFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), "--key=logging.level", "--value=log_err") suite.assert.NoError(err) - _, err = executeCommandSecure(rootCmd, "secure", "get", fmt.Sprintf("--config-file=%s", outFile.Name()), "--passphrase=12312312312312312312312312312312", "--key=logging.level") + _, err = executeCommandSecure(rootCmd, "secure", "get", fmt.Sprintf("--config-file=%s", outFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), "--key=logging.level") suite.assert.NoError(err) } diff --git a/common/config/config_parser.go b/common/config/config_parser.go index 46ec712b3..326d587ce 100644 --- a/common/config/config_parser.go +++ b/common/config/config_parser.go @@ -34,6 +34,7 @@ import ( "github.com/Seagate/cloudfuse/common" "github.com/Seagate/cloudfuse/common/log" + "github.com/awnumar/memguard" "github.com/spf13/cobra" @@ -73,12 +74,12 @@ type options struct { envTree *Tree completionFuncMap map[string]func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) secureConfig bool - passphrase string + passphrase *memguard.Enclave } var userOptions options -func SetSecureConfigOptions(passphrase string) { +func SetSecureConfigOptions(passphrase *memguard.Enclave) { userOptions.secureConfig = true userOptions.passphrase = passphrase } @@ -123,7 +124,7 @@ func ReadFromConfigBuffer(configData []byte) error { return nil } -func DecryptConfigFile(fileName string, passphrase string) error { +func DecryptConfigFile(fileName string, passphrase *memguard.Enclave) error { cipherText, err := os.ReadFile(fileName) if err != nil { return fmt.Errorf("Failed to read encrypted config file [%s]", err.Error()) diff --git a/common/config/config_test.go b/common/config/config_test.go index 779fd1b43..2cb40483a 100644 --- a/common/config/config_test.go +++ b/common/config/config_test.go @@ -31,6 +31,7 @@ import ( "testing" "github.com/Seagate/cloudfuse/common" + "github.com/awnumar/memguard" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -464,12 +465,15 @@ func (suite *ConfigTestSuite) TestConfigFileDescryption() { assert.NoError(err) assert.NotNil(plaintext) - cipherText, err := common.EncryptData(plaintext, "12312312312312312312312312312312") + passphrase := memguard.NewBufferFromBytes([]byte("12312312312312312312312312312312")) + encryptedPassphrase := passphrase.Seal() + + cipherText, err := common.EncryptData(plaintext, encryptedPassphrase) assert.NoError(err) err = os.WriteFile("test_enc.yaml", cipherText, 0644) assert.NoError(err) - err = DecryptConfigFile("test_enc.yaml", "12312312312312312312312312312312") + err = DecryptConfigFile("test_enc.yaml", encryptedPassphrase) assert.NoError(err) _ = os.Remove("test.yaml") diff --git a/common/util.go b/common/util.go index fcea6c699..f78295e0a 100644 --- a/common/util.go +++ b/common/util.go @@ -43,6 +43,7 @@ import ( "strings" "sync" + "github.com/awnumar/memguard" "gopkg.in/ini.v1" ) @@ -173,8 +174,10 @@ func NormalizeObjectName(name string) string { } // Encrypt given data using the key provided -func EncryptData(plainData []byte, key string) ([]byte, error) { - binaryKey, err := base64.StdEncoding.DecodeString(key) +func EncryptData(plainData []byte, key *memguard.Enclave) ([]byte, error) { + secretKey, _ := key.Open() + defer secretKey.Destroy() + binaryKey, err := base64.StdEncoding.DecodeString(secretKey.String()) if err != nil { return nil, fmt.Errorf("failed to base64 decode passphrase [%s]", err.Error()) } @@ -199,8 +202,10 @@ func EncryptData(plainData []byte, key string) ([]byte, error) { } // Decrypt given data using the key provided -func DecryptData(cipherData []byte, key string) ([]byte, error) { - binaryKey, err := base64.StdEncoding.DecodeString(key) +func DecryptData(cipherData []byte, key *memguard.Enclave) ([]byte, error) { + secretKey, _ := key.Open() + defer secretKey.Destroy() + binaryKey, err := base64.StdEncoding.DecodeString(secretKey.String()) if err != nil { return nil, fmt.Errorf("failed to base64 decode passphrase [%s]", err.Error()) } diff --git a/common/util_test.go b/common/util_test.go index ee069aa35..564f12555 100644 --- a/common/util_test.go +++ b/common/util_test.go @@ -34,6 +34,7 @@ import ( "runtime" "testing" + "github.com/awnumar/memguard" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -82,10 +83,13 @@ func (suite *typesTestSuite) TestEncryptBadKey() { key := make([]byte, 20) rand.Read(key) + passphrase := memguard.NewBufferFromBytes(key) + encryptedPassphrase := passphrase.Seal() + data := make([]byte, 1024) rand.Read(data) - _, err := EncryptData(data, string(key)) + _, err := EncryptData(data, encryptedPassphrase) suite.assert.Error(err) } @@ -94,10 +98,13 @@ func (suite *typesTestSuite) TestDecryptBadKey() { key := make([]byte, 20) rand.Read(key) + passphrase := memguard.NewBufferFromBytes(key) + encryptedPassphrase := passphrase.Seal() + data := make([]byte, 1024) rand.Read(data) - _, err := DecryptData(data, string(key)) + _, err := DecryptData(data, encryptedPassphrase) suite.assert.Error(err) } @@ -107,13 +114,16 @@ func (suite *typesTestSuite) TestEncryptDecrypt16() { rand.Read(binaryKey) key := base64.StdEncoding.EncodeToString(binaryKey) + passphrase := memguard.NewBufferFromBytes([]byte(key)) + encryptedPassphrase := passphrase.Seal() + data := make([]byte, 1024) rand.Read(data) - cipher, err := EncryptData(data, key) + cipher, err := EncryptData(data, encryptedPassphrase) suite.assert.NoError(err) - d, err := DecryptData(cipher, key) + d, err := DecryptData(cipher, encryptedPassphrase) suite.assert.NoError(err) suite.assert.EqualValues(data, d) } @@ -124,13 +134,16 @@ func (suite *typesTestSuite) TestEncryptDecrypt24() { rand.Read(binaryKey) key := base64.StdEncoding.EncodeToString(binaryKey) + passphrase := memguard.NewBufferFromBytes([]byte(key)) + encryptedPassphrase := passphrase.Seal() + data := make([]byte, 1024) rand.Read(data) - cipher, err := EncryptData(data, key) + cipher, err := EncryptData(data, encryptedPassphrase) suite.assert.NoError(err) - d, err := DecryptData(cipher, key) + d, err := DecryptData(cipher, encryptedPassphrase) suite.assert.NoError(err) suite.assert.EqualValues(data, d) } @@ -141,13 +154,16 @@ func (suite *typesTestSuite) TestEncryptDecrypt32() { rand.Read(binaryKey) key := base64.StdEncoding.EncodeToString(binaryKey) + passphrase := memguard.NewBufferFromBytes([]byte(key)) + encryptedPassphrase := passphrase.Seal() + data := make([]byte, 1024) rand.Read(data) - cipher, err := EncryptData(data, key) + cipher, err := EncryptData(data, encryptedPassphrase) suite.assert.NoError(err) - d, err := DecryptData(cipher, key) + d, err := DecryptData(cipher, encryptedPassphrase) suite.assert.NoError(err) suite.assert.EqualValues(data, d) } diff --git a/component/s3storage/s3storage.go b/component/s3storage/s3storage.go index 8b5cae70a..ff51100c1 100644 --- a/component/s3storage/s3storage.go +++ b/component/s3storage/s3storage.go @@ -92,6 +92,7 @@ func (s3 *S3Storage) Configure(isParent bool) error { memguard.ScrambleBytes([]byte(viper.GetString("s3storage.key-id"))) encryptedKeyID := dataBuf.Seal() + dataBuf.Destroy() if encryptedKeyID == nil { return fmt.Errorf("S3Storage::Configure : unable to store key-id securely") } @@ -103,6 +104,7 @@ func (s3 *S3Storage) Configure(isParent bool) error { memguard.ScrambleBytes([]byte(viper.GetString("s3storage.secret-key"))) encryptedSecretKey := dataBuf.Seal() + dataBuf.Destroy() if encryptedSecretKey == nil { return fmt.Errorf("S3Storage::Configure : unable to store secret-key securely") } From 9fea13c906a23809237b524ff6287097314acc0b Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Mon, 15 Jul 2024 12:10:19 -0600 Subject: [PATCH 3/9] Cleanup changes --- cmd/secure.go | 2 +- common/util.go | 4 ++-- component/s3storage/s3storage.go | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd/secure.go b/cmd/secure.go index 3907b8d6a..9b702cde3 100644 --- a/cmd/secure.go +++ b/cmd/secure.go @@ -133,7 +133,7 @@ func validateOptions() error { passphrase := memguard.NewBufferFromBytes([]byte(secOpts.PassPhrase)) memguard.ScrambleBytes(secOpts.PassPhrase) encryptedPassphrase = passphrase.Seal() - defer passphrase.Destroy() + passphrase.Destroy() if secOpts.ConfigFile == "" { return errors.New("config file not provided, check usage") diff --git a/common/util.go b/common/util.go index f78295e0a..377fadb63 100644 --- a/common/util.go +++ b/common/util.go @@ -176,8 +176,8 @@ func NormalizeObjectName(name string) string { // Encrypt given data using the key provided func EncryptData(plainData []byte, key *memguard.Enclave) ([]byte, error) { secretKey, _ := key.Open() - defer secretKey.Destroy() binaryKey, err := base64.StdEncoding.DecodeString(secretKey.String()) + secretKey.Destroy() if err != nil { return nil, fmt.Errorf("failed to base64 decode passphrase [%s]", err.Error()) } @@ -204,8 +204,8 @@ func EncryptData(plainData []byte, key *memguard.Enclave) ([]byte, error) { // Decrypt given data using the key provided func DecryptData(cipherData []byte, key *memguard.Enclave) ([]byte, error) { secretKey, _ := key.Open() - defer secretKey.Destroy() binaryKey, err := base64.StdEncoding.DecodeString(secretKey.String()) + secretKey.Destroy() if err != nil { return nil, fmt.Errorf("failed to base64 decode passphrase [%s]", err.Error()) } diff --git a/component/s3storage/s3storage.go b/component/s3storage/s3storage.go index ff51100c1..1e657771d 100644 --- a/component/s3storage/s3storage.go +++ b/component/s3storage/s3storage.go @@ -89,10 +89,9 @@ func (s3 *S3Storage) Configure(isParent bool) error { // Securely store key-id and secret-key in enclave if viper.GetString("s3storage.key-id") != "" { dataBuf := memguard.NewBufferFromBytes([]byte(viper.GetString("s3storage.key-id"))) - memguard.ScrambleBytes([]byte(viper.GetString("s3storage.key-id"))) - encryptedKeyID := dataBuf.Seal() dataBuf.Destroy() + if encryptedKeyID == nil { return fmt.Errorf("S3Storage::Configure : unable to store key-id securely") } From 76b5be2d6267f1c0fa1a9868681b1ebb7d8aea3e Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:53:24 -0600 Subject: [PATCH 4/9] Cleanup memguard --- cmd/config-gen.go | 5 +---- cmd/mount.go | 5 +---- cmd/mount_all.go | 5 +---- cmd/secure.go | 5 +---- common/util.go | 25 +++++++++++++++++++++---- component/s3storage/client.go | 10 ++++++++-- component/s3storage/s3storage.go | 9 ++------- 7 files changed, 35 insertions(+), 29 deletions(-) diff --git a/cmd/config-gen.go b/cmd/config-gen.go index a2310f432..52b868498 100644 --- a/cmd/config-gen.go +++ b/cmd/config-gen.go @@ -121,10 +121,7 @@ var generateConfig = &cobra.Command{ var templateConfig []byte var err error - passphrase := memguard.NewBufferFromBytes([]byte(options.PassPhrase)) - memguard.ScrambleBytes(options.PassPhrase) - encryptedPassphrase = passphrase.Seal() - passphrase.Destroy() + encryptedPassphrase = memguard.NewEnclave([]byte(opts.passphrase)) templateConfig, err = os.ReadFile(opts.configFilePath) if err != nil { diff --git a/cmd/mount.go b/cmd/mount.go index b57e0ce81..b5a78c4fc 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -214,10 +214,7 @@ func parseConfig() error { } } - passphrase := memguard.NewBufferFromBytes([]byte(options.PassPhrase)) - memguard.ScrambleBytes(options.PassPhrase) - encryptedPassphrase = passphrase.Seal() - passphrase.Destroy() + encryptedPassphrase = memguard.NewEnclave([]byte(options.PassPhrase)) cipherText, err := os.ReadFile(options.ConfigFile) if err != nil { diff --git a/cmd/mount_all.go b/cmd/mount_all.go index 96b3a80cb..5da2d57da 100644 --- a/cmd/mount_all.go +++ b/cmd/mount_all.go @@ -174,10 +174,7 @@ func processCommand() error { } } - passphrase := memguard.NewBufferFromBytes([]byte(options.PassPhrase)) - memguard.ScrambleBytes(options.PassPhrase) - encryptedPassphrase = passphrase.Seal() - passphrase.Destroy() + encryptedPassphrase = memguard.NewEnclave([]byte(options.PassPhrase)) } var containerList []string diff --git a/cmd/secure.go b/cmd/secure.go index 9b702cde3..cc932f838 100644 --- a/cmd/secure.go +++ b/cmd/secure.go @@ -130,10 +130,7 @@ func validateOptions() error { return fmt.Errorf("passphrase is not valid base64 encoded [%s]", err.Error()) } - passphrase := memguard.NewBufferFromBytes([]byte(secOpts.PassPhrase)) - memguard.ScrambleBytes(secOpts.PassPhrase) - encryptedPassphrase = passphrase.Seal() - passphrase.Destroy() + encryptedPassphrase = memguard.NewEnclave([]byte(secOpts.PassPhrase)) if secOpts.ConfigFile == "" { return errors.New("config file not provided, check usage") diff --git a/common/util.go b/common/util.go index 377fadb63..cae5ff75c 100644 --- a/common/util.go +++ b/common/util.go @@ -30,6 +30,7 @@ import ( "crypto/cipher" "crypto/rand" "encoding/base64" + "errors" "fmt" "io" "os" @@ -175,9 +176,17 @@ func NormalizeObjectName(name string) string { // Encrypt given data using the key provided func EncryptData(plainData []byte, key *memguard.Enclave) ([]byte, error) { - secretKey, _ := key.Open() + if key == nil { + return nil, errors.New("provided passphrase key is empty") + } + + secretKey, err := key.Open() + if err != nil || secretKey == nil { + return nil, errors.New("unable to decrypt passphrase key") + } + defer secretKey.Destroy() + binaryKey, err := base64.StdEncoding.DecodeString(secretKey.String()) - secretKey.Destroy() if err != nil { return nil, fmt.Errorf("failed to base64 decode passphrase [%s]", err.Error()) } @@ -203,9 +212,17 @@ func EncryptData(plainData []byte, key *memguard.Enclave) ([]byte, error) { // Decrypt given data using the key provided func DecryptData(cipherData []byte, key *memguard.Enclave) ([]byte, error) { - secretKey, _ := key.Open() + if key == nil { + return nil, errors.New("provided passphrase key is empty") + } + + secretKey, err := key.Open() + if err != nil || secretKey == nil { + return nil, errors.New("unable to decrypt passphrase key") + } + defer secretKey.Destroy() + binaryKey, err := base64.StdEncoding.DecodeString(secretKey.String()) - secretKey.Destroy() if err != nil { return nil, fmt.Errorf("failed to base64 decode passphrase [%s]", err.Error()) } diff --git a/component/s3storage/client.go b/component/s3storage/client.go index 90735243d..12ea4ee21 100644 --- a/component/s3storage/client.go +++ b/component/s3storage/client.go @@ -97,9 +97,15 @@ func (cl *Client) Configure(cfg Config) error { var credentialsProvider aws.CredentialsProvider if cl.Config.authConfig.KeyID != nil && cl.Config.authConfig.SecretKey != nil { - keyID, _ := cl.Config.authConfig.KeyID.Open() + keyID, err := cl.Config.authConfig.KeyID.Open() + if err != nil || keyID == nil { + return errors.New("unable to decrypt key id") + } defer keyID.Destroy() - secretKey, _ := cl.Config.authConfig.SecretKey.Open() + secretKey, err := cl.Config.authConfig.SecretKey.Open() + if err != nil || secretKey == nil { + return errors.New("unable to decrypt secret key") + } defer secretKey.Destroy() credentialsInConfig := keyID.String() != "" && secretKey.String() != "" if credentialsInConfig { diff --git a/component/s3storage/s3storage.go b/component/s3storage/s3storage.go index 1e657771d..cf0556cf0 100644 --- a/component/s3storage/s3storage.go +++ b/component/s3storage/s3storage.go @@ -88,9 +88,7 @@ func (s3 *S3Storage) Configure(isParent bool) error { secrets := ConfigSecrets{} // Securely store key-id and secret-key in enclave if viper.GetString("s3storage.key-id") != "" { - dataBuf := memguard.NewBufferFromBytes([]byte(viper.GetString("s3storage.key-id"))) - encryptedKeyID := dataBuf.Seal() - dataBuf.Destroy() + encryptedKeyID := memguard.NewEnclave([]byte(viper.GetString("s3storage.key-id"))) if encryptedKeyID == nil { return fmt.Errorf("S3Storage::Configure : unable to store key-id securely") @@ -99,11 +97,8 @@ func (s3 *S3Storage) Configure(isParent bool) error { } if viper.GetString("s3storage.secret-key") != "" { - dataBuf := memguard.NewBufferFromBytes([]byte(viper.GetString("s3storage.secret-key"))) - memguard.ScrambleBytes([]byte(viper.GetString("s3storage.secret-key"))) + encryptedSecretKey := memguard.NewEnclave([]byte(viper.GetString("s3storage.secret-key"))) - encryptedSecretKey := dataBuf.Seal() - dataBuf.Destroy() if encryptedSecretKey == nil { return fmt.Errorf("S3Storage::Configure : unable to store secret-key securely") } From 470ef9e45612f9ac97f52fb81541d1120b8080f2 Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:53:39 -0600 Subject: [PATCH 5/9] Add memguard for azure --- component/azstorage/azauth.go | 13 ++++++-- component/azstorage/azauth_test.go | 41 +++++++++++++++++--------- component/azstorage/azauthkey.go | 20 ++++++++++--- component/azstorage/azauthsas.go | 19 +++++++++--- component/azstorage/azauthspn.go | 18 ++++++++++- component/azstorage/block_blob_test.go | 3 +- component/azstorage/config.go | 21 ++++++++++--- component/azstorage/config_test.go | 10 ++++--- component/azstorage/datalake_test.go | 3 +- component/azstorage/utils.go | 10 ++++--- component/azstorage/utils_test.go | 14 +++++---- 11 files changed, 126 insertions(+), 46 deletions(-) diff --git a/component/azstorage/azauth.go b/component/azstorage/azauth.go index ae778da81..fd5b23e8e 100644 --- a/component/azstorage/azauth.go +++ b/component/azstorage/azauth.go @@ -29,6 +29,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Seagate/cloudfuse/common/log" + "github.com/awnumar/memguard" ) // AzAuthConfig : Config to authenticate to storage @@ -40,10 +41,10 @@ type azAuthConfig struct { AuthMode AuthType // Key config - AccountKey string + AccountKey *memguard.Enclave // SAS config - SASKey string + SASKey *memguard.Enclave // MSI config ApplicationID string @@ -53,7 +54,7 @@ type azAuthConfig struct { // SPN config TenantID string ClientID string - ClientSecret string + ClientSecret *memguard.Enclave OAuthTokenFilePath string ActiveDirectoryEndpoint string @@ -92,6 +93,9 @@ func getAzAuth(config azAuthConfig) azAuth { func getAzBlobAuth(config azAuthConfig) azAuth { base := azAuthBase{config: config} + base.config.AccountKey = config.AccountKey + base.config.SASKey = config.SASKey + base.config.ClientSecret = config.ClientSecret if config.AuthMode == EAuthType.KEY() { return &azAuthBlobKey{ azAuthKey{ @@ -130,6 +134,9 @@ func getAzBlobAuth(config azAuthConfig) azAuth { func getAzDatalakeAuth(config azAuthConfig) azAuth { base := azAuthBase{config: config} + base.config.AccountKey = config.AccountKey + base.config.SASKey = config.SASKey + base.config.ClientSecret = config.ClientSecret if config.AuthMode == EAuthType.KEY() { return &azAuthDatalakeKey{ azAuthKey{ diff --git a/component/azstorage/azauth_test.go b/component/azstorage/azauth_test.go index eda4b277a..6bcf52813 100644 --- a/component/azstorage/azauth_test.go +++ b/component/azstorage/azauth_test.go @@ -37,6 +37,7 @@ import ( "github.com/Seagate/cloudfuse/common" "github.com/Seagate/cloudfuse/common/log" + "github.com/awnumar/memguard" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -114,13 +115,15 @@ func generateEndpoint(useHttp bool, accountName string, accountType AccountType) func (suite *authTestSuite) TestBlockInvalidAuth() { defer suite.cleanupTest() + dataBuf := memguard.NewBufferFromBytes([]byte(storageTestConfigurationParameters.BlockKey)) + encryptedKey := dataBuf.Seal() stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.BlockContainer, authConfig: azAuthConfig{ AuthMode: EAuthType.INVALID_AUTH(), AccountType: EAccountType.BLOCK(), AccountName: storageTestConfigurationParameters.BlockAccount, - AccountKey: storageTestConfigurationParameters.BlockKey, + AccountKey: encryptedKey, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.BlockAccount, EAccountType.BLOCK()), }, } @@ -136,13 +139,15 @@ func (suite *authTestSuite) TestBlockInvalidAuth() { func (suite *authTestSuite) TestAdlsInvalidAuth() { defer suite.cleanupTest() + dataBuf := memguard.NewBufferFromBytes([]byte(storageTestConfigurationParameters.BlockKey)) + encryptedKey := dataBuf.Seal() stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.AdlsContainer, authConfig: azAuthConfig{ AuthMode: EAuthType.INVALID_AUTH(), AccountType: EAccountType.ADLS(), AccountName: storageTestConfigurationParameters.AdlsAccount, - AccountKey: storageTestConfigurationParameters.AdlsKey, + AccountKey: encryptedKey, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.AdlsAccount, EAccountType.ADLS()), }, } @@ -158,13 +163,15 @@ func (suite *authTestSuite) TestAdlsInvalidAuth() { func (suite *authTestSuite) TestInvalidAccountType() { defer suite.cleanupTest() + dataBuf := memguard.NewBufferFromBytes([]byte(storageTestConfigurationParameters.BlockKey)) + encryptedKey := dataBuf.Seal() stgConfig := AzStorageConfig{ - container: storageTestConfigurationParameters.BlockContainer, + container: storageTestConfigurationParameters.AdlsContainer, authConfig: azAuthConfig{ AuthMode: EAuthType.KEY(), AccountType: EAccountType.INVALID_ACC(), AccountName: storageTestConfigurationParameters.BlockAccount, - AccountKey: storageTestConfigurationParameters.BlockKey, + AccountKey: encryptedKey, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.BlockAccount, EAccountType.BLOCK()), }, } @@ -183,7 +190,7 @@ func (suite *authTestSuite) TestBlockInvalidSharedKey() { AuthMode: EAuthType.KEY(), AccountType: EAccountType.BLOCK(), AccountName: storageTestConfigurationParameters.BlockAccount, - AccountKey: "", + AccountKey: nil, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.BlockAccount, EAccountType.BLOCK()), }, } @@ -199,13 +206,15 @@ func (suite *authTestSuite) TestBlockInvalidSharedKey() { func (suite *authTestSuite) TestBlockInvalidSharedKey2() { defer suite.cleanupTest() + dataBuf := memguard.NewBufferFromBytes([]byte("abcd>=")) + encryptedKey := dataBuf.Seal() stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.BlockContainer, authConfig: azAuthConfig{ AuthMode: EAuthType.KEY(), AccountType: EAccountType.BLOCK(), AccountName: storageTestConfigurationParameters.BlockAccount, - AccountKey: "abcd>=", // string that will fail to base64 decode + AccountKey: encryptedKey, // string that will fail to base64 decode Endpoint: generateEndpoint(false, storageTestConfigurationParameters.BlockAccount, EAccountType.BLOCK()), }, } @@ -221,13 +230,15 @@ func (suite *authTestSuite) TestBlockInvalidSharedKey2() { func (suite *authTestSuite) TestBlockSharedKey() { defer suite.cleanupTest() + dataBuf := memguard.NewBufferFromBytes([]byte(storageTestConfigurationParameters.BlockKey)) + encryptedKey := dataBuf.Seal() stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.BlockContainer, authConfig: azAuthConfig{ AuthMode: EAuthType.KEY(), AccountType: EAccountType.BLOCK(), AccountName: storageTestConfigurationParameters.BlockAccount, - AccountKey: storageTestConfigurationParameters.BlockKey, + AccountKey: encryptedKey, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.BlockAccount, EAccountType.BLOCK()), }, } @@ -258,7 +269,7 @@ func (suite *authTestSuite) TestAdlsInvalidSharedKey() { AuthMode: EAuthType.KEY(), AccountType: EAccountType.ADLS(), AccountName: storageTestConfigurationParameters.AdlsAccount, - AccountKey: "", + AccountKey: nil, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.AdlsAccount, EAccountType.ADLS()), }, } @@ -274,13 +285,15 @@ func (suite *authTestSuite) TestAdlsInvalidSharedKey() { func (suite *authTestSuite) TestAdlsSharedKey() { defer suite.cleanupTest() + dataBuf := memguard.NewBufferFromBytes([]byte(storageTestConfigurationParameters.AdlsKey)) + encryptedKey := dataBuf.Seal() stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.AdlsContainer, authConfig: azAuthConfig{ AuthMode: EAuthType.KEY(), AccountType: EAccountType.ADLS(), AccountName: storageTestConfigurationParameters.AdlsAccount, - AccountKey: storageTestConfigurationParameters.AdlsKey, + AccountKey: encryptedKey, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.AdlsAccount, EAccountType.ADLS()), }, } @@ -311,7 +324,7 @@ func (suite *authTestSuite) TestBlockInvalidSasKey() { AuthMode: EAuthType.SAS(), AccountType: EAccountType.BLOCK(), AccountName: storageTestConfigurationParameters.BlockAccount, - SASKey: "", + SASKey: nil, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.BlockAccount, EAccountType.BLOCK()), }, } @@ -440,7 +453,7 @@ func (suite *authTestSuite) TestAdlsInvalidSasKey() { AuthMode: EAuthType.SAS(), AccountType: EAccountType.ADLS(), AccountName: storageTestConfigurationParameters.AdlsAccount, - SASKey: "", + SASKey: nil, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.AdlsAccount, EAccountType.ADLS()), }, } @@ -660,7 +673,7 @@ func (suite *authTestSuite) TestBlockInvalidSpn() { AccountName: storageTestConfigurationParameters.BlockAccount, ClientID: storageTestConfigurationParameters.SpnClientId, TenantID: storageTestConfigurationParameters.SpnTenantId, - ClientSecret: "", + ClientSecret: nil, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.BlockAccount, EAccountType.BLOCK()), }, } @@ -688,7 +701,7 @@ func (suite *authTestSuite) TestBlockInvalidTokenPathSpn() { AccountName: storageTestConfigurationParameters.BlockAccount, ClientID: storageTestConfigurationParameters.SpnClientId, TenantID: storageTestConfigurationParameters.SpnTenantId, - ClientSecret: "", + ClientSecret: nil, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.BlockAccount, EAccountType.BLOCK()), OAuthTokenFilePath: "newtoken.txt", }, @@ -728,7 +741,7 @@ func (suite *authTestSuite) TestAdlsInvalidSpn() { AccountName: storageTestConfigurationParameters.AdlsAccount, ClientID: storageTestConfigurationParameters.SpnClientId, TenantID: storageTestConfigurationParameters.SpnTenantId, - ClientSecret: "", + ClientSecret: nil, Endpoint: generateEndpoint(false, storageTestConfigurationParameters.AdlsAccount, EAccountType.ADLS()), }, } diff --git a/component/azstorage/azauthkey.go b/component/azstorage/azauthkey.go index ee52b9d5e..8beb5483e 100644 --- a/component/azstorage/azauthkey.go +++ b/component/azstorage/azauthkey.go @@ -50,12 +50,18 @@ type azAuthBlobKey struct { // getServiceClient : returns shared key based service client for blob func (azkey *azAuthBlobKey) getServiceClient(stConfig *AzStorageConfig) (interface{}, error) { - if azkey.config.AccountKey == "" { + if azkey.config.AccountKey == nil { log.Err("azAuthBlobKey::getServiceClient : Shared key for account is empty, cannot authenticate user") return nil, errors.New("shared key for account is empty, cannot authenticate user") } - cred, err := azblob.NewSharedKeyCredential(azkey.config.AccountName, azkey.config.AccountKey) + buff, err := azkey.config.AccountKey.Open() + if err != nil || buff == nil { + return nil, errors.New("unable to decrypt passphrase key") + } + //defer buff.Destroy() + + cred, err := azblob.NewSharedKeyCredential(azkey.config.AccountName, buff.String()) if err != nil { log.Err("azAuthBlobKey::getServiceClient : Failed to create shared key credential [%s]", err.Error()) return nil, err @@ -75,12 +81,18 @@ type azAuthDatalakeKey struct { // getServiceClient : returns shared key based service client for datalake func (azkey *azAuthDatalakeKey) getServiceClient(stConfig *AzStorageConfig) (interface{}, error) { - if azkey.config.AccountKey == "" { + if azkey.config.AccountKey == nil { log.Err("azAuthDatalakeKey::getServiceClient : Shared key for account is empty, cannot authenticate user") return nil, errors.New("shared key for account is empty, cannot authenticate user") } - cred, err := azdatalake.NewSharedKeyCredential(azkey.config.AccountName, azkey.config.AccountKey) + buff, err := azkey.config.AccountKey.Open() + if err != nil || buff == nil { + return nil, errors.New("unable to decrypt passphrase key") + } + // defer buff.Destroy() + + cred, err := azdatalake.NewSharedKeyCredential(azkey.config.AccountName, buff.String()) if err != nil { log.Err("azAuthDatalakeKey::getServiceClient : Failed to create shared key credential [%s]", err.Error()) return nil, err diff --git a/component/azstorage/azauthsas.go b/component/azstorage/azauthsas.go index 770e8a3df..b4eb65d89 100644 --- a/component/azstorage/azauthsas.go +++ b/component/azstorage/azauthsas.go @@ -30,6 +30,7 @@ import ( "strings" "github.com/Seagate/cloudfuse/common/log" + "github.com/awnumar/memguard" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" serviceBfs "github.com/Azure/azure-sdk-for-go/sdk/storage/azdatalake/service" @@ -46,13 +47,23 @@ type azAuthSAS struct { // SetOption : Sets the sas key information for the SAS auth. func (azsas *azAuthSAS) setOption(key, value string) { if key == "saskey" { - azsas.config.SASKey = value + azsas.config.SASKey = memguard.NewEnclave([]byte(value)) } } // GetEndpoint : Gets the SAS endpoint func (azsas *azAuthSAS) getEndpoint() string { - return azsas.config.Endpoint + "?" + strings.TrimLeft(azsas.config.SASKey, "?") + if azsas.config.SASKey != nil { + buff, err := azsas.config.SASKey.Open() + if err != nil || buff == nil { + return "" + } + defer buff.Destroy() + endpoint := azsas.config.Endpoint + "?" + strings.TrimLeft(buff.String(), "?") + return endpoint + } + + return "" } type azAuthBlobSAS struct { @@ -61,7 +72,7 @@ type azAuthBlobSAS struct { // getServiceClient : returns SAS based service client for blob func (azsas *azAuthBlobSAS) getServiceClient(stConfig *AzStorageConfig) (interface{}, error) { - if azsas.config.SASKey == "" { + if azsas.config.SASKey == nil { log.Err("azAuthBlobSAS::getServiceClient : SAS key for account is empty, cannot authenticate user") return nil, errors.New("sas key for account is empty, cannot authenticate user") } @@ -80,7 +91,7 @@ type azAuthDatalakeSAS struct { // getServiceClient : returns SAS based service client for datalake func (azsas *azAuthDatalakeSAS) getServiceClient(stConfig *AzStorageConfig) (interface{}, error) { - if azsas.config.SASKey == "" { + if azsas.config.SASKey == nil { log.Err("azAuthDatalakeSAS::getServiceClient : SAS key for account is empty, cannot authenticate user") return nil, errors.New("sas key for account is empty, cannot authenticate user") } diff --git a/component/azstorage/azauthspn.go b/component/azstorage/azauthspn.go index 40a6a5af6..e2bd1d19c 100644 --- a/component/azstorage/azauthspn.go +++ b/component/azstorage/azauthspn.go @@ -26,7 +26,10 @@ package azstorage import ( + "errors" + "github.com/Seagate/cloudfuse/common/log" + "github.com/awnumar/memguard" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" @@ -65,7 +68,20 @@ func (azspn *azAuthSPN) getTokenCredential() (azcore.TokenCredential, error) { } else { log.Trace("AzAuthSPN::getTokenCredential : Using client secret for fetching token") - cred, err = azidentity.NewClientSecretCredential(azspn.config.TenantID, azspn.config.ClientID, azspn.config.ClientSecret, &azidentity.ClientSecretCredentialOptions{ + var buff *memguard.LockedBuffer + if azspn.config.ClientSecret != nil { + buff, err = azspn.config.ClientSecret.Open() + if err != nil || buff == nil { + return nil, errors.New("unable to decrypt passphrase key") + } + defer buff.Destroy() + } else { + err := errors.New("AzAuthSPN::getTokenCredential : Client secret not provided for SPN") + log.Err(err.Error()) + return nil, err + } + + cred, err = azidentity.NewClientSecretCredential(azspn.config.TenantID, azspn.config.ClientID, buff.String(), &azidentity.ClientSecretCredentialOptions{ ClientOptions: clOpts, }) if err != nil { diff --git a/component/azstorage/block_blob_test.go b/component/azstorage/block_blob_test.go index a780d4126..425ab789c 100644 --- a/component/azstorage/block_blob_test.go +++ b/component/azstorage/block_blob_test.go @@ -285,7 +285,8 @@ func (s *blockBlobTestSuite) TestDefault() { s.assert.Equal(storageTestConfigurationParameters.BlockAccount, s.az.stConfig.authConfig.AccountName) s.assert.Equal(EAccountType.BLOCK(), s.az.stConfig.authConfig.AccountType) s.assert.False(s.az.stConfig.authConfig.UseHTTP) - s.assert.Equal(storageTestConfigurationParameters.BlockKey, s.az.stConfig.authConfig.AccountKey) + accountKey, _ := s.az.stConfig.authConfig.AccountKey.Open() + s.assert.Equal(storageTestConfigurationParameters.BlockKey, accountKey.String()) s.assert.Empty(s.az.stConfig.authConfig.SASKey) s.assert.Empty(s.az.stConfig.authConfig.ApplicationID) s.assert.Empty(s.az.stConfig.authConfig.ResourceID) diff --git a/component/azstorage/config.go b/component/azstorage/config.go index 4c7da4c89..9608834e4 100644 --- a/component/azstorage/config.go +++ b/component/azstorage/config.go @@ -33,6 +33,7 @@ import ( "github.com/Seagate/cloudfuse/common/config" "github.com/Seagate/cloudfuse/common/log" + "github.com/awnumar/memguard" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" @@ -433,7 +434,7 @@ func ParseAndValidateConfig(az *AzStorage, opt AzStorageOptions) error { if opt.AccountKey == "" { return errors.New("storage key not provided") } - az.stConfig.authConfig.AccountKey = opt.AccountKey + az.stConfig.authConfig.AccountKey = memguard.NewEnclave([]byte(opt.AccountKey)) case EAuthType.SAS(): az.stConfig.authConfig.AuthMode = EAuthType.SAS() if opt.SaSKey == "" { @@ -455,7 +456,7 @@ func ParseAndValidateConfig(az *AzStorage, opt AzStorageOptions) error { return errors.New("Client ID, Tenant ID or Client Secret not provided") } az.stConfig.authConfig.ClientID = opt.ClientID - az.stConfig.authConfig.ClientSecret = opt.ClientSecret + az.stConfig.authConfig.ClientSecret = memguard.NewEnclave([]byte(opt.ClientSecret)) az.stConfig.authConfig.TenantID = opt.TenantID az.stConfig.authConfig.OAuthTokenFilePath = opt.OAuthTokenFilePath case EAuthType.AZCLI(): @@ -594,9 +595,21 @@ func ParseAndReadDynamicConfig(az *AzStorage, opt AzStorageOptions, reload bool) if reload { log.Info("ParseAndReadDynamicConfig : SAS Key updated") - if err := az.storage.UpdateServiceClient("saskey", az.stConfig.authConfig.SASKey); err != nil { + var sasKey *memguard.LockedBuffer + var err error + if az.stConfig.authConfig.SASKey != nil { + sasKey, err = az.stConfig.authConfig.SASKey.Open() + if err != nil || sasKey == nil { + return err + } + defer sasKey.Destroy() + } else { + return errors.New("SAS key update failure") + } + + if err := az.storage.UpdateServiceClient("saskey", sasKey.String()); err != nil { az.stConfig.authConfig.SASKey = oldSas - _ = az.storage.UpdateServiceClient("saskey", az.stConfig.authConfig.SASKey) + _ = az.storage.UpdateServiceClient("saskey", sasKey.String()) return errors.New("SAS key update failure") } } diff --git a/component/azstorage/config_test.go b/component/azstorage/config_test.go index 246bd868a..288ca5fdd 100644 --- a/component/azstorage/config_test.go +++ b/component/azstorage/config_test.go @@ -237,7 +237,8 @@ func (s *configTestSuite) TestAuthModeKey() { opt.AccountKey = "abc" err = ParseAndValidateConfig(az, opt) assert.NoError(err) - assert.Equal(az.stConfig.authConfig.AccountKey, opt.AccountKey) + accountKey, _ := az.stConfig.authConfig.AccountKey.Open() + assert.Equal(opt.AccountKey, accountKey.String()) } func (s *configTestSuite) TestAuthModeSAS() { @@ -321,9 +322,10 @@ func (s *configTestSuite) TestAuthModeSPN() { opt.TenantID = "xyz" err = ParseAndValidateConfig(az, opt) assert.NoError(err) - assert.Equal(az.stConfig.authConfig.ClientID, opt.ClientID) - assert.Equal(az.stConfig.authConfig.ClientSecret, opt.ClientSecret) - assert.Equal(az.stConfig.authConfig.TenantID, opt.TenantID) + clientSecret, _ := az.stConfig.authConfig.ClientSecret.Open() + assert.Equal(opt.ClientID, az.stConfig.authConfig.ClientID) + assert.Equal(opt.ClientSecret, clientSecret.String()) + assert.Equal(opt.TenantID, az.stConfig.authConfig.TenantID) } func (s *configTestSuite) TestOtherFlags() { diff --git a/component/azstorage/datalake_test.go b/component/azstorage/datalake_test.go index 0a48633fc..4e6653797 100644 --- a/component/azstorage/datalake_test.go +++ b/component/azstorage/datalake_test.go @@ -144,7 +144,8 @@ func (s *datalakeTestSuite) TestDefault() { s.assert.Equal(storageTestConfigurationParameters.AdlsAccount, s.az.stConfig.authConfig.AccountName) s.assert.Equal(EAccountType.ADLS(), s.az.stConfig.authConfig.AccountType) s.assert.False(s.az.stConfig.authConfig.UseHTTP) - s.assert.Equal(storageTestConfigurationParameters.AdlsKey, s.az.stConfig.authConfig.AccountKey) + accountKey, _ := s.az.stConfig.authConfig.AccountKey.Open() + s.assert.Equal(accountKey.String(), storageTestConfigurationParameters.AdlsKey) s.assert.Empty(s.az.stConfig.authConfig.SASKey) s.assert.Empty(s.az.stConfig.authConfig.ApplicationID) s.assert.Empty(s.az.stConfig.authConfig.ResourceID) diff --git a/component/azstorage/utils.go b/component/azstorage/utils.go index 1c9c4be61..c53441e55 100644 --- a/component/azstorage/utils.go +++ b/component/azstorage/utils.go @@ -42,6 +42,7 @@ import ( "github.com/Seagate/cloudfuse/common" "github.com/Seagate/cloudfuse/common/log" "github.com/Seagate/cloudfuse/internal" + "github.com/awnumar/memguard" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" @@ -541,16 +542,17 @@ func split(prefixPath string, path string) string { return common.JoinUnixFilepath(paths...) } -func sanitizeSASKey(key string) string { +func sanitizeSASKey(key string) *memguard.Enclave { + encryptedKey := memguard.NewEnclave([]byte(key)) if key == "" { - return key + return encryptedKey } if key[0] != '?' { - return ("?" + key) + return memguard.NewEnclave([]byte("?" + key)) } - return key + return memguard.NewEnclave([]byte(key)) } func getMD5(fi *os.File) ([]byte, error) { diff --git a/component/azstorage/utils_test.go b/component/azstorage/utils_test.go index 43402ecf9..29da5efb8 100644 --- a/component/azstorage/utils_test.go +++ b/component/azstorage/utils_test.go @@ -305,14 +305,16 @@ func (s *utilsTestSuite) TestGetMD5() { func (s *utilsTestSuite) TestSanitizeSASKey() { assert := assert.New(s.T()) - key := sanitizeSASKey("") - assert.EqualValues("", key) + sanitizedKey := sanitizeSASKey("") + assert.Nil(sanitizedKey) - key = sanitizeSASKey("?abcd") - assert.EqualValues("?abcd", key) + sanitizedKey = sanitizeSASKey("?abcd") + key, _ := sanitizedKey.Open() + assert.EqualValues("?abcd", key.String()) - key = sanitizeSASKey("abcd") - assert.EqualValues("?abcd", key) + sanitizedKey = sanitizeSASKey("abcd") + key, _ = sanitizedKey.Open() + assert.EqualValues("?abcd", key.String()) } func (s *utilsTestSuite) TestBlockNonProxyOptions() { From 938659f2ab1b7fd8dfd90b52a9a1cd443f4f5333 Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:53:18 -0600 Subject: [PATCH 6/9] Cleanup memguard code --- common/config/config_test.go | 3 +-- common/util_test.go | 15 +++++---------- component/azstorage/azauth.go | 6 ------ component/azstorage/azauth_test.go | 18 ++++++------------ component/azstorage/azauthkey.go | 4 +++- component/s3storage/client_test.go | 10 ++-------- component/s3storage/config_test.go | 10 ++-------- component/s3storage/s3storage.go | 9 +++++++-- 8 files changed, 26 insertions(+), 49 deletions(-) diff --git a/common/config/config_test.go b/common/config/config_test.go index 2cb40483a..5bccbf134 100644 --- a/common/config/config_test.go +++ b/common/config/config_test.go @@ -465,8 +465,7 @@ func (suite *ConfigTestSuite) TestConfigFileDescryption() { assert.NoError(err) assert.NotNil(plaintext) - passphrase := memguard.NewBufferFromBytes([]byte("12312312312312312312312312312312")) - encryptedPassphrase := passphrase.Seal() + encryptedPassphrase := memguard.NewEnclave([]byte("12312312312312312312312312312312")) cipherText, err := common.EncryptData(plaintext, encryptedPassphrase) assert.NoError(err) diff --git a/common/util_test.go b/common/util_test.go index 564f12555..72bfeabe0 100644 --- a/common/util_test.go +++ b/common/util_test.go @@ -83,8 +83,7 @@ func (suite *typesTestSuite) TestEncryptBadKey() { key := make([]byte, 20) rand.Read(key) - passphrase := memguard.NewBufferFromBytes(key) - encryptedPassphrase := passphrase.Seal() + encryptedPassphrase := memguard.NewEnclave(key) data := make([]byte, 1024) rand.Read(data) @@ -98,8 +97,7 @@ func (suite *typesTestSuite) TestDecryptBadKey() { key := make([]byte, 20) rand.Read(key) - passphrase := memguard.NewBufferFromBytes(key) - encryptedPassphrase := passphrase.Seal() + encryptedPassphrase := memguard.NewEnclave(key) data := make([]byte, 1024) rand.Read(data) @@ -114,8 +112,7 @@ func (suite *typesTestSuite) TestEncryptDecrypt16() { rand.Read(binaryKey) key := base64.StdEncoding.EncodeToString(binaryKey) - passphrase := memguard.NewBufferFromBytes([]byte(key)) - encryptedPassphrase := passphrase.Seal() + encryptedPassphrase := memguard.NewEnclave([]byte(key)) data := make([]byte, 1024) rand.Read(data) @@ -134,8 +131,7 @@ func (suite *typesTestSuite) TestEncryptDecrypt24() { rand.Read(binaryKey) key := base64.StdEncoding.EncodeToString(binaryKey) - passphrase := memguard.NewBufferFromBytes([]byte(key)) - encryptedPassphrase := passphrase.Seal() + encryptedPassphrase := memguard.NewEnclave([]byte(key)) data := make([]byte, 1024) rand.Read(data) @@ -154,8 +150,7 @@ func (suite *typesTestSuite) TestEncryptDecrypt32() { rand.Read(binaryKey) key := base64.StdEncoding.EncodeToString(binaryKey) - passphrase := memguard.NewBufferFromBytes([]byte(key)) - encryptedPassphrase := passphrase.Seal() + encryptedPassphrase := memguard.NewEnclave([]byte(key)) data := make([]byte, 1024) rand.Read(data) diff --git a/component/azstorage/azauth.go b/component/azstorage/azauth.go index fd5b23e8e..3f6429e11 100644 --- a/component/azstorage/azauth.go +++ b/component/azstorage/azauth.go @@ -93,9 +93,6 @@ func getAzAuth(config azAuthConfig) azAuth { func getAzBlobAuth(config azAuthConfig) azAuth { base := azAuthBase{config: config} - base.config.AccountKey = config.AccountKey - base.config.SASKey = config.SASKey - base.config.ClientSecret = config.ClientSecret if config.AuthMode == EAuthType.KEY() { return &azAuthBlobKey{ azAuthKey{ @@ -134,9 +131,6 @@ func getAzBlobAuth(config azAuthConfig) azAuth { func getAzDatalakeAuth(config azAuthConfig) azAuth { base := azAuthBase{config: config} - base.config.AccountKey = config.AccountKey - base.config.SASKey = config.SASKey - base.config.ClientSecret = config.ClientSecret if config.AuthMode == EAuthType.KEY() { return &azAuthDatalakeKey{ azAuthKey{ diff --git a/component/azstorage/azauth_test.go b/component/azstorage/azauth_test.go index 6bcf52813..a29cc6654 100644 --- a/component/azstorage/azauth_test.go +++ b/component/azstorage/azauth_test.go @@ -115,8 +115,7 @@ func generateEndpoint(useHttp bool, accountName string, accountType AccountType) func (suite *authTestSuite) TestBlockInvalidAuth() { defer suite.cleanupTest() - dataBuf := memguard.NewBufferFromBytes([]byte(storageTestConfigurationParameters.BlockKey)) - encryptedKey := dataBuf.Seal() + encryptedKey := memguard.NewEnclave([]byte(storageTestConfigurationParameters.BlockKey)) stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.BlockContainer, authConfig: azAuthConfig{ @@ -139,8 +138,7 @@ func (suite *authTestSuite) TestBlockInvalidAuth() { func (suite *authTestSuite) TestAdlsInvalidAuth() { defer suite.cleanupTest() - dataBuf := memguard.NewBufferFromBytes([]byte(storageTestConfigurationParameters.BlockKey)) - encryptedKey := dataBuf.Seal() + encryptedKey := memguard.NewEnclave([]byte(storageTestConfigurationParameters.BlockKey)) stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.AdlsContainer, authConfig: azAuthConfig{ @@ -163,8 +161,7 @@ func (suite *authTestSuite) TestAdlsInvalidAuth() { func (suite *authTestSuite) TestInvalidAccountType() { defer suite.cleanupTest() - dataBuf := memguard.NewBufferFromBytes([]byte(storageTestConfigurationParameters.BlockKey)) - encryptedKey := dataBuf.Seal() + encryptedKey := memguard.NewEnclave([]byte(storageTestConfigurationParameters.BlockKey)) stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.AdlsContainer, authConfig: azAuthConfig{ @@ -206,8 +203,7 @@ func (suite *authTestSuite) TestBlockInvalidSharedKey() { func (suite *authTestSuite) TestBlockInvalidSharedKey2() { defer suite.cleanupTest() - dataBuf := memguard.NewBufferFromBytes([]byte("abcd>=")) - encryptedKey := dataBuf.Seal() + encryptedKey := memguard.NewEnclave([]byte("abcd>=")) stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.BlockContainer, authConfig: azAuthConfig{ @@ -230,8 +226,7 @@ func (suite *authTestSuite) TestBlockInvalidSharedKey2() { func (suite *authTestSuite) TestBlockSharedKey() { defer suite.cleanupTest() - dataBuf := memguard.NewBufferFromBytes([]byte(storageTestConfigurationParameters.BlockKey)) - encryptedKey := dataBuf.Seal() + encryptedKey := memguard.NewEnclave([]byte(storageTestConfigurationParameters.BlockKey)) stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.BlockContainer, authConfig: azAuthConfig{ @@ -285,8 +280,7 @@ func (suite *authTestSuite) TestAdlsInvalidSharedKey() { func (suite *authTestSuite) TestAdlsSharedKey() { defer suite.cleanupTest() - dataBuf := memguard.NewBufferFromBytes([]byte(storageTestConfigurationParameters.AdlsKey)) - encryptedKey := dataBuf.Seal() + encryptedKey := memguard.NewEnclave([]byte(storageTestConfigurationParameters.AdlsKey)) stgConfig := AzStorageConfig{ container: storageTestConfigurationParameters.AdlsContainer, authConfig: azAuthConfig{ diff --git a/component/azstorage/azauthkey.go b/component/azstorage/azauthkey.go index 8beb5483e..121267d90 100644 --- a/component/azstorage/azauthkey.go +++ b/component/azstorage/azauthkey.go @@ -59,7 +59,8 @@ func (azkey *azAuthBlobKey) getServiceClient(stConfig *AzStorageConfig) (interfa if err != nil || buff == nil { return nil, errors.New("unable to decrypt passphrase key") } - //defer buff.Destroy() + // TODO: Defering the destruction of the buffer causes a segfault later in the code in some cases. + // defer buff.Destroy() cred, err := azblob.NewSharedKeyCredential(azkey.config.AccountName, buff.String()) if err != nil { @@ -90,6 +91,7 @@ func (azkey *azAuthDatalakeKey) getServiceClient(stConfig *AzStorageConfig) (int if err != nil || buff == nil { return nil, errors.New("unable to decrypt passphrase key") } + // TODO: Defering the destruction of the buffer causes a segfault later in the code in some cases. // defer buff.Destroy() cred, err := azdatalake.NewSharedKeyCredential(azkey.config.AccountName, buff.String()) diff --git a/component/s3storage/client_test.go b/component/s3storage/client_test.go index a8def06d8..a096615ad 100644 --- a/component/s3storage/client_test.go +++ b/component/s3storage/client_test.go @@ -79,10 +79,7 @@ func newTestClient(configuration string) (*Client, error) { // Secure keyID in enclave var encryptedKeyID *memguard.Enclave if viper.GetString("s3storage.key-id") != "" { - dataBuf := memguard.NewBufferFromBytes([]byte(viper.GetString("s3storage.key-id"))) - memguard.ScrambleBytes([]byte(viper.GetString("s3storage.key-id"))) - - encryptedKeyID = dataBuf.Seal() + encryptedKeyID = memguard.NewEnclave([]byte(viper.GetString("s3storage.key-id"))) if encryptedKeyID == nil { return nil, fmt.Errorf("config error in %s. Here's why: %s", compName, "Error storing key ID securely") } @@ -91,10 +88,7 @@ func newTestClient(configuration string) (*Client, error) { // Secure secretKey in enclave var encryptedSecretKey *memguard.Enclave if viper.GetString("s3storage.secret-key") != "" { - dataBuf := memguard.NewBufferFromBytes([]byte(viper.GetString("s3storage.secret-key"))) - memguard.ScrambleBytes([]byte(viper.GetString("s3storage.secret-key"))) - - encryptedSecretKey = dataBuf.Seal() + encryptedSecretKey = memguard.NewEnclave([]byte(viper.GetString("s3storage.secret-key"))) if encryptedSecretKey == nil { return nil, fmt.Errorf("config error in %s. Here's why: %s", compName, "Error storing secret key securely") } diff --git a/component/s3storage/config_test.go b/component/s3storage/config_test.go index 9b46f85f4..f3baba2f4 100644 --- a/component/s3storage/config_test.go +++ b/component/s3storage/config_test.go @@ -32,7 +32,6 @@ import ( "github.com/Seagate/cloudfuse/common" "github.com/Seagate/cloudfuse/common/log" "github.com/awnumar/memguard" - "github.com/spf13/viper" "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/stretchr/testify/assert" @@ -67,13 +66,8 @@ func (s *configTestSuite) SetupTest() { PrefixPath: "testPrefixPath", } - dataBuf := memguard.NewBufferFromBytes([]byte("testKeyId")) - memguard.ScrambleBytes([]byte(viper.GetString("testKeyId"))) - encryptedKeyID := dataBuf.Seal() - - dataBuf = memguard.NewBufferFromBytes([]byte("testSecretKey")) - memguard.ScrambleBytes([]byte(viper.GetString("testSecretKey"))) - encryptedSecretKey := dataBuf.Seal() + encryptedKeyID := memguard.NewEnclave([]byte("testKeyId")) + encryptedSecretKey := memguard.NewEnclave([]byte("testKeyId")) s.secrets = ConfigSecrets{ KeyID: encryptedKeyID, diff --git a/component/s3storage/s3storage.go b/component/s3storage/s3storage.go index cf0556cf0..3a73a94c5 100644 --- a/component/s3storage/s3storage.go +++ b/component/s3storage/s3storage.go @@ -27,6 +27,7 @@ package s3storage import ( "context" + "errors" "fmt" "sync/atomic" "syscall" @@ -91,7 +92,9 @@ func (s3 *S3Storage) Configure(isParent bool) error { encryptedKeyID := memguard.NewEnclave([]byte(viper.GetString("s3storage.key-id"))) if encryptedKeyID == nil { - return fmt.Errorf("S3Storage::Configure : unable to store key-id securely") + err := errors.New("unable to store key-id securely") + log.Err("S3Storage::Configure : ", err.Error()) + return err } secrets.KeyID = encryptedKeyID } @@ -100,7 +103,9 @@ func (s3 *S3Storage) Configure(isParent bool) error { encryptedSecretKey := memguard.NewEnclave([]byte(viper.GetString("s3storage.secret-key"))) if encryptedSecretKey == nil { - return fmt.Errorf("S3Storage::Configure : unable to store secret-key securely") + err := errors.New("unable to store secret-key securely") + log.Err("S3Storage::Configure : ", err.Error()) + return err } secrets.SecretKey = encryptedSecretKey } From b1e56e2ba2eeae5b8e635409fda626eecf176c71 Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:53:33 -0600 Subject: [PATCH 7/9] Fix spelling and add to notice file --- NOTICE | 422 +++++++++++++++++++++++++++++++ component/azstorage/azauthkey.go | 4 +- 2 files changed, 424 insertions(+), 2 deletions(-) diff --git a/NOTICE b/NOTICE index cb2408920..baa115a6f 100644 --- a/NOTICE +++ b/NOTICE @@ -10365,4 +10365,426 @@ apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + + + + +**************************************************************************** + +============================================================================ +>>> github.com/awnumar/memcall +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + +**************************************************************************** + +============================================================================ +>>> github.com/awnumar/memguard +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --------------------- END OF THIRD PARTY NOTICE -------------------------------- diff --git a/component/azstorage/azauthkey.go b/component/azstorage/azauthkey.go index 121267d90..1e4e2ccae 100644 --- a/component/azstorage/azauthkey.go +++ b/component/azstorage/azauthkey.go @@ -59,7 +59,7 @@ func (azkey *azAuthBlobKey) getServiceClient(stConfig *AzStorageConfig) (interfa if err != nil || buff == nil { return nil, errors.New("unable to decrypt passphrase key") } - // TODO: Defering the destruction of the buffer causes a segfault later in the code in some cases. + // TODO: Deferring the destruction of the buffer causes a segfault later in the code in some cases. // defer buff.Destroy() cred, err := azblob.NewSharedKeyCredential(azkey.config.AccountName, buff.String()) @@ -91,7 +91,7 @@ func (azkey *azAuthDatalakeKey) getServiceClient(stConfig *AzStorageConfig) (int if err != nil || buff == nil { return nil, errors.New("unable to decrypt passphrase key") } - // TODO: Defering the destruction of the buffer causes a segfault later in the code in some cases. + // TODO: Deferring the destruction of the buffer causes a segfault later in the code in some cases. // defer buff.Destroy() cred, err := azdatalake.NewSharedKeyCredential(azkey.config.AccountName, buff.String()) From e65c7c33b546b9e9ec64e857bb95eaf95cb88368 Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:50:16 -0600 Subject: [PATCH 8/9] Fix mounting on Windows --- cmd/config-gen.go | 2 +- cmd/mount.go | 2 +- cmd/mount_all.go | 2 +- cmd/mount_windows.go | 2 +- cmd/secure.go | 2 +- common/util.go | 15 ++------------- internal/winservice/service_windows.go | 20 +++++++++++++++++--- 7 files changed, 24 insertions(+), 21 deletions(-) diff --git a/cmd/config-gen.go b/cmd/config-gen.go index 52b868498..c98d452a7 100644 --- a/cmd/config-gen.go +++ b/cmd/config-gen.go @@ -121,7 +121,7 @@ var generateConfig = &cobra.Command{ var templateConfig []byte var err error - encryptedPassphrase = memguard.NewEnclave([]byte(opts.passphrase)) + encryptedPassphrase = memguard.NewEnclave(opts.passphrase) templateConfig, err = os.ReadFile(opts.configFilePath) if err != nil { diff --git a/cmd/mount.go b/cmd/mount.go index b5a78c4fc..ade3f56ea 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -214,7 +214,7 @@ func parseConfig() error { } } - encryptedPassphrase = memguard.NewEnclave([]byte(options.PassPhrase)) + encryptedPassphrase = memguard.NewEnclave(options.PassPhrase) cipherText, err := os.ReadFile(options.ConfigFile) if err != nil { diff --git a/cmd/mount_all.go b/cmd/mount_all.go index 5da2d57da..d3c7cf6ec 100644 --- a/cmd/mount_all.go +++ b/cmd/mount_all.go @@ -174,7 +174,7 @@ func processCommand() error { } } - encryptedPassphrase = memguard.NewEnclave([]byte(options.PassPhrase)) + encryptedPassphrase = memguard.NewEnclave(options.PassPhrase) } var containerList []string diff --git a/cmd/mount_windows.go b/cmd/mount_windows.go index d96c57427..01cc0a9ea 100644 --- a/cmd/mount_windows.go +++ b/cmd/mount_windows.go @@ -43,7 +43,7 @@ func createDaemon(pipeline *internal.Pipeline, ctx context.Context, pidFileName // Use WinFSP to mount and if successful, add instance to persistent mount list func createMountInstance() error { - err := winservice.StartMount(options.MountPath, options.ConfigFile, options.PassPhrase) + err := winservice.StartMount(options.MountPath, options.ConfigFile, encryptedPassphrase) if err != nil { return err } diff --git a/cmd/secure.go b/cmd/secure.go index cc932f838..1f8acc2f2 100644 --- a/cmd/secure.go +++ b/cmd/secure.go @@ -130,7 +130,7 @@ func validateOptions() error { return fmt.Errorf("passphrase is not valid base64 encoded [%s]", err.Error()) } - encryptedPassphrase = memguard.NewEnclave([]byte(secOpts.PassPhrase)) + encryptedPassphrase = memguard.NewEnclave(secOpts.PassPhrase) if secOpts.ConfigFile == "" { return errors.New("config file not provided, check usage") diff --git a/common/util.go b/common/util.go index cae5ff75c..eaca3b6ac 100644 --- a/common/util.go +++ b/common/util.go @@ -29,7 +29,6 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "encoding/base64" "errors" "fmt" "io" @@ -186,12 +185,7 @@ func EncryptData(plainData []byte, key *memguard.Enclave) ([]byte, error) { } defer secretKey.Destroy() - binaryKey, err := base64.StdEncoding.DecodeString(secretKey.String()) - if err != nil { - return nil, fmt.Errorf("failed to base64 decode passphrase [%s]", err.Error()) - } - - block, err := aes.NewCipher(binaryKey) + block, err := aes.NewCipher(secretKey.Data()) if err != nil { return nil, err } @@ -222,12 +216,7 @@ func DecryptData(cipherData []byte, key *memguard.Enclave) ([]byte, error) { } defer secretKey.Destroy() - binaryKey, err := base64.StdEncoding.DecodeString(secretKey.String()) - if err != nil { - return nil, fmt.Errorf("failed to base64 decode passphrase [%s]", err.Error()) - } - - block, err := aes.NewCipher(binaryKey) + block, err := aes.NewCipher(secretKey.Data()) if err != nil { return nil, err } diff --git a/internal/winservice/service_windows.go b/internal/winservice/service_windows.go index 7e0532709..1c97c0a42 100644 --- a/internal/winservice/service_windows.go +++ b/internal/winservice/service_windows.go @@ -28,12 +28,14 @@ package winservice import ( "bytes" + "encoding/base64" "encoding/binary" "errors" "fmt" "github.com/Seagate/cloudfuse/common" "github.com/Seagate/cloudfuse/common/log" + "github.com/awnumar/memguard" "golang.org/x/sys/windows" ) @@ -51,7 +53,7 @@ const ( type Cloudfuse struct{} // StartMount starts the mount if the name exists in our Windows registry. -func StartMount(mountPath string, configFile string, passphrase string) error { +func StartMount(mountPath string, configFile string, passphrase *memguard.Enclave) error { // get the current user uid and gid to set file permissions userId, groupId, err := common.GetCurrentUser() if err != nil { @@ -61,7 +63,19 @@ func StartMount(mountPath string, configFile string, passphrase string) error { instanceName := mountPath - buf := writeCommandToUtf16(startCmd, SvcName, instanceName, mountPath, configFile, fmt.Sprint(userId), fmt.Sprint(groupId), passphrase) + var passphraseStr string + if passphrase != nil { + buff, err := passphrase.Open() + if err != nil || buff == nil { + return errors.New("unable to decrypt passphrase key") + } + + // Encode back to base64 when sending passphrase to cloudfuse + passphraseStr = base64.StdEncoding.EncodeToString(buff.Data()) + defer buff.Destroy() + } + + buf := writeCommandToUtf16(startCmd, SvcName, instanceName, mountPath, configFile, fmt.Sprint(userId), fmt.Sprint(groupId), passphraseStr) _, err = winFspCommand(buf) if err != nil { return err @@ -114,7 +128,7 @@ func StartMounts() error { } for _, inst := range mounts.Mounts { - err := StartMount(inst.MountPath, inst.ConfigFile, "") + err := StartMount(inst.MountPath, inst.ConfigFile, nil) if err != nil { log.Err("Unable to start mount with mountpath: ", inst.MountPath) } From 48e11aa4c5831e024c43919d0dd7792a3ac5040f Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:10:04 -0600 Subject: [PATCH 9/9] Fix unit test --- common/util_test.go | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/common/util_test.go b/common/util_test.go index 72bfeabe0..f26538a72 100644 --- a/common/util_test.go +++ b/common/util_test.go @@ -27,7 +27,6 @@ package common import ( "crypto/rand" - "encoding/base64" "fmt" "os" "path/filepath" @@ -108,11 +107,10 @@ func (suite *typesTestSuite) TestDecryptBadKey() { func (suite *typesTestSuite) TestEncryptDecrypt16() { // Generate a random key - binaryKey := make([]byte, 16) - rand.Read(binaryKey) - key := base64.StdEncoding.EncodeToString(binaryKey) + key := make([]byte, 16) + rand.Read(key) - encryptedPassphrase := memguard.NewEnclave([]byte(key)) + encryptedPassphrase := memguard.NewEnclave(key) data := make([]byte, 1024) rand.Read(data) @@ -127,11 +125,10 @@ func (suite *typesTestSuite) TestEncryptDecrypt16() { func (suite *typesTestSuite) TestEncryptDecrypt24() { // Generate a random key - binaryKey := make([]byte, 24) - rand.Read(binaryKey) - key := base64.StdEncoding.EncodeToString(binaryKey) + key := make([]byte, 24) + rand.Read(key) - encryptedPassphrase := memguard.NewEnclave([]byte(key)) + encryptedPassphrase := memguard.NewEnclave(key) data := make([]byte, 1024) rand.Read(data) @@ -146,11 +143,10 @@ func (suite *typesTestSuite) TestEncryptDecrypt24() { func (suite *typesTestSuite) TestEncryptDecrypt32() { // Generate a random key - binaryKey := make([]byte, 32) - rand.Read(binaryKey) - key := base64.StdEncoding.EncodeToString(binaryKey) + key := make([]byte, 32) + rand.Read(key) - encryptedPassphrase := memguard.NewEnclave([]byte(key)) + encryptedPassphrase := memguard.NewEnclave(key) data := make([]byte, 1024) rand.Read(data)