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

Refactors Drivers for OutputCredentialsProvider #878

Merged
merged 20 commits into from
Jan 18, 2019

Conversation

Ryxias
Copy link
Contributor

@Ryxias Ryxias commented Jan 16, 2019

to: @ryandeivert
cc: @airbnb/streamalert-maintainers
related to:
resolves:

Background

This PR sits on top of #875, in response to this comment. I split this into a separate PR to make it easier to understand.

Changes

I split out the new OutputCredentialsProvider guts into this new concept—CredentialsProvidingDriver. The idea is these drivers each are strategies for fetching Credentials from somewhere.

CredentialsProvidingDrivers

I implemented 4 drivers: S3, local files, spooled file, and ephemeral. I tried to design the interface to be plug-and-play-ready for a SSM migration in the future (#240).

New Way

The new algorithm can be found in setup_drivers(). The pseudocode works like this:

  • Ask Ephemeral Driver if credentials are already cached in memory. If so, load and return them.
  • Ask S3 Driver to load credentials from AWS S3
  • S3 credentials are downloaded, encrypted, and saved into a spool using the Spooled Driver
  • Credentials are read from the spool, decrypted, and cached into Ephemeral Driver
  • Credentials are returned

Other stuff

I removed a lot of bloat from OutputDispatcher. These old methods were S3-specific when the OutputDispatcher assumed all of its credentials came from S3. Now that the OutputDispatchers are de-coupled from S3, these methods are no longer relevant.

Testing

unit_test

--------------------------------------------------------------------------------------------
TOTAL                                                           4570    163    96%
[success] 6.55% tests.unit.streamalert.classifier.clients.test_firehose.TestFirehoseClient.test_send_batch: 0.8364s
[success] 2.40% tests.unit.stream_alert_alert_merger.test_main.TestAlertMerger.test_dispatch: 0.3069s
[success] 2.03% tests.unit.stream_alert_shared.test_config.TestConfigLoading.test_load_all: 0.2598s
[success] 1.57% tests.unit.stream_alert_alert_processor.test_outputs.test_output_base.TestOutputDispatcher.test_load_creds: 0.2000s
[success] 1.54% tests.unit.stream_alert_shared.test_rule_table.TestRuleTable.test_toggle_staged_state_false: 0.1970s
[success] 1.12% tests.unit.stream_alert_cli.test_cli_config.TestCLIConfig.test_aggregate_alarm_creation: 0.1433s
[success] 1.06% tests.unit.stream_alert_cli.test_cli_config.TestCLIConfig.test_add_threat_intel_downloader: 0.1360s
[success] 1.06% tests.unit.stream_alert_cli.test_cli_config.TestCLIConfig.test_toggle_metric: 0.1351s
[success] 1.05% tests.unit.stream_alert_cli.test_cli_config.TestCLIConfig.test_add_threat_intel_without_table_name: 0.1339s
[success] 1.04% tests.unit.stream_alert_cli.test_cli_config.TestCLIConfig.test_cluster_alarm_creation: 0.1332s
----------------------------------------------------------------------
Ran 874 tests in 12.904s

OK

rule_test

Summary:

Total Tests: 77
Pass: 77
Fail: 0

"""Driver for fetching credentials that are saved locally on the filesystem."""

def __init__(self, region, service_name):
self._region = region
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think region will ever be used for the 'local' one right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately I need it to pass into the Credentials constructor, because that class requires the region to use KMS to decrypt the credentials. I honestly could've passed in a CredentialsFactory kind of thing that would avoid having to hot-potato around all of these region, service_name and so on things.

Copy link
Contributor

Choose a reason for hiding this comment

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

but only the S3 driver would need to do the decryption right? or am I misunderstanding this?

if that's the case - the S3 driver constructor can take different params than the base (super) class and just call the super's init with the right params also. I could be misunderstanding the inheritance model, though

Copy link
Contributor Author

@Ryxias Ryxias Jan 18, 2019

Choose a reason for hiding this comment

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

Not exactly; the S3 driver (none of the "drivers") actually does not do any decryption on their own. I delegated decryption to this Credentials class.

I didn't actually use any traditional inheritance. The three "base" classes are actually supposed to behave like interfaces:

The implementations of CredentialsProvidingDriver are supposed to return Credentials, which can be is_encrypted() or not. The idea is (was?) to allow credentials to be passed between drivers while still encrypted. The only thing the driver needs to know is whether or not Credentials are encrypted "at rest" or not.

This is why all of the Drivers need a region; because they need to pass it to the Credentials class to be able to decrypt the ciphertext after being read from disk/memory/whatever. The exception is the EphemeralDriver, which is the only unencrypted-at-rest Driver.

@coveralls
Copy link

coveralls commented Jan 17, 2019

Coverage Status

Coverage increased (+0.2%) to 97.267% when pulling 419010d on dw--refactor-drivers into 5ffadd5 on dw--output-credentials-provider.


def __init__(self, config, defaults, region, service_name, prefix=None, aws_account_id=None):
self._config = config
self._region = region
Copy link
Contributor

Choose a reason for hiding this comment

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

subclasses of OutputCredentialsProvider that use 'boto3' clients and need a region could use the default_config function to return a botocore config (see: https://github.com/airbnb/streamalert/blob/master/stream_alert/shared/helpers/boto.py#L28)

TBH, I'd advocate for using the default_config method anywhere we are creating boto3 clients, since it enforces a shorter timeout (which is better for efficiency/cost in lambda).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍
So we would do something like

client = boto3.client('zzz', config=default_config(region=self._region))

Instead of

client = boto3.client('zzz', region_name=self._region)

?

Copy link
Contributor

Choose a reason for hiding this comment

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

no you could omit region entirely from the call to default_config (unless it could/would ever be something other than the current region.. which I don't think it would be in this case)

Copy link
Contributor

Choose a reason for hiding this comment

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

client = boto3.client('zzz', config=default_config())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I remembered something when I was confirming whether or not region can be globally configured; what about on the CLI handler? It looks like the region (among other configurations) is passed in from ... somewhere. How does the CLI handler know which REGION the code runs in? Is it inferred from the aws2fa.sh session?

@Ryxias Ryxias merged commit c4c7ce6 into dw--output-credentials-provider Jan 18, 2019
@Ryxias Ryxias deleted the dw--refactor-drivers branch January 18, 2019 21:57
Ryxias pushed a commit that referenced this pull request Jan 19, 2019
* Working? Draft of new driver-based credentials storage

* Higher quality refactor; needs tests

* Kinks ironed out with good tests this time

* First maybe working try

* Adds lots of tests for the Drivers

* Add more tests. Not final; still need to remove deprecated methods

* Rename method to reduce confusion

* Remove deprecated method load_encrypted_credentials_from_s3

* Removes deprecated method get_local_credentials_temp_dir

* Remove deprecated method get_formatted_output_credentials_name

* Removes deprecated method kms_decrypt

* Removes extraneous imports

* Extract globally injected REGION so the handler can be implemented properly

* Pylint`

* Pylint is my nemesis

* Remove extraneous method

* Use default_config for boto clients

* Code coverage. PR feedback.

* Add missing __init__.py file causing poor code coverage

* Fixes the tests to get past pylint garbage
Ryxias pushed a commit that referenced this pull request Jan 28, 2019
* Working? Draft of new driver-based credentials storage

* Higher quality refactor; needs tests

* Kinks ironed out with good tests this time

* First maybe working try

* Adds lots of tests for the Drivers

* Add more tests. Not final; still need to remove deprecated methods

* Rename method to reduce confusion

* Remove deprecated method load_encrypted_credentials_from_s3

* Removes deprecated method get_local_credentials_temp_dir

* Remove deprecated method get_formatted_output_credentials_name

* Removes deprecated method kms_decrypt

* Removes extraneous imports

* Extract globally injected REGION so the handler can be implemented properly

* Pylint`

* Pylint is my nemesis

* Remove extraneous method

* Use default_config for boto clients

* Code coverage. PR feedback.

* Add missing __init__.py file causing poor code coverage

* Fixes the tests to get past pylint garbage
Ryxias pushed a commit that referenced this pull request Jan 28, 2019
* Working? Draft of new driver-based credentials storage

* Higher quality refactor; needs tests

* Kinks ironed out with good tests this time

* First maybe working try

* Adds lots of tests for the Drivers

* Add more tests. Not final; still need to remove deprecated methods

* Rename method to reduce confusion

* Remove deprecated method load_encrypted_credentials_from_s3

* Removes deprecated method get_local_credentials_temp_dir

* Remove deprecated method get_formatted_output_credentials_name

* Removes deprecated method kms_decrypt

* Removes extraneous imports

* Extract globally injected REGION so the handler can be implemented properly

* Pylint`

* Pylint is my nemesis

* Remove extraneous method

* Use default_config for boto clients

* Code coverage. PR feedback.

* Add missing __init__.py file causing poor code coverage

* Fixes the tests to get past pylint garbage
Ryxias pushed a commit that referenced this pull request Jan 30, 2019
* Initial commit

* Now with tests

* Touchups

* Remove extraneous static method

* DRYs out some code related to S3 secret bucket name

* Proof-of-concept of mocking CredentialProvider

* Fix some pylints

* Removes output_cred_name() to consolidate functionality. Updates all tests

* pylint

* Refactors Drivers for OutputCredentialsProvider (#878)

* Working? Draft of new driver-based credentials storage

* Higher quality refactor; needs tests

* Kinks ironed out with good tests this time

* First maybe working try

* Adds lots of tests for the Drivers

* Add more tests. Not final; still need to remove deprecated methods

* Rename method to reduce confusion

* Remove deprecated method load_encrypted_credentials_from_s3

* Removes deprecated method get_local_credentials_temp_dir

* Remove deprecated method get_formatted_output_credentials_name

* Removes deprecated method kms_decrypt

* Removes extraneous imports

* Extract globally injected REGION so the handler can be implemented properly

* Pylint`

* Pylint is my nemesis

* Remove extraneous method

* Use default_config for boto clients

* Code coverage. PR feedback.

* Add missing __init__.py file causing poor code coverage

* Fixes the tests to get past pylint garbage

* Change account_id to a property

* Formatting

* (fixup) PR Feedback

* (fixup) Disentangles streamalert cli helper with stream_alert credentials provider.

* (fixup) Deprecate old methods

* pylint

* Removes deprecated code; adds tests for aws_api_client; DRYs out test code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants