-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
WIP - Refactoring/aws lambda config #199
WIP - Refactoring/aws lambda config #199
Conversation
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.
Great job! I really like your example of facade to manage API calls, very neat!
I left a couple of comments about the general design of your refactoring, hope they will be useful to feed the discussion about our global refactoring!
regions = Session().get_available_regions(service, partition_name) | ||
|
||
if chosen_regions: | ||
return list((Counter(regions) & Counter(chosen_regions)).elements()) |
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.
Very elegant! 💯
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.
Thanks <3 It comes from opinel. I feel like this line could be simplified to this:
return list((Counter(regions) & Counter(chosen_regions)).elements()) | |
return list((Set(regions) & Set(chosen_regions)) |
I didn't do it cause I'm not sure.
from ScoutSuite.providers.aws.aws_facade import AwsFacade | ||
|
||
|
||
class RegionsConfig(ResourceConfig): |
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.
I'm not really convinced about the design of RegionsConfig
and its "child" LambdaServiceConfig
. It looks like you're not really taking advantages of our composite structure and inheritance principles, by having RegionsConfig
and LambdaServiceConfig
inherited from ResourceConfig
, with RegionsConfig
instantiated in LambdaServiceConfig
with LambdasConfig
passed as an argument.
Why not defining RegionsConfig
as a base class, something like:
class RegionsConfig(ResourceConfig):
# TODO: add service as arg
async def fetch_all(self, credentials, regions=None, partition_name='aws', targets=None):
# TODO: Should be injected
facade = AwsFacade()
self['regions'] = await facade.build_region_list(service, regions, partition_name)
self['regions_count'] = len(self['regions'])
and then just defining LambdasConfig
as a child class of RegionsConfig
(and removing LambdaServiceConfig
which doesn't seem very useful, even though probably used in the current codebase), as following:
class LambdasConfig(RegionsConfig):
async def fetch_all(self, credentials, region, partition_name='aws', targets=None):
super(LambdasConfig, self).fetch_all('lambda', region, partition_name) # TODO: modify fetch_all to match this signature
# TODO: Should be injected
facade = AwsFacade()
for region in self['regions']:
functions = {}
for raw_function in facade.get_lambda_functions(region):
name, function = self.parse_function(raw_function)
functions[name] = function
self['functions_count'] = len(functions)
self['functions'] = functions
@staticmethod
def parse_function(function):
function['name'] = function.pop('FunctionName')
But we would have to deal with two things first, I believe:
- removing
LambdaServiceConfig
safely, ensuring compliance with the interface of the current codebase (mainly with the rule engine, in the end) - modifiying
fetch_all
signature, to be able to pass aservice
argument. More than that, it seems like you don't needcredentials
andtargets
argument. I would suggest to modify the base classResourceConfig
to be more general:
class ResourceConfig(dict):
async def fetch_all(self, **kwargs):
raise NotImplementedError()
async def finalize(self):
pass
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.
My idea was really trying to respect the composite structure as much as possible. I like the idea of making RegionConfig a base class. I feel like having one class handle two levels of the tree (in this case regions and functions) is somewhat against the purpose of having a composite pattern, because you are hiding multiple layers of the structure in one node. Splitting the service/region-specific configs in one class and the instance-specific configs in another one is a sweet spot.
I'll explore a few different implementations and push the most interesting here for review.
[M]odifiying
fetch_all
signature, to be able to pass aservice
argument. More than that, it seems like you don't needcredentials
andtargets
argument. I would suggest to modify the base classResourceConfig
to be more general
I agree, the fetch_all signature should be more general.
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.
My idea was really trying to respect the composite structure as much as possible. I like the idea of making RegionConfig a base class. I feel like having one class handle two levels of the tree (in this case regions and functions) is somewhat against the purpose of having a composite pattern, because you are hiding multiple layers of the structure in one node. Splitting the service/region-specific configs in one class and the instance-specific configs in another one is a sweet spot.
I'll explore a few different implementations and push the most interesting here for review.
I really like your new implementation! I think I finally got your point about having two separated classes, I was mainly confused by the terminology being used, I believe. Would you mind if I suggest some changes in the name of your classes, to make things clearer?
I agree, the fetch_all signature should be more general.
Done, I just pushed a more general fetch_all
method in refactoring/resource_config
.
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.
I think I finally got your point about having two separated classes [...]
I think too! Great teamwork ✋
Would you mind if I suggest some changes in the name of your classes, to make things clearer?
On the contrary; I'm definitely not satisfied with my classes names. I know we talked about removing the Config
suffix. This would give us something like: Resource
, Region
, LambdaService
, LambdaFunction
. Did you have another idea in mind?
EDIT: just saw your other comments. I like those names too, I'll apply your suggestions.
from ScoutSuite.providers.aws.aws_facade import AwsFacade | ||
|
||
|
||
class RegionsConfig(ResourceConfig): |
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.
My idea was really trying to respect the composite structure as much as possible. I like the idea of making RegionConfig a base class. I feel like having one class handle two levels of the tree (in this case regions and functions) is somewhat against the purpose of having a composite pattern, because you are hiding multiple layers of the structure in one node. Splitting the service/region-specific configs in one class and the instance-specific configs in another one is a sweet spot.
I'll explore a few different implementations and push the most interesting here for review.
I really like your new implementation! I think I finally got your point about having two separated classes, I was mainly confused by the terminology being used, I believe. Would you mind if I suggest some changes in the name of your classes, to make things clearer?
I agree, the fetch_all signature should be more general.
Done, I just pushed a more general fetch_all
method in refactoring/resource_config
.
self['regions'][region] = functions | ||
|
||
|
||
class LambdasConfig(ResourceConfig): |
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.
And since this classe stores lambdas of a specific region, I would call it RegionalLambdas
.
Co-Authored-By: Aboisier <aboisiermichaud@gmail.com>
@@ -0,0 +1,34 @@ | |||
import boto3 |
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.
why not just facade.py
?
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.
Yea I guess this would be more consistent with the codebase. It's so annoying though when you use the fuzzy search to open a file and it suggests 4 different files all named provider.py
😆
from botocore.session import Session | ||
from collections import Counter | ||
|
||
# TODO: Handle authentication better. I don't even know how it currently works. I think connect_service is called somewhere. |
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's handled by the authenticate
method of each provider (e.g. https://github.com/nccgroup/ScoutSuite/blob/master/ScoutSuite/providers/aws/provider.py#L52). For AWS we're passing the CLI params to Opinel, which does most of the work.
@@ -64,7 +64,7 @@ def __init__(self, metadata=None, thread_config=4, **kwargs): | |||
self.emr = EMRConfig(metadata['analytics']['emr'], thread_config) | |||
self.iam = IAMConfig(thread_config) | |||
self.kms = KMSConfig(metadata['security']['kms'], thread_config) | |||
self.awslambda = LambdaConfig(metadata['compute']['awslambda'], thread_config) |
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.
Out with the metadata ⛏ ⛏ ⛏
…nto refactoring/aws-lambda-config
Great work! I feel like this new architecture will really make the code more maintainable and easier to understand going forward! 👍 |
Thanks for reviewing and all! I'm just going to close this though as most of the changes are contained in #202 and it has a whole lot of conflicts with this branch. |
Here's a work in progress for migrating the Aws Lambda service to the new architecture. I had to implement a
RegionConfig
, which is generic. I think it's not too ugly.