stitchfix / stitches

Create a Microservice in Rails with minimal ceremony

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Alternative white lisiting method because `configuration.allowlist_regexp` is error prone

TildeWill opened this issue · comments

The pain of #61 highlights that this regex is cumbersome to maintain. In referrals, we have an entry that looks like this:

  configuration.allowlist_regexp = %r{\A/(referrals\/api\/contacts|referrals\/api\/referrals|assets|client|referrals\/api\/docs|referrals\/proof_of_life|referrals\/resque|referrals\/teaspoon)}

It turns out that there was an overly permissive entry in there which essentially white listed all of the referrals API - not desirable and tests are being back filled.

I'd like to add something that's a bit easier to read/maintain. I spent some time trying to replace the middleware by creating a Rails custom constraint. Constraints have a handle on the request, but not on the response and so I ran into trouble when trying to replicate these lines:

env[@configuration.env_var_to_hold_api_client_primary_key] = client.id
env[@configuration.env_var_to_hold_api_client] = client

Is there a) still a need for these lines, and b) a way to move them out of the middleware? Or maybe some third way to make it more obvious in routes.rb what does/does not require the api_key? A less desirable option would be to add a before_filter in the controllers but I'm guessing that choice was avoided on purpose?

The thinking was that a micro service would not have a ton of non-authed routes. The app you are referring too is a full stack app that exposes a microservice, which is a pattern we recommend against and that we should avoid. I think this is why your allow list is so complicated.

The simplest way I can think of to clean it up would be to accept an array of regexps:

configuration.allow_list = [
  "/referrals/foo",
  "/resque-web",
  # etc.
]

and then wrap those with %r{\a{#val}\Z} or something.

I'm not understanding how this plays into the need to store the api client in the environment that. Those should be totally separate concerns from what is allowed/denied.

I think this is why your allow list is so complicated.

Correct. The team is taking steps to move to the recommended architecture in this specific case.

The simplest way I can think of to clean it up would be to accept an array of regexps:

configuration.allow_list = [
  "/referrals/foo",
  "/resque-web",
  # etc.
]

and then wrap those with %r{\a{#val}\Z} or something.

That would be a big improvement in readability within the Stitches initializer within an app and would address some of the pain. But the discoverability of the allow_list is still pretty poor when you're rolling in to an application and trying to understand why some endpoints give you a 401 and others don't. I want to move API auth to a place that is closer to where someone might look, such as in routes.rb or in the controller.

Yeah, the allow list was added kinda hastily as we realized we needed a way to easily opt out some routes from auth—I would agree it's a "thing about routing" and thus kinda belongs in the routes file (thus your point that it's more discoverable). The main "feature" I would want to keep is "auth by default", i.e if you add a route in config/routes.rb in "the normal Rails Way" it should require auth. I think a routing constraint could do this?

I think a routing constraint could do this?

Correct.

What it can't do is set the client on the response as is currently happening here:

env[@configuration.env_var_to_hold_api_client_primary_key] = client.id
env[@configuration.env_var_to_hold_api_client] = client

It might not need to, but to leave the api_key middleware in would require two lookups to the api client per request. I guess the proposed routing constraint could simply check for the existence of the key in the header and handle the allow list and we keep the middleware the same? I dunno, we'll have to think this through.

I'll put a PR together to give us something concrete to point to