Skip to content

Commit 36141fd

Browse files
authored
support VAULT_RUN_MODE envvar (#104)
1 parent 33ea6fe commit 36141fd

10 files changed

Lines changed: 152 additions & 80 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
IMPROVEMENTS:
44
* Requests from the extension to Vault now set the User-Agent field accordingly.
5+
* Introduced a `VAULT_RUN_MODE` environment variable to allow user to run in proxy mode, file mode, or both.
6+
* The default value is 'default', which runs in *both* proxy and file mode.
57

68
## 0.9.0 (February 23, 2023)
79

internal/config/info.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ const (
44
ExtensionName = "vault-lambda-extension"
55
ExtensionVersion = "0.9.0"
66
VaultLogLevel = "VAULT_LOG_LEVEL" // Optional, one of TRACE, DEBUG, INFO, WARN, ERROR, OFF
7+
VaultRunMode = "VAULT_RUN_MODE"
78
)

internal/extension/client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"context"
99
"encoding/json"
1010
"fmt"
11-
"io/ioutil"
11+
"io"
1212
"net/http"
1313
)
1414

@@ -89,7 +89,7 @@ func (e *Client) Register(ctx context.Context, filename string) (*RegisterRespon
8989
return nil, fmt.Errorf("request failed with status %s", httpRes.Status)
9090
}
9191
defer httpRes.Body.Close()
92-
body, err := ioutil.ReadAll(httpRes.Body)
92+
body, err := io.ReadAll(httpRes.Body)
9393
if err != nil {
9494
return nil, err
9595
}
@@ -122,7 +122,7 @@ func (e *Client) NextEvent(ctx context.Context) (*NextEventResponse, error) {
122122
return nil, fmt.Errorf("request failed with status %s", httpRes.Status)
123123
}
124124
defer httpRes.Body.Close()
125-
body, err := ioutil.ReadAll(httpRes.Body)
125+
body, err := io.ReadAll(httpRes.Body)
126126
if err != nil {
127127
return nil, err
128128
}

