Xây dựng Content trên Marketplace
Tài liệu này hướng dẫn Creator upload nội dung (content pack) lên Marketplace của hệ thống SecOps. Nội dung package bao gồm cả Public và Private.
ccsp-client là công cụ CLI chính thức để quản lý các security package trên SecOps Marketplace.
Hỗ trợ: Linux · macOS · Windows
Hướng dẫn upload nội dung lên Git
Bước 1: Tải CLI
Tải CLI theo đường link sau: Coming soon
Bước 2: Xây dựng Content trên CLI
1. Cấu hình xác thực
- Đầu tiên, người dùng cần cấu hình IAM service account credentials:
ccsp-client add-access-key \
--id <ACCESS_KEY_ID> \
--secret <ACCESS_KEY_SECRET> \
--project <PROJECT_ID>
Trong đó:
- <ACCESS_KEY_ID> và <ACCESS_KEY_SECRET> được tạo từ tính năng "Access Key" trong Platform Management.
- Sau khi đã cấu hình xác thực, credentials được lưu tại:
| OS | Đường dẫn |
|---|---|
| Linux/ macOS | ~/.csop-client/config |
| Windows | %USERPROFILE%.csop-client\config |
2. Khởi tạo Package
- Tạo cấu trúc thư mục và file package.json mẫu cho một package mới.
# Cú pháp:
ccsp-client init <package-name>
# Ví dụ:
ccsp-client init my-security-package
- Output:
# Trả về dòng lệnh
Package 'my-security-package' initialized successfully!
# Cấu trúc package:
<package-name>/
├── package.json # Metadata bắt buộc
├── README.md # Tài liệu (khuyến nghị)
├── connectors/ # Cấu hình connector (.json)
├── dashboards/ # Định nghĩa dashboard (.json)
├── parsers/ # Log parser (.yaml / .yml)
└── rules/
├── streaming/ # Streaming detection rules (.yaml / .yml)
└── correlation/ # Correlation rules (.yaml / .yml)
Trong đó, chi tiết package JSON bao gồm:
| Trường | Kiểu dữ liệu | Bắt buộc | Mô tả |
|---|---|---|---|
| author | string | ✅ | Tên tác giả / username |
| category | array | ✅ | Danh sách category. Vd: Network, Firewall |
| capability | array | ✅ | Danh sách capability. Version hiện tại support 2 loại : rule, parser |
| description | string | ✅ | Mô tả ngắn gọn |
| name | string | ✅ | Tên định danh package. Vd: Fortigate Firewall |
| public | boolean | ✅ | Bao gồm: Public / Private (giá trị mặc định) |
| vendor | string | ✅ | Công ty / tổ chức sở hữu |
| version | string | ✅ | Semantic version MAJOR.MINOR.PATCH |
Bước 2: Xây dựng content pack
Hiện tại, hệ thống chỉ đang support xây dựng content cho: Parser và Rule (Streaming)
- Rule (Streaming rule): Đọc chi tiết
Bước 3: Kiểm thử nội dung
1. Validate cấu trúc
# Cú pháp:
ccsp-client validate <package-path>
# Ví dụ:
ccsp-client validate my-security-package
ccsp-client validate ./path/to/package
ccsp-client validate . # validate thư mục hiện tại
# Output thành công:
Validating package...
Package validation successful!
Your package is ready for testing and publishing.
# Output lỗi (Ví dụ):
Validating package...
Validation failed: package.json: missing required field "vendor"
- File package.json phải tồn tại và hợp lệ JSON
- Các trường bắt buộc: name, description, version, vendor, author, category
- Ít nhất một thư mục con (parsers/, rules/, dashboards/, connectors/) phải có nội dung
- Tất cả file .yaml/.yml phải có cú pháp YAML hợp lệ
- Tất cả file .json phải có cú pháp JSON hợp lệ
2. Kiểm thử ở Sandbox
Gửi parsers và rules đến backend sandbox để kiểm thử. Yêu cầu credentials.
# Cú pháp:
ccsp-client test <package-path>
# Ví dụ:
ccsp-client test my-security-package
ccsp-client test ./path/to/package
# Output thành công:
Testing package files...
✓ Test passed: parsers/apache-parser.yaml
✓ Test passed: rules/streaming/suspicious-login.json
✓ Test success: 2 file(s) tested successfully
# Ví dụ output lỗi:
Credentials not found. Please run 'ccsp-client add-access-key' first
validate trước test để phát hiện lỗi cú pháp mà không tốn API call..yaml/.yml trong rules/), backend không cần sample data, chỉ validate cú pháp và cấu trúc Sigma YAML. Kết quả trả về validation_status cho biết rule hợp lệ hay không.Bước 4: Xuất bản nội dung lên Marketplace
- Đối với Public Package, sau khi nội dung được publish lên hệ thống Git, các gói này sẽ ở trạng thái chờ (Pending). Quản trị viên cần thực hiện phê duyệt (Approve) để nội dung chính thức hiển thị trên Marketplace.
- Đối với Private Package, nội dung package sẽ hiển thị ngay trên Marketplace sau khi creator publish lên mà không cần qua bước phê duyệt của Admin
Validate, nén và upload package lên SecOps Marketplace. Yêu cầu credentials.
#Cú pháp:
ccsp-client publish <package-path>
ccsp-client publish <package-path>:<version>
# Ví dụ:
ccsp-client publish my-security-package
# Chỉ định version cụ thể
ccsp-client publish my-security-package:1.0.2
ccsp-client publish ./path/to/package:1.0.5
package.json# Output thành công:
Publishing package...
✓ Package validation passed
✓ Created archive: my-security-package.zip
✓ Package published successfully
Your package is now pending review by the marketplace team
Bước 5: Quản trị viên Approve Content Pack (chỉ áp dụng với Public Package)
Admin thực hiện approve content pack trên GIT. Sau khi được approve, nội dung của gói sẽ được đẩy lên Marketplace.
Hướng dẫn dành cho newbie: từ cài đặt credentials đến publish thành công package Fortigate Firewall lên SecOps Marketplace.
Mục lục
- Chuẩn bị
- Bước 1 — Cấu hình credentials
- Bước 2 — Khởi tạo package
- Bước 3 — Chỉnh sửa package.json
- Bước 4 — Tạo Parser
- Bước 5 — Tạo Rule Detection
- Bước 6 — Validate
- Bước 7 — Test
- Bước 8 — Publish
- Tổng kết cấu trúc thư mục
1. Chuẩn bị
Đảm bảo bạn đã có:
- File binary
ccsp-client(đặt trong thư mục làm việc hoặc trong$PATH) - IAM credentials:
ACCESS_KEY_IDvàACCESS_KEY_SECRETdo admin cấp PROJECT_IDcủa project bạn thuộc về
Kiểm tra version:
./ccsp-client --version
Bước 1 — Cấu hình credentials
Chạy lệnh sau một lần duy nhất. Credentials sẽ được lưu vào ~/.ccsp-client/config.
./ccsp-client add-access-key \
--id YOUR_ACCESS_KEY_ID \
--secret YOUR_ACCESS_KEY_SECRET \
--project YOUR_PROJECT_ID
🔒 File config tự động được tạo với quyền
0600— chỉ bạn mới đọc được.
Bước 2 — Khởi tạo package
./ccsp-client init "Fortigate Firewall"
Lệnh tạo thư mục Fortigate Firewall/ với cấu trúc sau:
Fortigate Firewall/
├── connectors/
├── dashboards/
├── parsers/
├── rules/
│ ├── streaming/
│ └── correlation/
└── package.json
Di chuyển vào thư mục vừa tạo:
cd "Fortigate Firewall"
Bước 3 — Chỉnh sửa package.json
Mở package.json và cập nhật các trường. Sau khi init, file trông như sau:
{
"name": "Fortigate Firewall",
"description": "",
"version": "1.0.0",
"vendor": "",
"author": "",
"category": [],
"capability": []
}
Sửa thành:
{
"name": "Fortigate Firewall",
"description": "Parser and detection rules for Fortinet FortiGate firewall logs",
"version": "1.0.0",
"vendor": "Fortinet",
"author": "your-username",
"category": ["Network", "Firewall"],
"capability": ["parser", "rule"],
"public": true
}
⚠️
categoryvàcapabilitythường bị rỗng sau khi init — bắt buộc phải điền tay.
Bước 4 — Tạo Parser
Tạo file parser tại parsers/fortigate-firewall-parser.yaml:
touch parsers/fortigate-firewall-parser.yaml
Dán nội dung sau vào file:
#regex
#conditional
log_message = ""
if (msg, err = to_string(.message); err == null) {
log_message = msg
}
event_timestamp = ""
log_date = ""
log_time = ""
event_module = "fortigate"
log_id = ""
log_type = ""
log_subtype = ""
event_category = ""
event_action = ""
event_outcome = ""
log_level = "info"
source_ip = ""
source_port = ""
source_interface = ""
destination_ip = ""
destination_port = ""
destination_interface = ""
network_protocol = ""
network_transport = ""
user_name = ""
group_name = ""
policy_id = ""
policy_uuid = ""
session_id = ""
action = ""
threat_name = ""
threat_category = ""
attack_name = ""
attack_id = ""
url_full = ""
url_domain = ""
file_name = ""
server_address = ""
client_address = ""
if (date_m, err = parse_regex(log_message, r'date=(?P<date>\d{4}-\d{2}-\d{2})'); err == null) {
log_date = to_string(date_m.date)
}
if (time_m, err = parse_regex(log_message, r'time=(?P<time>\d{2}:\d{2}:\d{2})'); err == null) {
log_time = to_string(time_m.time)
}
if (logid_m, err = parse_regex(log_message, r'logid="(?P<id>[^"]+)"'); err == null) {
log_id = to_string(logid_m.id)
}
if (type_m, err = parse_regex(log_message, r'\stype="(?P<type>[^"]+)"'); err == null) {
log_type = to_string(type_m.type)
}
if (subtype_m, err = parse_regex(log_message, r'subtype="(?P<subtype>[^"]+)"'); err == null) {
log_subtype = to_string(subtype_m.subtype)
}
if (level_m, err = parse_regex(log_message, r'level="(?P<level>[^"]+)"'); err == null) {
log_level = to_string(level_m.level)
}
if (srcip_m, err = parse_regex(log_message, r'srcip=(?P<ip>[0-9.]+)'); err == null) {
source_ip = to_string(srcip_m.ip)
}
if (srcport_m, err = parse_regex(log_message, r'srcport=(?P<port>\d+)'); err == null) {
source_port = to_string(srcport_m.port)
}
if (srcintf_m, err = parse_regex(log_message, r'srcintf="(?P<intf>[^"]+)"'); err == null) {
source_interface = to_string(srcintf_m.intf)
}
if (dstip_m, err = parse_regex(log_message, r'dstip=(?P<ip>[0-9.]+)'); err == null) {
destination_ip = to_string(dstip_m.ip)
}
if (dstport_m, err = parse_regex(log_message, r'dstport=(?P<port>\d+)'); err == null) {
destination_port = to_string(dstport_m.port)
}
if (dstintf_m, err = parse_regex(log_message, r'dstintf="(?P<intf>[^"]+)"'); err == null) {
destination_interface = to_string(dstintf_m.intf)
}
if (proto_m, err = parse_regex(log_message, r'\sproto=(?P<proto>\d+)'); err == null) {
proto_num = to_string(proto_m.proto)
if proto_num == "6" { network_transport = "tcp" }
if proto_num == "17" { network_transport = "udp" }
if proto_num == "1" { network_transport = "icmp" }
}
if (service_m, err = parse_regex(log_message, r'service="(?P<service>[^"]+)"'); err == null) {
network_protocol = downcase(to_string(service_m.service))
}
if (policyid_m, err = parse_regex(log_message, r'policyid=(?P<id>\d+)'); err == null) {
policy_id = to_string(policyid_m.id)
}
if (poluuid_m, err = parse_regex(log_message, r'poluuid="(?P<uuid>[^"]+)"'); err == null) {
policy_uuid = to_string(poluuid_m.uuid)
}
if (sessionid_m, err = parse_regex(log_message, r'sessionid=(?P<id>\d+)'); err == null) {
session_id = to_string(sessionid_m.id)
}
if (action_m, err = parse_regex(log_message, r'action="(?P<action>[^"]+)"'); err == null) {
action = to_string(action_m.action)
}
if (user_m, err = parse_regex(log_message, r'\suser="(?P<user>[^"]+)"'); err == null) {
user_name = to_string(user_m.user)
}
if (group_m, err = parse_regex(log_message, r'group="(?P<group>[^"]+)"'); err == null) {
group_name = to_string(group_m.group)
}
if (hostname_m, err = parse_regex(log_message, r'hostname="(?P<host>[^"]+)"'); err == null) {
url_domain = to_string(hostname_m.host)
server_address = to_string(hostname_m.host)
}
if (url_m, err = parse_regex(log_message, r'\surl="(?P<url>[^"]+)"'); err == null) {
url_full = to_string(url_m.url)
}
if (filename_m, err = parse_regex(log_message, r'filename="(?P<file>[^"]+)"'); err == null) {
file_name = to_string(filename_m.file)
}
if (attack_m, err = parse_regex(log_message, r'attack="(?P<attack>[^"]+)"'); err == null) {
attack_name = to_string(attack_m.attack)
threat_name = attack_name
}
if (attackid_m, err = parse_regex(log_message, r'attackid=(?P<id>\d+)'); err == null) {
attack_id = to_string(attackid_m.id)
}
if (virus_m, err = parse_regex(log_message, r'virus="(?P<virus>[^"]+)"'); err == null) {
threat_name = to_string(virus_m.virus)
}
if (catdesc_m, err = parse_regex(log_message, r'catdesc="(?P<cat>[^"]+)"'); err == null) {
threat_category = to_string(catdesc_m.cat)
}
log_type_str = to_string(log_type)
log_subtype_str = to_string(log_subtype)
action_str = to_string(action)
if log_type_str == "traffic" {
event_category = "network"
event_module = "fortigate.traffic"
if action_str == "accept" { event_action = "allowed"; event_outcome = "success" }
if action_str == "deny" { event_action = "denied"; event_outcome = "failure" }
if action_str == "close" { event_action = "connection_closed"; event_outcome = "success" }
}
if log_type_str == "utm" {
if log_subtype_str == "ips" {
event_category = "intrusion_detection"
event_module = "fortigate.ips"
if action_str == "dropped" { event_action = "blocked"; event_outcome = "success" }
if action_str == "detected" { event_action = "detected"; event_outcome = "success" }
}
if log_subtype_str == "virus" {
event_category = "malware"
event_module = "fortigate.antivirus"
if action_str == "blocked" { event_action = "blocked"; event_outcome = "success" }
if action_str == "detected" { event_action = "detected"; event_outcome = "success" }
}
if log_subtype_str == "webfilter" {
event_category = "web"
event_module = "fortigate.webfilter"
if action_str == "blocked" { event_action = "blocked"; event_outcome = "success" }
if action_str == "passthrough" { event_action = "allowed"; event_outcome = "success" }
}
if log_subtype_str == "app-ctrl" {
event_category = "network"
event_module = "fortigate.application"
if action_str == "blocked" { event_action = "blocked"; event_outcome = "success" }
if action_str == "pass" { event_action = "allowed"; event_outcome = "success" }
}
if log_subtype_str == "dns" {
event_category = "network"
event_module = "fortigate.dns"
if action_str == "pass" { event_action = "allowed"; event_outcome = "success" }
if action_str == "blocked" { event_action = "blocked"; event_outcome = "success" }
}
if log_subtype_str == "anomaly" {
event_category = "intrusion_detection"
event_module = "fortigate.anomaly"
threat_name = attack_name
if action_str == "clear_session" { event_action = "blocked"; event_outcome = "success" }
}
}
if log_type_str == "event" {
if log_subtype_str == "system" {
event_category = "authentication"
event_module = "fortigate.event"
if contains(log_message, "login") {
if contains(log_message, "success") { event_action = "logon_success"; event_outcome = "success" }
if contains(log_message, "failed") { event_action = "logon_failed"; event_outcome = "failure" }
}
}
if log_subtype_str == "user" {
event_category = "authentication"
event_module = "fortigate.event"
if contains(log_message, "authentication") {
if contains(log_message, "success") { event_action = "logon_success"; event_outcome = "success" }
if contains(log_message, "failure") { event_action = "logon_failed"; event_outcome = "failure" }
}
}
if log_subtype_str == "vpn" {
event_category = "network"
event_module = "fortigate.vpn"
event_action = "vpn_event"
event_outcome = "success"
}
if log_subtype_str == "router" {
event_category = "network"
event_module = "fortigate.router"
event_action = "routing_event"
event_outcome = "success"
}
}
if source_ip != "" { client_address = source_ip }
if event_category == "" { event_category = "network" }
if event_action == "" { event_action = "info" }
if event_outcome == "" { event_outcome = "unknown" }
#normalize
timestamp: format_timestamp!(parse_timestamp!(.timestamp, "%Y-%m-%dT%H:%M:%S.%3fZ"), "%Y-%m-%d %H:%M:%S")
event.module: event_module
event.category: event_category
event.action: event_action
event.outcome: event_outcome
log.level: log_level
message: log_message
source.ip: source_ip
source.port: source_port
destination.ip: destination_ip
destination.port: destination_port
network.transport: network_transport
network.protocol: network_protocol
user.name: user_name
group.name: group_name
url.full: url_full
url.domain: url_domain
file.name: file_name
server.address: server_address
client.address: client_address
threat.name: threat_name
Giải thích 3 section:
| Section | Mục đích |
|---|---|
#regex | Khai báo regex patterns dùng lại nhiều lần (có thể để trống) |
#conditional | VRL pipeline: extract fields từ raw log message |
#normalize | Map biến → ECS fields để lưu vào SIEM |
Bước 5 — Tạo Rule Detection
Tạo file streaming rule tại rules/streaming/Web-Filter-Detected.yaml:
touch rules/streaming/Web-Filter-Detected.yaml
Dán nội dung sau vào file:
title: "[FortiGate] Web Filter Detected"
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
sigma_status: experimental
description: >
Detects Web Filter events detected by FortiGate firewall.
This could indicate a potential security event if the Web Filter
is blocking access to malicious websites or monitoring user activity.
author: your-username
date: 2026/04/09
logsource:
product: "fortigate"
service: "webfilter"
category: "firewall"
detection:
selection_1:
event.category|contains: "utm"
selection_2:
event.type|contains: "webfilter"
condition: selection_1 AND selection_2
falsepositives: []
level: medium
⚠️ Mỗi rule phải có
idUUID v4 duy nhất. Tạo UUID bằng lệnh:uuidgen
Bước 6 — Validate
Validate kiểm tra cú pháp và cấu trúc không cần kết nối API:
cd .. # quay về thư mục chứa ccsp-client nếu cần
./ccsp-client validate "Fortigate Firewall"
Output thành công:
Validating package...
Package validation successful!
Your package is ready for testing and publishing.
Output lỗi thường gặp và cách sửa:
| Lỗi | Nguyên nhân | Cách sửa |
|---|---|---|
missing required field "vendor" | vendor trong package.json bị trống | Điền giá trị vào package.json |
missing required field "capability" | capability bị rỗng hoặc thiếu | Thêm "capability": ["parser", "rule"] |
no content found | Chưa có file trong parsers/ hoặc rules/ | Thêm ít nhất 1 file content |
invalid YAML syntax | Lỗi cú pháp YAML | Kiểm tra indentation, dấu ngoặc kép |
Bước 7 — Test
Test gửi parser và rule lên backend sandbox để kiểm thử (yêu cầu credentials từ Bước 1):
./ccsp-client test "Fortigate Firewall"
Output thành công:
Testing package files...
✓ Test passed: parsers/fortigate-firewall-parser.yaml
✓ Test passed: rules/streaming/Web-Filter-Detected.yaml
✓ Test success: 2 file(s) tested successfully
Output lỗi thường gặp và cách sửa:
| Lỗi | Nguyên nhân | Cách sửa |
|---|---|---|
Credentials not found | Chưa chạy add-access-key | Chạy lại Bước 1 |
no files found to test | Thư mục parsers/ và rules/ trống | Thêm file content |
validation_status: failed | Rule có lỗi cú pháp Sigma | Kiểm tra detection syntax |
Test failed: <tên file> | Nội dung parser/rule không hợp lệ | Xem thông báo lỗi chi tiết bên dưới |
💡 Tip: Luôn chạy
validatetrướctestđể phát hiện lỗi cú pháp ngay local, tránh tốn API call.
Bước 8 — Publish
Sau khi test thành công, publish package lên marketplace:
./ccsp-client publish "Fortigate Firewall":1.0.0
Output thành công:
Publishing package...
✓ Package validation passed
✓ Created archive: Fortigate Firewall.zip
✓ Package published successfully
Your package is now pending review by the marketplace team
Package sẽ ở trạng thái pending review cho đến khi được marketplace team duyệt.
⚠️ Khi muốn cập nhật package, tăng version trong
package.json(ví dụ1.0.0→1.1.0) rồi publish lại:./ccsp-client publish "Fortigate Firewall":1.1.0
Tổng kết cấu trúc thư mục
Sau khi hoàn thành, cấu trúc package trông như sau:
Fortigate Firewall/
├── package.json
├── parsers/
│ └── fortigate-firewall-parser.yaml ← Parser VRL pipeline
└── rules/
└── streaming/
└── Web-Filter-Detected.yaml ← Streaming detection rule
Tóm tắt các lệnh
# 1. Cấu hình credentials (1 lần duy nhất)
./ccsp-client add-access-key --id <ID> --secret <SECRET> --project <PROJECT>
# 2. Khởi tạo package
./ccsp-client init "Fortigate Firewall"
cd "Fortigate Firewall"
# 3. Sửa package.json, thêm parser và rule (xem hướng dẫn bên trên)
# 4. Validate (không cần API)
./ccsp-client validate "Fortigate Firewall"
# 5. Test (cần credentials)
./ccsp-client test "Fortigate Firewall"
# 6. Publish
./ccsp-client publish "Fortigate Firewall":1.0.0