Разположете файлов скенер за чувствителни данни в 40 реда код

В този урок ще създадем и внедрим сървър, който сканира файлове за чувствителни данни (като номера на кредитни карти) с API за предотвратяване на загуба на данни на Nightfall и рамката на Flask.

Услугата поглъща локален файл, сканира го за чувствителни данни с Nightfall и показва резултатите в прост табличен потребителски интерфейс. Ние ще разположим сървъра на Render (алтернатива на PaaS Heroku), така че да можете да обслужвате приложението си публично в производство, вместо да го изпълнявате от вашата локална машина. Ще се запознаете със следните инструменти и рамки: Python, Flask, Nightfall, Ngrok, Jinja, Render.

Ключови понятия

Преди да започнем с внедряването ни, започнете, като се запознаете с „как работи сканирането на файлове“ с Nightfall, така че да сте запознати с потока, който прилагаме.

С две думи, сканирането на файлове се извършва асинхронно от Nightfall; след като качите файл в Nightfall и задействате сканирането, ние извършваме сканирането във фонов режим. Когато сканирането приключи, Nightfall ви доставя резултатите, като отправя заявка към вашия сървър за уеб кукичка. Това асинхронно поведение позволява на Nightfall да сканира файлове с различни размери и сложност, без да се налага да държите отворена дълга синхронна заявка или непрекъснато да търсите актуализации. Въздействието на този модел е, че се нуждаете от крайна точка на webhook, която може да получава входящи известия от Nightfall, когато сканирането приключи – това е, което изграждаме в този урок.

Приготвяме се да започнем

Можете да разклоните примерното репо и да видите пълния код тук или да следвате по-долу. Ако започвате от нулата, създайте ново хранилище на GitHub.

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

Първо, нека започнем с инсталирането на нашите зависимости. Ще използваме Nightfall за класификация на данни, уеб рамката Flask в Python и Gunicorn като наш уеб сървър. Създайте requirements.txt и добавете следното към файла:

nightfall
Flask
Gunicorn

След това стартирайте pip install -r requirements.txt, за да извършите инсталацията.

Конфигуриране на откриване с Nightfall

След това ще се нуждаем от нашия ключ за API на Nightfall и тайна за подписване на Webhook; първият ни удостоверява в API на Nightfall, докато вторият удостоверява, че входящите уебкукички произхождат от Nightfall. Можете да извлечете своя ключ за API и тайна за подписване на Webhook от таблото за управление на Nightfall. Завършете Quickstart на 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

Ще използваме ngrok, за да разкрием нашия локален сървър за уебкукички чрез публичен тунел, до който Nightfall може да изпраща заявки. Изтеглете и инсталирайте 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, нека добавим логика за нашата входяща крайна точка на webhook, така че когато Nightfall завърши сканирането на файл, да може успешно да ни изпрати чувствителните открития.

Първо, какво означава да имаш открития? Ако даден файл има констатации, това означава, че Nightfall е идентифицирал чувствителни данни във файла, които съответстват на правилата за откриване, които сте конфигурирали. Например, ако сте казали на Nightfall да търси номера на кредитни карти, всеки подниз от полезния товар на заявката, който съответства на нашия детектор на кредитни карти, ще представлява чувствителни открития.

Ще хостваме нашия входящ webhook на /ingest с метод POST.

Nightfall ще ПУБЛИКУВА в крайната точка на webhook и във входящия полезен товар 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

Рестартирайте сървъра си, така че промените да се разпространят. Ще разгледаме конзолния изход на нашата крайна точка на webhook и ще обясним какво означава това в следващия раздел.

Сега искаме да задействаме заявка за сканиране на файл, така че Nightfall да сканира файла и да изпрати POST заявка до нашата крайна точка /ingest webhook, когато сканирането приключи. Ще напишем прост скрипт, който изпраща файл до Nightfall, за да го сканира за номера на кредитни карти. Създайте нов файл с име scan.py.

Първо ще установим нашите зависимости, ще инициализираме клиента Nightfall и ще посочим пътя към файла, който искаме да сканираме, както и крайната точка на webhook, която създадохме по-горе. Пътят на файла е относителен път към всеки файл, в този случай ние сканираме файла sample-pci-xs.csv, който е в същата директория като scan.py. Това е примерен CSV файл с 10 номера на кредитни карти в него - можете да го изтеглите в GitHub repo на урока.

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. Научете повече за изграждането на вградени правила за откриване тук или как да ги конфигурирате в Таблото за управление на 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 сървър получава заявката в нашата крайна точка на webhook (/ingest). В нашия код по-горе анализираме полезния товар на webhook и отпечатваме следното, когато има чувствителни констатации:

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. Това е временният подписан S3 URL адрес, до който имаме достъп, за да извлечем чувствителните констатации, открити от Nightfall.

Вторият URL все още няма да работи, ще го внедрим следващия. Този URL адрес създадохме в нашия ingest() метод по-горе - URL адресът извиква /view и предава URL адреса на констатациите по-горе като параметър на заявка, екраниран от URL.

Нека добавим метод към нашия Flask сървър, който отваря този URL адрес и показва констатациите във форматирана таблица, така че резултатите да са по-лесни за преглед, отколкото да ги изтеглите като JSON.

Ще направим това, като добавим метод view, който отговаря на GET заявки към маршрута /view. Маршрутът /view ще прочете URL адреса на S3 Findings URL чрез параметър на заявка. След това ще отвори 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 адреса „Преглед“, отпечатан в регистрационните файлове на сървъра, трябва да видим форматирана таблица с нашите резултати! Всъщност можем да въведем всеки предоставен от Nightfall подписан S3 URL (след като го екранираме) в параметъра findings_url на маршрута /view, за да го видим.

Разположете върху Render

Като дългогодишен потребител на 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, за да изпратите известие обратно от webhook до клиента, който е инициирал заявката за сканиране на файл
  • Създайте по-разширено правило за откриване, като използвате предварително изградени или персонализирани детектори
  • Добавете потребителски интерфейс, за да добавите повече интерактивни възможности, например позволявайки на потребителите да качват файлове или да четат файлове от URL адреси

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