Skip to content
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

Deny standalone service template ordering when product setting is enabled #476

Merged
merged 2 commits into from
Oct 22, 2018

Conversation

eclarizio
Copy link
Member

@eclarizio eclarizio commented Sep 24, 2018

The issue this PR fixes is that currently, you can simply POST to the service_template order endpoint with whatever data you want (ideally, there is sort of a flow, i.e. you request the dialog, you may request any dialog field refreshes, and then you can POST to the service_template order endpoint with the data you've gathered from the previous requests). With any random data, it won't really work, but for example if a dropdown only contains the options "A" or "B", but the API request is submitted with "C", it is accepted.

This change, along with a product setting called allow_api_service_ordering, that the users must set to false first (as it is defaulted to true via this PR), should ensure that only the API calls that come from the UIs will be accepted. Otherwise everything will behave as it does now. This is mostly just a quick fix solution until we can potentially implement RBAC for API users, or add code to the model to validate the values against expected values, but those options are both a lot more involved.

/cc @tinaafitz

https://bugzilla.redhat.com/show_bug.cgi?id=1632416

@eclarizio
Copy link
Member Author

@miq-bot add_reviewer @bdunne

@bdunne Can you take a look for me, please? And/or suggest anyone else that can take a look?

@miq-bot miq-bot requested a review from bdunne September 26, 2018 17:41
Copy link
Member

@bdunne bdunne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, this feels like a model validation issue more than an api problem

@@ -17,6 +19,14 @@ def order_service_template(id, data, scheduled_time = nil)
def service_template_ident(st)
"Service Template id:#{st.id} name:'#{st.name}'"
end

def api_request_allowed?
standalone_api_request? ? !Settings.product.deny_api_service_ordering : true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the Setting have a positive name?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only issue I have with changing it to a positive name is then the users who want it to work the way it works after this PR would be to set the value to "false", which feels weird but I don't know if we have a precedent for this or not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but I think the default should be in here anyway.

let(:product_settings) { double(:deny_api_service_ordering => true) }

before do
allow(Settings).to receive(:product).and_return(product_settings)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer stub_settings_merge

end

def standalone_api_request?
request.authorization.present? && request.authorization.to_s.split(" ", 2).first.downcase == "basic"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request.authorization.try(:starts_with?, "basic")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this will break if we allow other authentication types to the api

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but do you know of another way to distinguish standalone API calls versus API calls made from the UI?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of anything at the moment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can leverage the token manager namespace, api, ws, (might need to add param or separate api/ui as 2 namespaces).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I pulled the "basic" check from here: https://github.com/rails/rails/blob/fc5dd0b85189811062c85520fd70de8389b55aeb/actionpack/lib/action_controller/metal/http_authentication.rb#L104.

I suppose we could do .try(:downcase).try(:starts_with?, "basic")? Not sure how else to handle the fact that it could be "Basic", "BASIC", or any other various capitalizations.

@eclarizio
Copy link
Member Author

@bdunne I agree, but fixing the validation to take care of ensuring that the values passed in through the API versus actual acceptable values is much more involved because of dynamic dialogs and how we don't want to call into automate when we shouldn't have to (and the potential sequencing of those calls depending on which fields are dependent on which for refreshes). If the dialog has some expensive automate calls, submitting will take way longer than it should since for the majority of the cases the API call will have proper values already.

A PR could build on this one so that instead of just flat out denying the request a product setting is no longer needed and it delegates to the model to verify that the values passed in are acceptable values.

@eclarizio
Copy link
Member Author

Not sure what labels to put on this now that we've transitioned into Hammer.

@miq-bot add_label bug

@miq-bot miq-bot added the bug label Sep 27, 2018
Copy link
Member

@carbonin carbonin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see where @bdunne is coming from and would agree in an ideal world, but if validating the incoming values properly means double automate calls then I think this is a better stop-gap solution.

Generally though, I would like to see something less fragile than checking the auth type to determine if the request came from our UI or not. Given that this bug is of medium priority and not a blocker, I think it would be good to do this for the first pass. Maybe we could implement a special header value to denote API calls originating in the UI and make this information available across the API for all requests. Thoughts @bdunne @abellotti ?

end

def standalone_api_request?
request.authorization.present? && request.authorization.try(:starts_with?, "basic")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're doing the .present? check, why do we need the .try? Do we expect request.authorization not to respond to starts_with??

Also, is the premise here valid @abellotti @jvlcek? Does external auth not work with the API? Or am I misunderstanding this check?

Copy link
Member Author

@eclarizio eclarizio Sep 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question about external auth, I'm not sure. I mentioned before when @bdunne suggested using the try that I (originally) pulled this line from https://github.com/rails/rails/blob/fc5dd0b85189811062c85520fd70de8389b55aeb/actionpack/lib/action_controller/metal/http_authentication.rb#L104, and when I went to change to the try I forgot to take off the .present?.

standalone_api_request? ? Settings.product.allow_api_service_ordering : true
end

def standalone_api_request?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we name this better? I don't understand what it means for an API request to be "standalone", maybe reverse the logic and call it request_from_ui?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, I was struggling to come up with a good name, but reversing the logic and naming it request_from_ui? sounds better to me

@eclarizio
Copy link
Member Author

@carbonin I agree, ultimately the auth type was the only thing I could think of to determine the difference between UI API calls and API calls originating from curl or any other client, but if there is a better way to determine it, it's easy to replace the logic here. I thought of a special header as well, but originally I heard that this was supposed to be some kind of high priority customer issue, so I wanted to get something out relatively quickly. Tina created the BZ just so we would have something to link to this PR, but I have no insight into how high the priority is now for the customer.

@eclarizio eclarizio force-pushed the dialog_ordering_security_issue branch 2 times, most recently from 08779c9 to f6c1828 Compare September 27, 2018 19:10
@abellotti
Copy link
Member

I think it's best if we use/extend our UI assigned tokens for this. Since API calls coming in from the UI use their tokens, we already have that information (or easy to add) for differentiation. This way this will work for basic as well as ext-auth requests from the UI.

@eclarizio eclarizio force-pushed the dialog_ordering_security_issue branch 2 times, most recently from 09185ff to 8e470f1 Compare September 27, 2018 20:15
@eclarizio
Copy link
Member Author

@abellotti Is this more what you had in mind? I originally started down this path but couldn't figure out how to authenticate it. Thanks for the push in the right direction with the token_get_info method.

@mkanoor
Copy link
Contributor

mkanoor commented Sep 28, 2018

@eclarizio Isn't this a validation problem, say if you had two drop down items in a service dialog
state = NY
fruit = pick one of ['apple', 'banana', 'orange']
county = pick one of Dynamic content from Automate (e.g. get list of counties based on state)

When the caller sends in
state = NY, fruit = apple, county = Ulster its valid this can be called from the UI and from a CLI making the REST API call and if we made the automate call to get the list of counties we would know that Ulster is a valid county in NY
but when some one passes in

state = NY, fruit = banana, county = bergen this is invalid because Bergen is not a county in NY. When the user is in the UI we will never present Bergen as an option since we would have made the dynamic call

If during validation of the dialog if we fetched the list of the dynamic components we could have rejected that invalid county.

I think the validation is making an assumption that if the field is a drop down the user has selected a valid item so even in the static fruit list if someone passed in kiwi we would still let it thru.

@eclarizio
Copy link
Member Author

eclarizio commented Sep 28, 2018

If during validation of the dialog if we fetched the list of the dynamic components we could have rejected that invalid county.

We could, you're right, but that (to me) is an unnecessary automate call, because we should have already done that when the user selected NY from the UI, which (correct me if you think I'm wrong here) will be a large majority of the time.

Maybe in your example case, the automate call isn't very expensive, but if the dialog has multiple expensive automate calls, I don't think the trade off of always calling automate to validate the values is worth it compared to how often it would actually be useful since again, the majority of the order submit calls will be going through the UIs. Unless someone purposely mucks with data, it is impossible to actually send through values that don't exist.

Even in the event people use the API to POST order submits, in theory they should be doing a GET request on the dialog first, then POST-ing to the refresh endpoint when their data changes so they can get the correct values on the dialog fields, and then finally POST-ing to the order submit endpoint.

@mkanoor
Copy link
Contributor

mkanoor commented Sep 28, 2018

@eclarizio
There are 2 distinct phases here

  1. User interacting with the dialogs making selections, causing data to change, maybe some data input validation happening like checking regular expression, minimum length etc.

  2. The user presses the order button at this point we have a hash of all the selected values, these values have to be validated before the request is created or as the first step after the request is created. @Fryguy has correctly pointed out in the past that we do some validation before the MiqRequest is created which ends up holding the API calls and causing them to hang. We should create a request then validate all the parameters and once validation is successful mark the request as "Ready" for approval or Reject it.

The UI today mostly focuses on (1) but its easy to do (2) by doing some of the same things that were done in (1). That code is already there. It just needs to be used during the ordering phase.

(2) cannot be done in Automate, we cannot have Automate load up a dialog and for each field call all the Automate Methods to create dynamic content, all of that has to be done from the dialog side since there is already code doing this validation.

