summaryrefslogtreecommitdiff
path: root/vendor/github.com/containers/common/pkg/secrets/shelldriver/shelldriver.go
blob: 87dc317a98feca9b0d594976db4f5e0e0be12b61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package shelldriver

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"sort"
	"strings"
)

var (

	// errMissingConfig indicates that one or more of the external actions are not configured
	errMissingConfig = errors.New("missing config value")

	// errNoSecretData indicates that there is not data associated with an id
	errNoSecretData = errors.New("no secret data with ID")

	// errInvalidKey indicates that something about your key is wrong
	errInvalidKey = errors.New("invalid key")
)

type driverConfig struct {
	// DeleteCommand contains a shell command that deletes a secret.
	// The secret id is provided as environment variable SECRET_ID
	DeleteCommand string
	// ListCommand contains a shell command that lists all secrets.
	// The output is expected to be one id per line
	ListCommand string
	// LookupCommand contains a shell command that retrieves a secret.
	// The secret id is provided as environment variable SECRET_ID
	LookupCommand string
	// StoreCommand contains a shell command that stores a secret.
	// The secret id is provided as environment variable SECRET_ID
	// The secret value itself is provided over stdin
	StoreCommand string
}

func (cfg *driverConfig) ParseOpts(opts map[string]string) error {
	for key, value := range opts {
		switch key {
		case "delete":
			cfg.DeleteCommand = value
		case "list":
			cfg.ListCommand = value
		case "lookup":
			cfg.LookupCommand = value
		case "store":
			cfg.StoreCommand = value
		default:
			return fmt.Errorf("invalid shell driver option: %q", key)
		}
	}
	if cfg.DeleteCommand == "" ||
		cfg.ListCommand == "" ||
		cfg.LookupCommand == "" ||
		cfg.StoreCommand == "" {

		return errMissingConfig
	}
	return nil
}

// Driver is the passdriver object
type Driver struct {
	driverConfig
}

// NewDriver creates a new secret driver.
func NewDriver(opts map[string]string) (*Driver, error) {
	cfg := &driverConfig{}
	if err := cfg.ParseOpts(opts); err != nil {
		return nil, err
	}

	driver := &Driver{
		driverConfig: *cfg,
	}

	return driver, nil
}

// List returns all secret IDs
func (d *Driver) List() (secrets []string, err error) {
	cmd := exec.CommandContext(context.TODO(), "/bin/sh", "-c", d.ListCommand)
	cmd.Env = os.Environ()
	cmd.Stderr = os.Stderr

	buf := &bytes.Buffer{}
	cmd.Stdout = buf

	err = cmd.Run()
	if err != nil {
		return nil, err
	}

	parts := bytes.Split(buf.Bytes(), []byte("\n"))
	for _, part := range parts {
		id := strings.Trim(string(part), " \r\n")
		if len(id) > 0 {
			secrets = append(secrets, id)
		}
	}
	sort.Strings(secrets)

	return secrets, nil
}

// Lookup returns the bytes associated with a secret ID
func (d *Driver) Lookup(id string) ([]byte, error) {
	if strings.Contains(id, "..") {
		return nil, errInvalidKey
	}

	cmd := exec.CommandContext(context.TODO(), "/bin/sh", "-c", d.LookupCommand)
	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, "SECRET_ID="+id)
	cmd.Stderr = os.Stderr

	buf := &bytes.Buffer{}
	cmd.Stdout = buf

	err := cmd.Run()
	if err != nil {
		return nil, fmt.Errorf("%s: %w", id, errNoSecretData)
	}
	return buf.Bytes(), nil
}

// Store saves the bytes associated with an ID. An error is returned if the ID already exists
func (d *Driver) Store(id string, data []byte) error {
	if strings.Contains(id, "..") {
		return errInvalidKey
	}

	cmd := exec.CommandContext(context.TODO(), "/bin/sh", "-c", d.StoreCommand)
	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, "SECRET_ID="+id)

	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	cmd.Stdin = bytes.NewReader(data)

	return cmd.Run()
}

// Delete removes the secret associated with the specified ID.  An error is returned if no matching secret is found.
func (d *Driver) Delete(id string) error {
	if strings.Contains(id, "..") {
		return errInvalidKey
	}

	cmd := exec.CommandContext(context.TODO(), "/bin/sh", "-c", d.DeleteCommand)
	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, "SECRET_ID="+id)

	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout

	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("%s: %w", id, errNoSecretData)
	}

	return nil
}