| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | package main | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"log" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2023-02-12 11:58:26 +01:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 	"sync" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-12 11:58:26 +01:00
										 |  |  | 	restful "github.com/emicklei/go-restful" | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 	"m.cluseau.fr/go/httperr" | 
					
						
							| 
									
										
										
										
											2023-02-12 11:58:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 	"novit.tech/direktil/local-server/secretstore" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var secStore *secretstore.Store | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | func secStoreRoot() string            { return filepath.Join(*dataDir, "secrets") } | 
					
						
							|  |  |  | func secStorePath(name string) string { return filepath.Join(secStoreRoot(), name) } | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | func secKeysStorePath() string        { return secStorePath(".keys") } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func openSecretStore() { | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	keysPath := secKeysStorePath() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := os.MkdirAll(filepath.Dir(filepath.Dir(keysPath)), 0755); err != nil { | 
					
						
							|  |  |  | 		log.Fatal("failed to create dirs: ", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := os.MkdirAll(filepath.Dir(keysPath), 0700); err != nil { | 
					
						
							|  |  |  | 		log.Fatal("failed to secret store dir: ", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	secStore, err = secretstore.Open(keysPath) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case err == nil: | 
					
						
							|  |  |  | 		wPublicState.Change(func(v *PublicState) { | 
					
						
							|  |  |  | 			v.Store.New = false | 
					
						
							|  |  |  | 			v.Store.Open = false | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case os.IsNotExist(err): | 
					
						
							|  |  |  | 		secStore = secretstore.New() | 
					
						
							|  |  |  | 		wPublicState.Change(func(v *PublicState) { | 
					
						
							|  |  |  | 			v.Store.New = true | 
					
						
							|  |  |  | 			v.Store.Open = false | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		log.Fatal("failed to open keys store: ", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	unlockMutex = sync.Mutex{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ErrStoreAlreadyUnlocked = httperr.NewStd(http.StatusConflict, 1, "store already unlocked") | 
					
						
							|  |  |  | 	ErrInvalidPassphrase    = httperr.NewStd(http.StatusBadRequest, 2, "invalid passphrase") | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | func unlockSecretStore(passphrase []byte) (err httperr.Error) { | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 	unlockMutex.Lock() | 
					
						
							|  |  |  | 	defer unlockMutex.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if secStore.Unlocked() { | 
					
						
							|  |  |  | 		return ErrStoreAlreadyUnlocked | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if secStore.IsNew() { | 
					
						
							|  |  |  | 		err := secStore.Init(passphrase) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 			return httperr.Internal(err) | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err = secStore.SaveTo(secKeysStorePath()) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Print("secret store save error: ", err) | 
					
						
							|  |  |  | 			secStore.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 			return httperr.Internal(err) | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		if !secStore.Unlock([]byte(passphrase)) { | 
					
						
							|  |  |  | 			return ErrInvalidPassphrase | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	token := "" | 
					
						
							|  |  |  | 	if err := readSecret("admin-token", &token); err != nil { | 
					
						
							|  |  |  | 		if !os.IsNotExist(err) { | 
					
						
							|  |  |  | 			log.Print("failed to read admin token: ", err) | 
					
						
							|  |  |  | 			secStore.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 			return httperr.Internal(err) | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 		token, err = newToken(32) | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			secStore.Close() | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 			return httperr.Internal(err) | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err = writeSecret("admin-token", token) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Print("write error: ", err) | 
					
						
							|  |  |  | 			secStore.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 			return httperr.Internal(err) | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		log.Print("wrote new admin token") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	*adminToken = token | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 	{ | 
					
						
							|  |  |  | 		token, err := newToken(16) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			secStore.Close() | 
					
						
							|  |  |  | 			return httperr.Internal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		wState.Change(func(v *State) { | 
					
						
							|  |  |  | 			v.Store.DownloadToken = token | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 	wPublicState.Change(func(v *PublicState) { | 
					
						
							|  |  |  | 		v.Store.New = false | 
					
						
							|  |  |  | 		v.Store.Open = true | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	go updateState() | 
					
						
							| 
									
										
										
										
											2023-02-12 11:58:26 +01:00
										 |  |  | 	go migrateSecrets() | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 	return | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func readSecret(name string, value any) (err error) { | 
					
						
							|  |  |  | 	f, err := os.Open(secStorePath(name + ".data")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer f.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	in, err := secStore.NewReader(f) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return json.NewDecoder(in).Decode(value) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func writeSecret(name string, value any) (err error) { | 
					
						
							| 
									
										
										
										
											2023-02-12 11:58:26 +01:00
										 |  |  | 	path := secStorePath(name + ".data.new") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err = os.MkdirAll(filepath.Dir(path), 0700); err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := os.Create(path) | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = func() (err error) { | 
					
						
							|  |  |  | 		defer f.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		out, err := secStore.NewWriter(f) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return json.NewEncoder(out).Encode(value) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-12 11:58:26 +01:00
										 |  |  | 	err = os.Rename(f.Name(), secStorePath(name+".data")) | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-12 11:58:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 	go updateState() | 
					
						
							|  |  |  | 	return | 
					
						
							| 
									
										
										
										
											2023-02-12 11:58:26 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var secL sync.Mutex | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func updateSecret[T any](name string, update func(*T)) (err error) { | 
					
						
							|  |  |  | 	secL.Lock() | 
					
						
							|  |  |  | 	defer secL.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	v := new(T) | 
					
						
							|  |  |  | 	err = readSecret(name, v) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if !os.IsNotExist(err) { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err = nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	update(v) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return writeSecret(name, *v) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func updateSecretWithKey[T any](name, key string, update func(v *T)) (err error) { | 
					
						
							|  |  |  | 	secL.Lock() | 
					
						
							|  |  |  | 	defer secL.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	kvs := map[string]*T{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = readSecret(name, &kvs) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if !os.IsNotExist(err) { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err = nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	update(kvs[key]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return writeSecret(name, kvs) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type KVSecrets[T any] struct{ Name string } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s KVSecrets[T]) Data() (kvs map[string]T, err error) { | 
					
						
							|  |  |  | 	kvs = make(map[string]T) | 
					
						
							|  |  |  | 	err = readSecret(s.Name, &kvs) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if !os.IsNotExist(err) { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err = nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s KVSecrets[T]) Keys(prefix string) (keys []string, err error) { | 
					
						
							|  |  |  | 	kvs, err := s.Data() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	keys = make([]string, 0, len(kvs)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for k := range kvs { | 
					
						
							|  |  |  | 		if !strings.HasPrefix(k, prefix) { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		keys = append(keys, k[len(prefix):]) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 13:03:42 +01:00
										 |  |  | 	sort.Strings(keys) | 
					
						
							| 
									
										
										
										
											2023-02-12 11:58:26 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s KVSecrets[T]) Get(key string) (v T, found bool, err error) { | 
					
						
							|  |  |  | 	kvs, err := s.Data() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	v, found = kvs[key] | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s KVSecrets[T]) Put(key string, v T) (err error) { | 
					
						
							|  |  |  | 	secL.Lock() | 
					
						
							|  |  |  | 	defer secL.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	kvs, err := s.Data() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	kvs[key] = v | 
					
						
							|  |  |  | 	err = writeSecret(s.Name, kvs) | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s KVSecrets[T]) WsList(resp *restful.Response, prefix string) { | 
					
						
							|  |  |  | 	keys, err := s.Keys(prefix) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		httperr.New(http.StatusInternalServerError, err).WriteJSON(resp.ResponseWriter) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	resp.WriteEntity(keys) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s KVSecrets[T]) WsGet(resp *restful.Response, key string) { | 
					
						
							|  |  |  | 	keys, found, err := s.Get(key) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		httperr.New(http.StatusInternalServerError, err).WriteJSON(resp.ResponseWriter) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !found { | 
					
						
							|  |  |  | 		ErrNotFound.WriteJSON(resp.ResponseWriter) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	resp.WriteEntity(keys) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s KVSecrets[T]) WsPut(req *restful.Request, resp *restful.Response, key string) { | 
					
						
							|  |  |  | 	v := new(T) | 
					
						
							|  |  |  | 	err := req.ReadEntity(v) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		httperr.New(http.StatusBadRequest, err).WriteJSON(resp.ResponseWriter) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = s.Put(key, *v) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		httperr.New(http.StatusInternalServerError, err).WriteJSON(resp.ResponseWriter) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-07 21:29:19 +01:00
										 |  |  | } |