Granted "Validation" might take a long time but if there is an option that says "Strict Dialog Validation" and this can be done after the MiqRequest is created it would be ideal. We wont he holding the Request API call while we do the validation.

If we dont do this validation all the REST API calls are going in without any validation and we run into the issues reported in this BZ.

@miq-bot
Copy link
Member

miq-bot commented Oct 15, 2018

This pull request is not mergeable. Please rebase and repush.

@JPrause
Copy link
Member

JPrause commented Oct 18, 2018

@eclarizio is this will be able to be backported, can you add the hammer/yes and gaprindashvili/yes labels

@JPrause
Copy link
Member

JPrause commented Oct 18, 2018

@miq-bot add_label blocker

@eclarizio eclarizio force-pushed the dialog_ordering_security_issue branch 2 times, most recently from be62161 to 3045610 Compare October 18, 2018 15:02
def request_from_ui?
return false if request.headers["x-auth-token"].blank?
token_info.present?
!token_info.empty?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't these the same?

@@ -15,19 +17,24 @@ def order_service_template(id, data, scheduled_time = nil)

private

def service_template_ident(st)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this move should be in the diff

@@ -375,6 +375,13 @@ def sc_template_url(id, st_id = nil)
request_headers["x-auth-token"] = test_token
end

before do
stub_settings_merge(:product => double(:allow_api_service_ordering => true))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need a double here, it can be a nested hash

@@ -466,6 +466,15 @@

describe "Service Templates order" do
let(:service_template) { FactoryGirl.create(:service_template, :with_provision_resource_action_and_dialog, :orderable) }
let(:product_settings) { double(:allow_api_service_ordering => allow_api_service_ordering) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it doesn't feel like this should be in a let

@eclarizio eclarizio force-pushed the dialog_ordering_security_issue branch from 3045610 to 417873f Compare October 18, 2018 16:21
@eclarizio
Copy link
Member Author

@bdunne Thanks for the catches, was from the rebase and I was just concerned about fixing the conflicts, haha.

@bdunne bdunne closed this Oct 18, 2018
@bdunne bdunne reopened this Oct 18, 2018
@eclarizio
Copy link
Member Author

@miq-bot add_label gaprindashvili/yes, hammer/yes

@eclarizio eclarizio force-pushed the dialog_ordering_security_issue branch from 417873f to 364b585 Compare October 22, 2018 15:26
@eclarizio eclarizio force-pushed the dialog_ordering_security_issue branch from 364b585 to 86d0986 Compare October 22, 2018 15:40
@miq-bot
Copy link
Member

miq-bot commented Oct 22, 2018

Checked commits eclarizio/manageiq-api@b46e4c0~...86d0986 with ruby 2.3.3, rubocop 0.52.1, haml-lint 0.20.0, and yamllint 1.10.0
3 files checked, 0 offenses detected
Everything looks fine. 🏆

@bdunne bdunne merged commit 7343ad7 into ManageIQ:master Oct 22, 2018
@bdunne bdunne added this to the Sprint 97 Ending Oct 22, 2018 milestone Oct 22, 2018
@bdunne bdunne self-assigned this Oct 22, 2018
simaishi pushed a commit that referenced this pull request Oct 22, 2018
Deny standalone service template ordering when product setting is enabled

(cherry picked from commit 7343ad7)

https://bugzilla.redhat.com/show_bug.cgi?id=1632416
@simaishi
Copy link
Contributor

Hammer backport details:

$ git log -1
commit 55732b3d20dece9cf4743fa0cd850af5c0c11e82
Author: Brandon Dunne <brandondunne@hotmail.com>
Date:   Mon Oct 22 11:49:56 2018 -0400

    Merge pull request #476 from eclarizio/dialog_ordering_security_issue
    
    Deny standalone service template ordering when product setting is enabled
    
    (cherry picked from commit 7343ad7cad22f24639a23ff3a9d6c5182d64172d)
    
    https://bugzilla.redhat.com/show_bug.cgi?id=1632416

@simaishi
Copy link
Contributor

simaishi commented Nov 5, 2018

Gaprindashvili backport details:

$ git log -1
commit 068184ec96943dcff4cab92dff2a4c60e2bc10fe
Author: Brandon Dunne <brandondunne@hotmail.com>
Date:   Mon Oct 22 11:49:56 2018 -0400

    Merge pull request #476 from eclarizio/dialog_ordering_security_issue
    
    Deny standalone service template ordering when product setting is enabled
    
    (cherry picked from commit 7343ad7cad22f24639a23ff3a9d6c5182d64172d)
    
    https://bugzilla.redhat.com/show_bug.cgi?id=1646435

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants