tum пре 4 недеља
родитељ
комит
1d73c93084

+ 6 - 0
flask-openapi-shuffle/.dockerignore

1
+__pycache__/
2
+*.pyc
3
+.pytest_cache/
4
+.venv/
5
+venv/
6
+.git/

+ 16 - 0
flask-openapi-shuffle/Dockerfile

1
+FROM python:3.11-slim
2
+
3
+ENV PYTHONDONTWRITEBYTECODE=1 \
4
+    PYTHONUNBUFFERED=1
5
+
6
+WORKDIR /service
7
+
8
+COPY requirements.txt .
9
+RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+COPY app ./app
12
+COPY app.py .
13
+
14
+EXPOSE 8000
15
+
16
+CMD ["python", "app.py"]

+ 34 - 0
flask-openapi-shuffle/README.md

1
+# Flask OpenAPI Demo for Shuffle
2
+
3
+This project is an OpenAPI-first Flask service using Connexion.
4
+
5
+## Endpoints
6
+
7
+- `GET /health`
8
+- `POST /echo`
9
+- `GET /ioc/{value}`
10
+- Swagger UI: `GET /ui`
11
+- OpenAPI spec: `GET /openapi.json`
12
+
13
+## Run with Docker
14
+
15
+```bash
16
+cd /Users/simplicoltd./projects/soc/flask-openapi-shuffle
17
+docker compose up --build
18
+```
19
+
20
+Test quickly:
21
+
22
+```bash
23
+curl -s http://localhost:8000/health
24
+curl -s -X POST http://localhost:8000/echo -H 'Content-Type: application/json' -d '{"message":"hello"}'
25
+curl -s http://localhost:8000/ioc/8.8.8.8
26
+```
27
+
28
+## Use with Shuffle
29
+
30
+In Shuffle App Creator / OpenAPI import, use one of these URLs:
31
+
32
+- `http://localhost:8000/openapi.json` (if Shuffle can reach your host localhost)
33
+- `http://host.docker.internal:8000/openapi.json` (common when Shuffle runs in Docker on Mac/Windows)
34
+- `http://<your-host-ip>:8000/openapi.json` (Linux Docker or remote host)

+ 19 - 0
flask-openapi-shuffle/app.py

1
+import connexion
2
+
3
+
4
+def create_app():
5
+    cnx_app = connexion.FlaskApp(
6
+        __name__,
7
+        specification_dir="app",
8
+    )
9
+    cnx_app.add_api(
10
+        "openapi.yaml",
11
+        strict_validation=True,
12
+        validate_responses=True,
13
+    )
14
+    return cnx_app
15
+
16
+
17
+if __name__ == "__main__":
18
+    app = create_app()
19
+    app.run(host="0.0.0.0", port=8000)

+ 1 - 0
flask-openapi-shuffle/app/__init__.py

1
+# Package marker for Connexion operation imports.

+ 27 - 0
flask-openapi-shuffle/app/handlers.py

1
+from datetime import datetime, timezone
2
+
3
+
4
+def health_check():
5
+    return {"status": "ok", "timestamp": datetime.now(timezone.utc).isoformat()}, 200
6
+
7
+
8
+def echo_body(body):
9
+    return {"received": body, "length": len(str(body))}, 200
10
+
11
+
12
+def lookup_ioc(value, ioc_type="auto"):
13
+    normalized = value.strip().lower()
14
+    verdict = "unknown"
15
+    if "." in normalized and " " not in normalized:
16
+        verdict = "domain-like"
17
+    if normalized.count(".") == 3 and all(part.isdigit() for part in normalized.split(".")):
18
+        verdict = "ipv4-like"
19
+    if len(normalized) in {32, 40, 64} and all(c in "0123456789abcdef" for c in normalized):
20
+        verdict = "hash-like"
21
+
22
+    return {
23
+        "input": value,
24
+        "type": ioc_type,
25
+        "verdict": verdict,
26
+        "source": "demo-local",
27
+    }, 200

+ 106 - 0
flask-openapi-shuffle/app/openapi.yaml

