_Once upon a time, there was only AWS. The realm of Scout was dominated by this provider only, and as it grew bigger, jealous grew the other providers. GCP was then given a piece of land. GCP, receiving a lot of attention from the Supreme Beings of Scout, was making Azure jealous. And so Azure was given a piece of land. Even though it seemed like the providers were happily cohabiting, the Supreme Beings knew something was not right._
And this is why we need to refactor the providers' architecture. Basically, there is a whole lot of provider-checking conditions, which is a huge code smell. There is already a provider model in place, but a lot of attributes should be bubbled up to the generic provider class, and some others should be pushed down to their respective provider. Basically, we should take advantage of polymorphism.
metadata.json~ --threads). This is necessary as sometimes you need to run Scout while making sure it doesn't affect the call quotaAfter discussing with @misg and @Remi05, we decided we would try implementing a composite pattern. Here's our current state of mind:
When improving support for Azure, we noticed there was a lot of friction associated with implementing a new service. This is because Azure's API is very granular; a lot of API calls are needed to get one config. And regarding GCP, Scout Suite current support doesn't reflect very well the hierarchy of organization/project/region/zone used by Google (see #80 and #97). Thus, we tend to think that a composite pattern could address those issues (and make AWS support more readable and maintenable by the way, see the next section below). Basically, each time you would put an id in a resource path, you split in a new config. Each config is responsible for calling fetch_all on its children configs.
We noticed there was a lot of magic going on. A lot of the code was over-generalized, which encouraged the use of service-specific hacks. Also, there was a lot of magic string wizardry stuff (including heavy use of Python metaprogramming). For example, AWS's targets: they were used for injecting the right API call. This seemed unnecessary and made the code flow very hard to follow and test. We decided to move the API calls to their own configs. This might be a bit redundant, but we feel like it will make the code more readable and maintainable. It will also be more resilient to potential API breaking changes. The API clients instances will be provided by some kind of container class. This will allow for easier per-service unit testing by making the mocking of API clients easy. Another idea is to use a fa莽ade. This makes testing easier (less stuff to mock) and removes some code from the service. It makes sense to put the least code possible in the configs since they are basically data models.
This is a huge refactoring. We want to do this the right way, and the right way is to not do one humongous pull request with the whole refactoring at once. In order to do so, we are going to keep BaseConfig's current interface. This is going to allow us to migrate the services iteratively without changing the main structure. We will then remove the unused code.
We believe we will be able to make some other changes on the way. For example, we plan on making fetch_all async to allow better management of concurrency and improve scanning performance.
We are not sure this solution works, so we are currently working on a proof of concept. We will try migrating one service for each provider. We created a branch with the changes common to the three providers. We will each work on a separate branch and do our PRs onto the common branch.
@aboisier: AWS Lambda (see branch)
@misg: Azure SQL Databases (see branch)
@remi05: GCP ? (see branch)

I like the approach, going with a Composite (and a bit of Facade) pattern and taking advantage of polymorphism will significantly improve the architecture.
There is indeed a lot of magic happening, especially in RegionConfig's interaction with the metadata.json file. One reason for this is that the architecture is mostly undocumented, so it's a free-for-all of using BaseConfigs, using/not using callbacks, etc. Also a lot of poorly documented and obscure functions such as recurse() and go_to_and_do()/new_go_to_and_do() make understanding the code hard. Let's not make that mistake twice and ensure the Wiki clearly details Scout's inner workings. The changes you propose should also make understanding the code flow less painful.
For GCP I'd recommend doing the PoC with Cloud SQL, as it's a mostly straightforward service.
When improving support for Azure, we noticed there was a lot of friction associated with implementing a new service. This is because Azure's API is very granular; a lot of API calls are needed to get one config.
You could say the same thing of AWS, that's likely why so many callbacks were used.
regarding GCP, Scout Suite current support doesn't reflect very well the hierarchy of organization/project/region/zone
Exactly, as GCP doesn't have a RegionConfig it's impossible to distinguish resource hierarchy. I know this isn't directly related to this thread but Scout's data model (a big dict which acts as a tree) is very much tied to the code's architecture. While you're thinking of refactoring it might be worth giving some thought to whether this approach is still valid given the growth of the tool (as we've already seen it hit some limitations, i.e. the memory limit of browsers).
Also, there was a lot of magic string wizardry stuff (including heavy use of Python metaprogramming). For example, AWS's targets: they were used for injecting the right API call
Agreed. I looks pretty "cool" as you get this large metadata file to define all the API calls, but it's not intuitive (especially without explicit documentation).
Another idea is to use a fa莽ade
This will certainly be useful in some cases, e.g. how __main__.py interacts with the providers objects.
we plan on making
fetch_allasync to allow better management of concurrency and improve scanning performance.
I'm not sure how much this will improve over the current multi threaded approach, but give it a shot!
You'll have to make sure that even if a composite node has a lot of leaves, resource consumption doesn't skyrocket.
In addition, will this break python 2.7 compatibility?
After review, here's a list of remaining discrepancies between the refactored cloud providers that should be handled in order to make the refactoring as clear as possible:
AzureResources that inherits from the base class Resources, like AWSResources) instead of passed as a constructor argument at each level of the hierarchyscope terminology/concept used in AWS support should be explicitly used as well in Azure (and GCP) support rather than implicitly through the use of kwargs which is a bit obscure_fetch_children and _fetch_children_of_all_resources defined in AzureCompositeResources and AWSCompositeResources could be moved up to the base class CompositeResources since they are basically duplicated code (need to check with GCP implementation, although)get_and_set_concurrently helperAzure resource counts should be computed the same way used in AWS and GCP supports
I liked best how Azure implements it (directly in the composite resource class)
My bad, I had confused Azure with AWS.
Azure resource counts should be computed the same way used in AWS and GCP supports
I liked best how Azure implements it (directly in the composite resource class)
I'm confused, it seems like you're describing AWS's implementation: https://github.com/nccgroup/ScoutSuite/blob/master/ScoutSuite/providers/aws/resources/resources.py#L70
I'm confused, it seems like you're describing AWS's implementation:
Yes sorry, I had mixed up the two.
I think we can officially say the refactoring is done, can we?
I declare this issue CLOSED! 馃帀
Most helpful comment
I think we can officially say the refactoring is done, can we?