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

Commit 3407929

Browse files
author
Amey Bhide
committed
Add support for creating ephemeral users for multi-node Splunk deployments
1 parent 3d8f24b commit 3407929

File tree

5 files changed

+161
-7
lines changed

5 files changed

+161
-7
lines changed

backend.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func newBackend() logical.Backend {
4141
b.pathRolesList(),
4242
b.pathRoles(),
4343
b.pathCredsCreate(),
44+
b.pathCredsCreateMulti(),
4445
},
4546
Secrets: []*framework.Secret{
4647
b.pathSecretCreds(),
@@ -53,7 +54,7 @@ func newBackend() logical.Backend {
5354
return &b
5455
}
5556

56-
func (b *backend) ensureConnection(ctx context.Context, name string, config *splunkConfig) (*splunk.API, error) {
57+
func (b *backend) ensureConnection(ctx context.Context, config *splunkConfig) (*splunk.API, error) {
5758
if conn, ok := b.conn.Load(config.ID); ok {
5859
return conn.(*splunk.API), nil
5960
}

path_config_connection.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ func (b *backend) connectionWriteHandler(ctx context.Context, req *logical.Reque
170170
if config.URL == "" {
171171
return logical.ErrorResponse("empty URL"), nil
172172
}
173+
if isStandalone, ok := getValue(data, req.Operation, "is_standalone"); ok {
174+
config.IsStandalone = isStandalone.(bool)
175+
}
176+
173177
if verifyRaw, ok := getValue(data, req.Operation, "verify"); ok {
174178
config.Verify = verifyRaw.(bool)
175179
}

path_creds_create.go

Lines changed: 152 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ import (
1111
"github.com/splunk/vault-plugin-splunk/clients/splunk"
1212
)
1313

14+
const (
15+
SEARCHHEAD = "search_head"
16+
INDEXER = "indexer"
17+
)
1418
func (b *backend) pathCredsCreate() *framework.Path {
1519
return &framework.Path{
1620
Pattern: "creds/" + framework.GenericNameRegex("name"),
1721
Fields: map[string]*framework.FieldSchema{
18-
"name": &framework.FieldSchema{
22+
"name": {
1923
Type: framework.TypeString,
2024
Description: "Name of the role",
2125
},
@@ -30,8 +34,119 @@ func (b *backend) pathCredsCreate() *framework.Path {
3034
}
3135
}
3236

33-
func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
37+
func (b *backend) pathCredsCreateMulti() *framework.Path {
38+
return &framework.Path{
39+
Pattern: "creds/" + framework.GenericNameRegex("name") + "/" + framework.GenericNameRegex("node_fqdn"),
40+
Fields: map[string]*framework.FieldSchema{
41+
"name": {
42+
Type: framework.TypeString,
43+
Description: "Name of the role",
44+
},
45+
"node_fqdn": {
46+
Type: framework.TypeString,
47+
Description: "FQDN for the Splunk Stack node",
48+
},
49+
},
50+
51+
Callbacks: map[logical.Operation]framework.OperationFunc{
52+
logical.ReadOperation: b.credsReadHandler,
53+
},
54+
55+
HelpSynopsis: pathCredsCreateHelpSyn,
56+
HelpDescription: pathCredsCreateHelpDesc,
57+
}
58+
}
59+
60+
func (b *backend) credsReadHandlerStandalone(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
61+
name := d.Get("name").(string)
62+
role, err := roleConfigLoad(ctx, req.Storage, name)
63+
if err != nil {
64+
return nil, err
65+
}
66+
if role == nil {
67+
return logical.ErrorResponse(fmt.Sprintf("role not found: %q", name)), nil
68+
}
69+
70+
config, err := connectionConfigLoad(ctx, req.Storage, role.Connection)
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
// If role name isn't in allowed roles, send back a permission denied.
76+
if !strutil.StrListContains(config.AllowedRoles, "*") && !strutil.StrListContainsGlob(config.AllowedRoles, name) {
77+
return nil, fmt.Errorf("%q is not an allowed role for connection %q", name, role.Connection)
78+
}
79+
80+
conn, err := b.ensureConnection(ctx, config)
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
// Generate credentials
86+
userUUID, err := uuid.GenerateUUID()
87+
if err != nil {
88+
return nil, err
89+
}
90+
userPrefix := role.UserPrefix
91+
if role.UserPrefix == defaultUserPrefix {
92+
userPrefix = fmt.Sprintf("%s_%s", role.UserPrefix, req.DisplayName)
93+
}
94+
username := fmt.Sprintf("%s_%s", userPrefix, userUUID)
95+
passwd, err := uuid.GenerateUUID()
96+
if err != nil {
97+
return nil, errwrap.Wrapf("error generating new password {{err}}", err)
98+
}
99+
opts := splunk.CreateUserOptions{
100+
Name: username,
101+
Password: passwd,
102+
Roles: role.Roles,
103+
DefaultApp: role.DefaultApp,
104+
Email: role.Email,
105+
TZ: role.TZ,
106+
}
107+
if _, _, err := conn.AccessControl.Authentication.Users.Create(&opts); err != nil {
108+
return nil, err
109+
}
110+
111+
resp := b.Secret(secretCredsType).Response(map[string]interface{}{
112+
// return to user
113+
"username": username,
114+
"password": passwd,
115+
"roles": role.Roles,
116+
"connection": role.Connection,
117+
"url": conn.Params().BaseURL,
118+
}, map[string]interface{}{
119+
// store (with lease)
120+
"username": username,
121+
"role": name,
122+
"connection": role.Connection,
123+
})
124+
resp.Secret.TTL = role.DefaultTTL
125+
resp.Secret.MaxTTL = role.MaxTTL
126+
127+
return resp, nil
128+
}
129+
130+
func findNode(nodeFQDN string, hosts []splunk.ServerInfoEntry) (bool, error) {
131+
for _, host := range hosts {
132+
// check if node_fqdn is in either of HostFQDN or Host. User might not always the FQDN on the cli input
133+
if host.Content.HostFQDN == nodeFQDN || host.Content.Host == nodeFQDN {
134+
// Return true if the requested node is a search head
135+
for _, role := range host.Content.Roles {
136+
if role == SEARCHHEAD {
137+
return true, nil
138+
}
139+
}
140+
return false, fmt.Errorf("host: %s isn't search head; creating ephemeral creds is only supported for search heads", nodeFQDN)
141+
}
142+
}
143+
return false, fmt.Errorf("host: %s not found", nodeFQDN)
144+
}
145+
146+
func (b *backend) credsReadHandlerMulti(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
34147
name := d.Get("name").(string)
148+
node, _ := d.GetOk("node_fqdn")
149+
nodeFQDN := node.(string)
35150
role, err := roleConfigLoad(ctx, req.Storage, name)
36151
if err != nil {
37152
return nil, err
@@ -44,17 +159,38 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d
44159
if err != nil {
45160
return nil, err
46161
}
162+
// Check if isStandalone is set
163+
if config.IsStandalone {
164+
return nil, fmt.Errorf("expected is_standalone to be set for connection: %q", role.Connection)
165+
}
47166

48167
// If role name isn't in allowed roles, send back a permission denied.
49168
if !strutil.StrListContains(config.AllowedRoles, "*") && !strutil.StrListContainsGlob(config.AllowedRoles, name) {
50169
return nil, fmt.Errorf("%q is not an allowed role for connection %q", name, role.Connection)
51170
}
52171

53-
conn, err := b.ensureConnection(ctx, role.Connection, config)
172+
conn, err := b.ensureConnection(ctx, config)
173+
if err != nil {
174+
return nil, err
175+
}
176+
177+
nodes, _, err := conn.Deployment.GetSearchPeers()
178+
if err != nil {
179+
b.Logger().Error("Error while reading SearchPeers from cluster master", err)
180+
return nil, fmt.Errorf("unable to read searchpeers from cluster master")
181+
}
182+
_, err = findNode(nodeFQDN, nodes)
54183
if err != nil {
55184
return nil, err
56185
}
57186

187+
// Re-create connection for node
188+
config.URL = "https://" + nodeFQDN + ":8089"
189+
config.ID = ""
190+
conn, err = b.ensureConnection(ctx, config)
191+
if err != nil {
192+
return nil, err
193+
}
58194
// Generate credentials
59195
userUUID, err := uuid.GenerateUUID()
60196
if err != nil {
@@ -69,6 +205,7 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d
69205
if err != nil {
70206
return nil, errwrap.Wrapf("error generating new password {{err}}", err)
71207
}
208+
conn.Params().BaseURL = nodeFQDN
72209
opts := splunk.CreateUserOptions{
73210
Name: username,
74211
Password: passwd,
@@ -100,6 +237,18 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d
100237
return resp, nil
101238
}
102239

240+
func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
241+
name := d.Get("name").(string)
242+
node_fqdn, present := d.GetOk("node_fqdn")
243+
// if node_fqdn is specified then the treat the request for a multi-node deployment
244+
if present {
245+
b.Logger().Debug(fmt.Sprintf("node_fqdn: [%s] specified for role: [%s]. using clustered mode getting temporary creds", node_fqdn.(string), name))
246+
return b.credsReadHandlerMulti(ctx, req, d)
247+
}
248+
b.Logger().Debug(fmt.Sprintf("node_fqdn not specified for role: [%s]. using standalone mode getting temporary creds", name))
249+
return b.credsReadHandlerStandalone(ctx, req, d)
250+
}
251+
103252
const pathCredsCreateHelpSyn = `
104253
Request Splunk credentials for a certain role.
105254
`

path_rotate_root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func (b *backend) rotateRootUpdateHandler(ctx context.Context, req *logical.Requ
3434
if err != nil {
3535
return nil, err
3636
}
37-
conn, err := b.ensureConnection(ctx, name, oldConfig)
37+
conn, err := b.ensureConnection(ctx, oldConfig)
3838
if err != nil {
3939
return nil, err
4040
}

secret_creds.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (b *backend) secretCredsRenewHandler(ctx context.Context, req *logical.Requ
5151
if err != nil {
5252
return nil, err
5353
}
54-
conn, err := b.ensureConnection(ctx, role.Connection, config)
54+
conn, err := b.ensureConnection(ctx, config)
5555
if err != nil {
5656
return nil, err
5757
}
@@ -84,7 +84,7 @@ func (b *backend) secretCredsRevokeHandler(ctx context.Context, req *logical.Req
8484
if err != nil {
8585
return nil, err
8686
}
87-
conn, err := b.ensureConnection(ctx, connName, config)
87+
conn, err := b.ensureConnection(ctx, config)
8888
if err != nil {
8989
return nil, err
9090
}

0 commit comments

Comments
 (0)