@@ -11,11 +11,15 @@ import (
11
11
"github.com/splunk/vault-plugin-splunk/clients/splunk"
12
12
)
13
13
14
+ const (
15
+ SEARCHHEAD = "search_head"
16
+ INDEXER = "indexer"
17
+ )
14
18
func (b * backend ) pathCredsCreate () * framework.Path {
15
19
return & framework.Path {
16
20
Pattern : "creds/" + framework .GenericNameRegex ("name" ),
17
21
Fields : map [string ]* framework.FieldSchema {
18
- "name" : & framework. FieldSchema {
22
+ "name" : {
19
23
Type : framework .TypeString ,
20
24
Description : "Name of the role" ,
21
25
},
@@ -30,8 +34,119 @@ func (b *backend) pathCredsCreate() *framework.Path {
30
34
}
31
35
}
32
36
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 ) {
34
147
name := d .Get ("name" ).(string )
148
+ node , _ := d .GetOk ("node_fqdn" )
149
+ nodeFQDN := node .(string )
35
150
role , err := roleConfigLoad (ctx , req .Storage , name )
36
151
if err != nil {
37
152
return nil , err
@@ -44,17 +159,38 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d
44
159
if err != nil {
45
160
return nil , err
46
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
+ }
47
166
48
167
// If role name isn't in allowed roles, send back a permission denied.
49
168
if ! strutil .StrListContains (config .AllowedRoles , "*" ) && ! strutil .StrListContainsGlob (config .AllowedRoles , name ) {
50
169
return nil , fmt .Errorf ("%q is not an allowed role for connection %q" , name , role .Connection )
51
170
}
52
171
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 )
54
183
if err != nil {
55
184
return nil , err
56
185
}
57
186
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
+ }
58
194
// Generate credentials
59
195
userUUID , err := uuid .GenerateUUID ()
60
196
if err != nil {
@@ -69,6 +205,7 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d
69
205
if err != nil {
70
206
return nil , errwrap .Wrapf ("error generating new password {{err}}" , err )
71
207
}
208
+ conn .Params ().BaseURL = nodeFQDN
72
209
opts := splunk.CreateUserOptions {
73
210
Name : username ,
74
211
Password : passwd ,
@@ -100,6 +237,18 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d
100
237
return resp , nil
101
238
}
102
239
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
+
103
252
const pathCredsCreateHelpSyn = `
104
253
Request Splunk credentials for a certain role.
105
254
`
0 commit comments