internal/proxy/cache.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"encoding/hex"
1010
"fmt"
11+
"io"
1112
"io/ioutil"
1213
"net/http"
1314
"strings"
@@ -74,7 +75,7 @@ func NewCache(cc config.CacheConfig) *Cache {
7475
// constructs the CacheKey for this request and token and returns the SHA256
7576
// hash
7677
func makeRequestHash(logger hclog.Logger, r *http.Request, token string) (string, error) {
77-
reqBody, err := ioutil.ReadAll(r.Body)
78+
reqBody, err := io.ReadAll(r.Body)
7879
if err != nil {
7980
if r.Body != nil {
8081
if err := r.Body.Close(); err != nil {

internal/proxy/proxy_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"encoding/json"
88
"errors"
99
"fmt"
10-
"io/ioutil"
10+
"io"
1111
"net"
1212
"net/http"
1313
"net/http/httptest"
@@ -81,7 +81,7 @@ func TestProxy(t *testing.T) {
8181
require.NoError(t, err)
8282
require.Equal(t, http.StatusOK, resp.StatusCode)
8383
defer resp.Body.Close()
84-
body, err := ioutil.ReadAll(resp.Body)
84+
body, err := io.ReadAll(resp.Body)
8585
require.NoError(t, err)
8686
var secret api.Secret
8787
require.NoError(t, json.Unmarshal(body, &secret), string(body))

internal/runmode/runmode.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Package runmode determines if the vault lambda extension should run in default mode, file mode, or proxy mode.
2+
// default mode: uses both file and proxy mode
3+
// file mode: writes secrets to disk
4+
// proxy mode: forwards requests to a Vault server
5+
package runmode
6+
7+
import "strings"
8+
9+
type Mode string
10+
11+
var (
12+
ModeDefault Mode = "default"
13+
ModeFile Mode = "file"
14+
ModeProxy Mode = "proxy"
15+
)
16+
17+
var modes = map[Mode]struct{}{
18+
ModeFile: {},
19+
ModeProxy: {},
20+
ModeDefault: {},
21+
}
22+
23+
func ParseMode(rm string) Mode {
24+
_, ok := modes[Mode(strings.ToLower(rm))]
25+
if !ok {
26+
return ModeDefault
27+
}
28+
29+
return Mode(rm)
30+
}
31+
32+
func (m Mode) HasModeProxy() bool {
33+
return m == ModeDefault || m == ModeProxy
34+
}
35+
36+
func (m Mode) HasModeFile() bool {
37+
return m == ModeDefault || m == ModeFile
38+
}

internal/vault/client.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"encoding/base64"
99
"encoding/json"
1010
"fmt"
11-
"io/ioutil"
11+
"io"
1212
"os"
1313
"strings"
1414
"sync"
@@ -105,7 +105,6 @@ func (c *Client) Token(ctx context.Context) (string, error) {
105105

106106
// login authenticates to Vault using IAM auth, and sets the client's token.
107107
func (c *Client) login(ctx context.Context) error {
108-
109108
authConfig := config.AuthConfigFromEnv()
110109
roleToAssumeArn := authConfig.AssumedRoleArn
111110

@@ -114,16 +113,13 @@ func (c *Client) login(ctx context.Context) error {
114113
/* If passing in a role (through VAULT_ASSUMED_ROLE_ARN enviornment variable)
115114
to be assumed for Vault authentication, use it instead of the function execution role */
116115
if roleToAssumeArn != "" {
117-
118116
c.logger.Debug(fmt.Sprintf("Trying to assume role with arn of %s to authenticate with Vault", roleToAssumeArn))
119-
120117
sessionName := "vault_auth"
121118

122119
result, err := c.stsSvc.AssumeRole(&sts.AssumeRoleInput{
123120
RoleArn: &roleToAssumeArn,
124121
RoleSessionName: &sessionName,
125122
})
126-
127123
if err != nil {
128124
return fmt.Errorf("failed to assume role with arn of %s %w", roleToAssumeArn, err)
129125
}
@@ -163,7 +159,7 @@ func (c *Client) login(ctx context.Context) error {
163159
return err
164160
}
165161

166-
body, err := ioutil.ReadAll(req.HTTPRequest.Body)
162+
body, err := io.ReadAll(req.HTTPRequest.Body)
167163
if err != nil {
168164
return err
169165
}

main.go

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"encoding/json"
99
"errors"
1010
"fmt"
11-
"io/ioutil"
1211
"net"
1312
"net/http"
1413
"os"
@@ -24,6 +23,7 @@ import (
2423
"github.com/hashicorp/vault-lambda-extension/internal/config"
2524
"github.com/hashicorp/vault-lambda-extension/internal/extension"
2625
"github.com/hashicorp/vault-lambda-extension/internal/proxy"
26+
"github.com/hashicorp/vault-lambda-extension/internal/runmode"
2727
"github.com/hashicorp/vault-lambda-extension/internal/vault"
2828
"github.com/hashicorp/vault/api"
2929
)
@@ -33,19 +33,36 @@ func main() {
3333
Level: hclog.LevelFromString(os.Getenv(config.VaultLogLevel)),
3434
})
3535

36-
err := realMain(logger.Named(config.VaultLogLevel))
37-
if err != nil {
36+
runMode := runmode.ModeDefault
37+
if runModeEnv := os.Getenv(config.VaultRunMode); runModeEnv != "" {
38+
runMode = runmode.ParseMode(runModeEnv)
39+
}
40+
41+
h := newHandler(logger.Named(config.ExtensionName), runMode)
42+
if err := h.handle(); err != nil {
3843
logger.Error("Fatal error, exiting", "error", err)
3944
os.Exit(1)
4045
}
4146
}
4247

43-
func realMain(logger hclog.Logger) error {
48+
func newHandler(logger hclog.Logger, runMode runmode.Mode) *handler {
49+
return &handler{
50+
logger: logger,
51+
runMode: runMode,
52+
}
53+
}
54+
55+
type handler struct {
56+
logger hclog.Logger
57+
runMode runmode.Mode
58+
}
59+
60+
func (h *handler) handle() error {
4461
ctx, cancel := context.WithCancel(context.Background())
4562
defer cancel()
4663

4764
var wg sync.WaitGroup
48-
srv, err := runExtension(ctx, logger, &wg)
65+
cleanup, err := h.runExtension(ctx, &wg)
4966
if err != nil {
5067
return err
5168
}
@@ -57,16 +74,16 @@ func realMain(logger hclog.Logger) error {
5774
interruptChannel := make(chan os.Signal, 1)
5875
signal.Notify(interruptChannel, syscall.SIGTERM, syscall.SIGINT)
5976
select {
60-
case s := <-interruptChannel:
61-
logger.Info("Received signal, exiting", "signal", s)
77+
case sig := <-interruptChannel:
78+
h.logger.Info("Received signal, exiting", "signal", sig)
6279
case <-shutdownChannel:
63-
logger.Info("Received shutdown event, exiting")
80+
h.logger.Info("Received shutdown event, exiting")
6481
}
6582

6683
cancel()
67-
if err := srv.Shutdown(context.Background()); err != nil {
84+
if err := cleanup(context.Background()); err != nil {
6885
// Error from closing listeners, or context timeout:
69-
logger.Error("HTTP server shutdown error", "error", err)
86+
h.logger.Error("HTTP server shutdown error", "error", err)
7087
}
7188
}()
7289

@@ -76,20 +93,20 @@ func realMain(logger hclog.Logger) error {
7693
return err
7794
}
7895

79-
processEvents(ctx, logger, extensionClient)
96+
processEvents(ctx, h.logger, extensionClient)
8097

8198
// Once processEvents returns, signal that it's time to shutdown.
8299
shutdownChannel <- struct{}{}
83100

84101
// Ensure we wait for the HTTP server to gracefully shut down.
85102
wg.Wait()
86-
logger.Info("Graceful shutdown complete")
103+
h.logger.Info("Graceful shutdown complete")
87104

88105
return nil
89106
}
90107

91-
func runExtension(ctx context.Context, logger hclog.Logger, wg *sync.WaitGroup) (*http.Server, error) {
92-
logger.Info("Initialising")
108+
func (h *handler) runExtension(ctx context.Context, wg *sync.WaitGroup) (func(context.Context) error, error) {
109+
h.logger.Info("Initialising")
93110

94111
authConfig := config.AuthConfigFromEnv()
95112
vaultConfig := api.DefaultConfig()
@@ -114,7 +131,7 @@ func runExtension(ctx context.Context, logger hclog.Logger, wg *sync.WaitGroup)
114131
} else {
115132
ses = session.Must(session.NewSession())
116133
}
117-
client, err := vault.NewClient(config.ExtensionName, config.ExtensionVersion, logger.Named("vault-client"), vaultConfig, authConfig, ses)
134+
client, err := vault.NewClient(config.ExtensionName, config.ExtensionVersion, h.logger.Named("vault-client"), vaultConfig, authConfig, ses)
118135
if err != nil {
119136
return nil, fmt.Errorf("error getting client: %w", err)
120137
} else if client == nil {
@@ -135,34 +152,41 @@ func runExtension(ctx context.Context, logger hclog.Logger, wg *sync.WaitGroup)
135152

136153
client.VaultClient = client.VaultClient.WithRequestCallbacks(api.RequireState(newState), vault.UserAgentRequestCallback(uaFunc)).WithResponseCallbacks()
137154

138-
err = writePreconfiguredSecrets(client.VaultClient)
139-
if err != nil {
140-
return nil, err
155+
if h.runMode.HasModeFile() {
156+
if err := writePreconfiguredSecrets(client.VaultClient); err != nil {
157+
return nil, err
158+
}
141159
}
142160

143161
// clear out eventual consistency helpers
144162
client.VaultClient = client.VaultClient.WithRequestCallbacks().WithResponseCallbacks()
145163

146-
ln, err := net.Listen("tcp", "127.0.0.1:8200")
147-
if err != nil {
148-
return nil, fmt.Errorf("failed to listen on port 8200: %w", err)
149-
}
150-
srv := proxy.New(logger.Named("proxy"), client, config.CacheConfigFromEnv())
151-
wg.Add(1)
152-
go func() {
153-
defer wg.Done()
154-
logger.Info("Starting HTTP proxy server")
155-
err = srv.Serve(ln)
156-
if err != http.ErrServerClosed {
157-
logger.Error("HTTP server shutdown unexpectedly", "error", err)
164+
cleanupFunc := func(context.Context) error { return nil }
165+
if h.runMode.HasModeProxy() {
166+
ln, err := net.Listen("tcp", "127.0.0.1:8200")
167+
if err != nil {
168+
return nil, fmt.Errorf("failed to listen on port 8200: %w", err)
158169
}
159-
}()
160-
161-
logger.Info("Initialised")
170+
srv := proxy.New(h.logger.Named("proxy"), client, config.CacheConfigFromEnv())
171+
wg.Add(1)
172+
go func() {
173+
defer wg.Done()
174+
h.logger.Info("Starting HTTP proxy server")
175+
err = srv.Serve(ln)
176+
if err != http.ErrServerClosed {
177+
h.logger.Error("HTTP server shutdown unexpectedly", "error", err)
178+
}
179+
}()
180+
cleanupFunc = func(ctx context.Context) error {
181+
return srv.Shutdown(ctx)
182+
}
183+
}
162184

163-
return srv, nil
185+
h.logger.Info("Initialised")
186+
return cleanupFunc, nil
164187
}
165188

189+
// writePreconfiguredSecrets writes secrets to disk.
166190
func writePreconfiguredSecrets(client *api.Client) error {
167191
configuredSecrets, err := config.ParseConfiguredSecrets()
168192
if err != nil {
@@ -178,18 +202,18 @@ func writePreconfiguredSecrets(client *api.Client) error {
178202

179203
content, err := json.MarshalIndent(secret, "", " ")
180204
if err != nil {
181-
return err
205+
return fmt.Errorf("unable to marshal json: %w", err)
182206
}
207+
183208
dir := path.Dir(s.FilePath)
184209
if _, err = os.Stat(dir); os.IsNotExist(err) {
185-
err = os.MkdirAll(dir, 0755)
186-
if err != nil {
210+
if err := os.MkdirAll(dir, 0755); err != nil {
187211
return fmt.Errorf("failed to create directory %q for secret %s: %s", dir, s.Name(), err)
188212
}
189213
}
190-
err = ioutil.WriteFile(s.FilePath, content, 0644)
191-
if err != nil {
192-
return err
214+
215+
if err := os.WriteFile(s.FilePath, content, 0644); err != nil {
216+
return fmt.Errorf("error writing file: %w", err)
193217
}
194218
}
195219

0 commit comments

Comments
 (0)