Разверните файловый сканер для конфиденциальных данных в 40 строках кода

В этом руководстве мы создадим и развернем сервер, который сканирует файлы на наличие конфиденциальных данных (например, номеров кредитных карт) с помощью API предотвращения потери данных Nightfall и платформы Flask.

Служба принимает локальный файл, сканирует его на наличие конфиденциальных данных с помощью Nightfall и отображает результаты в простом табличном пользовательском интерфейсе. Мы развернем сервер на Render (альтернатива PaaS Heroku), чтобы вы могли публично обслуживать свое приложение в рабочей среде, а не запускать его на своем локальном компьютере. Вы познакомитесь со следующими инструментами и фреймворками: Python, Flask, Nightfall, Ngrok, Jinja, Render.

Ключевые идеи

Прежде чем мы приступим к нашей реализации, начните с ознакомления с тем, как работает сканирование файлов с помощью Nightfall, чтобы вы познакомились с процессом, который мы реализуем.

В двух словах, Nightfall выполняет сканирование файлов асинхронно; после того, как вы загрузите файл в Nightfall и запустите сканирование, мы выполним сканирование в фоновом режиме. Когда сканирование завершится, Nightfall предоставит вам результаты, отправив запрос на ваш сервер веб-перехватчиков. Такое асинхронное поведение позволяет Nightfall сканировать файлы разного размера и сложности, не требуя, чтобы вы удерживали открытым длинный синхронный запрос или постоянно запрашивали обновления. Влияние этого шаблона заключается в том, что вам нужна конечная точка веб-перехватчика, которая может получать входящие уведомления от Nightfall после завершения сканирования — это то, что мы создаем в этом руководстве.

Начиная

Вы можете разветвить образец репозитория и просмотреть полный код здесь или следовать инструкциям ниже. Если вы начинаете с нуля, создайте новый репозиторий GitHub.

Настройка зависимостей

Во-первых, давайте начнем с установки наших зависимостей. Мы будем использовать Nightfall для классификации данных, веб-фреймворк Flask на Python и Gunicorn в качестве нашего веб-сервера. Создайте requirements.txt и добавьте в файл следующее:

nightfall
Flask
Gunicorn

Затем запустите pip install -r requirements.txt, чтобы выполнить установку.

Настройка обнаружения с помощью Nightfall

Далее нам понадобится наш ключ API Nightfall и секрет подписи веб-хука; первый аутентифицирует нас в Nightfall API, а второй аутентифицирует, что входящие веб-перехватчики исходят от Nightfall. Вы можете получить свой ключ API и секрет подписи веб-хука из Dashboard Nightfall. Завершите сумрачный налет Быстрый старт, чтобы получить более подробное описание. Зарегистрируйтесь для получения бесплатной учетной записи Nightfall, если у вас ее нет.

Эти значения уникальны для вашей учетной записи и должны храниться в безопасности. Это означает, что мы будем хранить их как переменные среды и не должны хранить их непосредственно в коде или передавать их в систему управления версиями. Если эти значения когда-либо утекут, обязательно посетите панель управления Nightfall, чтобы повторно сгенерировать новые значения для этих секретов.

export NIGHTFALL_API_KEY=<your_key_here>
export NIGHTFALL_SIGNING_SECRET=<your_secret_here>

Настройка нашего сервера

Давайте начнем писать наш сервер Flask. Создайте файл с именем app.py. Мы начнем с импорта наших зависимостей и инициализации клиентов Flask и Nightfall:

from flask import Flask, request, render_template
from nightfall import Confidence, DetectionRule, Detector, RedactionConfig, MaskConfig, Nightfall
from datetime import datetime, timedelta
import urllib.request, urllib.parse, json

app = Flask(__name__)

nightfall = Nightfall(
	key=os.getenv('NIGHTFALL_API_KEY'),
	signing_secret=os.getenv('NIGHTFALL_SIGNING_SECRET')
)

Затем мы добавим наш первый маршрут, который будет отображать «Hello World», когда клиент переходит к /ping, просто для проверки того, что все работает:

@app.route("/ping")
def ping():
	return "Hello World", 200

Запустите gunicorn app:app в командной строке, чтобы запустить сервер, и перейдите на локальный сервер в веб-браузере. Вы увидите, где находится веб-браузер в журналах Gunicorn, обычно это будет 127.0.0.1:8000 или localhost:8000.

[2021-11-26 14:22:53 -0800] [61196] [INFO] Starting gunicorn 20.1.0
[2021-11-26 14:22:53 -0800] [61196] [INFO] Listening at: http://127.0.0.1:8000 (61196)
[2021-11-26 14:22:53 -0800] [61196] [INFO] Using worker: sync
[2021-11-26 14:22:53 -0800] [61246] [INFO] Booting worker with pid: 61246

