Русский
Русский
English
Статистика
Реклама

Restful api

Взаимодействие с Check Point SandBlast через API

11.09.2020 12:04:11 | Автор: admin

Эта статья будет полезна тем, кто знаком с технологиями Check Point по эмуляции файлов (Threat Emulation) и проактивной очистке файлов (Threat Extraction) и желает сделать шаг в сторону автоматизации данных задач. У Check Point есть Threat Prevention API, который работает как в облаке , так и на локальных устройствах, и функционально он идентичен проверке файлов в потоках web/smtp/ftp/smb/nfs трафика. Данная статья отчасти является авторской трактовкой набора статей из официальной документации, но построенная на своем опыте эксплуатации и на собственных примерах. Также в статье вы найдете авторские коллекции Postman для работы с Threat Prevention API.

Основные сокращения

Threat Prevention API работает с тремя основными компонентами, которые в API вызываются через следующие текстовые значения:

av - компонент Anti-Virus, отвечает за сигнатурный анализ известных угроз.

te - компонент Threat Emulation, отвечает за проверку файлов в песочнице, и вынесение вердикта зловредный (malicious)/чистый(benign) после эмуляции.

extraction - компонент Threat Extraction, отвечающий за быструю конвертацию офисных документов в безопасный вид (в котором удаляется весь потенциально вредоносный контент), в целях их быстрой доставки пользователям/системам.

Структура API и основные ограничения

Threat Prevention API использует всего 4 запроса - upload, query, download и quota. В заголовке для всех четырех запросов нужно передать API ключ, используя параметр Authorization. На первый взгляд, структура может показаться гораздо проще, чем в Management API, но количество полей в запросах upload и query и структура этих запросов достаточно комплексные. Их можно функционально сравнить с профилями Threat Prevention в политике безопасности шлюза/песочницы.

На данный момент, выпущена единственная версия Threat Prevention API - 1.0, в URL для API вызовов следует указывать v1 в той части, где требуется указать версию. В отличии от Management API, указывать версию API в URL адресе обязательно, иначе запрос не выполнится.

Компонент Anti-Virus при вызове без других компонентов (te, extraction) на данный момент поддерживает только запросы query с md5 хэш суммами. Threat Emulation и Threat Extraction поддерживает также sha1 и sha256 хэш суммы.

Очень важно не делать ошибок в запросах! Запрос может быть исполнен без ошибки, но не полностью. Чуть забегая вперед рассмотрим что может происходить при ошибках/опечатках в запросах.

Запрос с опечаткой с слове reports(reportss)
{ "request":  [  {"sha256": {{sha256}},"features": ["te"] , "te": {"images": [                    {                        "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",                        "revision": 1                    }                ],                reportss: ["tar", "pdf", "xml"]            }}] }
В ответе ошибки не будет, но при этом информации об отчетах не будет вовсе
{  "response": [    {      "status": {        "code": 1001,        "label": "FOUND",        "message": "The request has been fully answered."      },      "sha256": "9cc488fa6209caeb201678f8360a6bb806bd2f85b59d108517ddbbf90baec33a",      "file_type": "pdf",      "file_name": "",      "features": [        "te"      ],      "te": {        "trust": 10,        "images": [          {            "report": {              "verdict": "malicious"            },            "status": "found",            "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",            "revision": 1          }        ],        "score": -2147483648,        "combined_verdict": "malicious",        "severity": 4,        "confidence": 3,        "status": {          "code": 1001,          "label": "FOUND",          "message": "The request has been fully answered."        }      }    }  ]}
А вот на запрос без опечатки в ключе reports
{ "request":  [  {"sha256": {{sha256}},"features": ["te"] , "te": {"images": [                    {                        "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",                        "revision": 1                    }                ],                reports: ["tar", "pdf", "xml"]            }}] }
Мы получаем ответ, в котором уже содержатся id для загрузки отчетов
{  "response": [    {      "status": {        "code": 1001,        "label": "FOUND",        "message": "The request has been fully answered."      },      "sha256": "9cc488fa6209caeb201678f8360a6bb806bd2f85b59d108517ddbbf90baec33a",      "file_type": "pdf",      "file_name": "",      "features": [        "te"      ],      "te": {        "trust": 10,        "images": [          {            "report": {              "verdict": "malicious",              "full_report": "b684066e-e41c-481a-a5b4-be43c27d8b65",              "pdf_report": "e48f14f1-bcc7-4776-b04b-1a0a09335115",              "xml_report": "d416d4a9-4b7c-4d6d-84b9-62545c588963"            },            "status": "found",            "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",            "revision": 1          }        ],        "score": -2147483648,        "combined_verdict": "malicious",        "severity": 4,        "confidence": 3,        "status": {          "code": 1001,          "label": "FOUND",          "message": "The request has been fully answered."        }      }    }  ]}

Если же отправить неправильный/просроченный API ключ, то в ответ получим ошибку 403.

SandBlast API: в облаке и на локальных устройствах

