aboutsummaryrefslogtreecommitdiff
path: root/secrets/config_file_client.go
blob: b3c8620931e41810812f6d5079806808567528df (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
package secrets

import (
	"context"
	"encoding/json"
	"fmt"
	"io/fs"
	"path/filepath"
	"sync"
	"time"

	"github.com/mitchellh/mapstructure"
	"gopkg.in/yaml.v2"
)

type ConfigFileHandle struct{}

func (h *ConfigFileHandle) Reference() string { return "NOOP" }

var _ Handle = (*ConfigFileHandle)(nil)

// ConfigFileClient returns secrets from a JSON or YAML configuration
// file. This mode isn't as secure as using Vault or some other secret
// management service but can be useful for users who don't have access
// to such a service.
//
// Writes to this secret client will silently succeed while doing
// nothing.
type ConfigFileClient struct {
	c   chan Renewal
	cfg map[string]any
}

var _ Client = (*ConfigFileClient)(nil)
var _ ClientManager = (*ConfigFileClient)(nil)

// NewConfigFileClient creates a new ConfigFileClient by loading a named
// config file from a filesystem and unmarshalling it. The config file
// can be in JSON or YAML format, determined by a .json, .yaml, or .yml
// extension. The configuration must be nested within a key in that file
// to support sharing the file with other subsystems.
//
// Credentials should be stored in the config file in a format that
// matches their definitions in client.go
func NewConfigFileClient(filesystem fs.FS, name, key string) (ClientManager, error) {
	fp, err := filesystem.Open(name)
	if err != nil {
		return nil, err
	}
	defer fp.Close()

	fc := map[string]any{}

	switch e := filepath.Ext(name); e {
	case ".yaml", ".yml":
		if err := yaml.NewDecoder(fp).Decode(&fc); err != nil {
			return nil, err
		}
	case ".json":
		if err := json.NewDecoder(fp).Decode(&fc); err != nil {
			return nil, err
		}
	default:
		return nil, fmt.Errorf("Config files with extension %s are not supported", e)
	}

	ck, ok := fc[key]
	if !ok {
		return nil, fmt.Errorf("Key %s does not exist in config file", key)
	}

	cfg := map[string]any{}
	if err := mapstructure.Decode(ck, &cfg); err != nil {
		return nil, fmt.Errorf("Config file key was not of correct type: %w", err)
	}

	return &ConfigFileClient{
		c:   make(chan Renewal),
		cfg: cfg,
	}, nil
}

func (c *ConfigFileClient) DatabaseCredential(ctx context.Context, path string) (*Credential, Handle, error) {
	out := Credential{}
	hnd, err := c.RawSecret(ctx, path, &out)
	if err != nil {
		return nil, nil, err
	}
	return &out, hnd, nil
}

func (c *ConfigFileClient) Secret(ctx context.Context, path string, out any) (Handle, error) {
	return c.RawSecret(ctx, path, out)
}

func (c *ConfigFileClient) RawSecret(ctx context.Context, path string, out any) (Handle, error) {
	v, ok := c.cfg[path]
	if !ok {
		return nil, fmt.Errorf("Secret %s was not found in config file", path)
	}

	if err := mapstructure.Decode(v, &out); err != nil {
		return nil, err
	}

	return &ConfigFileHandle{}, nil
}

func (c *ConfigFileClient) AWSIAMUser(ctx context.Context, name string) (*AWSCredential, Handle, error) {
	out := AWSCredential{}
	hnd, err := c.RawSecret(ctx, name, &out)
	if err != nil {
		return nil, nil, err
	}
	return &out, hnd, nil
}

func (c *ConfigFileClient) AWSAssumeRoleSimple(ctx context.Context, name string) (*AWSCredential, Handle, error) {
	return c.AWSAssumeRole(ctx, name, "", time.Second)
}

func (c *ConfigFileClient) AWSAssumeRole(ctx context.Context, name string, sessionName string, ttl time.Duration) (*AWSCredential, Handle, error) {
	return nil, nil, fmt.Errorf("Assuming AWS roles is not supported by ConfigFileClient")
}

func (c *ConfigFileClient) WriteSecret(ctx context.Context, path string, in any) error {
	return nil
}

func (c *ConfigFileClient) Encrypt(ctx context.Context, suffix string, data []byte) (string, error) {
	return "", nil
}

func (c *ConfigFileClient) Decrypt(ctx context.Context, suffix, data string) ([]byte, error) {
	return nil, nil
}

func (c *ConfigFileClient) Destroy(h Handle) error {
	return nil
}

func (c *ConfigFileClient) MakeNonCritical(h Handle) error {
	return nil
}

func (c *ConfigFileClient) Authenticate(ctx context.Context) error {
	return nil
}

func (c *ConfigFileClient) Notifications() <-chan Renewal {
	return c.c
}

func (c *ConfigFileClient) Run(ctx context.Context, wg *sync.WaitGroup) error {
	wg.Add(1)
	defer wg.Done()

	for {
		select {
		case <-ctx.Done():
			close(c.c)
			return nil
		}
	}
}