Чтобы открыть наш локальный сервер веб-перехватчиков через общедоступный туннель, на который Nightfall может отправлять запросы, мы будем использовать ngrok. Загрузите и установите ngrok через их документацию по быстрому запуску здесь. Мы создадим туннель ngrok следующим образом:

./ngrok http 8000

После запуска этой команды ngrok создаст туннель в общедоступном Интернете, который перенаправит трафик со своего сайта на ваш локальный компьютер. Скопируйте конечную точку туннеля HTTPS, созданную ngrok: мы можем использовать ее в качестве URL-адреса веб-перехватчика при запуске сканирования файлов.

Account                       Nightfall Example
Version                       2.3.40
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://3ecedafba368.ngrok.io -> http://localhost:8000
Forwarding                    https://3ecedafba368.ngrok.io -> http://localhost:8000

Давайте установим эту конечную точку HTTPS как локальную переменную среды, чтобы мы могли ссылаться на нее позже:

export NIGHTFALL_SERVER_URL=https://3ecedafba368.ngrok.io

Совет. С помощью учетной записи Pro ngrok вы можете создать поддомен, чтобы URL-адрес вашего туннеля был согласованным, а не генерировался случайным образом при каждом запуске туннеля.

Обработка входящего веб-перехватчика

Прежде чем мы отправим запрос на сканирование файла в Nightfall, давайте добавим логику для нашей конечной точки входящего веб-перехватчика, чтобы, когда Nightfall завершит сканирование файла, он мог успешно отправить нам конфиденциальные данные.

Во-первых, что значит иметь выводы? Если в файле есть результаты, это означает, что Nightfall идентифицировал конфиденциальные данные в файле, которые соответствуют настроенным вами правилам обнаружения. Например, если вы приказали Nightfall искать номера кредитных карт, любая подстрока полезной нагрузки запроса, совпадающая с нашим детектором кредитных карт, будет представлять собой конфиденциальные результаты.

Мы разместим наш входящий веб-хук по адресу /ingest методом POST.

Nightfall отправит POST в конечную точку веб-перехватчика, а во входящей полезной нагрузке Nightfall укажет, есть ли в файле конфиденциальные данные, и предоставит ссылку, по которой мы можем получить доступ к конфиденциальным данным в формате JSON.

# respond to POST requests at /ingest
# Nightfall will send requests to this webhook endpoint with file scan results
@app.route("/ingest", methods=['POST'])
def ingest():
	data = request.get_json(silent=True)
	# validate webhook URL with challenge response
	challenge = data.get("challenge") 
	if challenge:
		return challenge
	# challenge was passed, now validate the webhook payload
	else: 
		# get details of the inbound webhook request for validation
		request_signature = request.headers.get('X-Nightfall-Signature')
		request_timestamp = request.headers.get('X-Nightfall-Timestamp')
		request_data = request.get_data(as_text=True)

		if nightfall.validate_webhook(request_signature, request_timestamp, request_data):
			# check if any sensitive findings were found in the file, return if not
			if not data["findingsPresent"]: 
				print("No sensitive data present!")
				return "", 200

			# there are sensitive findings in the file
			# URL escape the temporary signed S3 URL where findings are available for download
			escaped_url = urllib.parse.quote(data['findingsURL'])
			# print the download URL and the URL where we can view the results in our web app
			print(f"Sensitive data present. Findings available until {data['validUntil']}.\n\nDownload:\n{data['findingsURL']}\n\nView:\n{request.url_root}view?findings_url={escaped_url}\n")
			return "", 200
		else:
			return "Invalid webhook", 500

Перезапустите сервер, чтобы изменения распространились. Мы рассмотрим вывод консоли нашей конечной точки веб-перехватчика и объясним, что это значит, в следующем разделе.

Теперь мы хотим инициировать запрос на сканирование файла, чтобы Nightfall сканировал файл и отправил запрос POST на нашу конечную точку веб-перехватчика /ingest после завершения сканирования. Мы напишем простой скрипт, который отправляет файл в Nightfall для сканирования номера кредитных карт. Создайте новый файл с именем scan.py.

Во-первых, мы установим наши зависимости, инициализируем клиент Nightfall и укажем путь к файлу, который мы хотим сканировать, а также конечную точку веб-перехватчика, которую мы создали выше. Путь к файлу — это относительный путь к любому файлу, в данном случае мы сканируем файл sample-pci-xs.csv, который находится в том же каталоге, что и scan.py. Это пример CSV-файла с 10 номерами кредитных карт — вы можете скачать его в репозитории GitHub руководства.