1
+openapi: 3.0.3
2
+info:
3
+  title: SOC Demo Flask API
4
+  version: 1.0.0
5
+  description: OpenAPI-first Flask service for Shuffle testing.
6
+servers:
7
+  - url: http://localhost:8000
8
+paths:
9
+  /health:
10
+    get:
11
+      operationId: app.handlers.health_check
12
+      summary: Health check
13
+      responses:
14
+        "200":
15
+          description: Service is healthy
16
+          content:
17
+            application/json:
18
+              schema:
19
+                $ref: "#/components/schemas/HealthResponse"
20
+  /echo:
21
+    post:
22
+      operationId: app.handlers.echo_body
23
+      summary: Echo request body
24
+      requestBody:
25
+        required: true
26
+        content:
27
+          application/json:
28
+            schema:
29
+              $ref: "#/components/schemas/EchoRequest"
30
+      responses:
31
+        "200":
32
+          description: Echo response
33
+          content:
34
+            application/json:
35
+              schema:
36
+                $ref: "#/components/schemas/EchoResponse"
37
+  /ioc/{value}:
38
+    get:
39
+      operationId: app.handlers.lookup_ioc
40
+      summary: Basic IOC classification demo
41
+      parameters:
42
+        - name: value
43
+          in: path
44
+          required: true
45
+          schema:
46
+            type: string
47
+        - name: ioc_type
48
+          in: query
49
+          required: false
50
+          schema:
51
+            type: string
52
+            default: auto
53
+      responses:
54
+        "200":
55
+          description: IOC lookup result
56
+          content:
57
+            application/json:
58
+              schema:
59
+                $ref: "#/components/schemas/LookupResponse"
60
+components:
61
+  schemas:
62
+    HealthResponse:
63
+      type: object
64
+      properties:
65
+        status:
66
+          type: string
67
+          example: ok
68
+        timestamp:
69
+          type: string
70
+          format: date-time
71
+      required:
72
+        - status
73
+        - timestamp
74
+    EchoRequest:
75
+      type: object
76
+      additionalProperties: true
77
+      example:
78
+        message: test from Shuffle
79
+    EchoResponse:
80
+      type: object
81
+      properties:
82
+        received:
83
+          type: object
84
+          additionalProperties: true
85
+        length:
86
+          type: integer
87
+      required:
88
+        - received
89
+        - length
90
+    LookupResponse:
91
+      type: object
92
+      properties:
93
+        input:
94
+          type: string
95
+        type:
96
+          type: string
97
+        verdict:
98
+          type: string
99
+          example: domain-like
100
+        source:
101
+          type: string
102
+      required:
103
+        - input
104
+        - type
105
+        - verdict
106
+        - source

+ 6 - 0
flask-openapi-shuffle/docker-compose.yml

1
+services:
2
+  flask-openapi:
3
+    build: .
4
+    container_name: flask-openapi-shuffle
5
+    ports:
6
+      - "8000:8000"

+ 3 - 0
flask-openapi-shuffle/requirements.txt

1
+connexion[flask,swagger-ui]==3.1.0
2
+flask==3.1.0
3
+uvicorn==0.34.0

+ 38 - 9
run-combined-stack.sh

26
 
26
 
27
 Commands:
27
 Commands:
28
   up           Start services (default: up -d when no args)
28
   up           Start services (default: up -d when no args)
29
-  down         Stop services
29
+  down         Stop services (requires explicit target)
30
   logs         View logs
30
   logs         View logs
31
   status       Show container and endpoint status
31
   status       Show container and endpoint status
32
   help         Show this help message
32
   help         Show this help message
33
 
33
 
34
 Targets:
34
 Targets:
35
-  all|--all    All stacks (wazuh, iris, shuffle, pagerduty, integrator)
35
+  all|--all    All stacks (wazuh, iris, shuffle, pagerduty, integrator, flask-openapi-shuffle)
36
   wazuh        Wazuh single-node stack
36
   wazuh        Wazuh single-node stack
37
   iris         IRIS-web stack
37
   iris         IRIS-web stack
38
   shuffle      Shuffle stack
38
   shuffle      Shuffle stack
39
   pagerduty    PagerDuty stub
39
   pagerduty    PagerDuty stub
40
   integrator   SOC integrator stack
40
   integrator   SOC integrator stack
