برنامه نویسی

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

ورود به سیستم یک موضوع ضروری و رایج در توسعه نرم افزار است که نقشی حیاتی در نظارت، اشکال زدایی و عیب یابی برنامه ها ایفا می کند. اگر گزارش‌ها به درستی ایمن یا مدیریت نشوند، می‌توانند به هدفی برای هکرها و سایر عوامل مخرب تبدیل شوند که ممکن است تلاش کنند به این داده‌های حساس دسترسی پیدا کنند. با دور نگه داشتن داده‌های حساس از گزارش‌ها، می‌توانید به محافظت از حریم خصوصی کاربران و کاهش خطر نقض داده‌ها یا سایر حوادث امنیتی کمک کنید.

بهترین روش‌های بسیاری برای ورود به سیستم برای زبان‌های برنامه‌نویسی مختلف وجود دارد، و این مقاله فقط بر روی پنهان کردن داده‌های حساس در پایتون با استفاده از ماژول داخلی پایتون – ورود به سیستم تمرکز دارد.

در اینجا کد منبع آزمایشی در 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 ******.
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به روز رسانی ها:

  1. الگوهای regex داده های حساس را تعریف کنید
  2. یک کلاس فیلتر تعریف کنید SensitiveDataFilter برای پوشاندن داده های حساسی که با الگوهای regex مطابقت دارند
  3. افزودن و تنظیم تنظیمات فیلتر در 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
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به روز رسانی ها:

  1. فهرستی از کلیدها را تعریف کنید که مقادیر آن ها داده های حساس هستند. این کلیدها برای مکان یابی داده های حساس در فرهنگ لغت استفاده می شوند.
  2. با پردازش 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': '******'}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید به روز رسانی ها را مرور کنیم.

  1. من یک تابع جدید به نام ایجاد کردم mask_sensitive_args برای پردازش داده های متغیر در رکورد.arg:
  2. وقتی args دیکشنری است: تکرار همه کلیدها، جفت مقدار dict. جایگزین مقدار حساس اگر کلید در کلیدهای حساس.
  3. وقتی args یک تاپل است: تکرار تاپل. آیتم هر تاپل ممکن است رشته یا دیکت باشد.
    • اگر مورد یک رشته است، داده های حساس را با استفاده از الگوهای regex پنهان کنید
    • اگر مورد یک دستور است، از mask_sensitive_args استفاده کنید

جریان منطق فرآیند

  1. تغییر عملکرد موجود mask_sensitive_msg به منظور پشتیبانی از چندین سناریو args.
  2. عملکرد فیلتر را با یک بلوک try-catch بپیچید. ما هنگام پوشاندن داده‌ها همه استثناها را می‌گیریم و اگر در حین ماسک کردن خطایی رخ داد، داده‌ها را بدون ماسک چاپ می‌کنیم.

خلاصه

  1. وقتی داده‌های حساس را در گزارش‌ها پنهان می‌کنید، اگر داده‌ها با الگوی رایج مطابقت دارند، برای مثال SSN یا کارت اعتباری، الگوی regex را انتخاب کنید.
  2. هنگام پوشاندن داده های حساس (رشته تصادفی) در فرهنگ لغت، از نام کلید حساس برای مکان یابی مقدار یا اهرم حساس استفاده کنید. ثبت.args.
  3. همیشه به سیاهه های مربوطه خود توجه داشته باشید.

منابع:
اطلاعات حساس را از گزارش ها دور نگه دارید

با تشکر برای خواندن. منتظر نظرات و ایده های شما هستیم.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا