Skip to content

Commit 2907475

Browse files
authored
Merge pull request #5152 from StackStorm/client_and_cli_basic_auth_support
Add support to the client (CLI, http client) for sending additional basic auth credentials (aka MFA)
2 parents 35fee1d + 6306868 commit 2907475

11 files changed

Lines changed: 276 additions & 38 deletions

File tree

CHANGELOG.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,19 @@ Added
139139

140140
Contributed by @Kami.
141141

142+
* Add new ``credentials.basic_auth = username:password`` CLI configuration option.
143+
144+
This argument allows client to use additional set of basic auth credentials when talking to the
145+
StackStorm API endpoints (api, auth, stream) - that is, in addition to the token / api key
146+
native StackStorm auth.
147+
148+
This allows for simple basic auth based multi factor authentication implementation for
149+
installations which don't utilize SSO.
150+
151+
#5152
152+
153+
Contributed by @Kami.
154+
142155
Fixed
143156
~~~~~
144157

conf/st2rc.sample.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ username = test1
2121
password = testpassword
2222
# or authenticate with an api key.
2323
# api_key = <key>
24+
# Optional additional http basic auth credentials which are sent with each HTTP
25+
# request except the auth request to /v1/auth/tokens endpoint.
26+
# Available in StackStorm >= v3.4.0
27+
# NOTE: Username can't contain colon (:) character.
28+
# basic_auth = username:password
2429

2530
[api]
2631
url = http://127.0.0.1:9101/v1

st2client/st2client/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"api_key": ["credentials", "api_key"],
5858
"cacert": ["general", "cacert"],
5959
"debug": ["cli", "debug"],
60+
"basic_auth": ["credentials", "basic_auth"],
6061
}
6162

6263

@@ -87,6 +88,7 @@ def get_client(self, args, debug=False):
8788
"stream_url",
8889
"api_version",
8990
"cacert",
91+
"basic_auth",
9092
]
9193
cli_options = {opt: getattr(args, opt, None) for opt in cli_options}
9294
if cli_options.get("cacert", None) is not None:

st2client/st2client/client.py

