@@ -11,11 +11,16 @@ import (
1111 "github.com/splunk/vault-plugin-splunk/clients/splunk"
1212)
1313
14+ const (
15+ SEARCHHEAD = "search_head"
16+ INDEXER = "indexer"
17+ )
18+
1419func (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 {
@@ -50,7 +78,7 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d
5078 return nil , fmt .Errorf ("%q is not an allowed role for connection %q" , name , role .Connection )
5179 }
5280
53- conn , err := b .ensureConnection (ctx , role . Connection , config )
81+ conn , err := b .ensureConnection (ctx , config )
5482 if err != nil {
5583 return nil , err
5684 }
@@ -100,6 +128,129 @@ 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 host .Content .HostFQDN == nodeFQDN || 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 unset 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 , 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+ // Re-create connection for node
189+ config .URL = "https://" + nodeFQDN + ":8089"
190+ // XXX config.ID = ""
191+ conn , err = config .newConnection (ctx ) // XXX cache
192+ if err != nil {
193+ return nil , err
194+ }
195+ // Generate credentials
196+ userUUID , err := uuid .GenerateUUID ()
197+ if err != nil {
198+ return nil , err
199+ }
200+ userPrefix := role .UserPrefix
201+ if role .UserPrefix == defaultUserPrefix {
202+ userPrefix = fmt .Sprintf ("%s_%s" , role .UserPrefix , req .DisplayName )
203+ }
204+ username := fmt .Sprintf ("%s_%s" , userPrefix , userUUID )
205+ passwd , err := uuid .GenerateUUID ()
206+ if err != nil {
207+ return nil , errwrap .Wrapf ("error generating new password {{err}}" , err )
208+ }
209+ conn .Params ().BaseURL = nodeFQDN
210+ opts := splunk.CreateUserOptions {
211+ Name : username ,
212+ Password : passwd ,
213+ Roles : role .Roles ,
214+ DefaultApp : role .DefaultApp ,
215+ Email : role .Email ,
216+ TZ : role .TZ ,
217+ }
218+ if _ , _ , err := conn .AccessControl .Authentication .Users .Create (& opts ); err != nil {
219+ return nil , err
220+ }
221+
222+ resp := b .Secret (secretCredsType ).Response (map [string ]interface {}{
223+ // return to user
224+ "username" : username ,
225+ "password" : passwd ,
226+ "roles" : role .Roles ,
227+ "connection" : role .Connection ,
228+ "url" : conn .Params ().BaseURL ,
229+ }, map [string ]interface {}{
230+ // store (with lease)
231+ "username" : username ,
232+ "role" : name ,
233+ "connection" : role .Connection ,
234+ "node_fqdn" : nodeFQDN ,
235+ })
236+ resp .Secret .TTL = role .DefaultTTL
237+ resp .Secret .MaxTTL = role .MaxTTL
238+
239+ return resp , nil
240+ }
241+
242+ func (b * backend ) credsReadHandler (ctx context.Context , req * logical.Request , d * framework.FieldData ) (* logical.Response , error ) {
243+ name := d .Get ("name" ).(string )
244+ node_fqdn , present := d .GetOk ("node_fqdn" )
245+ // if node_fqdn is specified then the treat the request for a multi-node deployment
246+ if present {
247+ b .Logger ().Debug (fmt .Sprintf ("node_fqdn: [%s] specified for role: [%s]. using clustered mode getting temporary creds" , node_fqdn .(string ), name ))
248+ return b .credsReadHandlerMulti (ctx , req , d )
249+ }
250+ b .Logger ().Debug (fmt .Sprintf ("node_fqdn not specified for role: [%s]. using standalone mode getting temporary creds" , name ))
251+ return b .credsReadHandlerStandalone (ctx , req , d )
252+ }
253+
103254const pathCredsCreateHelpSyn = `
104255Request Splunk credentials for a certain role.
105256`
0 commit comments