Skip to content
This repository was archived by the owner on Mar 15, 2024. It is now read-only.

Commit e21410f

Browse files
author
Michael Weber
committed
Multi-tenancy Splunk plugin
1 parent b88d1a7 commit e21410f

13 files changed

+691
-459
lines changed

Gopkg.lock

Lines changed: 21 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
vault-plugin-splunk
22
-------------------
33

4-
### Building from source
4+
# Building from source
55

66
```shell
77
mkdir -p
@@ -11,11 +11,42 @@ make install # installs dep, golint etc.
1111
make
1212
```
1313

14+
# Testing
1415

1516

17+
## Vault Setup
1618

19+
```shell
20+
export VAULT_ADDR="https://localhost:8200"
21+
vault server -log-level debug -dev -dev-root-token-id="root" -config=vault.hcl
22+
vault auth root
23+
```
24+
25+
## Loading Plugin
1726

27+
```shell
28+
make build;
29+
mv ~/go/bin/vault-plugin-splunk /tmp/vault-plugins/;
30+
SHASUM=$(shasum -a 256 "/tmp/vault-plugins/vault-plugin-splunk" | cut -d " " -f1);
31+
vault write sys/plugins/catalog/secret/vault-plugin-splunk sha_256="$SHASUM" command="vault-plugin-splunk";
32+
curl -vk -H "X-Vault-Token: $(cat ~/.vault-token)" $VAULT_ADDR/v1/sys/plugins/reload/backend -XPUT -d '{"plugin":"vault-plugin-splunk"}'
33+
```
1834

1935

36+
# TODO
37+
* check expiring Splunk API session tokens (renew)
38+
* WAL?
39+
* TLS certs, timeouts config
40+
* comment strings: caps, punctuation
41+
* Full name
42+
* metrics?
43+
44+
* tests
45+
** TTLs roundtrip
46+
** externally deleted user
47+
** externally revoked admin access
48+
** not in allowed_roles
49+
** updating roles, connections with partial params
50+
** creating conns first, then roles, and vice versa
2051

2152

backend.go

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ package splunk
33
import (
44
"context"
55
"crypto/tls"
6-
"fmt"
76
"net/http"
7+
"reflect"
88
"strings"
99
"sync"
1010
"time"
1111

12-
"github.com/hashicorp/errwrap"
1312
"github.com/hashicorp/vault/logical"
1413
"github.com/hashicorp/vault/logical/framework"
1514
"github.com/splunk/vault-plugin-splunk/clients/splunk"
@@ -25,80 +24,77 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend,
2524
}
2625

2726
func newBackend() logical.Backend {
28-
var b backend
27+
b := backend{}
2928
b.Backend = &framework.Backend{
3029
Help: strings.TrimSpace(backendHelp),
3130
PathsSpecial: &logical.Paths{
3231
SealWrapStorage: []string{
33-
"config/root",
32+
"config/*",
3433
},
3534
},
3635
Paths: []*framework.Path{
37-
b.pathConfigRoot(),
38-
b.pathConfigRotateRoot(),
36+
b.pathConfigConnection(),
37+
b.pathConnectionsList(),
38+
b.pathResetConnection(),
39+
b.pathRotateRoot(),
40+
b.pathRolesList(),
3941
b.pathRoles(),
40-
b.pathListRoles(),
4142
b.pathCredsCreate(),
4243
},
4344
Secrets: []*framework.Secret{
4445
b.pathSecretCreds(),
4546
},
47+
// Clean: XXXX
48+
// Invalidate: XXXX
4649
BackendType: logical.TypeLogical,
4750
}
51+
b.connections = make(map[string]*splunk.API)
4852
return &b
4953
}
5054

5155
type backend struct {
5256
*framework.Backend
53-
5457
// Mutex to protect access to Splunk clients and client configs
55-
clientMutex sync.RWMutex
56-
splunkAPI *splunk.API
58+
sync.RWMutex
59+
connections map[string]*splunk.API
5760
}
5861

