-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lock when generating encryption key #107
Conversation
Pull Request Test Coverage Report for Build 865
💛 - Coveralls |
pkg/sm/encryption.go
Outdated
return err | ||
} | ||
if len(encryptionKey) != 0 { | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think here you should return the key instead null
pkg/sm/sm.go
Outdated
@@ -155,6 +155,9 @@ func (smb *ServiceManagerBuilder) Build() *ServiceManager { | |||
} | |||
|
|||
func initializeSecureStorage(secureStorage storage.Security) error { | |||
if err := secureStorage.Lock(); err != nil { | |||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to return an error if database is locked. I think it should be:
- Try to lock database.
- If it is already locked, then wait some time (2 seconds for example)
- Try to lock again
- Repeat several times(5 for example)
- If it couldn't acquire the lock, then panic
- If lock is acquired then check for the encryption key. If it is already there then do nothing, otherwise generate one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears that advisory locks block until a lock can be acquired:
https://medium.com/@codeflows/application-level-locking-with-postgres-advisory-locks-c29759eeb7a7
storage/postgres/security.go
Outdated
// Lock acquires a database lock so that only one process can manipulate the encryption key. | ||
// Returns an error if the process has already acquired the lock | ||
func (s *securityStorage) Lock() error { | ||
if s.isLocked { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it make sense to not rely on a second inmemory lock and to directly rely on the response from the db query?
storage/postgres/security.go
Outdated
// Returns an error if the process has already acquired the lock | ||
func (s *securityStorage) Lock() error { | ||
if s.isLocked { | ||
return fmt.Errorf("Lock is already acquired") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why error? The fact that the lock is locked, means its locked and if we decided that we do blocking select we should stick with that in all situations (even when in the same node)
storage/postgres/security.go
Outdated
return fmt.Errorf("Lock is already acquired") | ||
} | ||
_, err := s.db.Exec("SELECT pg_advisory_lock($1)", securityLockIndex) | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.rre this
storage/postgres/security.go
Outdated
return nil | ||
} | ||
|
||
_, err := s.db.Exec("SELECT pg_advisory_unlock($1)", securityLockIndex) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.rre
storage/postgres/security.go
Outdated
|
||
// Unlock releases the database lock. | ||
func (s *securityStorage) Unlock() error { | ||
if !s.isLocked { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again i would prefer to drop the inmemory lock
storage/postgres/security_test.go
Outdated
|
||
"github.com/DATA-DOG/go-sqlmock" | ||
"github.com/Peripli/service-manager/security" | ||
"github.com/jmoiron/sqlx" | ||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
) | ||
func TestAPostgresStorage(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably it would make sense to add some tests in sm_test.go (talking to a real db and validating basic scenarios)
- failure occurs if the first key is missing from application.yml/env/flags
- things happen on startup if first key is present
2.1. second key is generated
2.2 locking works (lock twice, verify first is ok, second returns correct db response or waits and eventually when 1st unlocks, 2cd proceeds)
pkg/sm/sm.go
Outdated
@@ -155,6 +155,9 @@ func (smb *ServiceManagerBuilder) Build() *ServiceManager { | |||
} | |||
|
|||
func initializeSecureStorage(secureStorage storage.Security) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just thinking out loud - should we explicitly fail if this method actually attempts to generate a new key (len(encryptionKey) == 0) and we already have data in the database? Is there a sane way to validate this? Because we would be in a very messed up situation if SM starts in such state
pkg/sm/sm.go
Outdated
@@ -155,6 +155,9 @@ func (smb *ServiceManagerBuilder) Build() *ServiceManager { | |||
} | |||
|
|||
func initializeSecureStorage(secureStorage storage.Security) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on line
return fmt.Errorf("Could not generate encryption key: %v", err)
wrap error with %v - its fine but in some places in SM we use %s and in others we do it with %v - should we try to be consistent?
test/main_test.go
Outdated
if os.Getenv("CLEAN_ENV") == "" { | ||
Skip("Environment is not clean to validate behavior of parallel encryption key generation") | ||
} else { | ||
command := exec.Command(binaryPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pass port as flag here so both can use different ports
storage/postgres/security.go
Outdated
// Lock acquires a database lock so that only one process can manipulate the encryption key. | ||
// Returns an error if the process has already acquired the lock | ||
func (s *securityStorage) Lock() error { | ||
if s.isLocked { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a mutex here to safeguard from future possible raceconditions
storage/postgres/security.go
Outdated
|
||
// Unlock releases the database lock. | ||
func (s *securityStorage) Unlock() error { | ||
if !s.isLocked { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a mutex here to safeguard from future possible raceconditions
// Lock acquires a database lock so that only one process can manipulate the encryption key. | ||
// Returns an error if the process has already acquired the lock | ||
func (s *securityStorage) Lock(ctx context.Context) error { | ||
s.mutex.Lock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this the correct lock ( e.x. this or RWLock )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RWMutex provides only R(W)Lock and R(W)Unlock for these explicit situations. RWMutext.Lock/Unlock is the same as Mutex.Lock/Unlock
Since this is both reading and writing the value, there's no difference between RWMutex.Lock and Mutex.Lock
Fixes #106
Opened #116 for test