Skip to content

Commit

Permalink
introduce mutex around library operations
Browse files Browse the repository at this point in the history
  • Loading branch information
maxlaverse committed Nov 7, 2024
1 parent 159da9a commit d1df47a
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 132 deletions.
5 changes: 3 additions & 2 deletions internal/bitwarden/embedded/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ type Account struct {
Secrets AccountSecrets `json:"-"`
}

func (a *Account) PrivateKeyDecrypted() bool {
return len(a.Secrets.MainKey.Key) > 0
func (a *Account) LoggedIn() bool {
return len(a.ProtectedRSAPrivateKey) > 0
}

func (a *Account) ToJSON() string {
Expand All @@ -47,6 +47,7 @@ func (s *AccountSecrets) GetOrganizationKey(orgId string) (*symmetrickey.Key, er
type OrganizationSecret struct {
Key symmetrickey.Key
OrganizationUUID string
Name string
}

type MachineAccountClaims struct {
Expand Down
48 changes: 42 additions & 6 deletions internal/bitwarden/embedded/password_manager_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"strings"
"sync"
"time"

"github.com/hashicorp/terraform-plugin-log/tflog"
Expand All @@ -27,14 +28,30 @@ type BaseVault interface {
}

type baseVault struct {
locked bool
loginAccount Account
objectStore map[string]models.Object
loginAccount Account
objectStore map[string]models.Object

// vaultOperationMutex protects the objectStore and loginAccount fields
// from concurrent access. Read operations are allowed to run concurrently,
// but write operations are serialized. In theory we could protect the two
// fields individually, but it's just much more easier to have a single
// mutex for both.
vaultOperationMutex sync.RWMutex

// verifyObjectEncryption is a flag that can be set to true to verify that
// every object that is encrypted can be decrypted back to its original.
verifyObjectEncryption bool
}

func (v *baseVault) GetObject(_ context.Context, obj models.Object) (*models.Object, error) {
if v.locked {
func (v *baseVault) GetObject(ctx context.Context, obj models.Object) (*models.Object, error) {
v.vaultOperationMutex.RLock()
defer v.vaultOperationMutex.RUnlock()

return v.getObject(ctx, obj)
}

func (v *baseVault) getObject(_ context.Context, obj models.Object) (*models.Object, error) {
if v.objectStore == nil {
return nil, models.ErrVaultLocked
}

Expand All @@ -47,7 +64,10 @@ func (v *baseVault) GetObject(_ context.Context, obj models.Object) (*models.Obj
}

func (v *baseVault) ListObjects(ctx context.Context, objType models.ObjectType, options ...bitwarden.ListObjectsOption) ([]models.Object, error) {
if v.locked {
v.vaultOperationMutex.RLock()
defer v.vaultOperationMutex.RUnlock()

if v.objectStore == nil {
return nil, models.ErrVaultLocked
}

Expand Down Expand Up @@ -117,12 +137,28 @@ func (v *baseVault) encryptFolder(_ context.Context, obj models.Object, secret A
return &encFolder, nil
}

func (v *baseVault) objectsLoaded() bool {
return v.objectStore != nil
}

func (v *baseVault) storeObject(ctx context.Context, obj models.Object) {
tflog.Trace(ctx, "Storing new object", map[string]interface{}{"object_id": obj.ID, "object_name": obj.Name, "object_folder_id": obj.FolderID})
v.objectStore[objKey(obj)] = obj
}

func (v *baseVault) storeObjects(ctx context.Context, objs []models.Object) {
for _, obj := range objs {
v.storeObject(ctx, obj)
}
}

func decryptAccountSecrets(account Account, password string) (*AccountSecrets, error) {
if len(account.Email) == 0 {
// A common mistake is trying to decrypt account secrets without an
// email, the content of an Account comes from two different API calls.
return nil, fmt.Errorf("BUG: email required to decrypt account secrets")
}

masterKey, err := keybuilder.BuildPreloginKey(password, account.Email, account.KdfConfig)
if err != nil {
return nil, fmt.Errorf("error building prelogin key: %w", err)
Expand Down
Loading

0 comments on commit d1df47a

Please sign in to comment.