refactor: reduce deployment to one compose file
Container-Image veröffentlichen / container (push) Has been cancelled
Container-Image veröffentlichen / container (push) Has been cancelled
This commit is contained in:
1 parent
888d9a54a7
commit
c8728746f1
12 files changed
+142
-334
No files matched your search
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
@@ -31,6 +32,48 @@ func LoadCipher(path string) (*Cipher, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read master key: %w", err)
|
||||
}
|
||||
return cipherFromKey(raw)
|
||||
}
|
||||
|
||||
// LoadOrCreateCipher loads an existing master key or creates one on first start.
|
||||
// The key must live on a persistent volume separate from the application data.
|
||||
func LoadOrCreateCipher(path string) (*Cipher, error) {
|
||||
cipher, err := LoadCipher(path)
|
||||
if err == nil {
|
||||
return cipher, nil
|
||||
}
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
return nil, fmt.Errorf("create master key directory: %w", err)
|
||||
}
|
||||
key := make([]byte, chacha20poly1305.KeySize)
|
||||
if _, err := rand.Read(key); err != nil {
|
||||
return nil, fmt.Errorf("create master key: %w", err)
|
||||
}
|
||||
encoded := []byte(base64.RawStdEncoding.EncodeToString(key))
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
|
||||
if errors.Is(err, os.ErrExist) {
|
||||
return LoadCipher(path)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create master key file: %w", err)
|
||||
}
|
||||
if _, err := file.Write(encoded); err != nil {
|
||||
_ = file.Close()
|
||||
_ = os.Remove(path)
|
||||
return nil, fmt.Errorf("write master key: %w", err)
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
_ = os.Remove(path)
|
||||
return nil, fmt.Errorf("close master key file: %w", err)
|
||||
}
|
||||
return &Cipher{key: key}, nil
|
||||
}
|
||||
|
||||
func cipherFromKey(raw []byte) (*Cipher, error) {
|
||||
raw = []byte(strings.TrimSpace(string(raw)))
|
||||
key, err := base64.RawStdEncoding.DecodeString(string(raw))
|
||||
if err != nil {
|
||||
|
||||
@@ -41,6 +41,34 @@ func TestCipherRoundTripAndFingerprint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadOrCreateCipherPersistsSecureKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
path := filepath.Join(t.TempDir(), "secrets", "master_key")
|
||||
first, err := LoadOrCreateCipher(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if permissions := info.Mode().Perm(); permissions != 0o600 {
|
||||
t.Fatalf("master key permissions = %o, want 600", permissions)
|
||||
}
|
||||
ciphertext, nonce, err := first.Encrypt("persistent token")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second, err := LoadOrCreateCipher(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plaintext, err := second.Decrypt(ciphertext, nonce)
|
||||
if err != nil || plaintext != "persistent token" {
|
||||
t.Fatalf("created key was not reused: plaintext=%q err=%v", plaintext, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordHash(t *testing.T) {
|
||||
t.Parallel()
|
||||
hash, err := HashPassword("a secure password")
|
||||
|
||||
Reference in new issue
Block a user