69 lines
2.2 KiB
Go
69 lines
2.2 KiB
Go
package serverwg
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
)
|
|
|
|
// LoadOrCreatePrivateKey reads a Server key or atomically creates a 0600 file.
|
|
func LoadOrCreatePrivateKey(path string) (wgtypes.Key, error) {
|
|
if raw, err := os.ReadFile(path); err == nil {
|
|
key, err := wgtypes.ParseKey(strings.TrimSpace(string(raw)))
|
|
if err != nil {
|
|
return wgtypes.Key{}, fmt.Errorf("parse Server private key %q: %w", path, err)
|
|
}
|
|
if err := os.Chmod(path, 0o600); err != nil {
|
|
return wgtypes.Key{}, fmt.Errorf("restrict Server private key permissions: %w", err)
|
|
}
|
|
return key, nil
|
|
} else if !errors.Is(err, os.ErrNotExist) {
|
|
return wgtypes.Key{}, fmt.Errorf("read Server private key %q: %w", path, err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
|
return wgtypes.Key{}, fmt.Errorf("create Server key directory: %w", err)
|
|
}
|
|
key, err := wgtypes.GeneratePrivateKey()
|
|
if err != nil {
|
|
return wgtypes.Key{}, fmt.Errorf("generate Server WireGuard private key: %w", err)
|
|
}
|
|
directory := filepath.Dir(path)
|
|
file, err := os.CreateTemp(directory, ".server-wg-*.tmp")
|
|
if err != nil {
|
|
return wgtypes.Key{}, fmt.Errorf("create temporary Server private key: %w", err)
|
|
}
|
|
temporaryPath := file.Name()
|
|
defer os.Remove(temporaryPath)
|
|
if err := file.Chmod(0o600); err != nil {
|
|
file.Close()
|
|
return wgtypes.Key{}, fmt.Errorf("restrict temporary Server private key: %w", err)
|
|
}
|
|
writeErr := func() error {
|
|
if _, err := file.WriteString(key.String() + "\n"); err != nil {
|
|
return err
|
|
}
|
|
return file.Sync()
|
|
}()
|
|
closeErr := file.Close()
|
|
if writeErr != nil {
|
|
return wgtypes.Key{}, fmt.Errorf("write Server private key: %w", writeErr)
|
|
}
|
|
if closeErr != nil {
|
|
return wgtypes.Key{}, fmt.Errorf("close Server private key: %w", closeErr)
|
|
}
|
|
// A hard link publishes the fully flushed inode without overwriting a key
|
|
// concurrently created by another Server process. Temporary and final files
|
|
// are guaranteed to be on the same data-directory filesystem.
|
|
if err := os.Link(temporaryPath, path); err != nil {
|
|
if errors.Is(err, os.ErrExist) {
|
|
return LoadOrCreatePrivateKey(path)
|
|
}
|
|
return wgtypes.Key{}, fmt.Errorf("publish Server private key %q: %w", path, err)
|
|
}
|
|
return key, nil
|
|
}
|