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

WIP - Refactoring/aws lambda config #199

Closed

Conversation

Aboisier
Copy link
Contributor

@Aboisier Aboisier commented Feb 20, 2019

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.

Copy link
Contributor

@misg misg left a 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())
Copy link
Contributor

Choose a reason for hiding this comment

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

Very elegant! 💯

Copy link
Contributor Author

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:

Suggested change
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):
Copy link
Contributor

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 a service argument. More than that, it seems like you don't need credentials and targets argument. I would suggest to modify the base class ResourceConfig to be more general:
class ResourceConfig(dict):

    async def fetch_all(self, **kwargs):
        raise NotImplementedError()

    async def finalize(self):
        pass

Copy link
Contributor Author

@Aboisier Aboisier Feb 21, 2019

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 a service argument. More than that, it seems like you don't need credentials and targets argument. I would suggest to modify the base class ResourceConfig to be more general

I agree, the fetch_all signature should be more general.

Copy link
Contributor

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.

Copy link
Contributor Author

@Aboisier Aboisier Feb 21, 2019

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):
Copy link
Contributor

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):
Copy link
Contributor

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.

misg and others added 2 commits February 20, 2019 22:45
Co-Authored-By: Aboisier <aboisiermichaud@gmail.com>
@@ -0,0 +1,34 @@
import boto3
Copy link
Collaborator

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?

Copy link
Contributor Author

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.
Copy link
Collaborator

@x4v13r64 x4v13r64 Feb 21, 2019

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)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Out with the metadata ⛏ ⛏ ⛏

@Aboisier Aboisier changed the title Refactoring/aws lambda config WIP - Refactoring/aws lambda config Feb 21, 2019
@Remi05
Copy link
Contributor

Remi05 commented Feb 22, 2019

Great work! I feel like this new architecture will really make the code more maintainable and easier to understand going forward! 👍

@Aboisier
Copy link
Contributor Author

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.

@Aboisier Aboisier closed this Feb 25, 2019
@Aboisier Aboisier mentioned this pull request Mar 13, 2019
53 tasks
@Aboisier Aboisier deleted the refactoring/aws-lambda-config branch March 20, 2019 17:56
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.

4 participants