def get_dynamodb_client(): region = config("dynamodb_region", namespace="cis", default="us-west-2") environment = config("environment", namespace="cis", default="local") if environment == "local": dynalite_host = config("dynalite_host", namespace="cis", default="localhost") dynalite_port = config("dynalite_port", namespace="cis", default="4567") session = Stubber(boto3.session.Session(region_name=region)).client client = session.client("dynamodb", endpoint_url="http://{}:{}".format(dynalite_host, dynalite_port)) else: session = boto3.session.Session(region_name=region) client = session.client("dynamodb") return client
class AWS(object): """ Contains all the code necessary for role assumption in the target AWS account which holds CIS data, enumerating dynamodb-tables, and enumerating kinesis streams. """ def __init__(self): self.config = get_config() self.assume_role_session = None self._boto_session = None def session(self, region_name=None): """Return a boto_session in the current account in the current region.""" if region_name is None: # Default to us-west-2 if no region provided. logger.debug( "No region provided. Defaulting boto session to us-west-2.") region_name = "us-west-2" if self._discover_cis_environment() == "local": # If we are running the lib locally return a botocore stub. # Must call the .client object in order to return full boto session from Stubber. logger.debug( "Local environment detected. Returning boto stub session.") self._boto_session = Stubber( boto3.session.Session(region_name=region_name)).client if self._boto_session: logger.debug( "A boto session already exists on the object. Returning already constructed session." ) else: logger.debug("Initializing new boto session for region: {}".format( region_name)) self._boto_session = boto3.session.Session(region_name=region_name) return self._boto_session def assume_role(self): """Use the boto session in the current account to assume a role passed in. """ if self._discover_cis_environment() == "local": self.assume_role_session = { "Credentials": { "AccessKeyId": "FAKEAKIA", "SecretAccessKey": "FAKEACCESSKEY", "SessionToken": "FAKESESSIONTOKEN", "Expiration": (datetime.utcnow() + timedelta(hours=1)).replace(tzinfo=None), }, "AssumedRoleUser": { "AssumedRoleId": "FAKEID", "Arn": "arn:aws:iam::123456789000:role/demo-assume-role", }, "PackedPolicySize": 123, } return self.assume_role_session if self.assume_role_session is not None and self._assume_role_is_expired( ) is False: return self.assume_role_session role_arn = self.config("assume_role_arn", namespace="cis", default="None").lower() logger.debug("Role arn provided is: {}".format(role_arn)) if role_arn == "none" or role_arn == "None": logger.debug( "Assume role arn not present. Skipping assume role operation." ) res = None else: sts = self._boto_session.client("sts") res = sts.assume_role(DurationSeconds=3600, RoleArn=role_arn, RoleSessionName="cis-aws-library") self.assume_role_session = res return res def identity_vault_client(self): """Discover DynamoDb table for the environment. Return a dictionary with a client and database arn""" self.assume_role() self._check_sessions_exist() if self._discover_cis_environment() == "local": # Assume we are using dynalite and setup for that dynalite_port = self.config("dynalite_port", namespace="cis", default="4567") dynalite_host = self.config("dynalite_host", namespace="cis", default="localhost") # Initialize a dynamodb client pointed at the dynalite endpoint dynamodb_client = self._boto_session.client( "dynamodb", endpoint_url="http://{}:{}".format(dynalite_host, dynalite_port)) dynamodb_resource = self._boto_session.resource( "dynamodb", endpoint_url="http://{}:{}".format(dynalite_host, dynalite_port)) # Construct a dictionary of standard information. identity_vault_info = { "client": dynamodb_client, "arn": self._discover_dynamo_table(dynamodb_client), "table": dynamodb_resource.Table( self._discover_dynamo_table(dynamodb_client).split("/") [1]), } else: if self.assume_role_session is not None: # Assume we are using an assumeRole because not local. dynamodb_client = self._boto_session.client( "dynamodb", aws_access_key_id=self.assume_role_session["Credentials"] ["AccessKeyId"], aws_secret_access_key=self. assume_role_session["Credentials"]["SecretAccessKey"], aws_session_token=self.assume_role_session["Credentials"] ["SessionToken"], ) dynamodb_resource = self._boto_session.resource( "dynamodb", aws_access_key_id=self.assume_role_session["Credentials"] ["AccessKeyId"], aws_secret_access_key=self. assume_role_session["Credentials"]["SecretAccessKey"], aws_session_token=self.assume_role_session["Credentials"] ["SessionToken"], ) identity_vault_info = { "client": dynamodb_client, "arn": self._discover_dynamo_table(dynamodb_client), "table": dynamodb_resource.Table( self._discover_dynamo_table(dynamodb_client).split("/") [1]), } else: # Assume we are using in a place that uses normal credentials. dynamodb_client = self._boto_session.client("dynamodb") dynamodb_resource = self._boto_session.resource("dynamodb") identity_vault_info = { "client": dynamodb_client, "arn": self._discover_dynamo_table(dynamodb_client), "table": dynamodb_resource.Table( self._discover_dynamo_table(dynamodb_client).split("/") [1]), } return identity_vault_info def input_stream_client(self): """Discover the input stream ARN for the cis_environment. Return a dictionary containing a kinesis client and the stream arn.""" self.assume_role() self._check_sessions_exist() if self._discover_cis_environment() == "local": # Assume we are using dynalite and setup for that kinesalite_port = self.config("kinesalite_port", namespace="cis", default="4567") kinesalite_host = self.config("kinesalite_host", namespace="cis", default="localhost") # Initialize a kinesis client pointed at the kinesalite endpoint kinesis_client = self._boto_session.client( "kinesis", endpoint_url="http://{}:{}".format(kinesalite_host, kinesalite_port)) # Construct a dictionary of standard information. stream_info = { "client": kinesis_client, "arn": self._discover_kinesis_stream(kinesis_client) } else: if self.assume_role_session is not None: # Assume we are using an assumeRole because not local. kinesis_client = self._boto_session.client( "kinesis", aws_access_key_id=self.assume_role_session["Credentials"] ["AccessKeyId"], aws_secret_access_key=self. assume_role_session["Credentials"]["SecretAccessKey"], aws_session_token=self.assume_role_session["Credentials"] ["SessionToken"], ) else: # Assume we are running somwhere that can assume role natively. kinesis_client = self._boto_session.client("kinesis") stream_info = { "client": kinesis_client, "arn": self._discover_kinesis_stream(kinesis_client) } return stream_info def _check_sessions_exist(self): if self._discover_cis_environment() == "local": logger.info( "CIS Local environment detected skipping cloud based validations." ) return if self._boto_session is not None and self.assume_role_session is not None: logger.info( "Boto3 session object and assumeRole exists proceeding to next check." ) return if self._boto_session is not None: logger.info( "Running without assumeRole. Likely an ec2 instance or lambda." ) return else: logger.error("You must initialize an assumeRole and boto session.") raise ValueError( "AssumeRole or Boto3 Session not initialized. Refusing operation." ) def _assume_role_is_expired(self): if self.assume_role_session is None: return True if self._discover_cis_environment() == "local": return False expiry = self.assume_role_session["Credentials"]["Expiration"] now = datetime.utcnow() if expiry.replace(tzinfo=None) > now.replace(tzinfo=None): return False else: return True def _discover_cis_environment(self): """Use everett config manager to determine the environment we are in.""" result = self.config("environment", namespace="cis", default="local").lower() return result def _discover_kinesis_stream(self, kinesis_client): """Enumerate all kinesis streams in the region for the current assumeRole. Return the stream arn matching the appropriate tagging configuration.""" kinesis_arn = self.config("kinesis_arn", namespace="cis", default="None") if kinesis_arn != "None": return kinesis_arn if self._discover_cis_environment() == "local": # Assume developer environment and return for an explicit stream name. return kinesis_client.describe_stream( StreamName="local-stream")["StreamDescription"]["StreamARN"] else: # Assume we are in AWS and list streams, describe streams, and check tags. streams = kinesis_client.list_streams(Limit=100) for stream in streams.get("StreamNames"): tags = kinesis_client.list_tags_for_stream( StreamName=stream).get("Tags") for tag in tags: if tag.get("Key") == "cis_environment" and tag.get( "Value") == self._discover_cis_environment(): return kinesis_client.describe_stream( StreamName=stream )["StreamDescription"]["StreamARN"] else: continue return None def _discover_dynamo_table(self, dynamodb_client): """Enumerate all tables in a region for the current assumerole session. Return the arn of table matching the appropriate tagging configuration.""" dynamodb_arn = self.config("dynamodb_arn", namespace="cis", default="None") if dynamodb_arn != "None": return dynamodb_arn if self._discover_cis_environment() == "local": # Assume that the local identity vault is always called local-identity-vault return dynamodb_client.describe_table( TableName="local-identity-vault")["Table"]["TableArn"] else: # Assume that we are in AWS and list tables, describe tables, and check tags. tables = dynamodb_client.list_tables(Limit=100) for table in tables.get("TableNames"): table_arn = dynamodb_client.describe_table( TableName=table)["Table"]["TableArn"] tags = dynamodb_client.list_tags_of_resource( ResourceArn=table_arn).get("Tags", []) for tag in tags: if tag.get("Key") == "cis_environment" and tag.get( "Value") == self._discover_cis_environment(): return table_arn else: continue return None