41
+  flask-openapi-shuffle  Flask OpenAPI demo stack
41
 
42
 
42
 Examples:
43
 Examples:
43
   ./run-combined-stack.sh
44
   ./run-combined-stack.sh
44
   ./run-combined-stack.sh up --all -d
45
   ./run-combined-stack.sh up --all -d
45
   ./run-combined-stack.sh up iris -d
46
   ./run-combined-stack.sh up iris -d
47
+  ./run-combined-stack.sh up flask-openapi-shuffle -d
46
   ./run-combined-stack.sh down shuffle
48
   ./run-combined-stack.sh down shuffle
49
+  ./run-combined-stack.sh down --all
47
   ./run-combined-stack.sh logs integrator -f
50
   ./run-combined-stack.sh logs integrator -f
48
   ./run-combined-stack.sh logs --all --tail 200
51
   ./run-combined-stack.sh logs --all --tail 200
49
   ./run-combined-stack.sh status
52
   ./run-combined-stack.sh status
104
     "${COMMAND}" ${ARGS[@]+"${ARGS[@]}"}
107
     "${COMMAND}" ${ARGS[@]+"${ARGS[@]}"}
105
 }
108
 }
106
 
109
 
110
+run_flask_openapi_shuffle() {
111
+  docker compose \
112
+    --project-name flask-openapi-shuffle \
113
+    --project-directory "${ROOT_DIR}/flask-openapi-shuffle" \
114
+    -f "${ROOT_DIR}/flask-openapi-shuffle/docker-compose.yml" \
115
+    "${COMMAND}" ${ARGS[@]+"${ARGS[@]}"}
116
+}
117
+
107
 run_target() {
118
 run_target() {
108
   local target="$1"
119
   local target="$1"
109
   case "${target}" in
120
   case "${target}" in
122
     integrator)
133
     integrator)
123
       run_soc_integrator
134
       run_soc_integrator
124
       ;;
135
       ;;
136
+    flask-openapi-shuffle)
137
+      run_flask_openapi_shuffle
138
+      ;;
125
     *)
139
     *)
126
       echo "Unknown target: ${target}"
140
       echo "Unknown target: ${target}"
127
-      echo "Use one of: wazuh, iris, shuffle, pagerduty, integrator"
141
+      echo "Use one of: wazuh, iris, shuffle, pagerduty, integrator, flask-openapi-shuffle"
128
       exit 1
142
       exit 1
129
       ;;
143
       ;;
130
   esac
144
   esac
134
   local mode="$1"
148
   local mode="$1"
135
 
149
 
136
   if [[ "${mode}" == "down" ]]; then
150
   if [[ "${mode}" == "down" ]]; then
151
+    run_flask_openapi_shuffle
137
     run_soc_integrator
152
     run_soc_integrator
138
     run_pagerduty_stub
153
     run_pagerduty_stub
139
     run_shuffle
154
     run_shuffle
145
     run_shuffle
160
     run_shuffle
146
     run_pagerduty_stub
161
     run_pagerduty_stub
147
     run_soc_integrator
162
     run_soc_integrator
163
+    run_flask_openapi_shuffle
148
   fi
164
   fi
149
 }
165
 }
150
 
166
 
157
   run_shuffle &
173
   run_shuffle &
158
   run_pagerduty_stub &
174
   run_pagerduty_stub &
159
   run_soc_integrator &
175
   run_soc_integrator &
176
+  run_flask_openapi_shuffle &
160
 
177
 
161
   trap 'kill 0' INT TERM
178
   trap 'kill 0' INT TERM
162
   wait
179
   wait
180
     integrator)
197
     integrator)
181
       run_soc_integrator
198
       run_soc_integrator
182
       ;;
199
       ;;
200
+    flask-openapi-shuffle)
201
+      run_flask_openapi_shuffle
202
+      ;;
183
     all|--all)
203
     all|--all)
184
       run_wazuh
204
       run_wazuh
185
       run_iris
205
       run_iris
186
       run_shuffle
206
       run_shuffle
187
       run_pagerduty_stub
207
       run_pagerduty_stub
188
       run_soc_integrator
208
       run_soc_integrator
209
+      run_flask_openapi_shuffle
189
       ;;
210
       ;;
190
     *)
211
     *)
191
       echo "Unknown logs target: ${target}"
212
       echo "Unknown logs target: ${target}"
192
-      echo "Use one of: wazuh, iris, shuffle, pagerduty, integrator"
213
+      echo "Use one of: wazuh, iris, shuffle, pagerduty, integrator, flask-openapi-shuffle"
193
       exit 1
214
       exit 1
194
       ;;
215
       ;;
195
   esac
216
   esac
196
 }
217
 }
197
 
218
 
198
 if [[ "${COMMAND}" == "down" ]]; then
219
 if [[ "${COMMAND}" == "down" ]]; then
199
-  TARGET="${1:-all}"
220
+  TARGET="${1:-}"
221
+  if [[ -z "${TARGET}" ]]; then
222
+    echo "Refusing to run 'down' without a target."
223
+    echo "Use one of:"
224
+    echo "  ./run-combined-stack.sh down <wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle>"
225
+    echo "  ./run-combined-stack.sh down --all"
226
+    exit 1
227
+  fi
228
+
200
   case "${TARGET}" in
229
   case "${TARGET}" in
201
-    wazuh|iris|shuffle|pagerduty|integrator)
230
+    wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle)
202
       ARGS=("${ARGS[@]:1}")
231
       ARGS=("${ARGS[@]:1}")
203
       run_target "${TARGET}"
232
       run_target "${TARGET}"
204
       ;;
233
       ;;
213
 elif [[ "${COMMAND}" == "logs" ]]; then
242
 elif [[ "${COMMAND}" == "logs" ]]; then
214
   LOGS_TARGET="${1:-all}"
243
   LOGS_TARGET="${1:-all}"
215
   case "${LOGS_TARGET}" in
244
   case "${LOGS_TARGET}" in
216
-    wazuh|iris|shuffle|pagerduty|integrator)
245
+    wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle)
217
       ARGS=("${ARGS[@]:1}")
246
       ARGS=("${ARGS[@]:1}")
218
       run_logs_for_target "${LOGS_TARGET}"
247
       run_logs_for_target "${LOGS_TARGET}"
219
       ;;
248
       ;;
225
       for arg in ${ARGS[@]+"${ARGS[@]}"}; do
254
       for arg in ${ARGS[@]+"${ARGS[@]}"}; do
226
         if [[ "${arg}" == "-f" || "${arg}" == "--follow" ]]; then
255
         if [[ "${arg}" == "-f" || "${arg}" == "--follow" ]]; then
227
           echo "For follow mode, specify one target:"
256
           echo "For follow mode, specify one target:"
228
-          echo "./run-combined-stack.sh logs <wazuh|iris|shuffle|pagerduty|integrator> -f"
257
+          echo "./run-combined-stack.sh logs <wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle> -f"
229
           exit 1
258
           exit 1
230
         fi
259
         fi
231
       done
260
       done
235
 elif [[ "${COMMAND}" == "up" ]]; then
264
 elif [[ "${COMMAND}" == "up" ]]; then
236
   TARGET="${1:-all}"
265
   TARGET="${1:-all}"
237
   case "${TARGET}" in
266
   case "${TARGET}" in
238
-    wazuh|iris|shuffle|pagerduty|integrator)
267
+    wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle)
239
       ARGS=("${ARGS[@]:1}")
268
       ARGS=("${ARGS[@]:1}")
240
       run_target "${TARGET}"
269
       run_target "${TARGET}"
241
       ;;
270
       ;;

+ 244 - 0
shuffle-workflows/sample-ip-ioc-check-workflow.json

1
+{
2
+  "workflow_as_code": false,
3
+  "actions": [
4
+    {
5
+      "app_name": "Shuffle Tools",
6
+      "app_version": "1.2.0",
7
+      "description": "Receive IP input from webhook payload.",
8
+      "app_id": "0671c57b-3af6-43f7-9501-b2f916c127c8",
9
+      "errors": [],
10
+      "id": "b6f4c2a8-3d6d-4d1f-8c70-03d09a1e6f11",
11
+      "is_valid": true,
12
+      "isStartNode": true,
13
+      "sharing": true,
14
+      "label": "Webhook Trigger (IP Input)",
15
+      "public": true,
16
+      "generated": false,
17
+      "large_image": "",
18
+      "environment": "Shuffle",
19
+      "name": "webhook",
20
+      "parameters": [
21
+        {
22
+          "name": "source_ip",
23
+          "value": "",
24
+          "description": "IP address to check as IOC.",
25
+          "required": true,
26
+          "multiline": false,
27
+          "multiselect": false,
28
+          "options": null,
29
+          "action_field": "",
30
+          "variant": "",
31
+          "configuration": false,
32
+          "tags": null,
33
+          "schema": {
34
+            "type": ""
35
+          },
36
+          "skip_multicheck": false,
37
+          "value_replace": null,
38
+          "unique_toggled": false,
39
+          "error": "",
40
+          "hidden": false
41
+        }
42
+      ],
43
+      "execution_variable": {
44
+        "description": "",
45
+        "id": "",
46
+        "name": "",
47
+        "value": ""
48
+      },
49
+      "position": {
50
+        "x": 100,
51
+        "y": 120
52
+      },
53
+      "authentication_id": "",
54
+      "category": "",
55
+      "reference_url": "",
56
+      "sub_action": false,
57
+      "run_magic_output": false,
58
+      "run_magic_input": false,
59
+      "execution_delay": 0,
60
+      "category_label": null,
61
+      "suggestion": false,
62
+      "parent_controlled": false,
63
+      "source_workflow": "",
64
+      "source_execution": ""
65
+    },
66
+    {
67
+      "app_name": "Shuffle Tools",
68
+      "app_version": "1.2.0",
69
+      "description": "Build request body for SOC IOC evaluation endpoint.",
70
+      "app_id": "0671c57b-3af6-43f7-9501-b2f916c127c8",
71
+      "errors": [],
72
+      "id": "e72dcb76-a265-4da1-a0dd-2f65f558b52f",
73
+      "is_valid": true,
74
+      "isStartNode": false,
75
+      "sharing": true,
76
+      "label": "Prepare IOC Check Payload",
77
+      "public": true,
78
+      "generated": false,
79
+      "large_image": "",
80
+      "environment": "Shuffle",
81
+      "name": "repeat_back_to_me",
82
+      "parameters": [
83
+        {
84
+          "name": "call",
85
+          "value": "{\"ioc_type\":\"ip\",\"ioc_value\":\"{{actions.b6f4c2a8-3d6d-4d1f-8c70-03d09a1e6f11.source_ip}}\",\"source_event\":{\"event_id\":\"shuffle-ip-check-sample\",\"network\":{\"src_ip\":\"{{actions.b6f4c2a8-3d6d-4d1f-8c70-03d09a1e6f11.source_ip}}\"}}}",
86
+          "description": "Use this JSON as body for POST /mvp/ioc/evaluate in a HTTP node.",
87
+          "required": false,
88
+          "multiline": true,
89
+          "multiselect": false,
90
+          "options": null,
91
+          "action_field": "",
92
+          "variant": "",
93
+          "configuration": false,
94
+          "tags": null,
95
+          "schema": {
96
+            "type": ""
97
+          },
98
+          "skip_multicheck": false,
99
+          "value_replace": null,
100
+          "unique_toggled": false,
101
+          "error": "",
102
+          "hidden": false
103
+        }
104
+      ],
105
+      "execution_variable": {
106
+        "description": "",
107
+        "id": "",
108
+        "name": "",
109
+        "value": ""
110
+      },
111
+      "position": {
112
+        "x": 430,
113
+        "y": 120
114
+      },
115
+      "authentication_id": "",
116
+      "category": "",
117
+      "reference_url": "",
118
+      "sub_action": false,
119
+      "run_magic_output": false,
120
+      "run_magic_input": false,
121
+      "execution_delay": 0,
122
+      "category_label": null,
123
+      "suggestion": false,
124
+      "parent_controlled": false,
125
+      "source_workflow": "",
126
+      "source_execution": ""
127
+    }
128
+  ],
129
+  "branches": [
130
+    {
131
+      "id": "branch-ip-ioc-1",
132
+      "source": "b6f4c2a8-3d6d-4d1f-8c70-03d09a1e6f11",
133
+      "destination": "e72dcb76-a265-4da1-a0dd-2f65f558b52f",
134
+      "success": true,
135
+      "label": ""
136
+    }
137
+  ],
138
+  "visual_branches": null,
139
+  "triggers": [],
140
+  "comments": [],
141
+  "configuration": {
142
+    "exit_on_error": false,
143
+    "start_from_top": false,
144
+    "skip_notifications": false
145
+  },
146
+  "created": 1771470000,
147
+  "edited": 1771470000,
148
+  "last_runtime": 0,
149
+  "due_date": 0,
150
+  "id": "d2ccf0bd-bf4d-4f77-8eea-c1a65f1ea3e9",
151
+  "is_valid": true,
152
+  "name": "Sample - IP IOC Check Payload Builder",
153
+  "description": "Sample Shuffle workflow JSON for IP IOC check integration. Trigger with source_ip, then pass generated JSON to HTTP POST /mvp/ioc/evaluate.",
154
+  "start": "b6f4c2a8-3d6d-4d1f-8c70-03d09a1e6f11",
155
+  "owner": "1050bd5b-b1bb-4c22-acfb-94156cdc0567",
156
+  "sharing": "private",
157
+  "execution_org": {
158
+    "name": "default",
159
+    "id": "03264040-f718-4a61-b9ac-61c7cac3fe99",
160
+    "users": [],
161
+    "role": "admin",
162
+    "child_orgs": null,
163
+    "region_url": "",
164
+    "is_partner": false,
165
+    "image": "",
166
+    "creator_org": "",
167
+    "branding": {
168
+      "enable_chat": false,
169
+      "home_url": "",
170
+      "theme": "",
171
+      "documentation_link": "",
172
+      "global_user": false,
173
+      "support_email": "",
174
+      "logout_url": "",
175
+      "brand_color": "",
176
+      "brand_name": ""
177
+    }
178
+  },
179
+  "org_id": "03264040-f718-4a61-b9ac-61c7cac3fe99",
180
+  "workflow_variables": null,
181
+  "execution_environment": "",
182
+  "previously_saved": true,
183
+  "categories": {
184
+    "intel": {
185
+      "name": "intel",
186
+      "count": 0,
187
+      "id": "",
188
+      "description": "",
189
+      "large_image": ""
190
+    }
191
+  },
192
+  "example_argument": "",
193
+  "public": false,
194
+  "default_return_value": "",
195
+  "contact_info": {
196
+    "name": "",
197
+    "url": ""
198
+  },
199
+  "published_id": "",
200
+  "revision_id": "",
201
+  "usecase_ids": null,
202
+  "input_questions": null,
203
+  "form_control": {
204
+    "input_markdown": "",
205
+    "output_yields": null,
206
+    "cleanup_actions": null,
207
+    "form_width": 0
208
+  },
209
+  "blogpost": "",
210
+  "video": "",
211
+  "status": "test",
212
+  "workflow_type": "",
213
+  "generated": false,
214
+  "hidden": false,
215
+  "background_processing": false,
216
+  "updated_by": "root",
217
+  "validated": false,
218
+  "validation": {
219
+    "valid": false,
220
+    "changed_at": 0,
221
+    "last_valid": 0,
222
+    "validation_ran": false,
223
+    "notifications_created": 0,
224
+    "environment": "",
225
+    "workflow_id": "",
226
+    "execution_id": "",
227
+    "node_id": "",
228
+    "total_problems": 0,
229
+    "errors": [],
230
+    "subflow_apps": []
231
+  },
232
+  "parentorg_workflow": "",
233
+  "childorg_workflow_ids": null,
234
+  "suborg_distribution": [],
235
+  "backup_config": {
236
+    "onprem_backup": false,
237
+    "upload_repo": "",
238
+    "upload_branch": "",
239
+    "upload_username": "",
240
+    "upload_token": "",
241
+    "tokens_encrypted": false
242
+  },
243
+  "auth_groups": null
244
+}

Разлика између датотеке није приказан због своје велике величине
+ 553 - 0
virustotal_v3.json


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
virustotal_v3.openapi.json


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
virustotal_v3.yaml