diff --git a/SecretsManagerRDSPostgreSQLRotationMultiUser/lambda_function.py b/SecretsManagerRDSPostgreSQLRotationMultiUser/lambda_function.py index c625dd4d..5f3d45eb 100644 --- a/SecretsManagerRDSPostgreSQLRotationMultiUser/lambda_function.py +++ b/SecretsManagerRDSPostgreSQLRotationMultiUser/lambda_function.py @@ -14,6 +14,10 @@ MAX_RDS_DB_INSTANCE_ARN_LENGTH = 256 +def sanitize_input_str( s :str ) -> str: + """ Per https://codeql.github.com/codeql-query-help/python/py-log-injection/ + """ + return s.replace('\r\n','').replace('\n','').encode() def lambda_handler(event, context): """Secrets Manager RDS PostgreSQL Handler @@ -61,17 +65,17 @@ def lambda_handler(event, context): # Make sure the version is staged correctly metadata = service_client.describe_secret(SecretId=arn) if "RotationEnabled" in metadata and not metadata['RotationEnabled']: - logger.error("Secret %s is not enabled for rotation" % arn) - raise ValueError("Secret %s is not enabled for rotation" % arn) + logger.error("Secret %s is not enabled for rotation" % sanitize_input_str(arn)) + raise ValueError("Secret %s is not enabled for rotation" % sanitize_input_str(arn)) versions = metadata['VersionIdsToStages'] if token not in versions: - logger.error("Secret version %s has no stage for rotation of secret %s." % (token, arn)) + logger.error("Secret version %s has no stage for rotation of secret %s." % (sanitize_input_str(token), sanitize_input_str(arn))) raise ValueError("Secret version %s has no stage for rotation of secret %s." % (token, arn)) if "AWSCURRENT" in versions[token]: - logger.info("Secret version %s already set as AWSCURRENT for secret %s." % (token, arn)) + logger.info("Secret version %s already set as AWSCURRENT for secret %s." % (sanitize_input_str(token), sanitize_input_str(arn))) return elif "AWSPENDING" not in versions[token]: - logger.error("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn)) + logger.error("Secret version %s not set as AWSPENDING for rotation of secret %s." % (sanitize_input_str(token), sanitize_input_str(arn))) raise ValueError("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn)) # Call the appropriate step @@ -88,7 +92,7 @@ def lambda_handler(event, context): finish_secret(service_client, arn, token) else: - logger.error("lambda_handler: Invalid step parameter %s for secret %s" % (step, arn)) + logger.error("lambda_handler: Invalid step parameter %s for secret %s" % (sanitize_input_str(step), sanitize_input_str(arn))) raise ValueError("Invalid step parameter %s for secret %s" % (step, arn)) @@ -117,7 +121,7 @@ def create_secret(service_client, arn, token): # Now try to get the secret version, if that fails, put a new secret try: get_secret_dict(service_client, arn, "AWSPENDING", token) - logger.info("createSecret: Successfully retrieved secret for %s." % arn) + logger.info("createSecret: Successfully retrieved secret for %s." % sanitize_input_str(arn)) except service_client.exceptions.ResourceNotFoundException: # Get the alternate username swapping between the original user and the user with _clone appended to it current_dict['username'] = get_alt_username(current_dict['username']) @@ -125,7 +129,7 @@ def create_secret(service_client, arn, token): # Put the secret service_client.put_secret_value(SecretId=arn, ClientRequestToken=token, SecretString=json.dumps(current_dict), VersionStages=['AWSPENDING']) - logger.info("createSecret: Successfully put secret for ARN %s and version %s." % (arn, token)) + logger.info("createSecret: Successfully put secret for ARN %s and version %s." % (sanitize_input_str(arn), sanitize_input_str(token))) def set_secret(service_client, arn, token): @@ -158,7 +162,7 @@ def set_secret(service_client, arn, token): conn = get_connection(pending_dict) if conn: conn.close() - logger.info("setSecret: AWSPENDING secret is already set as password in PostgreSQL DB for secret arn %s." % arn) + logger.info("setSecret: AWSPENDING secret is already set as password in PostgreSQL DB for secret arn %s." % sanitize_input_str(arn)) return # Make sure the user from current and pending match @@ -177,7 +181,7 @@ def set_secret(service_client, arn, token): # This ensures that the credential we are rotating is valid to protect against a confused deputy attack conn = get_connection(current_dict) if not conn: - logger.error("setSecret: Unable to log into database using current credentials for secret %s" % arn) + logger.error("setSecret: Unable to log into database using current credentials for secret %s" % sanitize_input_str(arn)) raise ValueError("Unable to log into database using current credentials for secret %s" % arn) conn.close() @@ -220,7 +224,7 @@ def set_secret(service_client, arn, token): cur.execute(alter_role + " WITH PASSWORD %s", (pending_dict['password'],)) conn.commit() - logger.info("setSecret: Successfully set password for %s in PostgreSQL DB for secret arn %s." % (pending_dict['username'], arn)) + logger.info("setSecret: Successfully set password for %s in PostgreSQL DB for secret arn %s." % (pending_dict['username'], sanitize_input_str(arn))) finally: conn.close() @@ -258,11 +262,11 @@ def test_secret(service_client, arn, token): finally: conn.close() - logger.info("testSecret: Successfully signed into PostgreSQL DB with AWSPENDING secret in %s." % arn) + logger.info("testSecret: Successfully signed into PostgreSQL DB with AWSPENDING secret in %s." % sanitize_input_str(arn)) return else: - logger.error("testSecret: Unable to log into database with pending secret of secret ARN %s" % arn) - raise ValueError("Unable to log into database with pending secret of secret ARN %s" % arn) + logger.error("testSecret: Unable to log into database with pending secret of secret ARN %s" % sanitize_input_str(arn)) + raise ValueError("Unable to log into database with pending secret of secret ARN %s" % sanitize_input_str(arn)) def finish_secret(service_client, arn, token): @@ -288,14 +292,14 @@ def finish_secret(service_client, arn, token): if "AWSCURRENT" in metadata["VersionIdsToStages"][version]: if version == token: # The correct version is already marked as current, return - logger.info("finishSecret: Version %s already marked as AWSCURRENT for %s" % (version, arn)) + logger.info("finishSecret: Version %s already marked as AWSCURRENT for %s" % (version, sanitize_input_str(arn))) return current_version = version break # Finalize by staging the secret version current service_client.update_secret_version_stage(SecretId=arn, VersionStage="AWSCURRENT", MoveToVersionId=token, RemoveFromVersionId=current_version) - logger.info("finishSecret: Successfully set AWSCURRENT stage to version %s for secret %s." % (token, arn)) + logger.info("finishSecret: Successfully set AWSCURRENT stage to version %s for secret %s." % (sanitize_input_str(token), sanitize_input_str(arn))) def get_connection(secret_dict): @@ -457,7 +461,7 @@ def get_secret_dict(service_client, arn, stage, token=None, master_secret=False) db_instance_info = fetch_instance_arn_from_system_tags(service_client, arn) if len(db_instance_info) != 0: secret_dict = get_connection_params_from_rds_api(secret_dict, db_instance_info) - logger.info("setSecret: Successfully fetched connection params for Master Secret %s from DescribeDBInstances API." % arn) + logger.info("setSecret: Successfully fetched connection params for Master Secret %s from DescribeDBInstances API." % sanitize_input_str(arn)) # For non-RDS-made Master Secrets that are missing `host`, this will error below when checking for required connection params. diff --git a/SecretsManagerRDSPostgreSQLRotationSingleUser/lambda_function.py b/SecretsManagerRDSPostgreSQLRotationSingleUser/lambda_function.py index fa17e81d..47240b77 100644 --- a/SecretsManagerRDSPostgreSQLRotationSingleUser/lambda_function.py +++ b/SecretsManagerRDSPostgreSQLRotationSingleUser/lambda_function.py @@ -12,6 +12,10 @@ logger = logging.getLogger() logger.setLevel(logging.INFO) +def sanitize_input_str( s :str ) -> str: + """ Per https://codeql.github.com/codeql-query-help/python/py-log-injection/ + """ + return s.replace('\r\n','').replace('\n','').encode() def lambda_handler(event, context): """Secrets Manager RDS PostgreSQL Handler @@ -56,17 +60,17 @@ def lambda_handler(event, context): # Make sure the version is staged correctly metadata = service_client.describe_secret(SecretId=arn) if "RotationEnabled" in metadata and not metadata['RotationEnabled']: - logger.error("Secret %s is not enabled for rotation" % arn) - raise ValueError("Secret %s is not enabled for rotation" % arn) + logger.error("Secret %s is not enabled for rotation" % sanitize_input_str(arn)) + raise ValueError("Secret %s is not enabled for rotation" % sanitize_input_str(arn)) versions = metadata['VersionIdsToStages'] if token not in versions: - logger.error("Secret version %s has no stage for rotation of secret %s." % (token, arn)) + logger.error("Secret version %s has no stage for rotation of secret %s." % (sanitize_input_str(token), sanitize_input_str(arn))) raise ValueError("Secret version %s has no stage for rotation of secret %s." % (token, arn)) if "AWSCURRENT" in versions[token]: - logger.info("Secret version %s already set as AWSCURRENT for secret %s." % (token, arn)) + logger.info("Secret version %s already set as AWSCURRENT for secret %s." % (sanitize_input_str(token), sanitize_input_str(arn))) return elif "AWSPENDING" not in versions[token]: - logger.error("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn)) + logger.error("Secret version %s not set as AWSPENDING for rotation of secret %s." % (sanitize_input_str(token), sanitize_input_str(arn))) raise ValueError("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn)) # Call the appropriate step @@ -83,7 +87,7 @@ def lambda_handler(event, context): finish_secret(service_client, arn, token) else: - logger.error("lambda_handler: Invalid step parameter %s for secret %s" % (step, arn)) + logger.error("lambda_handler: Invalid step parameter %s for secret %s" % (sanitize_input_str(step), sanitize_input_str(arn))) raise ValueError("Invalid step parameter %s for secret %s" % (step, arn)) @@ -112,13 +116,13 @@ def create_secret(service_client, arn, token): # Now try to get the secret version, if that fails, put a new secret try: get_secret_dict(service_client, arn, "AWSPENDING", token) - logger.info("createSecret: Successfully retrieved secret for %s." % arn) + logger.info("createSecret: Successfully retrieved secret for %s." % sanitize_input_str(arn)) except service_client.exceptions.ResourceNotFoundException: # Generate a random password current_dict['password'] = get_random_password(service_client) # Put the secret service_client.put_secret_value(SecretId=arn, ClientRequestToken=token, SecretString=json.dumps(current_dict), VersionStages=['AWSPENDING']) - logger.info("createSecret: Successfully put secret for ARN %s and version %s." % (arn, token)) + logger.info("createSecret: Successfully put secret for ARN %s and version %s." % (sanitize_input_str(arn), sanitize_input_str(token))) def set_secret(service_client, arn, token): @@ -154,7 +158,7 @@ def set_secret(service_client, arn, token): conn = get_connection(pending_dict) if conn: conn.close() - logger.info("setSecret: AWSPENDING secret is already set as password in PostgreSQL DB for secret arn %s." % arn) + logger.info("setSecret: AWSPENDING secret is already set as password in PostgreSQL DB for secret arn %s." % sanitize_input_str(arn)) return # Make sure the user from current and pending match @@ -189,7 +193,7 @@ def set_secret(service_client, arn, token): # If we still don't have a connection, raise a ValueError if not conn: - logger.error("setSecret: Unable to log into database with previous, current, or pending secret of secret arn %s" % arn) + logger.error("setSecret: Unable to log into database with previous, current, or pending secret of secret arn %s" % sanitize_input_str(arn)) raise ValueError("Unable to log into database with previous, current, or pending secret of secret arn %s" % arn) # Now set the password to the pending password @@ -202,7 +206,7 @@ def set_secret(service_client, arn, token): alter_role = "ALTER USER %s" % escaped_username cur.execute(alter_role + " WITH PASSWORD %s", (pending_dict['password'],)) conn.commit() - logger.info("setSecret: Successfully set password for user %s in PostgreSQL DB for secret arn %s." % (pending_dict['username'], arn)) + logger.info("setSecret: Successfully set password for user %s in PostgreSQL DB for secret arn %s." % (pending_dict['username'], sanitize_input_str(arn))) finally: conn.close() @@ -240,10 +244,10 @@ def test_secret(service_client, arn, token): finally: conn.close() - logger.info("testSecret: Successfully signed into PostgreSQL DB with AWSPENDING secret in %s." % arn) + logger.info("testSecret: Successfully signed into PostgreSQL DB with AWSPENDING secret in %s." % sanitize_input_str(arn)) return else: - logger.error("testSecret: Unable to log into database with pending secret of secret ARN %s" % arn) + logger.error("testSecret: Unable to log into database with pending secret of secret ARN %s" % sanitize_input_str(arn)) raise ValueError("Unable to log into database with pending secret of secret ARN %s" % arn) @@ -267,14 +271,14 @@ def finish_secret(service_client, arn, token): if "AWSCURRENT" in metadata["VersionIdsToStages"][version]: if version == token: # The correct version is already marked as current, return - logger.info("finishSecret: Version %s already marked as AWSCURRENT for %s" % (version, arn)) + logger.info("finishSecret: Version %s already marked as AWSCURRENT for %s" % (version, sanitize_input_str(arn))) return current_version = version break # Finalize by staging the secret version current service_client.update_secret_version_stage(SecretId=arn, VersionStage="AWSCURRENT", MoveToVersionId=token, RemoveFromVersionId=current_version) - logger.info("finishSecret: Successfully set AWSCURRENT stage to version %s for secret %s." % (token, arn)) + logger.info("finishSecret: Successfully set AWSCURRENT stage to version %s for secret %s." % (sanitize_input_str(token), sanitize_input_str(arn))) def get_connection(secret_dict):