252 lines
4.7 KiB
Go
252 lines
4.7 KiB
Go
package datastore
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
|
|
"git.dayanhub.com/sagi/envoid/internal/common"
|
|
"git.dayanhub.com/sagi/envoid/internal/config"
|
|
intErrors "git.dayanhub.com/sagi/envoid/internal/errors"
|
|
"git.dayanhub.com/sagi/envoid/internal/types"
|
|
"git.dayanhub.com/sagi/envoid/internal/variables"
|
|
"golang.org/x/crypto/scrypt"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
type Datastore struct {
|
|
db *db
|
|
}
|
|
|
|
func NewDataStore() (*Datastore, error) {
|
|
db, err := newDB()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Datastore{
|
|
db: db,
|
|
}, nil
|
|
}
|
|
|
|
func (d *Datastore) CreateEnv(name string) error {
|
|
tableName := envNameToTableName(name)
|
|
|
|
return d.db.createTableIfNotExists(tableName)
|
|
}
|
|
|
|
func (d *Datastore) DoesFileExists() bool {
|
|
pwd := config.GetConfig().PWD
|
|
filePath := fmt.Sprintf("%s/%s", pwd, variables.DBFileName)
|
|
|
|
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (d *Datastore) ListEnvironments() ([]string, error) {
|
|
return d.db.listTables()
|
|
}
|
|
|
|
func (d *Datastore) CreateEnvOffExsisting(newEnv string, baseEnv string) error {
|
|
tableNameName := envNameToTableName(newEnv)
|
|
tableNameBase := envNameToTableName(baseEnv)
|
|
|
|
err := d.CreateEnv(newEnv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = d.db.copyContentFromTo(tableNameBase, tableNameName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Datastore) Close() error {
|
|
return d.db.close()
|
|
}
|
|
|
|
func (d *Datastore) SetValue(key string, value string, encrypted *bool, envs []*types.Environment) error {
|
|
if encrypted == nil {
|
|
encrypted = common.BoolP(false)
|
|
}
|
|
|
|
if *encrypted {
|
|
v, err := enc(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
value = *v
|
|
}
|
|
|
|
for _, env := range envs {
|
|
tableName := envNameToTableName(env.Name)
|
|
|
|
if err := d.db.setVar(tableName, key, value, *encrypted); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Datastore) GetAll(envName string) ([]*types.EnvVar, error) {
|
|
tableName := envNameToTableName(envName)
|
|
|
|
vars, err := d.db.getAll(tableName)
|
|
if err != nil {
|
|
return vars, err
|
|
}
|
|
|
|
g := new(errgroup.Group)
|
|
|
|
for _, v := range vars {
|
|
g.Go(func() error {
|
|
if v.Encrypted {
|
|
if v.Value, err = dec(v.Value); err != nil {
|
|
return &intErrors.InvalidPasswordError{}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
if err := g.Wait(); err != nil {
|
|
return vars, err
|
|
}
|
|
|
|
sort.SliceStable(vars, func(i, j int) bool {
|
|
return vars[i].Key < vars[j].Key
|
|
})
|
|
|
|
return vars, nil
|
|
}
|
|
|
|
func (d *Datastore) RemoveVar(key string, envs []*types.Environment) {
|
|
for _, env := range envs {
|
|
tableName := envNameToTableName(env.Name)
|
|
d.db.rmVar(tableName, key)
|
|
}
|
|
}
|
|
|
|
func (d *Datastore) GetVar(envName string, key string) (*types.EnvVar, error) {
|
|
tableName := envNameToTableName(envName)
|
|
|
|
v, err := d.db.getVar(tableName, key)
|
|
if err != nil {
|
|
return v, err
|
|
}
|
|
|
|
if v.Encrypted {
|
|
if v.Value, err = dec(v.Value); err != nil {
|
|
return v, &intErrors.InvalidPasswordError{}
|
|
}
|
|
}
|
|
|
|
return v, err
|
|
}
|
|
|
|
func (d *Datastore) RemoveEnv(envName string) error {
|
|
tableName := envNameToTableName(envName)
|
|
|
|
return d.db.deleteTable(tableName)
|
|
}
|
|
|
|
func enc(s string) (*string, error) {
|
|
conf := config.GetConfig()
|
|
proj, _ := conf.GetProject(conf.PWD)
|
|
key, salt, err := deriveKey([]byte(proj.Password), nil)
|
|
data := []byte(s)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
blockCipher, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gcm, err := cipher.NewGCM(blockCipher)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
if _, err = rand.Read(nonce); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ciphertext := gcm.Seal(nonce, nonce, data, nil)
|
|
|
|
ciphertext = append(ciphertext, salt...)
|
|
str := string(ciphertext)
|
|
|
|
return &str, nil
|
|
}
|
|
|
|
func dec(s string) (string, error) {
|
|
data := []byte(s)
|
|
salt, data := data[len(data)-32:], data[:len(data)-32]
|
|
|
|
conf := config.GetConfig()
|
|
proj, _ := conf.GetProject(conf.PWD)
|
|
|
|
key, _, err := deriveKey([]byte(proj.Password), salt)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
blockCipher, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
gcm, err := cipher.NewGCM(blockCipher)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
|
|
|
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
str := string(plaintext)
|
|
|
|
return str, nil
|
|
}
|
|
|
|
func deriveKey(password, salt []byte) ([]byte, []byte, error) {
|
|
saltSize := 32
|
|
if salt == nil {
|
|
salt = make([]byte, saltSize)
|
|
if _, err := rand.Read(salt); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
N := 1048576
|
|
r := 8
|
|
p := 1
|
|
|
|
key, err := scrypt.Key(password, salt, N, r, p, saltSize)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return key, salt, nil
|
|
}
|