108 lines
3.1 KiB
Go
108 lines
3.1 KiB
Go
// Package internalsecrets initializes persistent deployment secrets without
|
|
// exposing their values through environment variables or logs.
|
|
package internalsecrets
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
)
|
|
|
|
const secretSize = 32
|
|
|
|
// Ensure creates path atomically with cryptographically random bytes. Existing
|
|
// destination files are validated and never replaced.
|
|
func Ensure(path string, uid, gid int, mode os.FileMode) (bool, error) {
|
|
if mode.Perm()&0o007 != 0 || mode.Perm()&0o700 == 0 {
|
|
return false, errors.New("secret permissions are invalid")
|
|
}
|
|
if err := validateExisting(path, mode); err == nil {
|
|
return false, nil
|
|
} else if !errors.Is(err, os.ErrNotExist) {
|
|
return false, err
|
|
}
|
|
|
|
value := make([]byte, secretSize)
|
|
if _, err := io.ReadFull(rand.Reader, value); err != nil {
|
|
return false, fmt.Errorf("generate secret: %w", err)
|
|
}
|
|
|
|
directory := filepath.Dir(path)
|
|
directoryMode := os.FileMode(0o700)
|
|
if mode.Perm()&0o040 != 0 {
|
|
directoryMode = 0o750
|
|
}
|
|
if err := os.MkdirAll(directory, directoryMode); err != nil {
|
|
return false, fmt.Errorf("create secret directory: %w", err)
|
|
}
|
|
if err := os.Chmod(directory, directoryMode); err != nil {
|
|
return false, fmt.Errorf("restrict secret directory: %w", err)
|
|
}
|
|
temporary, err := os.CreateTemp(directory, ".secret-*")
|
|
if err != nil {
|
|
return false, fmt.Errorf("create temporary secret: %w", err)
|
|
}
|
|
temporaryPath := temporary.Name()
|
|
defer os.Remove(temporaryPath)
|
|
if err := temporary.Chmod(mode); err != nil {
|
|
temporary.Close()
|
|
return false, fmt.Errorf("restrict temporary secret: %w", err)
|
|
}
|
|
if _, err := temporary.Write(value); err != nil {
|
|
temporary.Close()
|
|
return false, fmt.Errorf("write temporary secret: %w", err)
|
|
}
|
|
if err := temporary.Sync(); err != nil {
|
|
temporary.Close()
|
|
return false, fmt.Errorf("sync temporary secret: %w", err)
|
|
}
|
|
if err := temporary.Close(); err != nil {
|
|
return false, fmt.Errorf("close temporary secret: %w", err)
|
|
}
|
|
if err := os.Link(temporaryPath, path); err != nil {
|
|
if errors.Is(err, os.ErrExist) {
|
|
return false, validateExisting(path, mode)
|
|
}
|
|
return false, fmt.Errorf("install secret: %w", err)
|
|
}
|
|
if runtime.GOOS != "windows" && os.Geteuid() == 0 {
|
|
if err := os.Chown(path, uid, gid); err != nil {
|
|
return false, fmt.Errorf("set secret owner: %w", err)
|
|
}
|
|
if err := os.Chown(directory, uid, gid); err != nil {
|
|
return false, fmt.Errorf("set secret directory owner: %w", err)
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func validateExisting(path string, mode os.FileMode) error {
|
|
info, err := os.Lstat(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
|
|
return errors.New("secret must be a regular file")
|
|
}
|
|
if runtime.GOOS != "windows" && info.Mode().Perm() != mode.Perm() {
|
|
return errors.New("secret permissions do not match the required mode")
|
|
}
|
|
_, err = readSecret(path)
|
|
return err
|
|
}
|
|
|
|
func readSecret(path string) ([]byte, error) {
|
|
value, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(value) != secretSize {
|
|
return nil, fmt.Errorf("secret must contain exactly %d bytes", secretSize)
|
|
}
|
|
return value, nil
|
|
}
|