-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from trisacrypto/sc-22230
Local storage implementation
- Loading branch information
Showing
4 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package store | ||
|
||
import "errors" | ||
|
||
var ( | ||
ErrNotFound = errors.New("resource not found in store") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package local | ||
|
||
import ( | ||
"archive/zip" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"sync" | ||
|
||
"github.com/trisacrypto/courier/pkg/config" | ||
"github.com/trisacrypto/courier/pkg/store" | ||
) | ||
|
||
const ( | ||
passwordPrefix = "pkcs12" | ||
passwordFile = "pkcs12.password" | ||
certificatePrefix = "certificate" | ||
certificateFile = "certificate" | ||
) | ||
|
||
// Open the local storage backend. | ||
func Open(conf config.LocalStorageConfig) (store *Store, err error) { | ||
store = &Store{ | ||
path: conf.Path, | ||
} | ||
|
||
// Ensure the path exists | ||
if err = os.MkdirAll(conf.Path, 0755); err != nil { | ||
return nil, err | ||
} | ||
|
||
return store, nil | ||
} | ||
|
||
// Store implements the store.Store interface for local storage. | ||
type Store struct { | ||
sync.RWMutex | ||
path string | ||
} | ||
|
||
var _ store.Store = &Store{} | ||
|
||
// Close the local storage backend. | ||
func (s *Store) Close() error { | ||
return nil | ||
} | ||
|
||
//=========================================================================== | ||
// Password Methods | ||
//=========================================================================== | ||
|
||
// GetPassword retrieves a password by id from the local storage backend. | ||
func (s *Store) GetPassword(id string) (password []byte, err error) { | ||
s.RLock() | ||
defer s.RUnlock() | ||
return s.load(s.fullPath(passwordPrefix, id)) | ||
} | ||
|
||
// UpdatePassword updates a password by id in the local storage backend. If the | ||
// password does not exist, it is created. Otherwise, it is overwritten. | ||
func (s *Store) UpdatePassword(id string, password []byte) (err error) { | ||
s.Lock() | ||
defer s.Unlock() | ||
return s.store(s.fullPath(passwordPrefix, id), passwordFile, password) | ||
} | ||
|
||
//=========================================================================== | ||
// Certificate Methods | ||
//=========================================================================== | ||
|
||
// GetCertificate retrieves a certificate by id from the local storage backend. | ||
func (s *Store) GetCertificate(name string) (cert []byte, err error) { | ||
s.RLock() | ||
defer s.RUnlock() | ||
return s.load(s.fullPath(certificatePrefix, name)) | ||
} | ||
|
||
// UpdateCertificate updates a certificate in the local storage backend. | ||
func (s *Store) UpdateCertificate(name string, cert []byte) (err error) { | ||
s.Lock() | ||
defer s.Unlock() | ||
return s.store(s.fullPath(certificatePrefix, name), certificateFile, cert) | ||
} | ||
|
||
//=========================================================================== | ||
// Helper methods | ||
//=========================================================================== | ||
|
||
// fullPath returns the full path to an archive file in the local storage backend. | ||
func (s *Store) fullPath(prefix, name string) string { | ||
return filepath.Join(s.path, prefix+"-"+name+".zip") | ||
} | ||
|
||
// load returns file data by archive path from the local storage | ||
func (s *Store) load(path string) (data []byte, err error) { | ||
var archive *zip.ReadCloser | ||
if archive, err = zip.OpenReader(path); err != nil { | ||
if os.IsNotExist(err) { | ||
return nil, store.ErrNotFound | ||
} | ||
return nil, err | ||
} | ||
defer archive.Close() | ||
|
||
// Load the file from the archive | ||
if len(archive.File) == 0 { | ||
return nil, store.ErrNotFound | ||
} | ||
|
||
var reader io.ReadCloser | ||
if reader, err = archive.File[0].Open(); err != nil { | ||
return nil, err | ||
} | ||
defer reader.Close() | ||
|
||
return io.ReadAll(reader) | ||
} | ||
|
||
// store saves file data to an archive and file name in the local storage | ||
func (s *Store) store(path, name string, data []byte) (err error) { | ||
var archive *os.File | ||
if archive, err = os.Create(path); err != nil { | ||
return err | ||
} | ||
defer archive.Close() | ||
|
||
// Write the file to the archive | ||
zipWriter := zip.NewWriter(archive) | ||
defer zipWriter.Close() | ||
|
||
var writer io.Writer | ||
if writer, err = zipWriter.Create(name); err != nil { | ||
return err | ||
} | ||
|
||
if _, err = writer.Write(data); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package local_test | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/suite" | ||
"github.com/trisacrypto/courier/pkg/config" | ||
"github.com/trisacrypto/courier/pkg/store" | ||
"github.com/trisacrypto/courier/pkg/store/local" | ||
) | ||
|
||
type localStoreTestSuite struct { | ||
suite.Suite | ||
conf config.LocalStorageConfig | ||
store *local.Store | ||
} | ||
|
||
func (s *localStoreTestSuite) SetupSuite() { | ||
// Open the storage backend in a temporary directory | ||
var err error | ||
path := s.T().TempDir() | ||
s.store, err = local.Open(config.LocalStorageConfig{ | ||
Enabled: true, | ||
Path: path, | ||
}) | ||
s.NoError(err, "could not open local storage backend") | ||
} | ||
|
||
func (s *localStoreTestSuite) TearDownSuite() { | ||
// Remove the temporary directory | ||
s.NoError(s.store.Close(), "could not close local storage backend") | ||
s.NoError(os.RemoveAll(s.conf.Path), "could not remove temporary directory") | ||
} | ||
|
||
func TestLocalStore(t *testing.T) { | ||
suite.Run(t, new(localStoreTestSuite)) | ||
} | ||
|
||
func (s *localStoreTestSuite) TestPasswordStore() { | ||
require := s.Require() | ||
|
||
// Try to get a password that does not exist | ||
_, err := s.store.GetPassword("does-not-exist") | ||
require.ErrorIs(err, store.ErrNotFound, "should return error if password does not exist") | ||
|
||
// Create a password | ||
password := []byte("password") | ||
err = s.store.UpdatePassword("password_id", password) | ||
require.NoError(err, "should be able to create a password") | ||
|
||
// Get the password | ||
actual, err := s.store.GetPassword("password_id") | ||
require.NoError(err, "should be able to get a password") | ||
require.Equal(password, actual, "wrong password returned") | ||
} | ||
|
||
func (s *localStoreTestSuite) TestCertificateStore() { | ||
require := s.Require() | ||
|
||
// Try to get a certificate that does not exist | ||
_, err := s.store.GetCertificate("does-not-exist") | ||
require.ErrorIs(err, store.ErrNotFound, "should return error if certificate does not exist") | ||
|
||
// Create a certificate | ||
cert := []byte("certificate") | ||
err = s.store.UpdateCertificate("certificate_id", cert) | ||
require.NoError(err, "should be able to create a certificate") | ||
|
||
// Get the certificate | ||
actual, err := s.store.GetCertificate("certificate_id") | ||
require.NoError(err, "should be able to get a certificate") | ||
require.Equal(cert, actual, "wrong certificate returned") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package store | ||
|
||
// Store is a generic interface for storing and retrieving data. | ||
type Store interface { | ||
Close() error | ||
PasswordStore | ||
CertificateStore | ||
} | ||
|
||
// PasswordStore is a generic interface for storing and retrieving passwords. | ||
type PasswordStore interface { | ||
GetPassword(name string) ([]byte, error) | ||
UpdatePassword(name string, password []byte) error | ||
} | ||
|
||
// CertificateStore is a generic interface for storing and retrieving certificates. | ||
type CertificateStore interface { | ||
GetCertificate(name string) ([]byte, error) | ||
UpdateCertificate(name string, cert []byte) error | ||
} |