import os
from nightfall import Confidence, DetectionRule, Detector, RedactionConfig, MaskConfig, Nightfall

nightfall = Nightfall() # reads API key from NIGHTFALL_API_KEY environment variable by default

filepath = "sample-pci-xs.csv" # sample file with sensitive data
webhook_url = f"{os.getenv('NIGHTFALL_SERVER_URL')}/ingest"
```

Next, we will initiate the scan request to Nightfall, by specifying our filepath, webhook URL where the scan results should be posted, and our Detection Rule that specifies what sensitive data we are looking for.

In this simple example, we have specified an inline Detection Rule that detects Likely Credit Card Numbers. This Detection Rule is a simple starting point that just scratches the surface of the types of detection you can build with Nightfall. Learn more about building inline detection rules [here](https://docs.nightfall.ai/docs/creating-an-inline-detection-rule) or how to configure them in the Nightfall [Dashboard](https://app.nightfall.ai/developer-platform).

```python
scan_id, message = nightfall.scan_file(filepath, 
	webhook_url=webhook_url,
	detection_rules=[ DetectionRule([ 
		Detector(
			min_confidence=Confidence.LIKELY,
   			nightfall_detector="CREDIT_CARD_NUMBER",
   			display_name="Credit Card Number"
       	)])
	])

print(scan_id, message)

Затем мы инициируем запрос на сканирование в Nightfall, указав наш путь к файлу, URL-адрес веб-перехватчика, где должны быть опубликованы результаты сканирования, и наше правило обнаружения, которое указывает, какие конфиденциальные данные мы ищем.

В этом простом примере мы указали встроенное правило обнаружения, которое определяет вероятные номера кредитных карт. Это правило обнаружения — простая отправная точка, которая лишь поверхностно описывает типы обнаружения, которые вы можете построить с помощью Nightfall. Узнайте больше о построении встроенных правил обнаружения здесь или о том, как их настроить в Dashboard Nightfall.

scan_id, message = nightfall.scan_file(filepath, 
	webhook_url=webhook_url,
	detection_rules=[ DetectionRule([ 
		Detector(
			min_confidence=Confidence.LIKELY,
   			nightfall_detector="CREDIT_CARD_NUMBER",
   			display_name="Credit Card Number"
       	)])
	])

print(scan_id, message)

scan_id пригодится для идентификации результатов сканирования позже.

Просмотр конфиденциальных результатов

Давайте запустим scan.py, чтобы запустить наше задание сканирования файлов.

Как только Nightfall завершит сканирование файла, мы увидим, что наш сервер Flask получает запрос в конечной точке нашего веб-перехватчика (/ingest). В приведенном выше коде мы анализируем полезную нагрузку веб-перехватчика и выводим следующее, когда есть конфиденциальные данные:

Sensitive data present. Findings available until 2021-11-28T00:29:00.479700877Z.

Download:
https://files.nightfall.ai/d2160270-6b07-4304-b1ee-e7b98498be82.json?Expires=1638059340&Signature=AjSdNGlXWGXO0QGSi-lOoDBtbhJdLPE7IWXA7IaBCfLr~3X2IcZ1vavHF5iaEDaoZ-3etnZA4Nu8K8Dq8Kd81ShuX6Ze1o87mzb~8lD6WBk8hXShgW-TPBPpLMoBx2sA9TnefTqy94gI4ykt4tt1MttB67Cj69Miw-46cpFkgY9tannNPOF-90b3vlcS44PwqDUGrtTpQiN6WdsTT6LbpN1N92KbPJIRj3PkGwQW7VvpfM8L4wKmyVmVnRO3ixaW-mXXiOWk9rmfHP9UFMYnk99yaGHp4dZ1JfJiClci~Z8dBx288CrvXVjGUCXBJbdlwo6UrKQJCEk9i9vSbCpI2Q__&Key-Pair-Id=K24YOPZ1EKX0YC

View:
https://d3vwatchtower.ngrok.io/ingest/view?findings_url=https%3A//files.nightfall.ai/d2160270-6b07-4304-b1ee-e7b98498be82.json%3FExpires%3D1638059340%26Signature%3DAjSdNGlXWGXO0QGSi-lOoDBtbhJdLPE7IWXA7IaBCfLr~3X2IcZ1vavHF5iaEDaoZ-3etnZA4Nu8K8Dq8Kd81ShuX6Ze1o87mzb~8lD6WBk8hXShgW-TPBPpLMoBx2sA9TnefTqy94gI4ykt4tt1MttB67Cj69Miw-46cpFkgY9tannNPOF-90b3vlcS44PwqDUGrtTpQiN6WdsTT6LbpN1N92KbPJIRj3PkGwQW7VvpfM8L4wKmyVmVnRO3ixaW-mXXiOWk9rmfHP9UFMYnk99yaGHp4dZ1JfJiClci~Z8dBx288CrvXVjGUCXBJbdlwo6UrKQJCEk9i9vSbCpI2Q__%26Key-Pair-Id%3DK24YOPZ1EKX0YC

В нашем выводе мы печатаем два URL-адреса.

Первый URL предоставлен нам Nightfall. Это временный подписанный URL-адрес S3, к которому мы можем получить доступ для получения конфиденциальных данных, обнаруженных Nightfall.

Второй URL пока не работает, мы реализуем его дальше. Этот URL-адрес мы создали в нашем методе ingest() выше. URL-адрес вызывает /view и передает приведенный выше URL-адрес результатов в качестве параметра запроса с экранированием URL-адреса.

Давайте добавим на наш сервер Flask метод, который открывает этот URL-адрес и отображает результаты в отформатированной таблице, чтобы результаты было легче просматривать, чем загружать их в формате JSON.

Мы сделаем это, добавив метод view, отвечающий на запросы GET, к маршруту /view. Маршрут /view будет считывать URL-адрес URL-адреса результатов S3 через параметр запроса. Затем он откроет URL-адрес результатов, проанализирует его как JSON, передаст результаты в шаблон HTML и отобразит результаты в простой таблице HTML с помощью Jinja. Jinja — это простой механизм шаблонов на Python.

Добавьте следующее на наш сервер Flask в app.py:

# respond to GET requests at /view
# Users can access this page to view their file scan results in a table
@app.route("/view")
def view():
	# get the findings URL from the query parameters
	findings_url = request.args.get('findings_url')
	if findings_url:
		# download the findings from the findings URL and parse them as JSON
		with urllib.request.urlopen(findings_url) as url:
			data = json.loads(url.read().decode())
			# render the view.html template and provide the findings object to display in the template
			return render_template('view.html', findings=data['findings'])

Создайте табличное представление

Чтобы отобразить результаты в таблице HTML, мы создадим новый шаблон Flask. Создайте папку в каталоге вашего проекта с именем templates и добавьте в нее новый файл с именем view.html.

Наш шаблон использует Jinja для повторения наших результатов и создания строки таблицы для каждого конфиденциального результата.

<!DOCTYPE HTML>
<html>
<head>
    <title>File Scan Viewer</title>
    <style>
    	table, th, td {
		  border: 1px solid black;
		}
		table {
			width: 100%;
		}
	</style>
</head>

<body>
	<table>
		<thead>
			<tr>
				<th>Detector</th>
				<th>beforeContext</th>
				<th>Finding</th>
				<th>afterContext</th>
				<th>byteRangeStart</th>
				<th>byteRangeEnd</th>
				<th>Confidence</th>
			</tr>
		</thead>

		<tbody>
			{% for finding in findings %}
				<tr>
					<td>{{ finding['detector']['name'] }}</td>
					<td>{{ finding['beforeContext'] }}</td>
					<td>{{ finding['finding'] }}</td>
					<td>{{ finding['afterContext'] }}</td>
					<td>{{ finding['location']['byteRange']['start'] }}</td>
					<td>{{ finding['location']['byteRange']['start'] }}</td>
					<td>{{ finding['confidence'] }}</td>
				</tr>
			{% endfor %}
		</tbody>
	</table>

</body>
</html>

Теперь, если мы перезапустим наш сервер Flask, вызовем запрос на сканирование файла и перейдем к URL-адресу «Просмотр», напечатанному в журналах сервера, мы должны увидеть отформатированную таблицу с нашими результатами! Фактически, мы можем ввести любой подписанный URL-адрес S3, предоставленный Nightfall (после экранирования URL-адреса), в параметр findings_url маршрута /view, чтобы просмотреть его.

Развернуть на рендере

Как давний пользователь Heroku, я изначально был склонен написать это руководство с инструкциями по развертыванию нашего приложения на Heroku. Однако появились новые поставщики PaaS, и мне было любопытно попробовать их и посмотреть, как они сравниваются с Heroku. Одним из таких поставщиков является Render, где мы развернем наше приложение.

