From ebeacb9f1f048649fe33c5902f4c0d48bf805cd3 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Wed, 12 Jun 2024 15:41:40 -0400 Subject: [PATCH 1/8] feat: add credential store to securely pass api creds In ref to #185 this PR adds the ability to use AWS SSM parameter store or AWS Secrets Manager to pull your API credentials in order to authenticate with Falcon API. It's setup in a way to allow future credential stores such as azure kv or gcp kv stores to be added. --- config/config.ini | 20 +++++++++++++++ config/defaults.ini | 8 ++++++ fig/config/__init__.py | 47 +++++++++++++++++++++++++++++++++++ fig/config/credstore.py | 54 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 fig/config/credstore.py diff --git a/config/config.ini b/config/config.ini index 56bf0ab..e9c58dd 100644 --- a/config/config.ini +++ b/config/config.ini @@ -44,6 +44,26 @@ # Alternatively, use FALCON_APPLICATION_ID env variable. #application_id = my-acme-gcp-1 +[credentials_store] +# Uncomment to provide credentials store. Alternatively, use CREDENTIALS_STORE env variable. +# Supported values: ssm, secrets_manager +#store = ssm + +# Uncomment to provide SSM parameter name or path for client id. Alternatively, use SSM_CLIENT_ID env variable. +#ssm_client_id = /falcon/fig/client_id + +# Uncomment to provide SSM parameter name or path for client secret. Alternatively, use SSM_CLIENT_SECRET env variable. +#ssm_client_secret = /falcon/fig/client_secret + +# Uncomment to provide Secrets Manager secret name. Alternatively, use SECRETS_MANAGER_SECRET_NAME env variable. +#secrets_manager_secret_name = falcon/fig/credentials + +# Uncomment to provide Secrets Manager client id key. Alternatively, use SECRETS_MANAGER_CLIENT_ID_KEY env variable. +#secrets_manager_client_id_key = client_id + +# Uncomment to provide Secrets Manager client secret key. Alternatively, use SECRETS_MANAGER_CLIENT_SECRET_KEY env variable. +#secrets_manager_client_secret_key = client_secret + [generic] # Generic section is applicable only when GENERIC backend is enabled in the [main] section. # Generic backend can be used for outputting events to STDOUT diff --git a/config/defaults.ini b/config/defaults.ini index 26f8c42..2329f4f 100644 --- a/config/defaults.ini +++ b/config/defaults.ini @@ -21,6 +21,14 @@ application_id = fig-default-app-id reconnect_retry_count = 36 rtr_quarantine_keyword = infected +[credentials_store] +store = +ssm_client_id = +ssm_client_secret = +secrets_manager_secret_name = +secrets_manager_client_id_key = +secrets_manager_client_secret_key = + [generic] # Uses client_id and client_secret from [falcon] section diff --git a/fig/config/__init__.py b/fig/config/__init__.py index 9ea4d21..c1b726b 100644 --- a/fig/config/__init__.py +++ b/fig/config/__init__.py @@ -1,6 +1,7 @@ import os import configparser from functools import cached_property +from .credstore import CredStore class FigConfig(configparser.ConfigParser): @@ -19,6 +20,12 @@ class FigConfig(configparser.ConfigParser): ['falcon', 'client_secret', 'FALCON_CLIENT_SECRET'], ['falcon', 'reconnect_retry_count', 'FALCON_RECONNECT_RETRY_COUNT'], ['falcon', 'application_id', 'FALCON_APPLICATION_ID'], + ['credentials_store', 'store', 'CREDENTIALS_STORE'], + ['credentials_store', 'ssm_client_id', 'SSM_CLIENT_ID'], + ['credentials_store', 'ssm_client_secret', 'SSM_CLIENT_SECRET'], + ['credentials_store', 'secrets_manager_secret_name', 'SECRETS_MANAGER_SECRET_NAME'], + ['credentials_store', 'secrets_manager_client_id_key', 'SECRETS_MANAGER_CLIENT_ID_KEY'], + ['credentials_store', 'secrets_manager_client_secret_key', 'SECRETS_MANAGER_CLIENT_SECRET_KEY'], ['azure', 'workspace_id', 'WORKSPACE_ID'], ['azure', 'primary_key', 'PRIMARY_KEY'], ['azure', 'arc_autodiscovery', 'ARC_AUTODISCOVERY'], @@ -40,6 +47,7 @@ def __init__(self): super().__init__() self.read(['config/defaults.ini', 'config/config.ini', 'config/devel.ini']) self._override_from_env() + self._override_from_credentials_store() def _override_from_env(self): for section, var, envvar in self.__class__.ENV_DEFAULTS: @@ -47,6 +55,45 @@ def _override_from_env(self): if value: self.set(section, var, value) + def _override_from_credentials_store(self): + credentials_store = self.get('credentials_store', 'store') + if credentials_store: + self._validate_credentials_store() + region = self._get_region() + if not region: + raise Exception("No region was found for the credential store") + credstore = CredStore(self, region) + try: + client_id, client_secret = credstore.load_credentials(credentials_store) + except ValueError as e: + raise Exception("Error loading credentials from store: {}:{}".format(credentials_store, e)) from e + + self.set('falcon', 'client_id', client_id) + self.set('falcon', 'client_secret', client_secret) + + def _validate_credentials_store(self): + if self.get('credentials_store', 'store') not in ['ssm', 'secrets_manager']: + raise Exception('Malformed Configuration: expected credentials_store.store to be either ssm or secrets_manager') + if self.get('credentials_store', 'store') == 'ssm': + if not self.get('credentials_store', 'ssm_client_id') or not self.get('credentials_store', 'ssm_client_secret'): + raise Exception('Malformed Configuration: expected ssm_client_id and ssm_client_secret to be provided') + if self.get('credentials_store', 'store') == 'secrets_manager': + if not self.get('credentials_store', 'secrets_manager_secret_name') or not self.get('credentials_store', 'secrets_manager_client_id_key') or not self.get('credentials_store', 'secrets_manager_client_secret_key'): + raise Exception('Malformed Configuration: expected secrets_manager_secret_name, secrets_manager_client_id_key and secrets_manager_client_secret_key to be provided') + + def _get_region(self): + """Get the region to use for credential stores.""" + region = None + if 'AWS' in self.backends: + region = self.get('aws', 'region') + elif 'AWS_SQS' in self.backends: + region = self.get('aws_sqs', 'region') + elif 'CLOUDTRAIL_LAKE' in self.backends: + region = self.get('cloudtrail_lake', 'region') + elif 'CHRONICLE' in self.backends: + region = self.get('chronicle', 'region') + return region + def validate(self): for section, var, envvar in self.__class__.ENV_DEFAULTS: try: diff --git a/fig/config/credstore.py b/fig/config/credstore.py new file mode 100644 index 0000000..58fdb19 --- /dev/null +++ b/fig/config/credstore.py @@ -0,0 +1,54 @@ +import json +import boto3 +from botocore.exceptions import ClientError + + +class CredStore: + def __init__(self, config, region): + self.config = config + self.region = region + + def get_ssm_parameter(self, client, parameter_name): + """Retrieve a parameter from AWS SSM Parameter Store.""" + try: + response = client.get_parameter(Name=parameter_name, WithDecryption=True) + return response['Parameter']['Value'] + except ClientError as e: + raise Exception(f"Error retrieving SSM parameter ({parameter_name}): {e}") from e + + def get_secret(self, client, secret_name): + """Retrieve a secret from AWS Secrets Manager.""" + try: + response = client.get_secret_value(SecretId=secret_name) + return json.loads(response['SecretString']) + except ClientError as e: + raise Exception(f"Error retrieving Secrets Manager secret ({secret_name}): {e}") from e + + def load_credentials(self, store): + """Load credentials based on the specified store.""" + if store == 'ssm': + ssm_client = boto3.client('ssm', region_name=self.region) + falcon_client_id = self.get_ssm_parameter( + ssm_client, + self.config.get('credentials_store', 'ssm_client_id') + ) + falcon_client_secret = self.get_ssm_parameter( + ssm_client, + self.config.get('credentials_store', 'ssm_client_secret') + ) + elif store == 'secrets_manager': + secrets_client = boto3.client('secretsmanager', region_name=self.region) + secret = self.get_secret( + secrets_client, + self.config.get('credentials_store', 'secrets_manager_secret_name') + ) + falcon_client_id = secret.get( + self.config.get('credentials_store', 'secrets_manager_client_id_key') + ) + falcon_client_secret = secret.get( + self.config.get('credentials_store', 'secrets_manager_client_secret_key') + ) + else: + raise ValueError("Invalid credentials store specified.") + + return falcon_client_id, falcon_client_secret From 43a9ff2c12b6c03df1314d7d923644bf61870e39 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Thu, 13 Jun 2024 16:23:33 -0400 Subject: [PATCH 2/8] chore: enhance logic around using cred store This commit changes the logic around how to use a credential store via the configuration file. It's broken out into it's own section now, this gives the user greater flexibility to be able to use a credential store with different backends. Also added some additional error handling in the credstore for secrets_manager. --- config/config.ini | 14 ++++++++++++-- config/defaults.ini | 4 ++++ fig/config/__init__.py | 33 +++++++++++++------------------- fig/config/credstore.py | 42 +++++++++++++++++++++++++++++++---------- 4 files changed, 61 insertions(+), 32 deletions(-) diff --git a/config/config.ini b/config/config.ini index e9c58dd..a297f99 100644 --- a/config/config.ini +++ b/config/config.ini @@ -34,10 +34,12 @@ # Uncomment to provide Falcon Cloud. Alternatively, use FALCON_CLOUD_REGION env variable. #cloud_region = us-1 -# Uncomment to provide OAuth Secret. Alternatively, use FALCON_CLIENT_ID env variable. +# Uncomment to provide OAuth Client ID. +# Alternatively, use FALCON_CLIENT_ID env variable or a credentials store (see [credentials_store] section). #client_id = ABCD -# Uncomment to provide OAuth Secret. Alternatively, use FALCON_CLIENT_SECRET env variable. +# Uncomment to provide OAuth Secret. +# Alternatively, use FALCON_CLIENT_SECRET env variable or a credentials store (see [credentials_store] section). #client_secret = ABCD # Uncomment to provide application id. Needs to be different per each fig instance. @@ -49,12 +51,20 @@ # Supported values: ssm, secrets_manager #store = ssm +[ssm] +# Uncomment to provide aws region for SSM. Alternatively, use SSM_REGION env variable. +#region = us-west-2 + # Uncomment to provide SSM parameter name or path for client id. Alternatively, use SSM_CLIENT_ID env variable. #ssm_client_id = /falcon/fig/client_id # Uncomment to provide SSM parameter name or path for client secret. Alternatively, use SSM_CLIENT_SECRET env variable. #ssm_client_secret = /falcon/fig/client_secret +[secrets_manager] +# Uncomment to provide aws region for Secrets Manager. Alternatively, use SECRETS_MANAGER_REGION env variable. +#region = us-west-2 + # Uncomment to provide Secrets Manager secret name. Alternatively, use SECRETS_MANAGER_SECRET_NAME env variable. #secrets_manager_secret_name = falcon/fig/credentials diff --git a/config/defaults.ini b/config/defaults.ini index 2329f4f..32fb7da 100644 --- a/config/defaults.ini +++ b/config/defaults.ini @@ -23,8 +23,12 @@ rtr_quarantine_keyword = infected [credentials_store] store = + +[ssm] ssm_client_id = ssm_client_secret = + +[secrets_manager] secrets_manager_secret_name = secrets_manager_client_id_key = secrets_manager_client_secret_key = diff --git a/fig/config/__init__.py b/fig/config/__init__.py index c1b726b..7ef8b70 100644 --- a/fig/config/__init__.py +++ b/fig/config/__init__.py @@ -21,11 +21,11 @@ class FigConfig(configparser.ConfigParser): ['falcon', 'reconnect_retry_count', 'FALCON_RECONNECT_RETRY_COUNT'], ['falcon', 'application_id', 'FALCON_APPLICATION_ID'], ['credentials_store', 'store', 'CREDENTIALS_STORE'], - ['credentials_store', 'ssm_client_id', 'SSM_CLIENT_ID'], - ['credentials_store', 'ssm_client_secret', 'SSM_CLIENT_SECRET'], - ['credentials_store', 'secrets_manager_secret_name', 'SECRETS_MANAGER_SECRET_NAME'], - ['credentials_store', 'secrets_manager_client_id_key', 'SECRETS_MANAGER_CLIENT_ID_KEY'], - ['credentials_store', 'secrets_manager_client_secret_key', 'SECRETS_MANAGER_CLIENT_SECRET_KEY'], + ['ssm', 'ssm_client_id', 'SSM_CLIENT_ID'], + ['ssm', 'ssm_client_secret', 'SSM_CLIENT_SECRET'], + ['secrets_manager', 'secrets_manager_secret_name', 'SECRETS_MANAGER_SECRET_NAME'], + ['secrets_manager', 'secrets_manager_client_id_key', 'SECRETS_MANAGER_CLIENT_ID_KEY'], + ['secrets_manager', 'secrets_manager_client_secret_key', 'SECRETS_MANAGER_CLIENT_SECRET_KEY'], ['azure', 'workspace_id', 'WORKSPACE_ID'], ['azure', 'primary_key', 'PRIMARY_KEY'], ['azure', 'arc_autodiscovery', 'ARC_AUTODISCOVERY'], @@ -59,9 +59,7 @@ def _override_from_credentials_store(self): credentials_store = self.get('credentials_store', 'store') if credentials_store: self._validate_credentials_store() - region = self._get_region() - if not region: - raise Exception("No region was found for the credential store") + region = self._get_region(credentials_store) credstore = CredStore(self, region) try: client_id, client_secret = credstore.load_credentials(credentials_store) @@ -75,23 +73,18 @@ def _validate_credentials_store(self): if self.get('credentials_store', 'store') not in ['ssm', 'secrets_manager']: raise Exception('Malformed Configuration: expected credentials_store.store to be either ssm or secrets_manager') if self.get('credentials_store', 'store') == 'ssm': - if not self.get('credentials_store', 'ssm_client_id') or not self.get('credentials_store', 'ssm_client_secret'): + if not self.get('ssm', 'ssm_client_id') or not self.get('ssm', 'ssm_client_secret'): raise Exception('Malformed Configuration: expected ssm_client_id and ssm_client_secret to be provided') if self.get('credentials_store', 'store') == 'secrets_manager': - if not self.get('credentials_store', 'secrets_manager_secret_name') or not self.get('credentials_store', 'secrets_manager_client_id_key') or not self.get('credentials_store', 'secrets_manager_client_secret_key'): + if not self.get('secrets_manager', 'secrets_manager_secret_name') or not self.get('secrets_manager', 'secrets_manager_client_id_key') or not self.get('secrets_manager', 'secrets_manager_client_secret_key'): raise Exception('Malformed Configuration: expected secrets_manager_secret_name, secrets_manager_client_id_key and secrets_manager_client_secret_key to be provided') - def _get_region(self): + def _get_region(self, credentials_store): """Get the region to use for credential stores.""" - region = None - if 'AWS' in self.backends: - region = self.get('aws', 'region') - elif 'AWS_SQS' in self.backends: - region = self.get('aws_sqs', 'region') - elif 'CLOUDTRAIL_LAKE' in self.backends: - region = self.get('cloudtrail_lake', 'region') - elif 'CHRONICLE' in self.backends: - region = self.get('chronicle', 'region') + try: + region = self.get(credentials_store, 'region') + except configparser.NoOptionError as e: + raise Exception("No region was found for the credential store: {}".format(e)) from e return region def validate(self): diff --git a/fig/config/credstore.py b/fig/config/credstore.py index 58fdb19..9f76c2d 100644 --- a/fig/config/credstore.py +++ b/fig/config/credstore.py @@ -20,9 +20,32 @@ def get_secret(self, client, secret_name): """Retrieve a secret from AWS Secrets Manager.""" try: response = client.get_secret_value(SecretId=secret_name) - return json.loads(response['SecretString']) + if 'SecretString' in response: + return json.loads(response['SecretString']) + else: + raise Exception(f"SecretString not found in the response for secret ({secret_name})") except ClientError as e: - raise Exception(f"Error retrieving Secrets Manager secret ({secret_name}): {e}") from e + error_code = e.response['Error']['Code'] + if error_code == 'ResourceNotFoundException': + raise Exception(f"The requested secret ({secret_name}) was not found") from e + elif error_code == 'InvalidRequestException': + raise Exception(f"The request was invalid due to: {e}") from e + elif error_code == 'InvalidParameterException': + raise Exception(f"The request had invalid params: {e}") from e + else: + raise Exception(f"Error retrieving Secrets Manager secret ({secret_name}): {e}") from e + + def validate_secret_keys(self, secret, client_id_key, client_secret_key): + """Validate the presence of required keys in the secret.""" + falcon_client_id = secret.get(client_id_key) + falcon_client_secret = secret.get(client_secret_key) + + if falcon_client_id is None: + raise Exception(f"The client ID key ({client_id_key}) does not exist or is None in the retrieved secret.") + if falcon_client_secret is None: + raise Exception(f"The client secret key ({client_secret_key}) does not exist or is None in the retrieved secret.") + + return falcon_client_id, falcon_client_secret def load_credentials(self, store): """Load credentials based on the specified store.""" @@ -30,23 +53,22 @@ def load_credentials(self, store): ssm_client = boto3.client('ssm', region_name=self.region) falcon_client_id = self.get_ssm_parameter( ssm_client, - self.config.get('credentials_store', 'ssm_client_id') + self.config.get('ssm', 'ssm_client_id') ) falcon_client_secret = self.get_ssm_parameter( ssm_client, - self.config.get('credentials_store', 'ssm_client_secret') + self.config.get('ssm', 'ssm_client_secret') ) elif store == 'secrets_manager': secrets_client = boto3.client('secretsmanager', region_name=self.region) secret = self.get_secret( secrets_client, - self.config.get('credentials_store', 'secrets_manager_secret_name') - ) - falcon_client_id = secret.get( - self.config.get('credentials_store', 'secrets_manager_client_id_key') + self.config.get('secrets_manager', 'secrets_manager_secret_name') ) - falcon_client_secret = secret.get( - self.config.get('credentials_store', 'secrets_manager_client_secret_key') + falcon_client_id, falcon_client_secret = self.validate_secret_keys( + secret, + self.config.get('secrets_manager', 'secrets_manager_client_id_key'), + self.config.get('secrets_manager', 'secrets_manager_client_secret_key') ) else: raise ValueError("Invalid credentials store specified.") From d2e6d0bf691784beddc9a119f7db8b6d0329cf99 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Thu, 13 Jun 2024 16:27:47 -0400 Subject: [PATCH 3/8] lint: fix pylint errors --- fig/config/credstore.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/fig/config/credstore.py b/fig/config/credstore.py index 9f76c2d..4c83b9a 100644 --- a/fig/config/credstore.py +++ b/fig/config/credstore.py @@ -22,18 +22,16 @@ def get_secret(self, client, secret_name): response = client.get_secret_value(SecretId=secret_name) if 'SecretString' in response: return json.loads(response['SecretString']) - else: - raise Exception(f"SecretString not found in the response for secret ({secret_name})") + raise Exception(f"SecretString not found in the response for secret ({secret_name})") except ClientError as e: error_code = e.response['Error']['Code'] if error_code == 'ResourceNotFoundException': raise Exception(f"The requested secret ({secret_name}) was not found") from e - elif error_code == 'InvalidRequestException': + if error_code == 'InvalidRequestException': raise Exception(f"The request was invalid due to: {e}") from e - elif error_code == 'InvalidParameterException': + if error_code == 'InvalidParameterException': raise Exception(f"The request had invalid params: {e}") from e - else: - raise Exception(f"Error retrieving Secrets Manager secret ({secret_name}): {e}") from e + raise Exception(f"Error retrieving Secrets Manager secret ({secret_name}): {e}") from e def validate_secret_keys(self, secret, client_id_key, client_secret_key): """Validate the presence of required keys in the secret.""" From c77a7e8ac23e0796996b142c3381413b855cb32d Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Thu, 13 Jun 2024 17:19:18 -0400 Subject: [PATCH 4/8] docs: updated main readme with authentication examples TBD: consider updating backend deployment guides --- README.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 75f4afb..e068a7d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ This project facilitates the export of the individual detections and audit event API clients are granted one or more API scopes. Scopes allow access to specific CrowdStrike APIs and describe the actions that an API client can perform. +> [!NOTE] +> For more information on how to generate an API client, refer to the [CrowdStrike API documentation](https://falcon.crowdstrike.com/login/?unilogin=true&next=/documentation/page/a2a7fc0e/crowdstrike-oauth2-based-apis#mf8226da). + FIG requires the following API scopes at a minimum: - **Event streams**: [Read] @@ -19,6 +22,58 @@ FIG requires the following API scopes at a minimum: > Consult the backend guides for additional API scopes that may be required. +## Authentication + +FIG requires an API client ID and client secret to authenticate with the CrowdStrike API. There are three ways to provide these credentials: + +### Direct Configuration + +> [!IMPORTANT] +> This method is not recommended for production deployments. + +You can use the `config.ini` file to store your API client ID and client secret. The `config.ini` file should be located in the `config` directory. To configure authentication, add the following to the `config.ini` file: + +```ini +[falcon] +client_id = YOUR_CLIENT_ID +client_secret = YOUR_CLIENT_SECRET +``` + +### Environment Variables + +You can also provide your API client ID and client secret as environment variables. To do so, set the following environment variables: + +```bash +export FALCON_CLIENT_ID=YOUR_CLIENT_ID +export FALCON_CLIENT_SECRET=YOUR_CLIENT_SECRET +``` + +### Credential Store + +You can use a credential store to securely store your API client ID and client secret. FIG supports the following credential stores: + +- AWS Secrets Manager ('`secrets_manager`') +- AWS SSM Parameter Store ('`ssm`') + +To configure FIG to use a credential store, add the following to the `config.ini` file: + +```ini +[credentials_store] +#store = ssm|secrets_manager +``` + +After selecting a credential store, you must provide the necessary configuration for the store. For example, to use AWS Secrets Manager, add the following to the `config.ini` file: + +```ini +[secrets_manager] +region = YOUR_AWS_REGION +secrets_manager_secret_name = your/secret/name +secrets_manager_client_id_key = client_id_key_name +secrets_manager_client_secret_key = client_secret_key_name +``` + +Please refer to the [configuration options](./config/config.ini) for more details on the available options. + ## Backends w/ Available Deployment Guide(s) | Backend | Description | Deployment Guide(s) | General Guide(s) | @@ -84,7 +139,7 @@ To install as a container: pip install -r requirements.txt ``` -1. Modify the `./config/config.ini` file with your backend options +1. Modify the `./config/config.ini` file with your backend options or set the associated environment variables. 1. Run the application From c0e89763aa442a0aa32316d850190f44e921f8f2 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Thu, 13 Jun 2024 17:20:32 -0400 Subject: [PATCH 5/8] fix: added regions to defaults and config class --- config/defaults.ini | 2 ++ fig/config/__init__.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/config/defaults.ini b/config/defaults.ini index 32fb7da..6387afb 100644 --- a/config/defaults.ini +++ b/config/defaults.ini @@ -25,10 +25,12 @@ rtr_quarantine_keyword = infected store = [ssm] +region = ssm_client_id = ssm_client_secret = [secrets_manager] +region = secrets_manager_secret_name = secrets_manager_client_id_key = secrets_manager_client_secret_key = diff --git a/fig/config/__init__.py b/fig/config/__init__.py index 7ef8b70..e273b6b 100644 --- a/fig/config/__init__.py +++ b/fig/config/__init__.py @@ -21,8 +21,10 @@ class FigConfig(configparser.ConfigParser): ['falcon', 'reconnect_retry_count', 'FALCON_RECONNECT_RETRY_COUNT'], ['falcon', 'application_id', 'FALCON_APPLICATION_ID'], ['credentials_store', 'store', 'CREDENTIALS_STORE'], + ['ssm', 'region', 'SSM_REGION'], ['ssm', 'ssm_client_id', 'SSM_CLIENT_ID'], ['ssm', 'ssm_client_secret', 'SSM_CLIENT_SECRET'], + ['secrets_manager', 'region', 'SECRETS_MANAGER_REGION'], ['secrets_manager', 'secrets_manager_secret_name', 'SECRETS_MANAGER_SECRET_NAME'], ['secrets_manager', 'secrets_manager_client_id_key', 'SECRETS_MANAGER_CLIENT_ID_KEY'], ['secrets_manager', 'secrets_manager_client_secret_key', 'SECRETS_MANAGER_CLIENT_SECRET_KEY'], From c1bab182423e86c30ffe3e83919c9f4289f7e6bb Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Fri, 14 Jun 2024 09:51:53 -0400 Subject: [PATCH 6/8] docs: updated verbiage surrounding authentication + auto-discovery --- README.md | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e068a7d..e0f14e1 100644 --- a/README.md +++ b/README.md @@ -24,17 +24,23 @@ FIG requires the following API scopes at a minimum: ## Authentication -FIG requires an API client ID and client secret to authenticate with the CrowdStrike API. There are three ways to provide these credentials: +FIG requires the authentication of an API client ID and client secret, along with its associated cloud region, to establish a connection with the CrowdStrike API. -### Direct Configuration +FIG supports auto-discovery of the Falcon cloud region. If you do not specify a cloud region, FIG will attempt to auto-discover the cloud region based on the API client ID and client secret provided. > [!IMPORTANT] +> Auto-discovery is only available for [us-1, us-2, eu-1] regions. + +### Direct Configuration + +> [!NOTE] > This method is not recommended for production deployments. You can use the `config.ini` file to store your API client ID and client secret. The `config.ini` file should be located in the `config` directory. To configure authentication, add the following to the `config.ini` file: ```ini [falcon] +cloud_region = us-1 client_id = YOUR_CLIENT_ID client_secret = YOUR_CLIENT_SECRET ``` @@ -44,6 +50,7 @@ client_secret = YOUR_CLIENT_SECRET You can also provide your API client ID and client secret as environment variables. To do so, set the following environment variables: ```bash +export FALCON_CLOUD_REGION=us-1 export FALCON_CLIENT_ID=YOUR_CLIENT_ID export FALCON_CLIENT_SECRET=YOUR_CLIENT_SECRET ``` @@ -52,12 +59,18 @@ export FALCON_CLIENT_SECRET=YOUR_CLIENT_SECRET You can use a credential store to securely store your API client ID and client secret. FIG supports the following credential stores: -- AWS Secrets Manager ('`secrets_manager`') -- AWS SSM Parameter Store ('`ssm`') +- AWS Secrets Manager (`secrets_manager`) +- AWS SSM Parameter Store (`ssm`) + +> [!NOTE] +> You can use either direct configuration or environment variables to specify the credential store and its associated configurations. To configure FIG to use a credential store, add the following to the `config.ini` file: ```ini +[falcon] +cloud_region = us-1 + [credentials_store] #store = ssm|secrets_manager ``` @@ -72,7 +85,9 @@ secrets_manager_client_id_key = client_id_key_name secrets_manager_client_secret_key = client_secret_key_name ``` -Please refer to the [configuration options](./config/config.ini) for more details on the available options. +## Configuration + +Please refer to the [config.ini](./config/config.ini) file for more details on the available options along with their respective environment variables. ## Backends w/ Available Deployment Guide(s) @@ -139,7 +154,7 @@ To install as a container: pip install -r requirements.txt ``` -1. Modify the `./config/config.ini` file with your backend options or set the associated environment variables. +1. Modify the `./config/config.ini` file with your configuration options or set the associated environment variables. 1. Run the application From 51dc2a45f5e880af45cf85f58de528dc50d39c8c Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Fri, 14 Jun 2024 13:34:12 -0400 Subject: [PATCH 7/8] docs: adding how to update from git --- README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e0f14e1..b188fae 100644 --- a/README.md +++ b/README.md @@ -110,9 +110,7 @@ Please refer to the [config.ini](./config/config.ini) file for more details on t Please refer to the [FIG helm chart documentation](https://github.com/CrowdStrike/falcon-helm/tree/main/helm-charts/falcon-integration-gateway) for detailed instructions on deploying the FIG via helm chart for your respective backend(s). -### Manual Installation and Removal - -#### With Docker/Podman +### With Docker/Podman To install as a container: @@ -140,18 +138,22 @@ To install as a container: docker logs ``` -#### From Git Repository +### From Git Repository + +> [!NOTE] +> This method requires Python 3.7 or higher and a python package manager such as `pip` to be installed on your system. -1. Clone the repository +1. Clone and navigate to the repository ```bash git clone https://github.com/CrowdStrike/falcon-integration-gateway.git + cd falcon-integration-gateway ``` 1. Install the python dependencies. ```bash - pip install -r requirements.txt + pip3 install -r requirements.txt ``` 1. Modify the `./config/config.ini` file with your configuration options or set the associated environment variables. @@ -162,6 +164,53 @@ To install as a container: python3 -m fig ``` +### Updating the FIG from the Git Repository + +Depending on which configuration method you are using, follow the steps below to update the FIG from the Git repository. + +#### config.ini + +If you have made any changes to the `config.ini` file, you can update the FIG by following these steps: + +1. Make a backup of the `config/config.ini` file. +1. Remove the `falcon-integration-gateway` directory. +1. Clone and navigate to the repository again. +1. Install/update the python dependencies. +1. Update the `config/config.ini` file with your configuration settings. + > [!NOTE] + > Because the `config.ini` file may have new changes (ie, new sections or options), it is recommended to reapply your configuration settings from the backup to the new `config.ini` file. +1. Run the application. + +An example of the process: + +```bash +cp config/config.ini /tmp/config.ini +cd .. && rm -rf falcon-integration-gateway +git clone https://github.com/CrowdStrike/falcon-integration-gateway.git +cd falcon-integration-gateway +pip3 install --upgrade -r requirements.txt +# Review the new config.ini file and reapply your settings from the backup +python3 -m fig +``` + +This method ensures that your configuration settings are preserved while updating the FIG to the latest version. + +#### Environment Variables (only) + +If you are only using environment variables to configure the FIG, you can update the FIG by following these steps: + +1. Pull the latest changes from the repository. +1. Install/update the python dependencies. +1. Run the application. + +An example of the process: + +```bash +git pull +pip3 install --upgrade -r requirements.txt +python3 -m fig +``` + ## [Developers Guide](./docs/developer_guide.md) ## Statement of Support From 32e1b4e1fd228b534434e6066e9fe7508c932530 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Fri, 14 Jun 2024 13:39:48 -0400 Subject: [PATCH 8/8] docs: remove note since it's not allowed in subsection --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index b188fae..902b053 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,6 @@ If you have made any changes to the `config.ini` file, you can update the FIG by 1. Clone and navigate to the repository again. 1. Install/update the python dependencies. 1. Update the `config/config.ini` file with your configuration settings. - > [!NOTE] > Because the `config.ini` file may have new changes (ie, new sections or options), it is recommended to reapply your configuration settings from the backup to the new `config.ini` file. 1. Run the application.