ماسک کردن داده های حساس با استفاده از ماژول ورود به سیستم پایتون

ورود به سیستم یک موضوع ضروری و رایج در توسعه نرم افزار است که نقشی حیاتی در نظارت، اشکال زدایی و عیب یابی برنامه ها ایفا می کند. اگر گزارشها به درستی ایمن یا مدیریت نشوند، میتوانند به هدفی برای هکرها و سایر عوامل مخرب تبدیل شوند که ممکن است تلاش کنند به این دادههای حساس دسترسی پیدا کنند. با دور نگه داشتن دادههای حساس از گزارشها، میتوانید به محافظت از حریم خصوصی کاربران و کاهش خطر نقض دادهها یا سایر حوادث امنیتی کمک کنید.
بهترین روشهای بسیاری برای ورود به سیستم برای زبانهای برنامهنویسی مختلف وجود دارد، و این مقاله فقط بر روی پنهان کردن دادههای حساس در پایتون با استفاده از ماژول داخلی پایتون – ورود به سیستم تمرکز دارد.
در اینجا کد منبع آزمایشی در GitHub آمده است.
بیا شروع کنیم.
پیکربندی Logging را راه اندازی کنید
ابتدا فایلی با نام ایجاد می کنیم log.py با پیکربندی log مانند زیر. خروجی ها بر اساس آرگومان فرمت کننده قالب بندی می شوند. پیش فرض “کنسول” است.
# import modules
import re
import time
import logging
import logging.config
def init_logging(
log_level: str = "DEBUG", formatter: str = "console"
) -> logging.Logger:
LOG_CONFIG = {
"version": 1,
"handlers": {
"stdout": {
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
"formatter": formatter,
}
}, "formatters": {
"json": {
"format": (
'{"msg":"%(message)s","level":"%(levelname)s",'
'"file":"%(filename)s","line":%(lineno)d,'
'"module":"%(module)s","func":"%(funcName)s"}'
),
"datefmt": "%Y-%m-%dT%H:%M:%SZ",
},
"console": {
"format": "%(asctime)s %(levelname)s : %(message)s",
"datefmt": "%Y-%m-%dT%H:%M:%SZ",
},
},
"root": {"handlers": ["stdout"], "level": log_level},
}
logging.Formatter.converter = time.gmtime
logging.config.dictConfig(LOG_CONFIG)
logger = logging.getLogger(__name__)
return logger
سپس، یک فایل به نام ایجاد کنید main.py با کد زیر
from log import init_logging
LOG = init_logging()
if __name__ == "__main__":
test_case = (
"John [513-84-7329] made a payment with credit card 1234-5678-9012-3456."
)
LOG.info(test_case)
خروجی ها:
2024-06-03T06:45:38Z INFO : John [513-84-7329] made a payment with credit card 1234-5678-9012-3456.
بدیهی است که داده های حساسی در لاگ ها وجود دارد. 513-84-7329 شماره تامین اجتماعی ایالات متحده است، 1234-5678-9012-3456 یک شماره کارت اعتباری است. این داده ها بسیار محرمانه هستند و باید با یک سری ستاره پوشانده یا ویرایش شوند یا با یک مقدار درهم جایگزین شوند.
اگرچه فیلترها عمدتاً برای فیلتر کردن رکوردها بر اساس معیارهای پیچیدهتر از سطوح استفاده میشوند، اما آنها میتوانند هر رکوردی را که توسط کنترلکننده یا ثبتکنندهای که به آن متصل شدهاند پردازش میشود مشاهده کنند: اگر میخواهید کارهایی مانند شمارش تعداد را انجام دهید، میتواند مفید باشد. رکوردها توسط یک ثبتکننده یا کنترلکننده خاص پردازش میشوند، یا ویژگیهایی را در LogRecord در حال پردازش اضافه، تغییر یا حذف میکنند.
از logging.Filter https://docs.python.org/3/library/logging.html#filter-objects
با توجه به توضیحات بالا استفاده می کنم ورود به سیستم. فیلتر برای پوشاندن داده های حساس در گزارش ها.
راه اندازی logging.Filter در پیکربندی Logger
یک فیلتر ایجاد کنید و آن را در پیکربندی log پیکربندی کنید. کد زیر را اضافه کنید log.py فایل.
# 1. Define the regex patterns of sensitive data
regex_patterns = [
# U.S. Social Security numbers
r"\d{3}-\d{2}-\d{4}",
# Credit card numbers
r"\d{4}-\d{4}-\d{4}-\d{4}",
]
# 2. Define a filter class to mask sensitive data that match the pre-defined regex patterns
class SensitiveDataFilter(logging.Filter):
patterns = regex_patterns
def filter(self, record):
record.msg = self.mask_sensitive_data(record.msg)
return True
def mask_sensitive_data(self, message):
for pattern in self.patterns:
message = re.sub(pattern, "******", message)
return message
# 3. Add filter configuration in LOG_CONFIG
LOG_CONFIG = {
"version": 1,
"handlers": {
"stdout": {
...
# setup filters
"filters": ["sensitive_data_filter"],
}
},
# add filters in configuration
"filters": {
"sensitive_data_filter": {
"()": SensitiveDataFilter,
}
},
"formatters": {...},
"root": {...},
}
جایگزین کردن main.py با کد زیر ما پیام را در کنسول با استفاده از سه روش قالب بندی رشته برای مقایسه چاپ می کنیم. همه کار می کنند.
اگر به قالب بندی رشته در پایتون علاقه دارید، بهترین روش های قالب بندی رشته پایتون را بخوانید.
from log import init_logging
LOG = init_logging()
if __name__ == "__main__":
test_case = (
"John [513-84-7329] made a payment with credit card 1234-5678-9012-3456."
)
LOG.debug(f"use f-string: {test_case}")
LOG.debug("use str.format: {}".format(test_case))
LOG.debug("use string modulo method: %s" % (test_case))
خروجی ها:
2024-06-03T07:05:26Z DEBUG : use f-string: John [******] made a payment with credit card ******.
2024-06-03T07:05:26Z DEBUG : use str.format: John [******] made a payment with credit card ******.
2024-06-03T07:05:26Z DEBUG : use string modulo method: John [******] made a payment with credit card ******.
به روز رسانی ها:
- الگوهای regex داده های حساس را تعریف کنید
- یک کلاس فیلتر تعریف کنید SensitiveDataFilter برای پوشاندن داده های حساسی که با الگوهای regex مطابقت دارند
- افزودن و تنظیم تنظیمات فیلتر در LOG_CONFIG
فرآیند ماسک کردن واضح است. ما الگوهای regex را برای داده های حساس تعریف می کنیم و آنها را با ستاره جایگزین می کنیم.
داده های حساس را در مقادیر فرهنگ لغت بپوشانید
در مثال های بالا، داده های حساس دارای الگوهای regex هستند که به راحتی قابل تعریف هستند. با این حال، گاهی اوقات داده های حساس ما یک رشته تصادفی هستند، برای مثال، باید test_case را مانند زیر چاپ کنیم و فقط رمز عبور را پنهان کنیم. ما نمی توانیم یک الگوی regex برای رشته رمز عبور ‘fFwpUd!CJT4’ تعریف کنیم.
test_case = {'username': 'John Doe', 'password': 'fFwpUd!CJT4'}
پس از فرو رفتن در ماژول ورود، دو راه حل برای پنهان کردن داده های حساس در یک فرهنگ لغت پیدا کردم. آنها ممکن است بهترین نباشند، اما مشکل من را حل کردند.
گزینه 1. داده های حساس را با الگوی regex مطابق با record.msg ماسک کنید
راه حل اول از الگوی regex نیز استفاده می کند، اما این بار داده های حساس توسط کلید آن در فرهنگ لغت قرار می گیرند.
بیایید کلاس را به روز کنیم SensitiveDataFilter. کد زیر را اضافه کنید فایل log.py.
# Define a list of keys that values are sensitive data
sensitive_keys = (
"headers",
"credentials",
"Authorization",
"token",
"password",
)
# mask sensitive data in record.msg
class SensitiveDataFilter2(logging.Filter):
patterns = regex_patterns
sensitive_keys = sensitive_keys
def filter(self, record):
record.msg = self.mask_sensitive_msg(record.msg)
return True
def mask_sensitive_msg(self, message):
for pattern in self.patterns:
message = re.sub(pattern, "******", message)
# replace sensitive data with asterisks
for key in self.sensitive_keys:
pattern_str = rf"'{key}': '[^']+'"
replace = f"'{key}': '******'"
message = re.sub(pattern_str, replace, message)
return message
به روز رسانی ها:
- فهرستی از کلیدها را تعریف کنید که مقادیر آن ها داده های حساس هستند. این کلیدها برای مکان یابی داده های حساس در فرهنگ لغت استفاده می شوند.
- با پردازش record.msg، داده های حساس را در dictionary.values() بپوشانید. در عمل mask_sensitive_msg، رشته الگو را تعریف می کنیم ‘{key}’:[^’]+’. و رشته مسابقه را جایگزین کنید ‘{key}’: ******
خروجی ها:
2024-06-03T07:33:15Z DEBUG : use f-string: {'username': 'John Doe', 'password': '******'}
2024-06-03T07:33:15Z DEBUG : use str.format: {'username': 'John Doe', 'password': '******'}
2024-06-03T07:33:15Z DEBUG : use string modulo method: {'username': 'John Doe', 'password': '******'}
در راه حل، شی دیکشنری اساساً به عنوان بخشی از پیام log در نظر گرفته می شود. تنها تفاوت در الگوهای regex است. با این حال، از دیدگاه من پایدار نیست، برای سناریوهای خاص باعث خطا می شود. بنابراین راه حل دیگری را امتحان کردم.
گزینه 2. پوشاندن داده های حساس در record.args
هنگام بررسی مستندات ماژول ورود به سیستم، متوجه شدم که برای یک LogRecord، اطلاعات اولیه منتقل می شود پیام و ارگ، که با استفاده از آنها ترکیب می شوند msg % args برای ایجاد ویژگی پیام رکورد. با ارگ، می توانیم داده های حساس را قبل از ادغام در آنها پردازش کنیم پیام. در اینجا شرح است پیام و ارگ زمینه ها در الف LogRecord هدف – شی.
- پیام (هر کدام) – پیام توصیف رویداد، که میتواند یک رشته با فرمت %-با نگهدارندههای مکان برای دادههای متغیر، یا یک شی دلخواه باشد (به استفاده از اشیاء دلخواه به عنوان پیام مراجعه کنید).
- args (تبلی | دیکته[str, Any]) – داده های متغیر برای ادغام در آرگومان msg برای به دست آوردن توضیحات رویداد.
جالبه، درسته؟ بیایید کلاس را به روز کنیم SensitiveDataFilter مانند زیر تا کار کند.
class SensitiveDataFilter(logging.Filter):
patterns = regex_patterns
sensitive_keys = sensitive_keys
def filter(self, record):
try:
record.args = self.mask_sensitive_args(record.args)
record.msg = self.mask_sensitive_msg(record.msg)
return True
except Exception as e:
return True
def mask_sensitive_args(self, args):
if isinstance(args, dict):
new_args = args.copy()
for key in args.keys():
if key in sensitive_keys:
new_args[key] = "******"
else:
# mask sensitive data in dict values
new_args[key] = self.mask_sensitive_msg(args[key])
return new_args
# when there are multi arg in record.args
return tuple([self.mask_sensitive_msg(arg) for arg in args])
def mask_sensitive_msg(self, message):
# mask sensitive data in multi record.args
if isinstance(message, dict):
return self.mask_sensitive_args(message)
if isinstance(message, str):
for pattern in self.patterns:
message = re.sub(pattern, "******", message)
for key in self.sensitive_keys:
pattern_str = rf"'{key}': '[^']+'"
replace = f"'{key}': '******'"
message = re.sub(pattern_str, replace, message)
return message
جایگزین کردن main.py با کد زیر
from log import init_logging
LOG = init_logging()
if __name__ == "__main__":
test_case = {"username": "John Doe", "password": "xyz"}
LOG.debug("use args: %s", test_case)
LOG.debug("use multi args: %s %s", test_case, test_case)
خروجی ها:
2024-06-03T09:41:02Z DEBUG : use args: {'username': 'John Doe', 'password': '******'}
2024-06-03T09:41:02Z DEBUG : use multi args: {'username': 'John Doe', 'password': '******'} {'username': 'John Doe', 'password': '******'}
بیایید به روز رسانی ها را مرور کنیم.
- من یک تابع جدید به نام ایجاد کردم mask_sensitive_args برای پردازش داده های متغیر در رکورد.arg:
- وقتی args دیکشنری است: تکرار همه کلیدها، جفت مقدار dict. جایگزین مقدار حساس اگر کلید در کلیدهای حساس.
- وقتی args یک تاپل است: تکرار تاپل. آیتم هر تاپل ممکن است رشته یا دیکت باشد.
- اگر مورد یک رشته است، داده های حساس را با استفاده از الگوهای regex پنهان کنید
- اگر مورد یک دستور است، از mask_sensitive_args استفاده کنید
- تغییر عملکرد موجود mask_sensitive_msg به منظور پشتیبانی از چندین سناریو args.
- عملکرد فیلتر را با یک بلوک try-catch بپیچید. ما هنگام پوشاندن دادهها همه استثناها را میگیریم و اگر در حین ماسک کردن خطایی رخ داد، دادهها را بدون ماسک چاپ میکنیم.
خلاصه
- وقتی دادههای حساس را در گزارشها پنهان میکنید، اگر دادهها با الگوی رایج مطابقت دارند، برای مثال SSN یا کارت اعتباری، الگوی regex را انتخاب کنید.
- هنگام پوشاندن داده های حساس (رشته تصادفی) در فرهنگ لغت، از نام کلید حساس برای مکان یابی مقدار یا اهرم حساس استفاده کنید. ثبت.args.
- همیشه به سیاهه های مربوطه خود توجه داشته باشید.
منابع:
اطلاعات حساس را از گزارش ها دور نگه دارید
با تشکر برای خواندن. منتظر نظرات و ایده های شما هستیم.