Enhancing Data Lake Security: A Guide to PII Scanning in S3 buckets

Data Lake Security

Share This Post

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.

Why is safeguarding PII important?

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.

  The boundaries of Amazon AWS Macie & Comprehend

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`.

Data Lake Security

Pre-requisites

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:

  1. 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`).

  1. 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.

  1. 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

Subscribe To Our Newsletter

Sign up for Private AI’s mailing list to stay up to date with more fresh content, upcoming events, company news, and more! 

More To Explore

Download the Free Report

Request an API Key

Fill out the form below and we’ll send you a free API key for 500 calls (approx. 50k words). No commitment, no credit card required!

Language Packs

Expand the categories below to see which languages are included within each language pack.
Note: English capabilities are automatically included within the Enterprise pricing tier. 

French
Spanish
Portuguese

Arabic
Hebrew
Persian (Farsi)
Swahili

French
German
Italian
Portuguese
Russian
Spanish
Ukrainian
Belarusian
Bulgarian
Catalan
Croatian
Czech
Danish
Dutch
Estonian
Finnish
Greek
Hungarian
Icelandic
Latvian
Lithuanian
Luxembourgish
Polish
Romanian
Slovak
Slovenian
Swedish
Turkish

Hindi
Korean
Tagalog
Bengali
Burmese
Indonesian
Khmer
Japanese
Malay
Moldovan
Norwegian (Bokmål)
Punjabi
Tamil
Thai
Vietnamese
Mandarin (simplified)

Arabic
Belarusian
Bengali
Bulgarian
Burmese
Catalan
Croatian
Czech
Danish
Dutch
Estonian
Finnish
French
German
Greek
Hebrew
Hindi
Hungarian
Icelandic
Indonesian
Italian
Japanese
Khmer
Korean
Latvian
Lithuanian
Luxembourgish
Malay
Mandarin (simplified)
Moldovan
Norwegian (Bokmål)
Persian (Farsi)
Polish
Portuguese
Punjabi
Romanian
Russian
Slovak
Slovenian
Spanish
Swahili
Swedish
Tagalog
Tamil
Thai
Turkish
Ukrainian
Vietnamese

Rappel

Testé sur un ensemble de données composé de données conversationnelles désordonnées contenant des informations de santé sensibles. Téléchargez notre livre blanc pour plus de détails, ainsi que nos performances en termes d’exactitude et de score F1, ou contactez-nous pour obtenir une copie du code d’évaluation.

99.5%+ Accuracy

Number quoted is the number of PII words missed as a fraction of total number of words. Computed on a 268 thousand word internal test dataset, comprising data from over 50 different sources, including web scrapes, emails and ASR transcripts.

Please contact us for a copy of the code used to compute these metrics, try it yourself here, or download our whitepaper.