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

Commit 7720046

Browse files
author
Amey Bhide
committed
Add support for creating ephemeral users for multi-node stacks
1 parent 60b216c commit 7720046

File tree

3 files changed

+152
-2
lines changed

3 files changed

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

0 commit comments

Comments
 (0)