API запросы можно отправлять на устройства Check Point, на которых включен компонент (blade) Threat Emulation. В качестве адреса для запросов нужно использовать ip/url устройства и порт 18194 (например - https://10.10.57.19:18194/tecloud/api/v1/file/query). Также следует убедиться в том, что политикой безопасности на устройстве разрешено такое подключение. Авторизация через API ключ на локальных устройствах по умолчанию выключена и ключ Authorization в заголовках запросов можно не отправлять вовсе.

API запросы в облако CheckPoint нужно отправлять на адрес te.checkpoint.com (например - https://te.checkpoint.com/tecloud/api/v1/file/query). API ключ можно получить в виде триальной лицензии на 60 дней, обратившись к партнерам Check Point или в локальный офис компании.

На локальных устройствах Threat Extraction пока не поддерживается в стандартном Threat Prevention API и следует использовать Threat Prevention API for Security Gateway (о нем мы поговорим подробнее в конце статьи).

Локальные устройства не поддерживают запрос quota.

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

Вызов Upload API

Используемый метод - POST

Адрес для вызова - https://<service_address>/tecloud/api/v1/file/upload

Запрос состоит из двух частей (form-data): файла предназначенный для эмуляции/очистки и тела запроса с текстом.

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

Необходимый минимум для запроса upload

HTTP POST

https://<service_address>/tecloud/api/v1/file/upload

Headers:

Authorization: <api_key>

Body

{

"request":{

}

}

File

File

В таком случае файл на обработку попадет в соответствии с параметрами по умолчанию: компонент - te, образы ОС - Win XP и Win 7, без генерации отчета.

Комментарии по основным полям в текстовом запросе:

file_name и file_type можно оставить пустыми или не отправлять вовсе, так как это не особо полезная информация при загрузке файла. В API ответе данные поля заполнятся автоматически на основе имени загружаемого файла, а информацию в кэше все равно придется искать по md5/sha1/sha256 hash суммам.

Пример запроса с пустыми file_name и file_type
{"request":{"file_name": "","file_type": "",}}

features - список, в котором указывается необходимый функционал при обработке в песочнице - av (Anti-Virus), te (Threat Emulation), extraction (Threat Extraction). Если данный параметр не передать вовсе, то будет задействован только компонент по умолчанию - te(Threat Emulation).

Чтобы включить проверку в трех доступных компонентах, нужно указать эти компоненты в API запросе.

Пример запроса с проверкой в av, te и extraction
{ "request":  [  {"sha256": {{sha256}},"features": ["av", "te", "extraction"]  }] }

Ключи в разделе te

images - список, внутри которого должны быть указаны словари с id и номером ревизии операционных систем, в которых будет выполняться проверка. ID и номера ревизий одинаковые для всех локальных устройств и облака.

Список операционных систем и ревизий

Available OS Image ID

Revision

Image OS and Application

e50e99f3-5963-4573-af9e-e3f4750b55e2

1

Microsoft Windows: XP - 32bit SP3
Office: 2003, 2007
Adobe Acrobat Reader: 9.0
Flash Player9r115 andActiveX10.0
Java Runtime:1.6.0u22

7e6fe36e-889e-4c25-8704-56378f0830df

1

Microsoft Windows: 7 - 32bit
Office: 2003, 2007
Adobe Acrobat Reader: 9.0
Flash Player:10.2r152 (Plugin&ActiveX)
Java Runtime:1.6.0u0

8d188031-1010-4466-828b-0cd13d4303ff

1

Microsoft Windows: 7 - 32bit
Office: 2010
Adobe Acrobat Reader: 9.4
Flash Player:11.0.1.152 (Plugin&ActiveX)
Java Runtime:1.7.0u0

5e5de275-a103-4f67-b55b-47532918fa59

1

Microsoft Windows: 7 - 32bit
Office: 2013
Adobe Acrobat Reader: 11.0
Flash Player:15 (Plugin&ActiveX)
Java Runtime:1.7.0u9

3ff3ddae-e7fd-4969-818c-d5f1a2be336d

1

Microsoft Windows: 7 - 64bit
Office: 2013 (32bit)
Adobe Acrobat Reader: 11.0.01
Flash Player:13 (Plugin&ActiveX)
Java Runtime:1.7.0u9

6c453c9b-20f7-471a-956c-3198a868dc92

1

Microsoft Windows: 8.1 - 64bit
Office: 2013 (64bit)
Adobe Acrobat Reader: 11.0.10
Flash Player:18.0.0.160 (Plugin&ActiveX)
Java Runtime:1.7.0u9

10b4a9c6-e414-425c-ae8b-fe4dd7b25244

1

Microsoft Windows: 10
Office: Professional Plus 2016 en-us
Adobe Acrobat Reader: DC 2015 MUI
Flash Player:20 (Plugin&ActiveX)
Java Runtime:1.7.0u9

Если ключ images не указать вовсе, то эмуляция будет проходить в образах, рекомендованных Check Point (на данный момент это Win XP и Win 7). Данные образы рекомендованы исходя из соображений наилучшего баланса производительности и catch rate.

reports - список отчетов, которые мы запрашиваем на случай, если файл окажется вредоносным. Доступны следующие варианты:

  1. summary - .tar.gz архив, содержащий в себе отчет об эмуляции по всем запрошенным image'ам (как html страницу, так и такие компоненты как видеоролик из ОС эмулятора, дамп сетевого трафика, отчет в json, так и сам сэмпл в архиве под защитой пароля). В ответе ищем ключ - summary_report для последующей загрузки отчета.

  2. pdf - документ об эмуляции в одном image, который многие привыкли получать через Smart Console. В ответе ищем ключ - pdf_report для последующей загрузки отчета.

  3. xml - документ об эмуляции в одном image, удобный для последующего парсинга параметров в отчете. В ответе ищем ключ - xml_report для последующей загрузки отчета.

  4. tar - .tar.gz архив, содержащий в себе отчет об эмуляции в одном запрошенным image'ам (как html страницу, так и такие компоненты как видеоролик из ОС эмулятора, дамп сетевого трафика, отчет в json, так и сам сэмпл в архиве под защитой пароля). В ответе ищем ключ - full_report для последующей загрузки отчета.

Что внутри отчета summary
Ключи full_report, pdf_report, xml_report есть в словаре для каждой ОС
{  "response": [    {      "status": {        "code": 1001,        "label": "FOUND",        "message": "The request has been fully answered."      },      "sha256": "9e6f07d03b37db0d3902bde4e239687a9e3d650e8c368188c7095750e24ad2d5",      "file_type": "html",      "file_name": "",      "features": [        "te"      ],      "te": {        "trust": 10,        "images": [          {            "report": {              "verdict": "malicious",              "full_report": "8d18067e-b24d-4103-8469-0117cd25eea9",              "pdf_report": "05848b2a-4cfd-494d-b949-6cfe15d0dc0b",              "xml_report": "ecb17c9d-8607-4904-af49-0970722dd5c8"            },            "status": "found",            "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",            "revision": 1          },          {            "report": {              "verdict": "malicious",              "full_report": "d7c27012-8e0c-4c7e-8472-46cc895d9185",              "pdf_report": "488e850c-7c96-4da9-9bc9-7195506afe03",              "xml_report": "e5a3a78d-c8f0-4044-84c2-39dc80ddaea2"            },            "status": "found",            "id": "6c453c9b-20f7-471a-956c-3198a868dc92",            "revision": 1          }        ],        "score": -2147483648,        "combined_verdict": "malicious",        "severity": 4,        "confidence": 3,        "status": {          "code": 1001,          "label": "FOUND",          "message": "The request has been fully answered."        }      }    }  ]}
А вот ключ summary_report - есть один для эмуляции в целом
{  "response": [    {      "status": {        "code": 1001,        "label": "FOUND",        "message": "The request has been fully answered."      },      "sha256": "d57eadb7b2f91eea66ea77a9e098d049c4ecebd5a4c70fb984688df08d1fa833",      "file_type": "exe",      "file_name": "",      "features": [        "te"      ],      "te": {        "trust": 10,        "images": [          {            "report": {              "verdict": "malicious",              "full_report": "c9a1767b-741e-49da-996f-7d632296cf9f",              "xml_report": "cc4dbea9-518c-4e59-b6a3-4ea463ca384b"            },            "status": "found",            "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",            "revision": 1          },          {            "report": {              "verdict": "malicious",              "full_report": "ba520713-8c0b-4672-a12f-0b4a1575b913",              "xml_report": "87bdb8ca-dc44-449d-a9ab-2d95e7fe2503"            },            "status": "found",            "id": "6c453c9b-20f7-471a-956c-3198a868dc92",            "revision": 1          }        ],        "score": -2147483648,        "combined_verdict": "malicious",        "severity": 4,        "confidence": 3,        "summary_report": "7e7db12d-5df6-4e14-85f3-2c1e29cd3e34",        "status": {          "code": 1001,          "label": "FOUND",          "message": "The request has been fully answered."        }      }    }  ]}

Можно запросить одновременно отчеты tar и xml и pdf, можно summary и tar и xml. Одновременно запросить summary отчет и pdf не получится.

Ключи в разделе extraction

Для threat extraction используется всего два ключа:

method - pdf(конвертация в pdf, используется по умолчанию) или clean(очистка активного содержимого).

extracted_parts_codes - список кодов для удаления активного содержимого, применимо только для метода clean

Коды для удаления содержимого из файлов

Code

Description

1025

Linked Objects

1026

Macros and Code

1034

Sensitive Hyperlinks

1137

PDF GoToR Actions

1139

PDF Launch Actions

1141

PDF URI Actions

1142

PDF Sound Actions

1143

PDF Movie Actions

1150

PDF JavaScript Actions

1151

PDF Submit Form Actions

1018

Database Queries

1019

Embedded Objects

1021

Fast Save Data

1017

Custom Properties

1036

Statistic Properties

1037

Summary Properties

Для загрузки очищенной копии потребуется сделать ещё и запрос query (о нем пойдет речь далее) через несколько секунд, указав hash сумму файла и компонент extraction в тексте запроса. Забрать очищенный файл можно будет с помощью id из ответа на запрос query - extracted_file_download_id. Ещё раз, чуть забегая вперед, привожу примеры запроса и ответа query для поиска id на загрузку очищенного документа.

Запрос query для поиска ключа extracted_file_download_id
{ "request":  [  {"sha256": "9a346005ee8c9adb489072eb8b5b61699652962c17596de9c326ca68247a8876","features": ["extraction"] , "extraction": {        "method": "pdf"            }}] }
Ответ на запрос query (найдите ключ extracted_file_download_id)
{    "response": [        {            "status": {                "code": 1001,                "label": "FOUND",                "message": "The request has been fully answered."            },            "sha256": "9a346005ee8c9adb489072eb8b5b61699652962c17596de9c326ca68247a8876",            "file_type": "",            "file_name": "",            "features": [                "extraction"            ],            "extraction": {                "method": "pdf",                "extract_result": "CP_EXTRACT_RESULT_SUCCESS",                "extracted_file_download_id": "b5f2b34e-3603-4627-9e0e-54665a531ab2",                "output_file_name": "kp-20-xls.cleaned.xls.pdf",                "time": "0.013",                "extract_content": "Macros and Code",                "extraction_data": {                    "input_extension": "xls",                    "input_real_extension": "xls",                    "message": "OK",                    "output_file_name": "kp-20-xls.cleaned.xls.pdf",                    "protection_name": "Potential malicious content extracted",                    "protection_type": "Conversion to PDF",                    "protocol_version": "1.0",                    "risk": 5.0,                    "scrub_activity": "Active content was found - XLS file was converted to PDF",                    "scrub_method": "Convert to PDF",                    "scrub_result": 0.0,                    "scrub_time": "0.013",                    "scrubbed_content": "Macros and Code"                },                "tex_product": false,                "status": {                    "code": 1001,                    "label": "FOUND",                    "message": "The request has been fully answered."                }            }        }    ]}

Общие сведения

В одном API вызове можно отправить только один файл на проверку.

Компонент av не требует дополнительного раздела с ключами, достаточно указать его в словаре features.

Вызов Query API

Используемый метод - POST

Адрес для вызова - https://<service_address>/tecloud/api/v1/file/query

Перед тем как отправлять файл на загрузку (запрос upload), желательно выполнить проверку кэша песочницы (запрос query) в целях оптимизации нагрузки на API сервер, так как возможно на API сервере уже есть информация и вердикт по загружаемому файлу. Вызов состоит только из текстовой части. Обязательная часть запроса - sha1/sha256/md5 hash сумма файла. Её кстати можно получить в ответе на запрос upload.

Необходимый минимум для запроса query

HTTP POST

https://<service_address>/tecloud/api/v1/file/query

Headers:

Authorization: <api_key>

Body

{

"request":{

"sha256": <sha256 hash sum>

}

}

Пример ответа на запрос upload, где видны sha1/md5/sha256 hash суммы
{  "response": {    "status": {      "code": 1002,      "label": "UPLOAD_SUCCESS",      "message": "The file was uploaded successfully."    },    "sha1": "954b5a851993d49ef8b2412b44f213153bfbdb32",    "md5": "ac29b7c26e7dcf6c6fdb13ac0efe98ec",    "sha256": "313c0feb009356495b7f4a60e96737120beb30e1912c6d866218cee830aebd90",    "file_type": "",    "file_name": "kp-20-doc.doc",    "features": [      "te"    ],    "te": {      "trust": 0,      "images": [        {          "report": {            "verdict": "unknown"          },          "status": "not_found",          "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",          "revision": 1        }      ],      "score": -2147483648,      "status": {        "code": 1002,        "label": "UPLOAD_SUCCESS",        "message": "The file was uploaded successfully."      }    }  }}

Запрос query помимо hash суммы в идеале должен быть таким же, как был (или планируется быть) запрос upload, или даже "уже" (содержать в запросе query меньше полей чем в запросе upload). В случае, когда запрос query содержит в себе больше полей, чем было в запросе upload, вы получите в ответе не всю требуемую информацию.

Вот пример ответа на запрос query, где были найдены не все требуемые данные
{  "response": [    {      "status": {        "code": 1006,        "label": "PARTIALLY_FOUND",        "message": "The request cannot be fully answered at this time."      },      "sha256": "313c0feb009356495b7f4a60e96737120beb30e1912c6d866218cee830aebd90",      "file_type": "doc",      "file_name": "",      "features": [        "te",        "extraction"      ],      "te": {        "trust": 10,        "images": [          {            "report": {              "verdict": "malicious",              "pdf_report": "4e9cddaf-03a4-489f-aa03-3c18f8d57a52",              "xml_report": "9c18018f-c761-4dea-9372-6a12fcb15170"            },            "status": "found",            "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",            "revision": 1          }        ],        "score": -2147483648,        "combined_verdict": "malicious",        "severity": 4,        "confidence": 1,        "status": {          "code": 1001,          "label": "FOUND",          "message": "The request has been fully answered."        }      },      "extraction": {        "method": "pdf",        "tex_product": false,        "status": {          "code": 1004,          "label": "NOT_FOUND",          "message": "Could not find the requested file. Please upload it."        }      }    }  ]}

Обратите внимание на поля code и label. Данные поля встречаются три раза в словарях status. Вначале видим глобальный ключ "code": 1006 и "label": "PARTIALLY_FOUND". Далее данные ключи встречаются по каждому отдельному компоненту, которые мы запросили - te и extraction. И если для te понятно, что данные найдены, то для extraction информация отсутствует.

Вот так выглядел запрос query для примера выше
{ "request":  [  {"sha256": {{sha256}},"features": ["te", "extraction"] , "te": {"images": [                    {                        "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",                        "revision": 1                    }                ],                "reports": [                    "xml", "pdf"                ]            }}] }
Если отправить запрос query без компонента extraction
{ "request":  [  {"sha256": {{sha256}},"features": ["te"] , "te": {"images": [                    {                        "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",                        "revision": 1                    }                ],                "reports": [                    "xml", "pdf"                ]            }}] }
То и в ответе будет полная информация ("code": 1001, "label": "FOUND")
{  "response": [    {      "status": {        "code": 1001,        "label": "FOUND",        "message": "The request has been fully answered."      },      "sha256": "313c0feb009356495b7f4a60e96737120beb30e1912c6d866218cee830aebd90",      "file_type": "doc",      "file_name": "",      "features": [        "te"      ],      "te": {        "trust": 10,        "images": [          {            "report": {              "verdict": "malicious",              "pdf_report": "4e9cddaf-03a4-489f-aa03-3c18f8d57a52",              "xml_report": "9c18018f-c761-4dea-9372-6a12fcb15170"            },            "status": "found",            "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",            "revision": 1          }        ],        "score": -2147483648,        "combined_verdict": "malicious",        "severity": 4,        "confidence": 1,        "status": {          "code": 1001,          "label": "FOUND",          "message": "The request has been fully answered."        }      }    }  ]}
Если никакой информации в кэше нет вовсе, то в ответе будет "label": "NOT_FOUND"
{  "response": [    {      "status": {        "code": 1004,        "label": "NOT_FOUND",        "message": "Could not find the requested file. Please upload it."      },      "sha256": "313c0feb009356495b7f4a60e96737120beb30e1912c6d866218cee830aebd91",      "file_type": "",      "file_name": "",      "features": [        "te"      ],      "te": {        "trust": 0,        "images": [          {            "report": {              "verdict": "unknown"            },            "status": "not_found",            "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",            "revision": 1          }        ],        "score": -2147483648,        "status": {          "code": 1004,          "label": "NOT_FOUND",          "message": "Could not find the requested file. Please upload it."        }      }    }  ]}

В одном API вызове можно отправить сразу несколько хэш сумм на проверку. В ответе будут возвращены данные в том же самом порядке, как они были отправлены в запросе.

Пример запроса query с несколькими sha256 суммами
{ "request":  [  {"sha256": "b84531d3829bf6131655773a3863d6b16f6389b7f4036aef9b81c0cb60e7fd81"        },        {"sha256": "b84531d3829bf6131655773a3863d6b16f6389b7f4036aef9b81c0cb60e7fd82"        }] }
Ответ на запрос query с несколькими sha256 суммами
{  "response": [    {      "status": {        "code": 1001,        "label": "FOUND",        "message": "The request has been fully answered."      },      "sha256": "b84531d3829bf6131655773a3863d6b16f6389b7f4036aef9b81c0cb60e7fd81",      "file_type": "dll",      "file_name": "",      "features": [        "te"      ],      "te": {        "trust": 10,        "images": [          {            "report": {              "verdict": "malicious"            },            "status": "found",            "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",            "revision": 1          }        ],        "score": -2147483648,        "combined_verdict": "malicious",        "severity": 4,        "confidence": 3,        "status": {          "code": 1001,          "label": "FOUND",          "message": "The request has been fully answered."        }      }    },    {      "status": {        "code": 1004,        "label": "NOT_FOUND",        "message": "Could not find the requested file. Please upload it."      },      "sha256": "b84531d3829bf6131655773a3863d6b16f6389b7f4036aef9b81c0cb60e7fd82",      "file_type": "",      "file_name": "",      "features": [        "te"      ],      "te": {        "trust": 0,        "images": [          {            "report": {              "verdict": "unknown"            },            "status": "not_found",            "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",            "revision": 1          }        ],        "score": -2147483648,        "status": {          "code": 1004,          "label": "NOT_FOUND",          "message": "Could not find the requested file. Please upload it."        }      }    }  ]}

Запрос сразу нескольких hash сумму в запросе query также благоприятно скажется на производительности API сервера.

Вызов Download API

Используемый метод - POST (согласно документации), GET также работает (и может показаться более логичным)

Адрес для вызова - https://<service_address>/tecloud/api/v1/file/download?id=<id>

В заголовке требуется передать API ключ, тело запроса - пустое, id для загрузки передается в url адресе.

В ответ на query запрос, в случае если эмуляция завершена и при загрузке файла были запрошены отчеты, будут видны id для загрузки отчетов. В случае, если запрашивается очищенная копия, то следует искать id для загрузки очищенного документа.

Итого, ключами в ответе на запрос query, содержащими значение id для загрузки могут быть:

  • summary_report

  • full_report

  • pdf_report

  • xml_report

  • extracted_file_download_id

Безусловно, чтобы в ответе на запрос query были получена эти ключи, их нужно указать в запросе (для отчетов) или не забыть сделать запрос по функции extraction (для очищенных документов)

Вызов Quota API

Используемый метод - POST

Адрес для вызова - https://<service_address>/tecloud/api/v1/file/quota

Для проверки оставшейся квоты в облаке используется запрос quota. Тело запроса пустое.

Пример ответа на запрос quota
{  "response": [    {      "remain_quota_hour": 1250,      "remain_quota_month": 10000000,      "assigned_quota_hour": 1250,      "assigned_quota_month": 10000000,      "hourly_quota_next_reset": "1599141600",      "monthly_quota_next_reset": "1601510400",      "quota_id": "TEST",      "cloud_monthly_quota_period_start": "1421712300",      "cloud_monthly_quota_usage_for_this_gw": 0,      "cloud_hourly_quota_usage_for_this_gw": 0,      "cloud_monthly_quota_usage_for_quota_id": 0,      "cloud_hourly_quota_usage_for_quota_id": 0,      "monthly_exceeded_quota": 0,      "hourly_exceeded_quota": 0,      "cloud_quota_max_allow_to_exceed_percentage": 1000,      "pod_time_gmt": "1599138715",      "quota_expiration": "0",      "action": "ALLOW"    }  ]}

Threat Prevention API for Security Gateway

Данный API был разработан раньше, чем Threat Prevention API и предназначался только для локальных устройств. На данный момент он может быть полезен только в том случае, если вам нужен Threat Extraction API. Для Threat Emulation лучше использовать обычный Threat Prevention API. Чтобы включить TP API for SG и сконфигурировать API ключ требуется выполнить действия из sk113599. Рекомендую обратить внимание на шаг 6b и проверить доступность страницы https://<IPAddressofSecurityGateway>/UserCheck/TPAPI потому как в случае отрицательного результата дальнейшая конфигурация не имеет смысла. На данный url будут отправляться все API вызовы. Тип вызова (upload/query) регулируется в ключе тела вызова - request_name. Также обязательными ключами являются - api_key (нужно запомнить его в процессе конфигурации) и protocol_version (на данный момент актуальная версия 1.1). Официальную документацию для данного API вы можете найти в sk137032. К относительным преимуществам можно отнести возможность отправлять сразу несколько файлов на эмуляцию при их загрузке, так как файлы отправляются в виде текстовой строки base64. Чтобы кодировать/декодировать файлы в/из base64 можно использовать для целей демонстрации в Postman онлайн конвертер, например - https://base64.guru. В практических целях при написании кода следует использовать встроенные методы encode и decode.

Теперь остановимся подробнее на функциях te и extraction в данном API.

Для компонента te предусмотрен словарь te_options в запросах upload/query, а ключи в данном запросе полностью совпадают с ключами te в Threat Prevention API.

Пример запроса для эмуляции файла в Win10 с отчетами
{"request": [{    "protocol_version": "1.1",    "api_key": "<api_key>",    "request_name": "UploadFile",    "file_enc_data": "<base64_encoded_file>",    "file_orig_name": "<filename>",    "te_options": {        "images": [                {                    "id": "10b4a9c6-e414-425c-ae8b-fe4dd7b25244",                    "revision": 1                }            ],        "reports": ["summary", "xml"]    }    }    ]}

Для компонента extraction предусмотрен словарь scrub_options. В данном запросе указывается метод очистки: конвертация в PDF, очистка от активного содержимого или же выбрать режим в соответствии с профилем Threat Prevention(указывается имя профиля). Отличительной особенностью ответа на API запрос с extraction для файла является то, что вы получаете очищенную копию в ответе на этот запрос в виде шифрованной строки base64 (вам не нужно делать запрос query и искать id для загрузки документа)

Пример запроса на очистку файла
    {"request": [{"protocol_version": "1.1","api_key": "<API_KEY>","request_name": "UploadFile","file_enc_data": "<base64_encoded_file>","file_orig_name": "hi.txt","scrub_options": {"scrub_method": 2}}]}
Ответ на запрос
{"response": [{"protocol_version": "1.1","src_ip": "<IP_ADDRESS>","scrub": {"file_enc_data": "<base64_encoded_converted_to_PDF_file>","input_real_extension": "js","message": "OK","orig_file_url": "","output_file_name": "hi.cleaned.pdf","protection_name": "Extract potentially malicious content","protection_type": "Conversion to PDF","real_extension": "txt","risk": 0,"scrub_activity": "TXT file was converted to PDF","scrub_method": "Convert to PDF","scrub_result": 0,"scrub_time": "0.011","scrubbed_content": ""}}]} 

Несмотря на то, что для получения очищенной копии требуется меньше API запросов, я считаю такой вариант менее предпочтительным и удобным, нежели запрос form-data, используемый в Threat Prevention API.

Коллекции Postman

Мною были созданы коллекции в Postman как для Threat Prevention API, так и для Threat Prevention API for Security Gateway, где представлены наиболее распространённые API запросы. Для того, чтобы ip/url API сервера и ключ подставлялись в запросы автоматически, а hash сумма sha256 после загрузки файла также запоминалась, внутри коллекций созданы три переменные(найти их можно перейдя в настройках коллекции Edit -> Variables): te_api(требуется заполнить), api_key(требуется заполнить, кроме случая использования TP API с локальными устройствами), sha256 (оставить пустым, в TP API for SG не используется).

Скачать коллекцию Postman для Threat Prevention API

Скачать коллекцию Postman для Threat Prevention for Security Gateway API

Примеры использования

В сообществе Check Mates представлены скрипты, написанные на Python, которые проверяют файлы из нужной директории как через TP API, так и TP API for SG.

Подробнее..

Fastify.js не только самый быстрый веб-фреймворк для node.js

04.05.2021 18:23:50 | Автор: admin
Последние 10 лет среди веб-фреймворков для node.js самой большой популярностью пользуется Express.js. Всем, кто с ним работал, известно, что сложные приложения на Express.js бывает сложно структурировать. Но, как говорится, привычка вторая натура. От Express.js бывает сложно отказаться. Как, например, сложно бросить курить. Кажется, что нам непременно нужна эта бесконечная цепь middleware, и если у нас забрать возможность создавать их по любому поводу и без повода проект остановится.

Отрадно, что сейчас, наконец, появился достойный претендент на место главного веб-фреймворка всех и вся я имею в виду не Fastify.js, а, конечно же, Nest.js. Хотя по количественным показателям популярности, до Express.js ему очень и очень далеко.

Таблица. Показатели популярности пакетов по данным npmjs.org, github.com
Пакет Количество загрузок Количество звезд
1 connect 4 373 963 9 100
2 express 16 492 569 52 900
3 koa 844 877 31 100
4 nestjs 624 603 36 700
5 hapi 389 530 13 200
6 fastify 216 240 18 600
7 restify 93 665 10 100
8 polka 71 394 4 700


Express.js по-прежнему работает в более чем в 2/3 веб-приложений для node.js. Более того, 2/3 наиболее популярных веб-фреймворков для node.js используют подходы Express.js. (Точнее было бы сказать, подходы библиотеки Connect.js, на которой до версии 4 базировался Express.js).

В предлагаемом сообщении обсуждаются особенности основных веб-фреймворков для node.js, и что делает Fastify.js фреймворком другого уровня, что позволяет выбрать его как фреймворк для разработки Вашего следующего проекта.


Критика фреймворков, основаных на синхронных middleware



Что же плохого может быть в таком коде?

app.get('/', (req, res) => {  res.send('Hello World!')})


1. Функция, которая обрабатывает роут, не возвращает значение. Вместо этого необходимо вызвать один из методов объекта response (res). Если это метод не будет вызван явно, даже после возврата из функции клиент и сервер останутся в состоянии ожидания ответа сервера пока для каждого из них не истечет таймаут. Это только прямые убытки, но есть еще и упущенная выгода. То что эта функция не возвращает значения, делает невозможным просто реализовать востребованную функциональность, например валидацию или логирование возвращаемых клиенту ответов.

2. Обработка ошибок всегда синхронная. Редкий роут обходится без вызовов асинхронных операций. Так как Express.js создавался в допромисовую эру, стандартный обработчик ошибок не сработает и нужно ошибки обрабатывать так:

app.get('/', async (req, res, next) => {   try {      ...   } catch (ex) {      next(ex);   }})


или так:

app.get('/', (req, res, next) => {   doAcync().catch(next)})


3. Сложность асинхронной инициализации сервисов. Например, приложение работает с базой данных и обращается к базе данных как к сервису, сохранив ссылку в переменной. Инициализация роутов в Express.js всегда синхронная. Это означает, что когда на роуты начнут приходить первые запросы клиентов, асинхронная инициализация сервиса, вероятно еще не успеет отработать, так что придется тащить в роуты асинхронный код с получением ссылки на этот сервис. Все это, конечно, реализуемо. Но слишком далеко уходит от наивной простоты изначального кода:

app.get('/', (req, res) => {  res.send('Hello World!')})


4. Ну и наконец, последнее но немаловажное. В большинстве Express.js приложений работет примерно такой код:

app.use(someFuction);app.use(anotherFunction());app.use((req, res, nexn) => ..., next());app.get('/', (req, res) => {  res.send('Hello World!')})


Когда Вы разрабатываете свою часть приложения, то можете быть уверенным что до вашего кода уже успели отработать 10-20 middleware, которые вешают на объект req всевозможные свойства, и, даже, могут модифицировать исходный запрос, ровно как и в том что столько же если не больше middleware может бтоь добавлено после того, как вы разработаете свою часть приложения. Хотя, к слову сказать, в документации Express.js для навешивания дополнительных свойств неоднозначно рекомендуется объект res.locals:

// из документации Express.jsapp.use(function (req, res, next) {  res.locals.user = req.user  res.locals.authenticated = !req.user.anonymous  next()})


Исторические попытки преодоления недостатков Express.js



Не удивительно, что основной автор Express.js и Connect.js TJ Holowaychuk оставил проект, чтобы начать разработку нового фреймворка Koa.js. Koa.js добавляет асинхронность в Express.js. Например, такой код избавляет от необходимости перехватывать асинхронные ошибки в коде каждого роута и выносит обработчик в один middleware:

app.use(async (ctx, next) => {  try {    await next();  } catch (err) {    // will only respond with JSON    ctx.status = err.statusCode || err.status || 500;    ctx.body = {      message: err.message    };  }})


Первые версии Koa.js имели замысел внедрить генераторы для обработки асинхронных вызовов:

// from http://blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators/var request = Q.denodeify(require('request')); // Example of calling library code that returns a promisefunction doHttpRequest(url) {    return request(url).then(function(resultParams) {        // Extract just the response object        return resultParams[];    });}app.use(function *() {    // Example with a return value    var response = yield doHttpRequest('http://example.com/');    this.body = "Response length is " + response.body.length;});


Внедрение async/await свело на нет полезность этой части Koa.js, и сейчас подобных примеров нет даже в документации фреймворка.

Почти ровесник Express.js фреймворк Hapi.js. Контроллеры в Hapi.js уже возвращают значение, что является шагом вперед, по сравнению с Express.js. Не получив популярность сравнимую с Express.js, мега-успешной стала составная часть проекта Hapi.js библиотека Joi, которая имеет количество загрузок с npmjs.org 3 388 762, и сейчас используется как на бэкенде, так и на фронтенде. Поняв, что валидация входящих объектов это не какой-то особый случай, а необходимый атрибут каждого приложения валидация в Hapi.js была включена как составляющая часть фреймворка, и как параметр в определении роута:

server.route({    method: 'GET',    path: '/hello/{name}',    handler: function (request, h) {        return `Hello ${request.params.name}!`;    },    options: {        validate: {            params: Joi.object({                name: Joi.string().min(3).max(10)            })        }    }});


В настоящее время, библиотека Joi выделена в самостоятельный проект.

Если мы определели схему валидации объекта, тем самымы мы определили и сам объект. Остается совсем немного, чтобы создать самодокументируемый роут, в котором изменение в схеме валидации данных приводит к изменению документации, и, таким образом, документация всегда соответствует программному коду.

На сегодняшний день, одно из лучших решений в документации API swagger/openAPI. Было бы очень удачно, если бы схема, описания с учетом требований swagger/openAPI, могла быть использована и для валидации и для формирования документации.

Fastify.js



Подитожу те требования, который мне кажутся существенными при выборе веб-фреймворка:

1. Наличие полноценных контроллеров (возвращаемое значение функции возвращется клиенту в теле ответа).
2. Удобная обработка синхронных и асинхронных ошибок.
3. Валидация входных параметров.
4. Самодокуметирование на основании определений роутов и схем валидации входных/выходных параметров.
5. Инстанциирование асинхронных сервисов.
6. Расширяемость.

Всем этим пунктам соответствует Nest.js, с которым я сейчас работаю на нескольких проектах. Особенностью Next.js является широкое применение декораторов, что может быть в ряде случаев ограничением, если в технических требованиях указано использовать стандартный JavaScript (а как известно со стандартизацией декораторов в JavaScript сиутация застопорилась несколько лет назад, и, похоже, не скоро найдет свое разрешение).

Поэтому альтернативой может стать фреймворк Fastify.js, особенности применения которого я сейчас разберу.

Fastify.js поддерживает и привычный для разработчиков на Express.js стиль формирования ответа сервера, и более перспективный в форме возвращаемого значения функции, при этом оставляя возможность гибко манипулировать другими параметрами ответа (статусом, заголовками):

// Require the framework and instantiate itconst fastify = require('fastify')({  logger: true})// Declare a routefastify.get('/', (request, reply) => {  reply.send({ hello: 'world' })})// Run the server!fastify.listen(3000, (err, address) => {  if (err) throw err  // Server is now listening on ${address}})


const fastify = require('fastify')({  logger: true})fastify.get('/',  (request, reply) => {  reply.type('application/json').code(200)  return { hello: 'world' }})fastify.listen(3000, (err, address) => {  if (err) throw err  // Server is now listening on ${address}})


Обработка ошибок может быть встроенной (из коробки) и кастомной.

const createError = require('fastify-error');const CustomError = createError('403_ERROR', 'Message: ', 403);function raiseAsyncError() {  return new Promise((resolve, reject) => {    setTimeout(() => reject(new CustomError('Async Error')), 5000);  });}async function routes(fastify) {  fastify.get('/sync-error', async () => {    if (true) {      throw new CustomError('Sync Error');    }    return { hello: 'world' };  });  fastify.get('/async-error', async () => {    await raiseAsyncError();    return { hello: 'world' };  });}


Оба варианта и синхронный и асинхронный отрабатывают одинаково встроенным обработчиком ошибок. Конечно, встроенных возможностей всегда мало. Кастомизируем обработчик ошибок:

fastify.setErrorHandler((error, request, reply) => {  console.log(error);  reply.status(error.status || 500).send(error);});  fastify.get('/custom-error', () => {    if (true) {      throw { status: 419, data: { a: 1, b: 2} };    }    return { hello: 'world' };  });


Эта часть кода упрощена (ошибка выбрасывает литерал). Аналогично можно выбрасывать и кастомную ошибку. (Определение кастомных сериализуемых ошибок это отдельная тема, поэтому пример не приводится).

Для валидации Fastify.js использует библиотеку Ajv.js, которая реализует интерфенйс swagger/openAPI. Этот факт делает возможным интеграцию Fastify.js со swagger/openAPI и самодокументирвоание API.

По умолчанию валидация не самая строгая (поля опциональоные и допускаются отсутствующие в схеме поля). Для того чтобы сделать валдиацию строгой необходимо определить параметры в конфигурации Ajv, и в схеме валидации:

const fastify = require('fastify')({  logger: true,  ajv: {    customOptions: {      removeAdditional: false,      useDefaults: true,      coerceTypes: true,      allErrors: true,      strictTypes: true,      nullable: true,      strictRequired: true,    },    plugins: [],  },});  const opts = {    httpStatus: 201,    schema: {      description: 'post some data',      tags: ['test'],      summary: 'qwerty',      additionalProperties: false,      body: {        additionalProperties: false,        type: 'object',        required: ['someKey'],        properties: {          someKey: { type: 'string' },          someOtherKey: { type: 'number', minimum: 10 },        },      },      response: {        200: {          type: 'object',          additionalProperties: false,          required: ['hello'],          properties: {            value: { type: 'string' },            otherValue: { type: 'boolean' },            hello: { type: 'string' },          },        },        201: {          type: 'object',          additionalProperties: false,          required: ['hello-test'],          properties: {            value: { type: 'string' },            otherValue: { type: 'boolean' },            'hello-test': { type: 'string' },          },        },      },    },  };  fastify.post('/test', opts, async (req, res) => {    res.status(201);    return { hello: 'world' };  });}


Поскольку схема входящих объектов уже определена, генерация документации swagger/openAPI сводится к инсталляции плагина:

fastify.register(require('fastify-swagger'), {  routePrefix: '/api-doc',  swagger: {    info: {      title: 'Test swagger',      description: 'testing the fastify swagger api',      version: '0.1.0',    },    securityDefinitions: {      apiKey: {        type: 'apiKey',        name: 'apiKey',        in: 'header',      },    },    host: 'localhost:3000',    schemes: ['http'],    consumes: ['application/json'],    produces: ['application/json'],  },  hideUntagged: true,  exposeRoute: true,});


Валидация ответа также возможна. Для этого необходимо инсталлировать плагин:

fastify.register(require('fastify-response-validation'));


Валидация достаточно гибкая. Например ответ каждого статуса будет проверяться по своей схеме валидации.

Код связанный с написание статьи можно найти здесь.

Дополнительные источники информации

1. blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators
2. habr.com/ru/company/dataart/blog/312638

apapacy@gmail.com
4 мая 2021 года
Подробнее..

Тривиальная и неправильная облачная компиляция

28.01.2021 00:21:03 | Автор: admin


Введение


Данная статья не история успеха, а скорее руководство как не надо делать. Весной 2020 для поддержания спортивного тонуса участвовал в студенческом хакатоне (спойлер: заняли 2-е место). Удивительно, но задача из полуфинала оказалась более интересной и сложной чем финальная. Как вы поняли, о ней и своём решении расскажу под катом.


Задача


Данный кейс был предложен Deutsche Bank в направлении WEB-разработка.
Необходимо было разработать онлайн-редактор для проекта Алгосимулятор тестового стенда для проверки работы алгоритмов электронной торговли на языке Java. Каждый алгоритм реализуется в виде наследника класса AbstractTradingAlgorythm.


AbstractTradingAlgorythm.java
public abstract class AbstractTradingAlgorithm {    abstract void handleTicker(Ticker ticker) throws Exception;    public void receiveTick(String tick) throws Exception {        handleTicker(Ticker.parse(tick));    }    static class Ticker {        String pair;        double price;       static Ticker parse(String tick) {           Ticker ticker = new Ticker();           String[] tickerSplit = tick.split(",");           ticker.pair = tickerSplit[0];           ticker.price = Double.valueOf(tickerSplit[1]);           return ticker;       }    }}

Сам же редактор во время работы говорит тебе три вещи:


  1. Наследуешь ли ты правильный класс
  2. Будут ли ошибки на этапе компиляции
  3. Успешен ли тестовый прогон алгоритма. В данном случае подразумевается, что "В результате вызова new <ClassName>().receiveTick(RUBHGD,100.1) отсутствуют runtime exceptions".


Ну окей, скелет веб-сервиса через spring накидать дело на 5-10 минут. Пункт 1 работа для регулярных выражений, поэтому даже думать об этом сейчас не буду. Для пункта 2 можно конечно написать синтаксический анализатор, но зачем, когда это уже сделали за меня. Может и пункт 3 получится сделать, использовав наработки по пункту 2. В общем, дело за малым, уместить в один метод, ну например, компиляцию исходного кода программы на Java, переданного в контроллер строкой.


Решение


Здесь и начинается самое интересное. Забегая вперёд, как сделали другие ребята: установили на машину джаву, отдавали команды на ось и грепали stdout. Конечно, это более универсальный метод, но во-первых, нам сказали слово Java, а во-вторых...



у каждого свой путь.


Естественно, Java окружение устанавливать и настраивать всё же придётся. Правда компилировать и исполнять код мы будем не в терминале, а, как бы это ни звучало, в коде. Начиная с 6 версии, в Java SE присутствует пакет javax.tools, добавленный в стандартный API для компиляции исходного кода Java.
Теперь привычные понятия такие, как файлы с исходным кодом, параметры компилятора, каталоги с выходными файлами, сообщения компилятора, превратились в абстракции, используемые при работе с интерфейсом JavaCompiler, через реализации которого ведётся основная работа с задачами компиляции. Подробней о нём можно прочитать в официальной документации. Главное, что оттуда сейчас перейдёт моментально в текст статьи, это класс JavaSourceFromString. Дело в том, что, по умолчанию, исходный код загружается из файловой системы. В нашем же случае исходный код будет приходить строкой извне.


JavaSourceFromString.java
import javax.tools.SimpleJavaFileObject;import java.net.URI;public class JavaSourceFromString extends SimpleJavaFileObject {    final String code;    public JavaSourceFromString(String name, String code) {        super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);        this.code = code;    }    @Override    public CharSequence getCharContent(boolean ignoreEncodingErrors) {        return code;    }}

Далее, в принципе уже ничего сложного нет. Получаем строку, имя класса и преобразуем их в объект JavaFileObject. Этот объект передаём в компилятор, компилируем и собираем вывод, который и возвращаем на клиент.
Сделаем класс Validator, в котором инкапсулируем процесс компиляции и тестового прогона некоторого исходника.


public class Validator {    private JavaSourceFromString sourceObject;    public Validator(String className, String source) {        sourceObject = new JavaSourceFromString(className, source);    }}

Далее добавим компиляцию.


public class Validator {    ...    public List<Diagnostic<? extends JavaFileObject>> compile() {        // получаем компилятор, установленный в системе        var compiler = ToolProvider.getSystemJavaCompiler();        // компилируем        var compilationUnits = Collections.singletonList(sourceObject);        var diagnostics = new DiagnosticCollector<JavaFileObject>();        compiler.getTask(null, null, diagnostics, null, null, compilationUnits).call();        // возворащаем диагностику        return diagnostics.getDiagnostics();    }}

Пользоваться этим можно как-то так.


public void MyMethod() {        var className = "TradeAlgo";        var sourceString = "public class TradeAlgo extends AbstractTradingAlgorithm{\n" +                "@Override\n" +                "    void handleTicker(Ticker ticker) throws Exception {\n" +                "       System.out.println(\"TradeAlgo::handleTicker\");\n" +                "    }\n" +                "}\n";        var validator = new Validator(className, sourceString);        for (var message : validator.compile()) {            System.out.println(message);        }    }

При этом, если компиляция прошла успешно, то возвращённый методом compile список будет пуст. Что интересно? А вот что.

На приведённом изображении вы можете видеть директорию проекта после завершения выполнения программы, во время выполнения которой была осуществлена компиляция. Красным прямоугольником обведены .class файлы, сгенерированные компилятором. Куда их девать, и как это чистить, не знаю жду в комментариях. Но что это значит? Что скомпилированные классы присоединяются в runtime, и там их можно использовать. А значит, следующий пункт задачи решается тривиально с помощью средств рефлексии.
Создадим вспомогательный POJO для хранения результата прогона.


TestResult.java
public class TestResult {    private boolean success;    private String comment;    public TestResult(boolean success, String comment) {        this.success = success;        this.comment = comment;    }    public boolean success() {        return success;    }    public String getComment() {        return comment;    }}

Теперь модифицируем класс Validator с учётом новых обстоятельств.


public class Validator {    ...    private String className;    private boolean compiled = false;    public Validator(String className, String source) {        this.className = className;        ...    }    ...    public TestResult testRun(String arg) {        var result = new TestResult(false, "Failed to compile");        if (compiled) {            try {                // загружаем класс                var classLoader = URLClassLoader.newInstance(new URL[]{new File("").toURI().toURL()});                var c = Class.forName(className, true, classLoader);                // создаём объект класса                var constructor = c.getConstructor();                var instance = constructor.newInstance();                // выполняем целевой метод                c.getDeclaredMethod("receiveTick", String.class).invoke(instance, arg);                result = new TestResult(true, "Success");            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException | RuntimeException | MalformedURLException | InstantiationException e) {                var sw = new StringWriter();                e.printStackTrace(new PrintWriter(sw));                result = new TestResult(false, sw.toString());            }        }        return result;    }}

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


public void MyMethod() {        ...        var result = validator.testRun("RUBHGD,100.1");        System.out.println(result.success() + " " + result.getComment());    }

Вставить этот код в реализацию API контроллера задача нетрудная, поэтому подробности её решения можно опустить.


Какие проблемы?


  1. Ещё раз напомню про кучу .class файлов.


  2. Поскольку опять же идёт работа с компиляцией некоторых классов, есть риск отказа в записи любого непредвиденного .class файла.


  3. Самое главное, наличие уязвимости для инъекции вредосного кода на языке программирования Java. Ведь, на самом деле, пользователь может написать что угодно в теле вызываемого метода. Это может быть вызов полного перегруза системы, затирание всей файловой системы машины и тому подобное. В общем, исполняемую среду нужно изолировать, как минимум, а в рамках хакатона этим в команде никто естественно не занимался ввиду нехватки времени.



Поэтому делать в точности как я не надо)


P.S. Ссылка на гитхаб с исходным кодом из статьи.

Подробнее..

Мифология REST

02.06.2021 12:04:43 | Автор: admin

Мифология REST


Матчасть


Мало какая технология в истории IT вызывала столько ожесточённых споров, как REST. Самое удивительное при этом состоит в том, что спорящие стороны, как правило, совершенно не представляют себе предмет спора.


Начнём с самого начала. В 2000 году один из авторов спецификаций HTTP и URI Рой Филдинг защитил докторскую диссертацию на тему Архитектурные стили и дизайн архитектуры сетевого программного обеспечения, пятая глава которой была озаглавлена как Representational State Transfer (REST). Диссертация доступна по ссылке.


Как нетрудно убедиться, прочитав эту главу, она представляет собой довольно абстрактный обзор распределённой сетевой архитектуры, вообще не привязанной ни к HTTP, ни к URL. Более того, она вовсе не посвящена правилам дизайна API; в этой главе Филдинг методично перечисляет ограничения, с которыми приходится сталкиваться разработчику распределённого сетевого программного обеспечения. Вот они:


  • клиент и сервер не знают внутреннего устройства друг друга (клиент-серверная архитектура);
  • сессия хранится на клиенте (stateless-дизайн);
  • данные должны размечаться как кэшируемые или некэшируемые;
  • интерфейсы взаимодействия должны быть стандартизированы;
  • сетевые системы являются многослойными, т.е. сервер может быть только прокси к другим серверам;
  • функциональность клиента может быть расширена через поставку кода с сервера.

Всё, на этом определение REST заканчивается. Дальше Филдинг конкретизирует некоторые аспекты имплементации систем в указанных ограничениях, но все они точно так же являются совершенно абстрактными. Буквально: ключевая информационная абстракция в REST ресурс; любая информация, которой можно дать наименование, может быть ресурсом.


Ключевой вывод, который следует из определения REST по Филдингу, вообще-то, таков: любое сетевое ПО в мире соответствует принципам REST, за очень-очень редкими исключениями.


В самом деле:


  • очень сложно представить себе систему, в которой не было бы хоть какого-нибудь стандартизованного интерфейса взаимодействия, иначе её просто невозможно будет разрабатывать;
  • раз есть интерфейс взаимодействия, значит, под него всегда можно мимикрировать, а значит, требование независимости имплементации клиента и сервера всегда выполнимо;
  • раз можно сделать альтернативную имплементацию сервера значит, можно сделать и многослойную архитектуру, поставив дополнительный прокси между клиентом и сервером;
  • поскольку клиент представляет собой вычислительную машину, он всегда хранит хоть какое-то состояние и кэширует хоть какие-то данные;
  • наконец, code-on-demand вообще лукавое требование, поскольку всегда можно объявить данные, полученные по сети, инструкциями на некотором формальном языке, а код клиента их интерпретатором.

Да, конечно, вышеприведённое рассуждение является софизмом, доведением до абсурда. Самое забавное в этом упражнении состоит в том, что мы можем довести его до абсурда и в другую сторону, объявив ограничения REST неисполнимыми. Например, очевидно, что требование code-on-demand противоречит требованию независимости клиента и сервера клиент должен уметь интерпретировать код с сервера, написанный на вполне конкретном языке. Что касается правила на букву S (stateless), то систем, в которых сервер вообще не хранит никакого контекста клиента в мире вообще практически нет, поскольку ничего полезного для клиента в такой системе сделать нельзя. (Что, кстати, постулируется в соответствующем разделе прямым текстом: коммуникация не может получать никаких преимуществ от того, что на сервере хранится какой-то контекст.)


Наконец, сам Филдинг внёс дополнительную энтропию в вопрос, выпустив в 2008 году разъяснение, что же он имел в виду. В частности, в этой статье утверждается, что:


  • REST API не должно зависеть от протокола;
  • разработка REST API должна фокусироваться на описании медиатипов, представляющих ресурсы; при этом клиент вообще ничего про эти медиатипы знать не должен;
  • в REST API не должно быть фиксированных имён ресурсов и операций над ними, клиент должен извлекать эту информацию из ответов сервера.

Короче говоря, REST по Филдингу подразумевает, что клиент, получив каким-то образом ссылку на точку входа REST API, далее должен быть в состоянии полностью выстроить взаимодействие с API, не обладая вообще никаким априорным знанием о нём, и уж тем более не должен содержать никакого специально написанного кода для работы с этим API.


Оставляя за скобками тот факт, что Филдинг весьма вольно истолковал свою же диссертацию, просто отметим, что ни одна существующая система в мире не удовлетворяет описанию REST по Филдингу-2008.


Здравое зерно REST


Нам неизвестно, почему из всех обзоров абстрактной сетевой архитектуры именно диссертация Филдинга обрела столь широкую популярность; очевидно другое: теория Филдинга, преломившись в умах миллионов программистов (включая самого Филдинга), превратилась в целую инженерную субкультуру. Путём редукции абстракций REST применительно конкретно к протоколу HTTP и стандарту URL родилась химера RESTful API, конкретного смысла которой никто не знает.


Хотим ли мы тем самым сказать, что REST является бессмысленной концепцией? Отнюдь нет. Мы только хотели показать, что она допускает чересчур широкую интерпретацию, в чём одновременно кроется и её сила, и её слабость.


С одной стороны, благодаря многообразию интерпретаций, разработчики API выстроили какое-то размытое, но всё-таки полезное представление о правильной архитектуре API. С другой стороны, если бы Филдинг чётко расписал в 2000 году, что же он конкретно имел в виду, вряд ли бы об этой диссертации знало больше пары десятков человек.


Что же правильного в REST-подходе к дизайну API (таком, как он сформировался в коллективном сознании широких масс программистов)? То, что такой дизайн позволяет добиться более эффективного использования времени времени программистов и компьютеров.


Если коротко обобщить все сломанные в попытках определить REST копья, то мы получим следующий принцип: лучше бы ты разрабатывал распределённую систему так, чтобы промежуточные агенты умели читать метаданные запросов и ответов, проходящих через эту систему (готовая заповедь для RESTful-пастафарианства практически!).


У протокола HTTP есть очень важное достоинство: он предоставляет стороннему наблюдателю довольно подробную информацию о том, что произошло с запросом и ответом, даже если этот наблюдатель ничего не знает о семантике операции:


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

Немаловажно здесь то, что все эти сведения можно получить, не вычитывая тело ответа целиком достаточно прочитать служебную информацию из заголовков.


Почему это полезно? Потому что современный стек взаимодействия между клиентом и сервером является (как предсказывал Филдинг) многослойным. Разработчик пишет код поверх какого-то фреймворка, который отправляет запросы; фреймворк базируется на API языка программирования, которое, в свою очередь, обращается к API операционной системы. Далее запрос (возможно, через промежуточные HTTP-прокси) доходит до сервера, который, в свою очередь, тоже представляет собой несколько слоёв абстракции в виде фреймворка, языка программирования и ОС; к тому же, перед конечным сервером, как правило, находится веб-сервер, проксирующий запрос, а зачастую и не один. В современных облачных архитектурах HTTP-запрос, прежде чем дойти до конечного обработчика, пройдёт через несколько абстракций в виде прокси и гейтвеев. Если бы все эти агенты трактовали мета-информацию о запросе одинаково, это позволило бы обрабатывать многие ситуации оптимальнее тратить меньше ресурсов и писать меньше кода.


(На самом деле, в отношении многих технических аспектов промежуточные агенты и так позволяют себе разные вольности, не спрашивая разработчиков. Например, свободно менять Accept-Encoding и Content-Length при проксировании запросов.)


Каждый из аспектов, перечисленных Филдингом в REST-принципах, позволяет лучше организовать работу промежуточного ПО. Ключевым здесь является stateless-принцип: промежуточные прокси могут быть уверены, что метаинформация запроса его однозначно описывает.


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


// Получение профиляGET /meCookie: session_id=<идентификатор сессии>// Удаление профиляGET /delete-meCookie: session_id=<идентификатор сессии>

Почему такая система неудачна с точки зрения промежуточного агента?


  1. Сервер не может кэшировать ответы; все /me для него одинаковые, поскольку он не умеет получать уникальный идентификатор пользователя из куки; в том числе промежуточные прокси не могут и заранее наполнить кэш, так как не знают идентификаторов сессий.
  2. На сервере сложно организовать шардирование, т.е. хранение информации о разных пользователях в разных сегментах сети; для этого опять же потребуется уметь обменивать сессию на идентификатор пользователя.

Первую проблему можно решить, сделав операции более машиночитаемыми, например, перенеся идентификатор сессии в URL:


// Получение профиляGET /me?session_id=<идентификатор сессии>// Удаление профиляGET /delete-me?session_id=<идентификатор сессии>

Шардирование всё ещё нельзя организовать, но теперь сервер может иметь кэш (в нём будут появляться дубликаты для разных сессий одного и того же пользователя, но хотя бы ответить из кэша неправильно будет невозможно), но возникнут другие проблемы:


  1. URL обращения теперь нельзя сохранять в логах, так как он содержит секретную информацию; более того, появится риск утечки данных со страниц пользователей, т.к. одного URL теперь достаточно для получения данных.
  2. Ссылку на удаление пользователя клиент обязан держать в секрете. Если её, например, отправить в мессенджер, то робот-префетчер мессенджера удалит профиль пользователя.

Как же сделать эти операции правильно с точки зрения REST? Вот так:


// Получение профиляGET /user/{user_id}Authorization: Bearer <token>// Удаление профиляDELETE /user/{user_id}Authorization: Bearer <token>

Теперь URL запроса в точности идентифицирует ресурс, к которому обращаются, поэтому можно организовать кэш и даже заранее наполнить его; можно организовать маршрутизацию запроса в зависимости от идентификатора пользователя, т.е. появляется возможность шардирования. Префетчер мессенджера не пройдёт по DELETE-ссылке; а если он это и сделает, то без заголовка Authorization операция выполнена не будет.


Наконец, неочевидная польза такого решения заключается в следующем: промежуточный сервер-гейтвей, обрабатывающий запрос, может проверить заголовок Authorization и переслать запрос далее без него (желательно, конечно, по безопасному соединению или хотя бы подписав запрос). И, в отличие от схемы с идентификатором сессии, мы всё ёщё можем свободно организовывать кэширование данных в любых промежуточных узлах. Более того, агент может легко модифицировать операцию: например, для авторизованных пользователей пересылать запрос дальше как есть, а неавторизованным показывать публичный профиль, пересылая запрос на специальный URL, ну, скажем, GET /user/{user_id}/public-profile для этого достаточно всего лишь дописать /public-profile к URL, не изменяя все остальные части запроса. Для современных микросервисных архитектур возможность корректно и дёшево модифицировать запрос при маршрутизации является самым ценным преимуществом в концепции REST.


Шагнём ещё чуть вперёд. Предположим, что гейтвей спроксировал запрос DELETE /user/{user_id} в нужный микросервис и не дождался ответа. Какие дальше возможны варианты?


Вариант 1. Можно сгенерировать HTML-страницу с ошибкой, вернуть её веб-серверу, чтобы тот вернул её клиенту, чтобы клиент показал её пользователю, и дождаться реакции пользователя. Мы прогнали через систему сколько-то байтов и переложили решение проблемы на конечного потребителя. Попутно заметим, что при этом на уровне логов веб-сервера ошибка неотличима от успеха и там, и там какой-то немашиночитаемый ответ со статусом 200 и, если действительно пропала сетевая связность между гейтвеем и микросервисом, об этом никто не узнает.


Вариант 2. Можно вернуть веб-серверу подходящую HTTP-ошибку, например, 504, чтобы тот вернул её клиенту, чтобы клиент обработал ошибку и, сообразно своей логике, что-то предпринял по этому поводу, например, отправил запрос повторно или показал ошибку пользователю. Мы прокачали чуть меньше байтов, попутно залогировав исключительную ситуацию, но теперь переложили решение на разработчика клиента это ему надлежит не забыть написать код, который умеет работать с ошибкой 504.


Вариант 3. Гейтвей, зная, что метод DELETE идемпотентен, может сам повторить запрос; если исполнить запрос не получилось проследовать по варианту 1 или 2. В этой ситуации мы переложили ответственность за решение на архитектора системы, который должен спроектировать политику перезапросов внутри неё (и гарантировать, что все операции за DELETE действительно идемпотенты), но мы получили важное свойство: система стала самовосстанавливающейся. Теперь она может сама побороть какие-то ситуации, которые раньше вызывали исключения.


Внимательный читатель может заметить, что вариант (3) при этом является наиболее технически сложным из всех, поскольку включает в себя варианты (1) и (2): для правильной работы всей схемы разработчику клиента всё равно нужно написать код работы с ошибкой. Это, однако, не так; есть очень большая разница в написании кода для системы (3) по сравнению с (1) и (2): разработчику клиента не надо знать как устроена политика перезапросов сервера. Он может быть уверен, что сервер уже сам выполнил необходимые действия, и нет никакого смысла немедленно повторять запрос. Все серверные методы с этой точки зрения для клиента начинают выглядеть одинаково, а значит, эту функциональность (ожидание перезапроса, таймауты) можно передать на уровень фреймворка.


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


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


Разумеется все подобные оптимизации можно выполнить и без опоры на стандартную номенклатуру методов / статусов / заголовков HTTP, или даже вовсе поверх другого протокола. Достаточно разработать одинаковый формат данных, содержащий нужную мета-информацию, и научить промежуточные агенты и фреймворки его читать. В общем-то, именно это Филдинг и утверждает в своей диссертации. Но, конечно, очень желательно, чтобы этот код уже был кем-то написан за нас.


Заметим, что многочисленные советы как правильно разрабатывать REST API, которые можно найти в интернете, никак не связаны с изложенными выше принципами, а зачастую и противоречат им:


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


    • чтобы URL был ключом кэширования для кэшируемых операций и ключом идемпотентности для идемпотентных;
    • чтобы при маршрутизации запроса в многослойной системе можно было легко понять, куда будет маршрутизироваться эта операция по её URL как машине, так и человеку.

  2. Используйте HTTP-глаголы для описания действий того, что происходит с ресурсом это правило попросту ставит телегу впереди лошади. Глагол указывает всем промежуточным агентам, является ли операция (не)модифицирующей, (не)кэшируемой, (не)идемпотентной и есть ли у запроса тело; вместо того, чтобы выбирать строго по этим четырём критериям, предлагается воспользоваться какой-то мнемоникой если глагол подходит к смыслу операции, то и ок. Это в некоторых случаях просто опасно: вам может показаться, что DELETE /list?element_index=3 прекрасно описывает ваше намерение удалить третий элемент списка, но т.к. эта операция неидемпотентна, использовать метод DELETE здесь нельзя;


  3. Используйте POST для создания сущностей, GET для доступа к ним, PUT для полной перезаписи, PATCH для частичной и DELETE для удаления вновь мнемоника, позволяющая на пальцах прикинуть, какие побочные эффекты возможны у какого из методов. Если попытаться разобраться в вопросе глубже, то получится, что вообще-то этот совет находится где-то между бесполезен и вреден:


    • использовать метод GET в API имеет смысл тогда и только тогда, когда вы можете указать заголовки кэширования; если выставить Cache-Control в no-cache то получится просто неявный POST; если их не указать совсем, то какой-то промежуточный агент может взять и додумать их за вас;
    • создание сущностей желательно делать идемпотентным, в идеале за PUT (например, через схему с драфтами);
    • частичная перезапись через PATCH опасная и двусмысленная операция, лучше её декомпозировать через более простые PUT;
    • наконец, в современных системах сущности очень редко удаляются скорее архивируются или помечаются скрытыми, так что и здесь PUT /archive?entity_id будет уместнее.

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


  5. Используйте множественное число для сущностей, приписывайте слэш в конце URL и тому подобные советы по стилистике кода, не имеющие никакого отношения к REST.



Осмелимся в конце этого раздела сформулировать четыре правила, которые действительно позволят вам написать хорошее REST API:


  1. Соблюдайте стандарт HTTP, особенно в части семантики методов, статусов и заголовков.
  2. Используйте URL как ключ кэша и ключ идемпотентности.
  3. Проектируйте архитектуру так, чтобы для организации маршрутизации запросов внутри многослойной системы было достаточно манипулировать частями URL (хост, путь, query-параметры), статусами и заголовками.
  4. Рассматривайте сигнатуры вызовов HTTP-методов вашего API как код, и применяйте к нему те же стилистические правила, что и к коду: сигнатуры должны быть семантичными, консистентными и читабельными.

Преимущества и недостатки REST


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


Главным недостатком REST является то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее даже если вы их об этом не просили. Более того, так как стандарты HTTP являются сложными, концепция REST непонятной, а разработчики программного обеспечения неидеальными, то промежуточные агенты могут трактовать метаданные запроса неправильно. Особенно это касается каких-то экзотических и сложных в имплементации стандартов.


Разработка распределённых систем в парадигме REST это всегда некоторый торг: какую функциональность вы готовы отдать на откуп чужому коду, а какую нет. Увы, нащупывать баланс приходится методом проб и ошибок.


О метапрограммировании и REST по Филдингу


Отдельно всё-таки выскажемся о трактовке REST по Филдингу-2008, которая, на самом деле, уходит корнями в распространённую концепцию HATEOAS. С одной стороны, она является довольно логичным продолжением принципов, изложенных выше: если машиночитаемыми будут не только метаданные текущей исполняемой операции, но и всех возможных операций над ресурсом, это, конечно, позволит построить гораздо более функциональные сетевые агенты. Вообще сама идея метапрограммирования, когда клиент является настолько сложной вычислительной машиной, что способен расширять сам себя без необходимости привлечь разработчика, который прочитает документацию API и напишет код работы с ним, конечно, выглядит весьма привлекательной для любого технократа.


Недостатком этой идеи является тот факт, что клиент будет расширять сам себя без привлечения разработчика, который прочитает документацию API и напишет код работы с ним. Возможно, в идеальном мире так работает; в реальном нет. Любое большое API неидеально, в нём всегда есть концепции, для понимания которых (пока что) требуется живой человек. А поскольку, повторимся, API работает мультипликатором и ваших возможностей, и ваших ошибок, автоматизированное метапрограммирование поверх API чревато очень-очень дорогими ошибками.


Пока сильный ИИ не разработан, мы всё-таки настаиваем на том, что код работы с API должен писать живой человек, который опирается на подробную документацию, а не догадки о смысле гиперссылок в ответе сервера.


--


Это черновик будущей главы книги о разработке API. Работа ведётся на Github. Англоязычный вариант этой же главы опубликован на medium. Я буду признателен, если вы пошарите его на реддит я сам не могу согласно политике платформы.

Подробнее..
Категории: Api , Rest , Restful api

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru