1package env
2
3import (
4 "context"
5 "fmt"
6 "os/exec"
7 "strings"
8 "time"
9
10 "github.com/rs/zerolog/log"
11)
12
13type SecretProvider interface {
14 GetSecret(path string) (string, error)
15 Name() string
16}
17
18type PassProvider struct {
19 timeout time.Duration
20}
21
22func NewPassProvider() *PassProvider {
23 return &PassProvider{
24 timeout: 10 * time.Second,
25 }
26}
27
28func (p *PassProvider) Name() string {
29 return "pass"
30}
31
32func (p *PassProvider) GetSecret(path string) (string, error) {
33 ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
34 defer cancel()
35
36 cmd := exec.CommandContext(ctx, "pass", "show", path)
37 output, err := cmd.Output()
38 if err != nil {
39 if exitError, ok := err.(*exec.ExitError); ok {
40 return "", fmt.Errorf("pass command failed: %s", string(exitError.Stderr))
41 }
42 return "", fmt.Errorf("failed to execute pass command: %w", err)
43 }
44
45 secret := strings.TrimSpace(string(output))
46 if secret == "" {
47 return "", fmt.Errorf("empty secret returned from pass for path: %s", path)
48 }
49
50 log.Debug().
51 Str("path", path).
52 Msg("Successfully retrieved secret from pass")
53
54 return secret, nil
55}
56
57type SopsProvider struct {
58 timeout time.Duration
59}
60
61func NewSopsProvider() *SopsProvider {
62 return &SopsProvider{
63 timeout: 10 * time.Second,
64 }
65}
66
67func (s *SopsProvider) Name() string {
68 return "sops"
69}
70
71func (s *SopsProvider) GetSecret(path string) (string, error) {
72 ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
73 defer cancel()
74
75 // Extract key from path format: file.yaml:key.nested.path
76 parts := strings.SplitN(path, ":", 2)
77 if len(parts) != 2 {
78 return "", fmt.Errorf("sops path must be in format 'file:key', got: %s", path)
79 }
80
81 filePath := parts[0]
82 keyPath := parts[1]
83
84 // Use sops -d --extract to get specific key
85 cmd := exec.CommandContext(ctx, "sops", "-d", "--extract", fmt.Sprintf("[\"%s\"]", keyPath), filePath)
86 output, err := cmd.Output()
87 if err != nil {
88 if exitError, ok := err.(*exec.ExitError); ok {
89 return "", fmt.Errorf("sops command failed: %s", string(exitError.Stderr))
90 }
91 return "", fmt.Errorf("failed to execute sops command: %w", err)
92 }
93
94 secret := strings.TrimSpace(string(output))
95 if secret == "" {
96 return "", fmt.Errorf("empty secret returned from sops for path: %s", path)
97 }
98
99 log.Debug().
100 Str("path", path).
101 Msg("Successfully retrieved secret from sops")
102
103 return secret, nil
104}