Skip to content
This repository has been archived by the owner on Feb 16, 2023. It is now read-only.

Commit

Permalink
Merge pull request #239 from secrethub/release/v0.33.0
Browse files Browse the repository at this point in the history
Release v0.33.0
  • Loading branch information
SimonBarendse authored Aug 20, 2021
2 parents 7b81fd9 + 848b061 commit b0900a4
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 25 deletions.
20 changes: 19 additions & 1 deletion pkg/secrethub/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ package secrethub

import (
"github.com/secrethub/secrethub-go/internals/api"
"github.com/secrethub/secrethub-go/internals/api/uuid"
"github.com/secrethub/secrethub-go/internals/crypto"
"github.com/secrethub/secrethub-go/internals/errio"
"github.com/secrethub/secrethub-go/pkg/secrethub/credentials"
)

// Errors
var (
ErrNoDecryptionKey = errClient.Code("no_decryption_key").Error("client is not initialized with a method to decrypt the account key")
ErrNoDecryptionKey = errClient.Code("no_decryption_key").Error("client is not initialized with a method to decrypt the account key")
ErrIncorrectAccountID = errClient.Code("incorrect_account_id").Error("the incorrect account ID was provided. To delete the currently authenticated account please provide its ID.")
)

// AccountService handles operations on SecretHub accounts.
type AccountService interface {
// Me retrieves the authenticated account of the client.
Me() (*api.Account, error)
// Delete deletes the authenticated account of the client if it's ID is provided as a parameter.
// Do not use this method. Account deletes should only be performed from the CLI.
Delete(accountID uuid.UUID) error
// Get retrieves an account by name.
Get(name string) (*api.Account, error)
// Keys returns an account key service.
Expand All @@ -37,6 +42,19 @@ func (s accountService) Me() (*api.Account, error) {
return s.client.getMyAccount()
}

// Delete deletes the authenticated account of the client if it's ID is provided as a parameter.
// Do not use this method. Account deletes should only be performed from the CLI.
func (s accountService) Delete(accountID uuid.UUID) error {
account, err := s.client.getMyAccount()
if err != nil {
return err
}
if accountID != account.AccountID {
return ErrIncorrectAccountID
}
return s.client.httpClient.DeleteMyAccount()
}

// Get retrieves an account by name.
func (s accountService) Get(name string) (*api.Account, error) {
accountName, err := api.NewAccountName(name)
Expand Down
63 changes: 40 additions & 23 deletions pkg/secrethub/acl.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package secrethub

import (
"context"
"fmt"

"golang.org/x/sync/errgroup"

"github.com/secrethub/secrethub-go/internals/crypto"

"github.com/secrethub/secrethub-go/internals/api"
"github.com/secrethub/secrethub-go/internals/api/uuid"
"github.com/secrethub/secrethub-go/internals/errio"
Expand Down Expand Up @@ -287,37 +292,49 @@ func (re *reencrypter) Add(blindName string) error {
return err
}

return re.reencrypt(encryptedTree, accountKey)
}

func (re *reencrypter) reencrypt(encryptedTree *api.EncryptedTree, accountKey *crypto.RSAPrivateKey) error {
errs, _ := errgroup.WithContext(context.Background())

for _, dir := range encryptedTree.Directories {
_, ok := re.dirs[dir.DirID]
if !ok {
decrypted, err := dir.Decrypt(accountKey)
if err != nil {
return err
}
encrypted, err := re.client.encryptDirFor(decrypted, re.encryptFor)
if err != nil {
return err
errs.Go(func() error {
_, ok := re.dirs[dir.DirID]
if !ok {
decrypted, err := dir.Decrypt(accountKey)
if err != nil {
return err
}
encrypted, err := re.client.encryptDirFor(decrypted, re.encryptFor)
if err != nil {
return err
}
re.dirs[dir.DirID] = encrypted
}
re.dirs[dir.DirID] = encrypted
}
return nil
})
}

for _, secret := range encryptedTree.Secrets {
_, ok := re.secrets[secret.SecretID]
if !ok {
decrypted, err := secret.Decrypt(accountKey)
if err != nil {
return err
errs.Go(func() error {
_, ok := re.secrets[secret.SecretID]
if !ok {
decrypted, err := secret.Decrypt(accountKey)
if err != nil {
return err
}
encrypted, err := re.client.encryptSecretFor(decrypted, re.encryptFor)
if err != nil {
return err
}
re.secrets[secret.SecretID] = encrypted
}
encrypted, err := re.client.encryptSecretFor(decrypted, re.encryptFor)
if err != nil {
return err
}
re.secrets[secret.SecretID] = encrypted
}
return nil
})
}

return nil
return errs.Wait()
}

func (re *reencrypter) Secrets() []api.SecretAccessRequest {
Expand Down
153 changes: 153 additions & 0 deletions pkg/secrethub/acl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package secrethub

import (
"testing"
"time"

"github.com/secrethub/secrethub-go/internals/api"
"github.com/secrethub/secrethub-go/internals/api/uuid"
"github.com/secrethub/secrethub-go/internals/assert"
"github.com/secrethub/secrethub-go/internals/crypto"
"github.com/secrethub/secrethub-go/pkg/secrethub/internals/http"
)

func TestReencypter_reencrypt(t *testing.T) {
fromPrivateKey, err := crypto.GenerateRSAPrivateKey(2048)
assert.OK(t, err)

fromPublicKey := fromPrivateKey.Public()
fromEncodedPublicKey, err := fromPublicKey.Encode()
assert.OK(t, err)

forPrivateKey, err := crypto.GenerateRSAPrivateKey(2048)
assert.OK(t, err)

forPublicKey := forPrivateKey.Public()
forEncodedPublicKey, err := forPublicKey.Encode()
assert.OK(t, err)

firstAccount := api.Account{
AccountID: uuid.New(),
Name: "first-user",
PublicKey: fromEncodedPublicKey,
AccountType: "",
CreatedAt: time.Time{},
}

secondAccount := api.Account{
AccountID: uuid.New(),
Name: "second-user",
PublicKey: forEncodedPublicKey,
AccountType: "",
CreatedAt: time.Time{},
}

fakeClient := Client{
httpClient: &http.Client{},
account: &secondAccount,
accountKey: &forPrivateKey,
}

cases := map[string]struct {
dirs []*api.Dir
secrets []*api.Secret
}{
"no directories": {
dirs: nil,
secrets: nil,
},
"one directory": {
dirs: []*api.Dir{
{
DirID: uuid.New(),
Name: "first-dir",
},
},
secrets: nil,
},
"multiple directories": {
dirs: []*api.Dir{
{
DirID: uuid.New(),
Name: "first-dir",
},
{
DirID: uuid.New(),
Name: "second-dir",
},
{
DirID: uuid.New(),
Name: "third-dir",
},
{
DirID: uuid.New(),
Name: "fourth-dir",
},
{
DirID: uuid.New(),
Name: "fifth-dir",
},
},
secrets: nil,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
fakeReencrypter := reencrypter{
dirs: make(map[uuid.UUID]api.EncryptedNameForNodeRequest),
secrets: make(map[uuid.UUID]api.SecretAccessRequest),
encryptFor: &secondAccount,
client: &fakeClient,
}

encryptedTree := createEncryptedTree(t, tc.dirs, tc.secrets, fakeReencrypter, firstAccount)
err = fakeReencrypter.reencrypt(&encryptedTree, &fromPrivateKey)
assert.OK(t, err)

count := 0
for _, dir := range fakeReencrypter.dirs {
assert.Equal(t, dir.AccountID, secondAccount.AccountID)
decryptedDir, err := encryptedTree.Directories[tc.dirs[count].DirID].Decrypt(&fromPrivateKey)
assert.OK(t, err)
assert.Equal(t, tc.dirs[count].Name, decryptedDir.Name)
count++
}

//TODO: In order to test for secrets encryption and decryption, a refactoring of the Client is in order, as to be able to mock the HTTP Go client.
count = 0
for _, secret := range fakeReencrypter.secrets {
assert.Equal(t, secret.Name.AccountID, secondAccount.AccountID)
decryptedSecret, err := encryptedTree.Secrets[count].Decrypt(&fromPrivateKey)
assert.OK(t, err)
assert.Equal(t, tc.secrets[count].Name, decryptedSecret.Name)
count++
}
})
}
}

func createEncryptedTree(t *testing.T, dirs []*api.Dir, secrets []*api.Secret, reencrypter reencrypter, account api.Account) api.EncryptedTree {
encryptedTree := api.EncryptedTree{
Directories: make(map[uuid.UUID]*api.EncryptedDir),
Secrets: make([]*api.EncryptedSecret, len(secrets)),
}
for _, dir := range dirs {
request, err := reencrypter.client.encryptDirFor(dir, &account)
assert.OK(t, err)
encryptedTree.Directories[dir.DirID] = &api.EncryptedDir{
DirID: dir.DirID,
EncryptedName: request.EncryptedName,
}
}

for i, secret := range secrets {
request, err := reencrypter.client.encryptSecretFor(secret, &account)
assert.OK(t, err)
encryptedTree.Secrets[i] = &api.EncryptedSecret{
DirID: secret.DirID,
EncryptedName: request.Name.EncryptedName,
}
}

return encryptedTree
}
2 changes: 1 addition & 1 deletion pkg/secrethub/client_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package secrethub

// ClientVersion is the current version of the client
// Do not edit this unless you know what you're doing.
const ClientVersion = "v0.32.0"
const ClientVersion = "v0.33.0"
7 changes: 7 additions & 0 deletions pkg/secrethub/fakeclient/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ package fakeclient

import (
"github.com/secrethub/secrethub-go/internals/api"
"github.com/secrethub/secrethub-go/internals/api/uuid"
"github.com/secrethub/secrethub-go/pkg/secrethub"
)

// AccountService is a mock of the AccountService interface.
type AccountService struct {
MeFunc func() (*api.Account, error)
DeleteFunc func(accountID uuid.UUID) error
GetFunc func(name string) (*api.Account, error)
AccountKeyService secrethub.AccountKeyService
}
Expand All @@ -23,6 +25,11 @@ func (s *AccountService) Get(name string) (*api.Account, error) {
return s.GetFunc(name)
}

// Delete implements the AccountService interface Delete function.
func (s *AccountService) Delete(accountID uuid.UUID) error {
return s.DeleteFunc(accountID)
}

// Me implements the AccountService interface Me function.
func (s *AccountService) Me() (*api.Account, error) {
return s.MeFunc()
Expand Down
8 changes: 8 additions & 0 deletions pkg/secrethub/internals/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (

// Current account
pathMeUser = "%s/me/user"
pathMeAccount = "%s/me/account"
pathMeRepos = "%s/me/repos"
pathMeKey = "%s/me/key?key_version=v2"
pathMeEmailVerification = "%s/me/user/verification-email"
Expand Down Expand Up @@ -173,6 +174,13 @@ func (c *Client) GetMyUser() (*api.User, error) {
return out, errio.Error(err)
}

// DeleteMyAccount
func (c *Client) DeleteMyAccount() error {
rawURL := fmt.Sprintf(pathMeAccount, c.base.String())
err := c.delete(rawURL, true, nil)
return errio.Error(err)
}

// CreateCredential creates a new credential for the account.
func (c *Client) CreateCredential(in *api.CreateCredentialRequest) (*api.Credential, error) {
out := &api.Credential{}
Expand Down

0 comments on commit b0900a4

Please sign in to comment.