@@ -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+ )
1418func (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+
103252const pathCredsCreateHelpSyn = `
104253Request Splunk credentials for a certain role.
105254`
0 commit comments