初版功能完成
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user