Lines changed: 108 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def __init__(
6363
debug=False,
6464
token=None,
6565
api_key=None,
66+
basic_auth=None,
6667
):
6768
# Get CLI options. If not given, then try to get it from the environment.
6869
self.endpoints = dict()
@@ -129,115 +130,195 @@ def __init__(
129130

130131
self.api_key = api_key
131132

133+
if basic_auth:
134+
# NOTE: We assume username can't contain colons
135+
if len(basic_auth.split(":", 1)) != 2:
136+
raise ValueError(
137+
"basic_auth config options needs to be in the "
138+
"username:password notation"
139+
)
140+
141+
self.basic_auth = tuple(basic_auth.split(":", 1))
142+
else:
143+
self.basic_auth = None
144+
132145
# Instantiate resource managers and assign appropriate API endpoint.
133146
self.managers = dict()
134147
self.managers["Token"] = ResourceManager(
135-
models.Token, self.endpoints["auth"], cacert=self.cacert, debug=self.debug
148+
models.Token,
149+
self.endpoints["auth"],
150+
cacert=self.cacert,
151+
debug=self.debug,
152+
basic_auth=self.basic_auth,
136153
)
137154
self.managers["RunnerType"] = ResourceManager(
138155
models.RunnerType,
139156
self.endpoints["api"],
140157
cacert=self.cacert,
141158
debug=self.debug,
159+
basic_auth=self.basic_auth,
142160
)
143161
self.managers["Action"] = ActionResourceManager(
144-
models.Action, self.endpoints["api"], cacert=self.cacert, debug=self.debug
162+
models.Action,
163+
self.endpoints["api"],
164+
cacert=self.cacert,
165+
debug=self.debug,
166+
basic_auth=self.basic_auth,
145167
)
146168
self.managers["ActionAlias"] = ActionAliasResourceManager(
147169
models.ActionAlias,
148170
self.endpoints["api"],
149171
cacert=self.cacert,
150172
debug=self.debug,
173+
basic_auth=self.basic_auth,
151174
)
152175
self.managers["ActionAliasExecution"] = ActionAliasExecutionManager(
153176
models.ActionAliasExecution,
154177
self.endpoints["api"],
155178
cacert=self.cacert,
156179
debug=self.debug,
180+
basic_auth=self.basic_auth,
157181
)
158182
self.managers["ApiKey"] = ResourceManager(
159-
models.ApiKey, self.endpoints["api"], cacert=self.cacert, debug=self.debug
183+
models.ApiKey,
184+
self.endpoints["api"],
185+
cacert=self.cacert,
186+
debug=self.debug,
187+
basic_auth=self.basic_auth,
160188
)
161189
self.managers["Config"] = ConfigManager(
162-
models.Config, self.endpoints["api"], cacert=self.cacert, debug=self.debug
190+
models.Config,
191+
self.endpoints["api"],
192+
cacert=self.cacert,
193+
debug=self.debug,
194+
basic_auth=self.basic_auth,
163195
)
164196
self.managers["ConfigSchema"] = ResourceManager(
165197
models.ConfigSchema,
166198
self.endpoints["api"],
167199
cacert=self.cacert,
168200
debug=self.debug,
201+
basic_auth=self.basic_auth,
169202
)
170203
self.managers["Execution"] = ExecutionResourceManager(
171204
models.Execution,
172205
self.endpoints["api"],
173206
cacert=self.cacert,
174207
debug=self.debug,
208+
basic_auth=self.basic_auth,
175209
)
176210
# NOTE: LiveAction has been deprecated in favor of Execution. It will be left here for
177211
# backward compatibility reasons until v3.2.0
178212
self.managers["LiveAction"] = self.managers["Execution"]
179213
self.managers["Inquiry"] = InquiryResourceManager(
180-
models.Inquiry, self.endpoints["api"], cacert=self.cacert, debug=self.debug
214+
models.Inquiry,
215+
self.endpoints["api"],
216+
cacert=self.cacert,
217+
debug=self.debug,
218+
basic_auth=self.basic_auth,
181219
)
182220
self.managers["Pack"] = PackResourceManager(
183-
models.Pack, self.endpoints["api"], cacert=self.cacert, debug=self.debug
221+
models.Pack,
222+
self.endpoints["api"],
223+
cacert=self.cacert,
224+
debug=self.debug,
225+
basic_auth=self.basic_auth,
184226
)
185227
self.managers["Policy"] = ResourceManager(
186-
models.Policy, self.endpoints["api"], cacert=self.cacert, debug=self.debug
228+
models.Policy,
229+
self.endpoints["api"],
230+
cacert=self.cacert,
231+
debug=self.debug,
232+
basic_auth=self.basic_auth,
187233
)
188234
self.managers["PolicyType"] = ResourceManager(
189235
models.PolicyType,
190236
self.endpoints["api"],
191237
cacert=self.cacert,
192238
debug=self.debug,
239+
basic_auth=self.basic_auth,
193240
)
194241
self.managers["Rule"] = ResourceManager(
195-
models.Rule, self.endpoints["api"], cacert=self.cacert, debug=self.debug
242+
models.Rule,
243+
self.endpoints["api"],
244+
cacert=self.cacert,
245+
debug=self.debug,
246+
basic_auth=self.basic_auth,
196247
)
197248
self.managers["Sensor"] = ResourceManager(
198-
models.Sensor, self.endpoints["api"], cacert=self.cacert, debug=self.debug
249+
models.Sensor,
250+
self.endpoints["api"],
251+
cacert=self.cacert,
252+
debug=self.debug,
253+
basic_auth=self.basic_auth,
199254
)
200255
self.managers["TriggerType"] = ResourceManager(
201256
models.TriggerType,
202257
self.endpoints["api"],
203258
cacert=self.cacert,
204259
debug=self.debug,
260+
basic_auth=self.basic_auth,
205261
)
206262
self.managers["Trigger"] = ResourceManager(
207-
models.Trigger, self.endpoints["api"], cacert=self.cacert, debug=self.debug
263+
models.Trigger,
264+
self.endpoints["api"],
265+
cacert=self.cacert,
266+
debug=self.debug,
267+
basic_auth=self.basic_auth,
208268
)
209269
self.managers["TriggerInstance"] = TriggerInstanceResourceManager(
210270
models.TriggerInstance,
211271
self.endpoints["api"],
212272
cacert=self.cacert,
213273
debug=self.debug,
274+
basic_auth=self.basic_auth,
214275
)
215276
self.managers["KeyValuePair"] = ResourceManager(
216277
models.KeyValuePair,
217278
self.endpoints["api"],
218279
cacert=self.cacert,
219280
debug=self.debug,
281+
basic_auth=self.basic_auth,
220282
)
221283
self.managers["Webhook"] = WebhookManager(
222-
models.Webhook, self.endpoints["api"], cacert=self.cacert, debug=self.debug
284+
models.Webhook,
285+
self.endpoints["api"],
286+
cacert=self.cacert,
287+
debug=self.debug,
288+
basic_auth=self.basic_auth,
223289
)
224290
self.managers["Timer"] = ResourceManager(
225-
models.Timer, self.endpoints["api"], cacert=self.cacert, debug=self.debug
291+
models.Timer,
292+
self.endpoints["api"],
293+
cacert=self.cacert,
294+
debug=self.debug,
295+
basic_auth=self.basic_auth,
226296
)
227297
self.managers["Trace"] = ResourceManager(
228-
models.Trace, self.endpoints["api"], cacert=self.cacert, debug=self.debug
298+
models.Trace,
299+
self.endpoints["api"],
300+
cacert=self.cacert,
301+
debug=self.debug,
302+
basic_auth=self.basic_auth,
229303
)
230304
self.managers["RuleEnforcement"] = ResourceManager(
231305
models.RuleEnforcement,
232306
self.endpoints["api"],
233307
cacert=self.cacert,
234308
debug=self.debug,
309+
basic_auth=self.basic_auth,
235310
)
236311
self.managers["Stream"] = StreamManager(
237-
self.endpoints["stream"], cacert=self.cacert, debug=self.debug
312+
self.endpoints["stream"],
313+
cacert=self.cacert,
314+
debug=self.debug,
315+
basic_auth=self.basic_auth,
238316
)
239317
self.managers["Workflow"] = WorkflowManager(
240-
self.endpoints["api"], cacert=self.cacert, debug=self.debug
318+
self.endpoints["api"],
319+
cacert=self.cacert,
320+
debug=self.debug,
321+
basic_auth=self.basic_auth,
241322
)
242323

243324
# Service Registry
@@ -246,24 +327,31 @@ def __init__(
246327
self.endpoints["api"],
247328
cacert=self.cacert,
248329
debug=self.debug,
330+
basic_auth=self.basic_auth,
249331
)
250332

251333
self.managers["ServiceRegistryMembers"] = ServiceRegistryMembersManager(
252334
models.ServiceRegistryMember,
253335
self.endpoints["api"],
254336
cacert=self.cacert,
255337
debug=self.debug,
338+
basic_auth=self.basic_auth,
256339
)
257340

258341
# RBAC
259342
self.managers["Role"] = ResourceManager(
260-
models.Role, self.endpoints["api"], cacert=self.cacert, debug=self.debug
343+
models.Role,
344+
self.endpoints["api"],
345+
cacert=self.cacert,
346+
debug=self.debug,
347+
basic_auth=self.basic_auth,
261348
)
262349
self.managers["UserRoleAssignment"] = ResourceManager(
263350
models.UserRoleAssignment,
264351
self.endpoints["api"],
265352
cacert=self.cacert,
266353
debug=self.debug,
354+
basic_auth=self.basic_auth,
267355
)
268356

269357
@add_auth_token_to_kwargs_from_env
@@ -275,7 +363,10 @@ def get_user_info(self, **kwargs):
275363
"""
276364
url = "/user"
277365
client = httpclient.HTTPClient(
278-
root=self.endpoints["api"], cacert=self.cacert, debug=self.debug
366+
root=self.endpoints["api"],
367+
cacert=self.cacert,
368+
debug=self.debug,
369+
basic_auth=self.basic_auth,
279370
)
280371
response = client.get(url=url, **kwargs)
281372

st2client/st2client/config_parser.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@
5959
"username": {"type": "string", "default": None},
6060
"password": {"type": "string", "default": None},
6161
"api_key": {"type": "string", "default": None},
62+
"basic_auth": {
63+
# Basic auth credentials in username:password notation
64+
"type": "string",
65+
"default": None,
66+
},
6267
},
6368
"api": {"url": {"type": "string", "default": None}},
6469
"auth": {"url": {"type": "string", "default": None}},

0 commit comments

Comments
 (0)