dev/zhi #1
@@ -1,10 +1,12 @@
|
|||||||
module pass_mgr
|
module pass_mgr
|
||||||
|
|
||||||
go 1.22
|
go 1.24.0
|
||||||
|
|
||||||
require github.com/spf13/cobra v1.8.0
|
require github.com/spf13/cobra v1.8.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
|
golang.org/x/term v0.40.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,5 +6,9 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
|||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||||
|
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -173,27 +175,17 @@ func rotateCmd() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func adminInitCmd() *cobra.Command {
|
func adminInitCmd() *cobra.Command {
|
||||||
var keyPath string
|
return &cobra.Command{
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "admin init",
|
Use: "admin init",
|
||||||
Short: "Initialize pass_mgr with admin key",
|
Short: "Initialize pass_mgr with admin key",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Require --key-path parameter
|
if err := initAdminInteractive(); err != nil {
|
||||||
if keyPath == "" {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error: --key-path is required")
|
|
||||||
fmt.Fprintln(os.Stderr, "Usage: pass_mgr admin init --key-path <path-to-key-file>")
|
|
||||||
fmt.Fprintln(os.Stderr, "The key file must contain a password with at least 6 characters")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if err := initAdmin(keyPath); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("pass_mgr initialized successfully")
|
fmt.Println("pass_mgr initialized successfully")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVar(&keyPath, "key-path", "", "Path to admin key file (required, password must be >= 6 chars)")
|
|
||||||
return cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCmd() *cobra.Command {
|
func setCmd() *cobra.Command {
|
||||||
@@ -256,32 +248,56 @@ func loadAdminKey() ([]byte, error) {
|
|||||||
return hash[:], nil
|
return hash[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initAdmin(keyPath string) error {
|
func initAdminInteractive() error {
|
||||||
|
fmt.Print("Enter admin password: ")
|
||||||
|
password1, err := term.ReadPassword(int(syscall.Stdin))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read password: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Trim whitespace/newlines
|
||||||
|
password1 = []byte(strings.TrimSpace(string(password1)))
|
||||||
|
|
||||||
|
// Validate password length
|
||||||
|
if len(password1) < 6 {
|
||||||
|
return fmt.Errorf("password must be at least 6 characters long (got %d)", len(password1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print("Confirm admin password: ")
|
||||||
|
password2, err := term.ReadPassword(int(syscall.Stdin))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read password confirmation: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Trim whitespace/newlines
|
||||||
|
password2 = []byte(strings.TrimSpace(string(password2)))
|
||||||
|
|
||||||
|
// Check passwords match
|
||||||
|
if string(password1) != string(password2) {
|
||||||
|
return fmt.Errorf("passwords do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the key
|
||||||
|
return saveAdminKey(password1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveAdminKey(key []byte) error {
|
||||||
homeDir := getHomeDir()
|
homeDir := getHomeDir()
|
||||||
adminDir := filepath.Join(homeDir, AdminKeyDir)
|
adminDir := filepath.Join(homeDir, AdminKeyDir)
|
||||||
|
|
||||||
// Create admin directory
|
// Create admin directory
|
||||||
if err := os.MkdirAll(adminDir, 0700); err != nil {
|
if err := os.MkdirAll(adminDir, 0700); err != nil {
|
||||||
return fmt.Errorf("failed to create admin directory: %w", err)
|
return fmt.Errorf("failed to create admin directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read provided key
|
|
||||||
key, err := os.ReadFile(keyPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read key file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate password length (must be >= 6 characters)
|
|
||||||
if len(key) < 6 {
|
|
||||||
return fmt.Errorf("password must be at least 6 characters long (got %d)", len(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save key
|
// Save key
|
||||||
keyFile := filepath.Join(adminDir, AdminKeyFile)
|
keyFile := filepath.Join(adminDir, AdminKeyFile)
|
||||||
if err := os.WriteFile(keyFile, key, 0600); err != nil {
|
if err := os.WriteFile(keyFile, key, 0600); err != nil {
|
||||||
return fmt.Errorf("failed to save key: %w", err)
|
return fmt.Errorf("failed to save key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save config
|
// Save config
|
||||||
config := Config{
|
config := Config{
|
||||||
KeyHash: fmt.Sprintf("%x", sha256.Sum256(key)),
|
KeyHash: fmt.Sprintf("%x", sha256.Sum256(key)),
|
||||||
@@ -292,10 +308,25 @@ func initAdmin(keyPath string) error {
|
|||||||
if err := os.WriteFile(configPath, configData, 0600); err != nil {
|
if err := os.WriteFile(configPath, configData, 0600); err != nil {
|
||||||
return fmt.Errorf("failed to save config: %w", err)
|
return fmt.Errorf("failed to save config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initAdmin(keyPath string) error {
|
||||||
|
// Read provided key
|
||||||
|
key, err := os.ReadFile(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read key file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate password length (must be >= 6 characters)
|
||||||
|
if len(key) < 6 {
|
||||||
|
return fmt.Errorf("password must be at least 6 characters long (got %d)", len(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return saveAdminKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
func getSecretsDir() string {
|
func getSecretsDir() string {
|
||||||
if workspaceDir != "" && agentID != "" {
|
if workspaceDir != "" && agentID != "" {
|
||||||
return filepath.Join(workspaceDir, SecretsDirName, agentID)
|
return filepath.Join(workspaceDir, SecretsDirName, agentID)
|
||||||
|
|||||||
Reference in New Issue
Block a user