1package config
2
3import (
4 "crypto/rand"
5 "encoding/base64"
6 "errors"
7 "io/fs"
8 "os"
9 "path/filepath"
10)
11
12func EnsureSessionKey(secretsPath string) (bool, error) {
13 dir := filepath.Dir(secretsPath)
14 if err := os.Mkdir(dir, 0o700); err != nil && !errors.Is(err, fs.ErrExist) {
15 return false, err
16 }
17 var data []byte
18 info, err := os.Lstat(secretsPath)
19 fresh := errors.Is(err, fs.ErrNotExist)
20 if !fresh {
21 if err != nil || !info.Mode().IsRegular() {
22 return false, nil
23 }
24 var reason string
25 data, reason = readRegular(secretsPath, maxSecretsSize, reasonSecretsTooLarge, secretsModeReason)
26 if reason != "" {
27 return false, nil
28 }
29 vars, problems := parseSecrets(secretsPath, data)
30 if _, ok := vars[nameSessionKey]; ok || len(problems) > 0 {
31 return false, nil
32 }
33 if len(data) > 0 && data[len(data)-1] != '\n' {
34 data = append(data, '\n')
35 }
36 }
37 key := make([]byte, sessionKeyLength)
38 if _, err := rand.Read(key); err != nil {
39 return false, err
40 }
41 data = append(data, nameSessionKey+"="+base64.StdEncoding.EncodeToString(key)+"\n"...)
42 tmp, err := os.CreateTemp(dir, ".secrets.env-*")
43 if err != nil {
44 return false, err
45 }
46 defer os.Remove(tmp.Name())
47 _, err = tmp.Write(data)
48 if err == nil {
49 err = tmp.Sync()
50 }
51 if cerr := tmp.Close(); err == nil {
52 err = cerr
53 }
54 if err != nil {
55 return false, err
56 }
57
58 if fresh {
59 err = os.Link(tmp.Name(), secretsPath)
60 if errors.Is(err, fs.ErrExist) {
61 return false, nil
62 }
63 if err == nil {
64 err = os.Remove(tmp.Name())
65 }
66 } else {
67 err = os.Rename(tmp.Name(), secretsPath)
68 }
69 if err != nil {
70 return false, err
71 }
72 d, err := os.Open(dir)
73 if err != nil {
74 return false, err
75 }
76 err = d.Sync()
77 if cerr := d.Close(); err == nil {
78 err = cerr
79 }
80 return err == nil, err
81}