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

Commit 55aa792

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

File tree

3 files changed

+151
-2
lines changed

3 files changed

+151
-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: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ import (
99
"github.com/hashicorp/vault/logical"
1010
"github.com/hashicorp/vault/logical/framework"
1111
"github.com/splunk/vault-plugin-splunk/clients/splunk"
12+
"strings"
1213
)
1314

15+
const (
16+
SEARCHHEAD = "search_head"
17+
INDEXER = "indexer"
18+
)
1419
func (b *backend) pathCredsCreate() *framework.Path {
1520
return &framework.Path{
1621
Pattern: "creds/" + framework.GenericNameRegex("name"),
1722
Fields: map[string]*framework.FieldSchema{
18-
"name": &framework.FieldSchema{
23+
"name": {
1924
Type: framework.TypeString,
2025
Description: "Name of the role",
2126
},
@@ -30,7 +35,30 @@ func (b *backend) pathCredsCreate() *framework.Path {
3035
}
3136
}
3237

33-
func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
38+
func (b *backend) pathCredsCreateMulti() *framework.Path {
39+
return &framework.Path{
40+
Pattern: "creds/" + framework.GenericNameRegex("name") + "/" + framework.GenericNameRegex("node_fqdn"),
41+
Fields: map[string]*framework.FieldSchema{
42+
"name": {
43+
Type: framework.TypeString,
44+
Description: "Name of the role",
45+
},
46+
"node_fqdn": {
47+
Type: framework.TypeString,
48+
Description: "FQDN for the Splunk Stack node",
49+
},
50+
},
51+
52+
Callbacks: map[logical.Operation]framework.OperationFunc{
53+
logical.ReadOperation: b.credsReadHandler,
54+
},
55+
56+
HelpSynopsis: pathCredsCreateHelpSyn,
57+
HelpDescription: pathCredsCreateHelpDesc,
58+
}
59+
}
60+
61+
func (b *backend) credsReadHandlerStandalone(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
3462
name := d.Get("name").(string)
3563
role, err := roleConfigLoad(ctx, req.Storage, name)
3664
if err != nil {
@@ -100,6 +128,122 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d
100128
return resp, nil
101129
}
102130

131+
func findNode(nodeFQDN string, hosts []splunk.ServerInfoEntry) (bool, error) {
132+
for _, host := range hosts {
133+
// check if node_fqdn is in either of HostFQDN or Host. User might not always the FQDN on the cli input
134+
if strings.Contains(host.Content.HostFQDN, nodeFQDN) || strings.Contains(host.Content.Host, nodeFQDN) {
135+
// Return true if the requested node is a search head
136+
for _, role := range host.Content.Roles {
137+
if role == SEARCHHEAD {
138+
return true, nil
139+
}
140+
}
141+
return false, fmt.Errorf("host: %s isn't search head; creating ephemeral creds is only supported for search heads", nodeFQDN)
142+
}
143+
}
144+
return false, fmt.Errorf("host: %s not found", nodeFQDN)
145+
}
146+
147+
func (b *backend) credsReadHandlerMulti(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
148+
name := d.Get("name").(string)
149+
node, _ := d.GetOk("node_fqdn")
150+
nodeFQDN := node.(string)
151+
role, err := roleConfigLoad(ctx, req.Storage, name)
152+
if err != nil {
153+
return nil, err
154+
}
155+
if role == nil {
156+
return logical.ErrorResponse(fmt.Sprintf("role not found: %q", name)), nil
157+
}
158+
159+
config, err := connectionConfigLoad(ctx, req.Storage, role.Connection)
160+
if err != nil {
161+
return nil, err
162+
}
163+
// Check if isStandalone is set
164+
if config.IsStandalone {
165+
return nil, fmt.Errorf("expected is_standalone to be set for connection: %q", role.Connection)
166+
}
167+
168+
// If role name isn't in allowed roles, send back a permission denied.
169+
if !strutil.StrListContains(config.AllowedRoles, "*") && !strutil.StrListContainsGlob(config.AllowedRoles, name) {
170+
return nil, fmt.Errorf("%q is not an allowed role for connection %q", name, role.Connection)
171+
}
172+
173+
conn, err := b.ensureConnection(ctx, role.Connection, config)
174+
if err != nil {
175+
return nil, err
176+
}
177+
178+
nodes, _, err := conn.Deployment.GetSearchPeers()
179+
if err != nil {
180+
b.Logger().Error("Error while reading SearchPeers from cluster master", err)
181+
return nil, fmt.Errorf("unable to read searchpeers from cluster master")
182+
}
183+
_, err = findNode(nodeFQDN, nodes)
184+
if err != nil {
185+
return nil, err
186+
}
187+
188+
/*
189+
// Generate credentials
190+
userUUID, err := uuid.GenerateUUID()
191+
if err != nil {
192+
return nil, err
193+
}
194+
userPrefix := role.UserPrefix
195+
if role.UserPrefix == defaultUserPrefix {
196+
userPrefix = fmt.Sprintf("%s_%s", role.UserPrefix, req.DisplayName)
197+
}
198+
username := fmt.Sprintf("%s_%s", userPrefix, userUUID)
199+
passwd, err := uuid.GenerateUUID()
200+
if err != nil {
201+
return nil, errwrap.Wrapf("error generating new password {{err}}", err)
202+
}
203+
opts := splunk.CreateUserOptions{
204+
Name: username,
205+
Password: passwd,
206+
Roles: role.Roles,
207+
DefaultApp: role.DefaultApp,
208+
Email: role.Email,
209+
TZ: role.TZ,
210+
}
211+
if _, _, err := conn.AccessControl.Authentication.Users.Create(&opts); err != nil {
212+
return nil, err
213+
}
214+
215+
resp := b.Secret(secretCredsType).Response(map[string]interface{}{
216+
// return to user
217+
"username": username,
218+
"password": passwd,
219+
"roles": role.Roles,
220+
"connection": role.Connection,
221+
"url": conn.Params().BaseURL,
222+
}, map[string]interface{}{
223+
// store (with lease)
224+
"username": username,
225+
"role": name,
226+
"connection": role.Connection,
227+
})
228+
resp.Secret.TTL = role.DefaultTTL
229+
resp.Secret.MaxTTL = role.MaxTTL
230+
231+
return resp, nil
232+
*/
233+
return nil, fmt.Errorf("XXX")
234+
}
235+
func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
236+
name := d.Get("name").(string)
237+
node_fqdn, present := d.GetOk("node_fqdn")
238+
// if node_fqdn is specified then the treat the request for a multi-node deployment
239+
if present {
240+
b.Logger().Debug(fmt.Sprintf("node_fqdn: [%s] specified for role: [%s]. using clustered mode getting temporary creds", node_fqdn.(string), name))
241+
return b.credsReadHandlerMulti(ctx, req, d)
242+
}
243+
b.Logger().Debug(fmt.Sprintf("node_fqdn not specified for role: [%s]. using standalone mode getting temporary creds", name))
244+
return b.credsReadHandlerStandalone(ctx, req, d)
245+
}
246+
103247
const pathCredsCreateHelpSyn = `
104248
Request Splunk credentials for a certain role.
105249
`

0 commit comments

Comments
 (0)