Raw
 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}