Развернуть наш сервис на Render очень просто. Если вы знакомы с Heroku, процесс очень похож. После того, как вы зарегистрируетесь или войдете в Render (бесплатно), мы сделаем следующее:

  1. Создайте новый Web Service в Render и дайте Render разрешение на доступ к вашему новому репозиторию.
  2. При создании используйте следующие значения:
  • Окружающая среда: Python
  • Команда сборки: pip install -r requirements.txt
  • Команда запуска: gunicorn app:app

Давайте также установим переменные среды во время создания. Это те же значения, которые мы устанавливаем локально.

NIGHTFALL_API_KEY
NIGHTFALL_SIGNING_SECRET

Сканировать файл (в производстве)

После завершения развертывания Render вы получите базовый URL-адрес вашего приложения. Установите это как ваш NIGHTFALL_SERVER_URL локально и повторно запустите scan.py - на этот раз запрос на сканирование файла обслуживается вашим рабочим сервером Flask, работающим на Render!

export NIGHTFALL_SERVER_URL=https://your-app-url.onrender.com
python3 scan.py

Чтобы убедиться в этом, перейдите на вкладку Logs в консоли приложения Render, вы увидите вывод веб-хука результатов сканирования вашего файла:

Nov 26 04:29:06 PM  Sensitive data present. Findings available until 2021-11-28T00:28:24.564972786Z.
Nov 26 04:29:06 PM  
Nov 26 04:29:06 PM  Download:
Nov 26 04:29:06 PM  https://files.nightfall.ai/d6b6ee4f-d1a8-4fb6-b35a-cb6f88d58083.json?Expires=1638059304&Signature=hz1TN5UXjCGTxCxq~jT2wfuUWlj9Se-mWNL1K-tJhiAIXUg1FxJrCVP2iH1I4TNymFBuOnj5TTiLGpD8tZAKGm9J0lTHncZkaeaU8KZQ2j-~8qYQVlunNj019sqtTkMbVRfakzYzW-qWHEvLXN-PFcGYX05g3LZHvW802-lAVlM-WpGApw2u8BnzoY1pdWAxpJ0VIN1Zax4UuVeQBKieR7k8H9v9HdYYJlVGkVA5F9EzklLy99fyD8r4WR~jfqN5Fr1KceDtsxffC6MPuZ8nIIdSG5~tVtjCjgIjyh3IePPW1Wq-E8yZiVAhpDDbYX1wngUTwlAu~MU7N39vd8mlYQ__&Key-Pair-Id=K24YOPZ1EKX0YC
Nov 26 04:29:06 PM  
Nov 26 04:29:06 PM  View:
Nov 26 04:29:06 PM  https://flask-file-scanner-example.onrender.com/view?findings_url=https%3A//files.nightfall.ai/d6b6ee4f-d1a8-4fb6-b35a-cb6f88d58083.json%3FExpires%3D1638059304%26Signature%3Dhz1TN5UXjCGTxCxq~jT2wfuUWlj9Se-mWNL1K-tJhiAIXUg1FxJrCVP2iH1I4TNymFBuOnj5TTiLGpD8tZAKGm9J0lTHncZkaeaU8KZQ2j-~8qYQVlunNj019sqtTkMbVRfakzYzW-qWHEvLXN-PFcGYX05g3LZHvW802-lAVlM-WpGApw2u8BnzoY1pdWAxpJ0VIN1Zax4UuVeQBKieR7k8H9v9HdYYJlVGkVA5F9EzklLy99fyD8r4WR~jfqN5Fr1KceDtsxffC6MPuZ8nIIdSG5~tVtjCjgIjyh3IePPW1Wq-E8yZiVAhpDDbYX1wngUTwlAu~MU7N39vd8mlYQ__%26Key-Pair-Id%3DK24YOPZ1EKX0YC

Перейдите по ссылке View выше в своем браузере, чтобы убедиться, что вы видите результаты, отформатированные в виде таблицы на вашем рабочем сайте.

Поздравляем, вы успешно создали сервер сканирования файлов и развернули его в рабочей среде! Теперь вы готовы построить более продвинутую бизнес-логику вокруг вашего сканера файлов. Вот несколько идей о том, как расширить этот учебник:

  • Используйте WebSockets для отправки уведомления от веб-перехватчика клиенту, который инициировал запрос на сканирование файла.
  • Создайте более сложное правило обнаружения, используя готовые или пользовательские детекторы.
  • Добавьте пользовательский интерфейс, чтобы добавить дополнительные интерактивные возможности, например разрешить пользователям загружать файлы или читать файлы с URL-адресов.

Первоначально опубликовано на https://docs.nightfall.ai 7 декабря 2021 г.