Introduction
Data lakes are messy with vast amounts of data scattered everywhere as they serve as repositories for a myriad of structured and unstructured data. Amidst this chaos lies a treasure trove of valuable and sometimes sensitive information, including data that can directly or indirectly identify individuals, known as Personally Identifiable Information (PII). As organizations utilize data lakes like Amazon S3 for various downstream applications, including AI-driven apps, data analytics, machine learning, and collaborative data sharing, it is crucial to handle this data lake security. As a best practice, proactive risk management strategies are essential to prevent data leaks. Continuously monitoring S3 buckets for PII has emerged as a common technique of a data security strategy. Not only does effective PII scanning mitigate the risk of breaches, but it also reinforces data governance practices, ensuring integrity and security across the board.
In this guide, we’ll delve into the details of how you can enhance S3 data lake security through PII scanning using Private AI.
What is PII?
Personally Identifiable Information (PII) refers to any data that could identify an individual, either directly or in combination with other data points. This includes information such as full names, social security numbers, driver’s license numbers, passport numbers, email addresses, phone numbers, home addresses, and biometric data such as fingerprints or facial recognition data but also so-called indirect identifiers like postal code, date of birth, race, or gender. Additionally, domain-specific data such as financial information like bank account numbers and credit card numbers, as well as medical information such as patient records and health insurance information, are also considered PII. Essentially, any data point that can be used on its own or when combined with other data to identify a person constitutes PII. Protecting this information is crucial to safeguarding individuals’ privacy, data lake security, and preventing identity theft or other malicious activities.
Why is safeguarding PII important?
From compliance mandates to data governance and data lake security considerations, here are a few reasons why safeguarding PII is important:
Compliance and Regulatory Requirements: Adhering to stringent laws such as the General Data Protection Regulation (GDPR) and the Health Insurance Portability and Accountability Act (HIPAA) is mandatory for those organizations to which these laws apply. Failure to comply can result in severe legal consequences and hefty fines. Therefore, it is imperative for organizations to stay informed about these laws and ensure full compliance to avoid potential penalties.
Data Governance: PII management is a fundamental element of data governance. For example, particular rules apply mandating the maximum retention period of PII, after which the data must be destroyed or anonymized. By implementing proper policies and controls for safeguarding PII, organizations can instill trust in their data processes and enhance and enhance data lake security.
Pre-Data Migration and Cloud Adoption: If you’re thinking about migrating to the cloud, it is essential to conduct thorough pre-assessments of your data assets and implement stringent security measures to safeguard PII from unauthorized access or breaches. Knowing what PII is included in your data sets will ensure that the measures you take are proportionate to the risk associated with the cloud migration process and the harm that would result from a breach.
Data Analytics and Machine Learning: Data analytics and machine learning, by their very nature, consume vast amounts of data to train algorithms and derive insights. Consequently, the responsible collection and processing of PII is paramount to preserving user privacy and preventing the misuse of personal data. By implementing robust privacy protection measures, organizations can ensure the data lake security and that sensitive information is used responsibly and in accordance with privacy regulations.
Data Sharing and Collaboration: Effective data sharing and collaboration initiatives must prioritize the proper management of PII at every stage. Whether sharing data internally or externally, organizations must adhere to relevant regulations and laws governing the processing of PII to mitigate risks and maintain the trust of stakeholders.
The boundaries of Amazon AWS Macie & Comprehend
Limited to the AWS Ecosystem: Macie works well for basic identifiable entities, especially when used within the AWS ecosystem. This could be a drawback for businesses using a wide variety of cloud services, or those storing data on-premises.
Cost: Both the AWS services (Amazon Comprehend and Macie) are billed based on usage, which means that if you have a large amount of data to process, it can become expensive. Private AI provides flexibility, allowing for on-premise and cloud deployments with predictable pricing.
Languages: Macie may not support less common languages or dialects. Non-Latin scripts may also pose a challenge. To name only two examples, note that Hindi and Punjabi are not supported by Macei.
Consider the following text in Hindi:
“एमिली पटेल, उम्र 25 वर्ष, 1010 चेरी एवेन्यू, मेट्रो सिटी में रहती हैं। उनका ईमेल पता है emily.patel@fastmail.com, और फोन नंबर है 555-1111। उनका मेट्रो बैंक में खाता है, खाता संख्या 654321 है।”
This translates to:
“Emily Patel, aged 25, resides at 1010 Cherry Avenue, Metro City. Her email address is emily.patel@fastmail.com, and her phone number is 555-1111. She has an account at Metro Bank with the account number 654321.”
Private AI handled this file seamlessly and identified the PII. The example below is available through the Private AI File Web Demo.
File types : AWS Comprehend and Macie may be limited in the types of files they can process. For example, they can’t process audio files. Private AI can handle multiple files, including support for documents (PDF, Word, Excel and Powerpoint), Images (JPEG, TIFF, PNF, BMP), Audio (WAV, MP3, MP4, M4A and WebM) and more.
In contrast to existing solutions like AWS Macie and Amazon Comprehend, Private AI offers a comprehensive solution to the challenges associated with managing PII. In this guide, we will go through a scenario with Private AI where we can: 1. Detect and deidentify PII in JSON format stored in S3; and 2. Extract PII entities and metadata for analytical purposes.
So, let’s get started…
Scenario: De-identifying PII in JSON format file stored in S3 and also the extracted PII stored in MySQL, which can be queried by entity type to find metadata of files.
In this scenario, we’ll illustrate how to automate the redaction of JSON files stored in an S3 bucket. These files contain sensitive information, and we want to maskPII before storing them in separate, redacted S3 buckets and also store the extracted entities including metadata in MySQL.
The setup involves configuring a Lambda function to process newly created JSON files in the source S3 bucket. Upon detection of a new file, the Lambda function triggers, takes the JSON text as input, applies redaction to mask PII, and then saves the redacted file into a designated S3 bucket.
Below is the configuration snippet from a serverless framework’s `serverless.yml` file tailored for this task:
```yaml
s3_json_pipeline:
handler: app.s3_json_pii_pipeline
events:
- s3:
bucket: ${self:custom.bucket}
event: s3:ObjectCreated:*
existing: true
rules:
- prefix: lakehouse/health_raw/
- suffix: .json
In summary, this configuration sets up the Lambda function named `s3_json_pii_pipeline` to respond to S3 events when new objects are created in the specified bucket. It’s designed to process both new objects and any existing ones that meet the criteria of having keys prefixed with `lakehouse/health_raw/` and suffixed with `.json`.
Pre-requisites
- Get an active AWS S3 account
- Locally install the AWS CLI and Docker tools
- Go through the Private AI container quick start
- AWS Python SDK(Boto3)
- Dataset: Create a health dataset in JSON format via https://www.mockaroo.com/
Note: Ideally, for security and privacy reasons, you might want to run the container in your own VPC. Refer to the VPC installation documents to learn more. |
For this scenario, we will assume that the container is already installed and exposes a REST API endpoint.
Step 1: Set up the S3 client in Python
To interact with Amazon S3 services from Python, we’ll define a `setup_s3_client` method. This method creates an S3 client using Boto3 in the specified AWS region (defaulting to “eu-west-2”). The client is configured with up to 10 retries on request failures, ensuring robustness in communication with the S3 service.
```python
import os
import boto3
from botocore.config import Config
def setup_s3_client():
region_name = os.environ.get("AWS_DEFAULT_REGION", "eu-west-2")
retry_config = Config(
region_name=region_name,
signature_version="v4",
retries={"max_attempts": 10, "mode": "standard"}
)
return boto3.client("s3", config=retry_config)
```
Next, let’s proceed with creating the buckets for our scenario.
Step 2: Create the buckets in python
To create a raw S3 bucket, execute the following bash script on the terminal, which leverages the AWS CLI. We will specify the desired bucket name (in this case `datalake_test_raw`) and the region (`eu-west-2`):
```bash
echo "Creating bucket datalake_test_raw in region eu-west-2"
export RAW_BUCKET_NAME="datalake-test-raw"
export REGION="eu-west-2"
aws s3api create-bucket --bucket ${RAW_BUCKET_NAME} --region ${REGION} --create-bucket-configuration LocationConstraint=${REGION}
```
Similarly, to create a processed S3 bucket, execute the following command, specifying the bucket name (`datalake_test_processed`) and the region (`eu-west-2`):
```bash
echo "Creating bucket datalake_test_processed in region eu-west-2"
export PROCESSED_BUCKET_NAME="datalake-test-processed"
aws s3api create-bucket --bucket ${PROCESSED_BUCKET_NAME} --region ${REGION} --create-bucket-configuration LocationConstraint=${REGION}
```
Step 3: Define helper functions to read and write files from S3
The ‘read_file_from_s3’ function is responsible for getting a file from an S3 bucket by using the provided bucket name and key. On the other hand, the ‘write_file_to_s3’ function can be used to upload content to an S3 bucket with a provided key.
def read_file_from_s3(bucket, key):
try:
response = s3.get_object(Bucket=bucket, Key=key)
return response["Body"].read().decode("utf-8")
except Exception as e:
logger.error(f"Error reading file from S3: {e}")
return None
def write_file_to_s3(bucket, key, content):
try:
s3.put_object(Body=content, Bucket=bucket, Key=key)
except Exception as e:
logger.error(f"Error writing file to S3: {e}")
Step 4: Setup the Private AI PAIClient for de-identification
De-identification is a process of cleansing a record or dataset by removing PII. In this code snippet, we set up the PAIClient client object in python, which will leverage Private AI’s de-identification API. Entities and processed text are extracted from the response.
from privateai_client import PAIClient
from privateai_client import request_objects
client = PAIClient(
url="https://api.private-ai.com/deid/", api_key=os.environ.get("PRIVATEAI_API_KEY")
)
entity_detection_obj = request_objects.entity_detection_obj(
accuracy="high", return_entity=True)
processed_text_obj = request_objects.processed_text_obj(type="MARKER")
Step 5: Create the database and get the database object
In this step, let us create the database table to store the de-identification response and entity metadata. This table is defined in an RDS(MySQL) database.
DROP TABLE IF EXISTS files_pii_entities_log;
CREATE TABLE IF NOT EXISTS files_pii_entities_log
(
-- File name, assumed to be a unique identifier for each file
file_name VARCHAR(255) NOT NULL,
entity_id INT AUTO_INCREMENT,
-- Processed text entities
entities_processed_text VARCHAR(255) NULL,
-- Raw text entities
entities_text VARCHAR(255) NULL,
-- Location of entities within the document
entities_location VARCHAR(1000) NULL,
-- Best label for the identified entity
entities_best_label VARCHAR(255) NULL,
-- All labels associated with the entity
entities_labels VARCHAR(1000) NULL,
-- Flag indicating if the response was in JSON format
json_response TINYINT(1) NULL,
-- HTTP status code of the response
status_code INT NULL,
-- Flag indicating if the operation was successful
ok TINYINT(1) NULL,
-- Detected languages in the file
languages_detected VARCHAR(1000) NULL,
-- Timestamp when the record was created
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Timestamp when the record was last updated
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Composite primary key for uniqueness and indexing
PRIMARY KEY (file_name, entity_id)
);
-- Consider adding indexes based on the query patterns. For example:
-- CREATE INDEX idx_entities ON files_pii_entities_log(entities_text);
-- CREATE INDEX idx_status ON files_pii_entities_log(status_code);
Now, let’s extend the Python code to create a helper function for the database.
The `get_mysql_connection` function tries to connect to a MySQL database using SSL verification. If successful, it returns the connection object; otherwise, it prints an error and returns `None`.
Each time a JSON document is created in S3, we remove PII from the JSON document (de-identification) and store in an RDS database the extracted entities that are discovered and entity metadata, such as the label of the entity with maximum likelihood. This helper method gets a connection object to the persistent RDS (MySQL engine) database that stores the metadata.
def get_mysql_connection() -> Optional[pymysql.connections.Connection]:
ssl_ca_file_path = certifi.where()
print(f"ssl_ca_file_path: {ssl_ca_file_path}")
if not ssl_ca_file_path or not os.path.exists(ssl_ca_file_path):
print("SSL CA file path not found for this OS.")
return None
try:
connection = pymysql.connect(
host=os.environ.get("RDS_ENDPOINT"),
port=int(os.environ.get("RDS_PORT")),
user=os.environ.get("RDS_USER"),
password=os.environ.get("RDS_PASSWORD"),
database="pii",
ssl_verify_cert=True,
ssl_verify_identity=True,
ssl_ca=certifi.where(),
)
return connection
except pymysql.MySQLError as e:
print(f"Database connection error: {e}")
return None
For this function to work, you need to set up several environment variables including the RDS endpoint, port, username and password. You can find this information for your RDS instance by following the instructions here.
Step 6: Define helper function to de-identify and process JSON text
De-identification is a process of cleansing a record or dataset by removing PII. The Python helper function we are going to define does three things:
- Text Processing Request Preparation:
– Constructs a request object `process_text_request` using some `process_text_obj` method/function. This object likely contains the text to be processed, settings for processing, and possibly some pre-initialized objects for entity detection and processed text.
– Calls the external Private AI service through PAIClient (`client.process_text`).
- Logs response and entity metadata :
– If the database connection is successful, it logs information about identified entities into a database table (`files_pii_entities_log`) based on the response received from the external service.
– It iterates over `response.entities[0]` (assuming it’s a list) to extract information for logging.
– Inserts data into the database table using `insert_into_database()` function.
- Returns output:
– Prints some information including the processed text from the response.
– Returns the processed text (first element of `response.processed_text`) as the result of the function.
def deidentify_using_private_ai(text: str, key: str) -> Optional[str]:
process_text_request = request_objects.process_text_obj(
text=[text],
link_batch=False,
entity_detection=entity_detection_obj,
processed_text=processed_text_obj,)
response = client.process_text(process_text_request)
print(
f"response: {response} response: {response.languages_detected} and labels: {response.best_labels}")
languages_detected = ", ".join([
f"{lang}: {score}"
for lang_score in response.languages_detected
for lang, score in lang_score.items()])
# Establish a database connection
connection = None
try:
connection = get_mysql_connection()
if not connection:
return None
entities_log_query = "INSERT INTO files_pii_entities_log (file_name, entities_processed_text, entities_text, entities_location, entities_best_label, entities_labels, json_response, status_code, ok, languages_detected) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s);"
for entity in response.entities[0]:
print(f"Inserting entity {entity}")
data_tuple = (
key,
entity.get("processed_text"),
entity.get("text")
str(entity.get("location")),entity.get("best_label"),
str(entity.get("labels")),
response.json_response,
response.status_code,
response.ok,
languages_detected,
)
insert_into_database(connection, entities_log_query, data_tuple)
except pymysql.MySQLError as e:
print(f"Database operation error: {e}")
finally:
if connection:
connection.close()
print(f"Processed text: {response.processed_text}")
return response.processed_text[0]
Step 7: Wrap the code around a lambda function handler
Next, let’s define an AWS Lambda function that processes file uploads to an S3 bucket. Invoked by an event, it checks for a valid event body and extracts the bucket name and file key. Then, it reads and interprets the file content as JSON. If successful, it calls ‘deidentify_using_private_ai’. Finally, it returns a response containing metadata and the deidentified content.
def deidentified_content(event, context):
status_code = 200
if not event.get('body'):
return {'statusCode': 400, 'body': json.dumps({'message': 'No body was found'})}
# Get file information from S3 event
s3_event = json.loads(event["body"])
if not s3_event.get("bucket_name") or not s3_event.get("file_key"):
return {'statusCode': 400, 'body': json.dumps({'message': 'No bucket_name or file_key was found'})}
bucket_name = s3_event["bucket_name"]
file_key = s3_event["file_key"]
# Read json content from S3
file_content = read_file_from_s3(bucket_name, file_key)
# Safely load the content to json
try:
file_content = json.loads(file_content)
except Exception as e:
return {'statusCode': 400, 'body': json.dumps({'message': f'Error reading file from S3: {e}'})}
# Deidentify and process the content
deidentified_content = deidentify_using_private_ai(json.dumps(file_content), file_key)
print(f"deidentified_content: {deidentified_content}")
data = {
"statusCode": status_code,
"bucket_name": bucket_name,
"file_key": file_key,
"demasked_content": deidentified_content
}
return json.dumps(data)
Step 8: Create a serverless config and deploy the lambda function
The YAML snippet defines a serverless function named “redact_pii” with a corresponding handler “app.deidentified_content”. This function is triggered by an HTTP GET request at the “/redactpii” endpoint.
```yaml
functions:
redact_pii:
handler: app.deidentified_content
events:
- httpApi:
path: /redactpii
method: get
```
The command “sls deploy” is used to deploy the serverless lambda application, enabling the execution of the defined functions in the cloud environment.
To deploy the serverless lambda application, use the command:
```bash
sls deploy
```
Step 9: Deidentify a single JSON document
To interact with the /redactpii HTTP endpoint, provide a JSON file named “sample.json” with prefix ‘lakehouse/health_raw/’ in the datalake-test-raw bucket.
Input JSON:
```json
{
"users": [
{
"name": "John Doe",
"email": "johndoe@example.com",
"phone": "123-456-7890",
"address": "123 Main St, City, State, 12345"
}
]
}
```
Output JSON:
```json
{
"users": [
{
"name": "[NAME_1]",
"email": "[EMAIL_ADDRESS_1]",
"phone": "[PHONE_NUMBER_1]",
"address": "[LOCATION_ADDRESS_1]"
}
]
}
```
This output JSON demonstrates redacted PII, replacing names with “[NAME_1]”, email addresses with “[EMAIL_ADDRESS_1]”, phone numbers with “[PHONE_NUMBER_1]”, and addresses with “[LOCATION_ADDRESS_1]”. Now, if the JSON documents that were previously generated by Mockaroo have other PII fields such as id, which maps to HEALTHCARE_NUMBER, those field values will be redacted as well.
Step 10: Analyze the metadata
Now, imagine a scenario where you’re interested in retrieving JSON files containing metadata of types “phone_number” or “numerical_pii”. Leveraging the metadata stored in the RDS (MySQL) database, you can execute a straightforward query, such as the one shown below:
SELECT
*
FROM
files_pii_entities_log
WHERE
entities_best_label IN ('PHONE_NUMBER', 'NUMERICAL_PII');
Step 11: Setup automation to invoke the lambda function
To automate the invocation of your Lambda function upon each S3 upload, you can follow the tutorial provided by AWS. By setting this up, you can seamlessly integrate your Lambda function with S3, ensuring that it automatically executes in response to file uploads in S3, thereby streamlining your PII handling workflow.
Conclusion
This guide offers an in-depth walkthrough on Data Lake Security and effectively scanning and redacting Personally Identifiable Information (PII) within AWS S3 buckets. It underscores the significance of actively monitoring S3 buckets for PII in contexts such as compliance, data governance, and pre-data migration phases. The discussion delves into the limitations of native AWS tools like Comprehend and Macie, proposing Private AI as a versatile alternative with support for diverse file types, languages, and cloud environments. Step-by-step instructions are provided for setting up essential AWS and Private AI components, along with scripts for automating PII redaction in S3 buckets and logging detections to an RDS database for analytical purposes.
Get started with Private AI today
Sign up for our Community API
The “get to know us” plan. Our full product, but limited to 75 API calls per day and hosted by us.
Get Started Today