-
-
Notifications
You must be signed in to change notification settings - Fork 745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extend auth to secure chatops #2920
Conversation
…er optional but on by default to not break backward compat
Thanks for the PR. I will look into it some time in the next couple of days. We did already had some discussion about how to handle RBAC in ChatOps (how to map ChatOps users to StackStorm users) in the past. There are some things we haven't decided on yet, but by quickly looking at your description it looks that some base changes (username field, is_service, etc.) are generic and good enough and something we can re-use in any case. In short, my idea for authenticating and mapping ChatOps users back to StackStorm was for the user on the chat to authenticate to StackStorm via bot using a ChatOps specific token. Then we would also need some logic in the adapters on how to handle the user uniqueness (on IRC for example, you need to use username+host since the nickname is not unique, in other cases, IDs are available so we can just leverage that...). |
I would like this field to be more generic. We shouldn't assume just a single ChatOps bot and a single adapter in this architecture, especially when we're starting the work on dumbing down chatops clients and moving alias-related features to the server side. Maybe I want an e-mail service parsing incoming e-mails as aliases and executing them, or a twitter-based adapter alongside with the bot? Or some other interface, or even multi-tenancy support for ChatOps (which is quite possible in the future). Long story short, a user could have more than one "chatops id." Here's my proposal: why don't we make this field a dict, where the key is the service, and the value is the username in the service? Something like this would probably make sense in the UserDB model:
And if we're considering ChatOps multi-tenancy (we should IMO), we should include tenant ID as well:
Other than that, the proposal looks fine to me on the high level. Another thing I'd advise is breaking the feature into multiple PRs: username aliases would be a no-brainer to merge, but other things could become more tricky—and it would make sense to keep the discussion on different parts separate as well. |
On @Kami's comment: I think that we can safely assume username uniqueness, because, firstly, IRC is a clear outcast here, and secondly, I think this should be handled on the ChatOps client/adapter level: i.e. Hubot should check if the user's authenticated with NickServ to execute a protected command. If we're not assuming ChatOps username uniqueness on the st2 back-end level, we'll just descend into madness from here. And it's outside the scope of this PR anyway, let's keep it small and manageable. Speaking of IRC: if we don't check NickServ, there's just no good option for a unique user ID. If we settle on username+host and later, at some point in my life, I'll have to use IRC with StackStorm, then I will seek you out and make you experience the rage of a thousand white hot blinding suns of hateful fury, seeing as I connect to the internet from a hundred places a year and my host will just change all the time. 😛 It's not that secure either: if you're using NAT, or a shared pool of IPs (i.e. a mobile network), or work from a cafe every once in a while, you're screwed. |
If you want to expose secure processes or data over IRC you need your head inspecting anyway. |
Store a dict of nickname-origin pairs instead of just one chatops_id string to make the field more future-proof. We may not need it immediately, but some features that will require this change are already planned.
Replace the `chatops_id` string with `nicknames` dict
There's a good chance we not going to stick to pecan for long anyway. See #2727. Switching authentication scheme should be also discussed prior to implementation. Some of the limitations that led to current design are not relevant anymore, that should make it easier. On the other hand, we had requests for SSO in the past. And with that in mind, single size is surely not going to fit all, so we would need to find a way to make auth check pluggable. |
We've talked through that on community, but it seems useful to have a more permanent record. The current implementation of impersonalization only works for 'standalone' auth while tests only cover 'proxy' auth. Both problems need to be addressed. "By whom?" is another question. I'd say that the way we branch between proxy and standalone auth at the beginning of the controller is not acceptable anymore (and likely never was). My vision is that we need to break the controller in three parts:
On the tests side, we need to make sure we also test 'standalone' code path with a different set of tests. |
ko @enykeev and @emedvedev this is done and ready for review. I've setup all the logic for |
I love you. ❤️ ❤️ ❤️ Overall, looks good to me. I'm satisfied with both design and proposed implementation (especially since we were able to get nicknames addressed as part of this PR, too), and I'll try to find some time to make a thorough review later tonight. I'd also like @Kami and/or @lakshmi-kannan to review this. |
class ProxyAuthHandler(AuthHandlerBase): | ||
def handle_auth(self, request, headers=None, remote_addr=None, | ||
remote_user=None, authorization=None, **kwargs): | ||
remote_addr = headers.get('x-forwarded-for', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/x-forwarded-for/X-Forwarded-For for consistency with other headers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it was already like that.
extra = {'username': username, 'user': user} | ||
LOG.audit('Registered new user "%s".' % (username), extra=extra) | ||
if add_missing_user: | ||
user = UserDB(name=username) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
user_db to be consistent across the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
] | ||
|
||
|
||
def abort_request(status_code=http_client.UNAUTHORIZED, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems un-necessary redirection and the default message is not great.
To start enabling better security controls over the chatops process I'd like to propose the following changes (to be added to this PR).