59-
func (b *backend) splunkClient(ctx context.Context, s logical.Storage) (*splunk.API, error) {
60-
b.clientMutex.RLock()
61-
if b.splunkAPI != nil {
62-
b.clientMutex.RUnlock()
63-
return b.splunkAPI, nil
62+
// XXXX ensureConnection
63+
func (b *backend) GetConnection(ctx context.Context, s logical.Storage, name string) (*splunk.API, error) {
64+
b.RLock()
65+
if conn, ok := b.connections[name]; ok {
66+
b.RUnlock()
67+
return conn, nil
6468
}
6569

6670
// Upgrade the lock for writing
67-
b.clientMutex.RUnlock()
68-
b.clientMutex.Lock()
69-
defer b.clientMutex.Unlock()
70-
71-
// check client again, in the event that a client was being created while we
72-
// waited for Lock()
73-
if b.splunkAPI != nil {
74-
return b.splunkAPI, nil
71+
b.RUnlock()
72+
b.Lock()
73+
defer b.Unlock()
74+
75+
return b.connectionUnlocked(ctx, s, name)
76+
}
77+
78+
func (b *backend) connectionUnlocked(ctx context.Context, s logical.Storage, name string) (*splunk.API, error) {
79+
if conn, ok := b.connections[name]; ok {
80+
return conn, nil
7581
}
7682

77-
rawRootConfig, err := s.Get(ctx, "config/root")
83+
// create connection
84+
config, err := b.connectionConfig(ctx, s, name)
7885
if err != nil {
7986
return nil, err
8087
}
81-
if rawRootConfig == nil {
82-
return nil, fmt.Errorf("no configuration found for config/root")
83-
}
84-
var config rootConfig
85-
if err := rawRootConfig.DecodeJSON(&config); err != nil {
86-
return nil, errwrap.Wrapf("error reading root configuration: {{err}}", err)
87-
}
88-
89-
if config.Username == "" || config.BaseURL == "" {
90-
return nil, fmt.Errorf("empty username or BaseURL")
91-
}
9288

9389
p := &splunk.APIParams{
94-
BaseURL: config.BaseURL,
90+
BaseURL: config.URL,
9591
Config: oauth2.Config{
9692
ClientID: config.Username,
9793
ClientSecret: config.Password,
9894
},
9995
}
10096
tr := &http.Transport{
101-
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // XXX
97+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // XXXX
10298
}
10399
// client is the underlying transport for API calls, including Login (for obtaining session token)
104100
client := &http.Client{
@@ -107,8 +103,44 @@ func (b *backend) splunkClient(ctx context.Context, s logical.Storage) (*splunk.
107103
}
108104
ctx = context.WithValue(context.Background(), oauth2.HTTPClient, client)
109105

110-
b.splunkAPI = p.NewAPI(ctx)
111-
return b.splunkAPI, nil
106+
b.connections[name] = p.NewAPI(ctx)
107+
return b.connections[name], nil
108+
}
109+
110+
// ClearConnection closes the connection and
111+
// removes it from the b.connections map.
112+
func (b *backend) ClearConnection(name string) error {
113+
b.Lock()
114+
defer b.Unlock()
115+
return b.clearConnectionUnlocked(name)
116+
}
117+
118+
func (b *backend) clearConnectionUnlocked(name string) error {
119+
_, ok := b.connections[name]
120+
if ok {
121+
delete(b.connections, name)
122+
}
123+
return nil
124+
}
125+
126+
func getValue(data *framework.FieldData, op logical.Operation, key string) (interface{}, bool) {
127+
if raw, ok := data.GetOk(key); ok {
128+
return raw, true
129+
}
130+
if op == logical.CreateOperation {
131+
return data.Get(key), true
132+
}
133+
return nil, false
134+
}
135+
136+
func decodeValue(data *framework.FieldData, op logical.Operation, key string, v interface{}) error {
137+
raw, ok := getValue(data, op, key)
138+
if ok {
139+
rraw := reflect.ValueOf(raw)
140+
rv := reflect.ValueOf(v)
141+
rv.Elem().Set(rraw)
142+
}
143+
return nil
112144
}
113145

114146
const backendHelp = `

clients/splunk/splunk.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,23 @@ var jsonOutputMode = outputMode{"json"}
1515

1616
// API https://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTprolog
1717
type API struct {
18+
params *APIParams
1819
client *Client
1920
Introspection *IntrospectionService
2021
AccessControl *AccessControlService
2122
// XXX ...
2223
}
2324

25+
func (api *API) Params() *APIParams {
26+
return api.params
27+
}
28+
2429
func (params *APIParams) NewAPI(ctx context.Context) *API {
2530
client := params.NewClient(ctx)
31+
paramsCopy := *params
2632

2733
return &API{
34+
params: &paramsCopy,
2835
client: client.Path("services/"),
2936
Introspection: newIntrospectionService(client.New()),
3037
AccessControl: newAccessControlService(client.New()),

0 commit comments

Comments
 (0)