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

Commit 12ca3c5

Browse files
author
Amey Bhide
committed
Add support for creating ephemeral users for multi-node Splunk deployments
1 parent 60b216c commit 12ca3c5

File tree

3 files changed

+150
-2
lines changed

3 files changed

+150
-2
lines changed

backend.go

Lines changed: 1 addition & 0 deletions
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(),

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: 145 additions & 2 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,7 +34,30 @@ 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) {
3461
name := d.Get("name").(string)
3562
role, err := roleConfigLoad(ctx, req.Storage, name)
3663
if err != nil {
@@ -100,6 +127,122 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d
100127
return resp, nil
101128
}
102129

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) {
147+
name := d.Get("name").(string)
148+
node, _ := d.GetOk("node_fqdn")
149+
nodeFQDN := node.(string)
150+
role, err := roleConfigLoad(ctx, req.Storage, name)
151+
if err != nil {
152+
return nil, err
153+
}
154+
if role == nil {
155+
return logical.ErrorResponse(fmt.Sprintf("role not found: %q", name)), nil
156+
}
157+
158+
config, err := connectionConfigLoad(ctx, req.Storage, role.Connection)
159+
if err != nil {
160+
return nil, err
161+
}
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+
}
166+
167+
// If role name isn't in allowed roles, send back a permission denied.
168+
if !strutil.StrListContains(config.AllowedRoles, "*") && !strutil.StrListContainsGlob(config.AllowedRoles, name) {
169+
return nil, fmt.Errorf("%q is not an allowed role for connection %q", name, role.Connection)
170+
}
171+
172+
conn, err := b.ensureConnection(ctx, role.Connection, 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)
183+
if err != nil {
184+
return nil, err
185+
}
186+
187+
/*
188+
// Generate credentials
189+
userUUID, err := uuid.GenerateUUID()
190+
if err != nil {
191+
return nil, err
192+
}
193+
userPrefix := role.UserPrefix
194+
if role.UserPrefix == defaultUserPrefix {
195+
userPrefix = fmt.Sprintf("%s_%s", role.UserPrefix, req.DisplayName)
196+
}
197+
username := fmt.Sprintf("%s_%s", userPrefix, userUUID)
198+
passwd, err := uuid.GenerateUUID()
199+
if err != nil {
200+
return nil, errwrap.Wrapf("error generating new password {{err}}", err)
201+
}
202+
opts := splunk.CreateUserOptions{
203+
Name: username,
204+
Password: passwd,
205+
Roles: role.Roles,
206+
DefaultApp: role.DefaultApp,
207+
Email: role.Email,
208+
TZ: role.TZ,
209+
}
210+
if _, _, err := conn.AccessControl.Authentication.Users.Create(&opts); err != nil {
211+
return nil, err
212+
}
213+
214+
resp := b.Secret(secretCredsType).Response(map[string]interface{}{
215+
// return to user
216+
"username": username,
217+
"password": passwd,
218+
"roles": role.Roles,
219+
"connection": role.Connection,
220+
"url": conn.Params().BaseURL,
221+
}, map[string]interface{}{
222+
// store (with lease)
223+
"username": username,
224+
"role": name,
225+
"connection": role.Connection,
226+
})
227+
resp.Secret.TTL = role.DefaultTTL
228+
resp.Secret.MaxTTL = role.MaxTTL
229+
230+
return resp, nil
231+
*/
232+
return nil, fmt.Errorf("XXX")
233+
}
234+
func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
235+
name := d.Get("name").(string)
236+
node_fqdn, present := d.GetOk("node_fqdn")
237+
// if node_fqdn is specified then the treat the request for a multi-node deployment
238+
if present {
239+
b.Logger().Debug(fmt.Sprintf("node_fqdn: [%s] specified for role: [%s]. using clustered mode getting temporary creds", node_fqdn.(string), name))
240+
return b.credsReadHandlerMulti(ctx, req, d)
241+
}
242+
b.Logger().Debug(fmt.Sprintf("node_fqdn not specified for role: [%s]. using standalone mode getting temporary creds", name))
243+
return b.credsReadHandlerStandalone(ctx, req, d)
244+
}
245+
103246
const pathCredsCreateHelpSyn = `
104247
Request Splunk credentials for a certain role.
105248
`

0 commit comments

Comments
 (0)