Removed all irrelevant files

This commit is contained in:
Frikky
2025-07-18 14:36:24 +02:00
parent 98c3480aba
commit 3c46e88970
18 changed files with 0 additions and 1610 deletions
-109
View File
@@ -1,109 +0,0 @@
import json
import yaml
items = []
openapi = {
"openapi": "3.0.2",
"info": {
"title": "MISP",
"description": "MISP API generated from the misp book: https://github.com/MISP/misp-book/blob/master/automation/README.md",
"version": "1.0.0",
"contact": {
"name": "@frikkylikeme",
"url": "https://twitter.com/frikkylikeme",
"email": "frikky@shuffler.io"
}
},
"paths": {},
"components": {
"schemas": {},
"securitySchemes": {
"ApiKeyAuth": {
"type": "apikey",
"in": "header",
"name": "Authorization",
}
},
}
}
with open("misp.txt", "r") as tmp:
newitem = {}
recorditem = False
counter = 0
itemsplit = tmp.read().split("\n")
for item in itemsplit:
counter += 1
if item.startswith("### ") and "/" in item:
try:
path = item.split(" ")[2]
method = item.split(" ")[1].lower()
newitem = {
"path": path,
"method": method,
}
try:
openapi["paths"][path][method] = {}
except KeyError:
openapi["paths"][path] = {}
openapi["paths"][path][method] = {}
except IndexError:
newitem = {}
continue
recorditem = True
#print(newitem)
if not recorditem:
continue
if "Description" in item:
openapi["paths"][newitem["path"]][newitem["method"]]["description"] = itemsplit[counter+1]
elif "URL Arguments" in item:
parameters = []
innercnt = 0
openapi["paths"][newitem["path"]][newitem["method"]]["parameters"] = []
while True:
curline = itemsplit[counter+1+innercnt]
if "#" in curline:
break
innercnt += 1
if not curline:
continue
print(curline)
parameters.append({
"description": curline.split(" ")[1],
"in": "query",
"name": curline.split(" ")[1],
"required": True,
"schema": {"type": "string"},
})
openapi["paths"][newitem["path"]][newitem["method"]]["parameters"] = parameters
elif "Output" in item:
# FIXME
innercnt = 0
while True:
curline = itemsplit[counter+1+innercnt]
if "#" in curline:
break
innercnt += 1
if "json" in curline:
continue
#print(curline)
print(json.dumps(openapi, indent=4))
generatedfile = "generated/misp.yaml"
with open(generatedfile, "w+") as tmp:
tmp.write(yaml.dump(openapi))
-311
View File
@@ -1,311 +0,0 @@
import requests
import yaml
import json
import os
import io
import base64
from PIL import Image
#import tkinter
#import _tkinter
#tkinter._test()
#sudo apt-get install python-imaging-tk
#sudo apt-get install python3-tk
# USAGE:
# 1. Find the item here:
# https://apphub.swimlane.com/swimbundles/swimlane/sw_alienvault_threatcrowd
# 2.
# https://jsonlint.com/
# META: data["meta"]. Stuff like count. May be useful :)
def parse_data(data):
openapi = {
"openapi": "3.0.2",
"info": {
"title": "",
"description": "",
"version": "1.0.0",
"contact": {
"name": "@frikkylikeme",
"url": "https://twitter.com/frikkylikeme",
"email": "frikky@shuffler.io"
}
},
"paths": {},
"components": {
"schemas": {},
"securitySchemes": {},
}
}
data = data["swimbundle"]
filename = "%s.yaml" % data["product"].replace(" ", "_").lower()
openapi["info"]["title"] = "%s %s" % (data["vendor"], data["product"])
openapi["info"]["description"] = "Automated generation of %s" % (openapi["info"]["title"])
# data["description"]
# https://swagger.io/docs/specification/authentication/
try:
asset = data["asset"]
inputparams = asset["inputParameters"]
try:
openapi["servers"] = [inputparams["api_url"]["example"]]
except KeyError as e:
#print(inputparams)
#print("Field error: %s" % e)
pass
authset = False
try:
tmpauth = inputparams["api_user"]
tmpauth = inputparams["api_key"]
openapi["components"]["securitySchemes"] = {
"BasicAuth": {
"type": "http",
"scheme": "basic"
}
}
authset = True
except KeyError as e:
pass
try:
tmpauth = inputparams["username"]
tmpauth = inputparams["password"]
openapi["components"]["securitySchemes"] = {
"BasicAuth": {
"type": "http",
"scheme": "basic"
}
}
authset = True
except KeyError as e:
pass
#if not authset:
# print("AUTH NOT SET: %s" % inputparams)
except KeyError as e:
print("KeyError asset: %s" % e)
cnt = 0
paramnames = []
for task in data["tasks"]:
method = "post"
openapi["paths"]["tmp%d" % cnt] = {}
openapi["paths"]["tmp%d" % cnt][method] = {
"summary": task["name"],
"description": task["description"],
"parameters": [],
"responses": {
"200": {
"description": "Successful request",
}
},
}
taskcategory = task["family"]
taskname = task["name"]
paramnames.append(taskname)
for key, value in task["inputParameters"].items():
schema = "string"
inVar = "query"
if value["type"] == 6:
inVar = "body"
schema = "string"
schemaset = False
if value["type"] != 1:
if (value["type"] == 7):
schema = "boolean"
schemaset = True
if schema == "string" and schemaset:
print("Should change type: %d" % value["type"])
print(task["name"])
print(value["name"])
example = ""
try:
example = value["example"]
except KeyError:
pass
description = ""
try:
description = value["description"]
except KeyError:
pass
required = False
try:
required = value["required"]
except KeyError:
pass
openapi["paths"]["tmp%d" % cnt][method]["parameters"].append({
"name": value["name"],
"required": required,
"example": example,
"description": description,
"schema": {"type": schema},
"in": inVar
})
if len(task["availableOutputVariables"]) > 0:
openapi["paths"]["tmp%d" % cnt][method]["responses"]["200"]["content"] = {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tmp%d" % cnt
}
}
}
#responses:
# '200':
# content:
# application/json:
# schema:
# $ref: '#/components/schemas/tmp1'
#description: Successful request
openapi["components"]["schemas"]["tmp%d" % cnt] = {
"type": "object",
"properties": {},
}
for key, value in task["availableOutputVariables"].items():
if key == "response_code":
continue
openapi["components"]["schemas"]["tmp%d" % cnt]["properties"][key] = {
"type": "string"
}
cnt += 1
print("%s: %d" % (openapi["info"]["title"], len(paramnames)))
return filename, openapi
def dump_data(filename, openapi, category):
generatedfile = "generated/%s/%s" % (category, filename)
try:
with open(generatedfile, "w+") as tmp:
tmp.write(yaml.dump(openapi))
except FileNotFoundError:
try:
os.mkdir("generated/%s" % category)
with open(generatedfile, "w+") as tmp:
tmp.write(yaml.dump(openapi))
except FileExistsError:
pass
if __name__ == "__main__":
#https://apphub.swimlane.com/
categories = [
"Investigation",
"Endpoint Security & Management",
"Network Security & Management",
"Communication",
"SIEM & Log Management",
"Governance & Risk Management",
"Vulnerability & Patch Management",
"Ticket Management",
"DevOps & Application Security",
"Identity & Access Management",
"Infrastructure",
"Miscellaneous",
]
search_category = categories[2]
total = 0
for search_category in categories:
number = 1
innertotal = 0
while(True):
url = "https://apphub.swimlane.io/api/search/swimbundles?page=%d" % number
json = {"fields": {"family": search_category}}
ret = requests.post(
url,
json=json,
)
if ret.status_code != 201:
print("RET NOT 201: %d" % ret.status_code)
break
parsed = ret.json()
try:
category = parsed["data"][0]["swimbundleMeta"]["family"][0]
except KeyError:
category = ""
except IndexError:
category = ""
if category == "":
break
for data in parsed["data"]:
try:
filename, openapi = parse_data(data)
except:
try:
print("Skipping %s %s because of an error" % (data["vendor"], data["product"]))
except KeyError:
pass
continue
openapi["tags"] = [
{
"name": category,
}
]
appid = data["swimbundleMeta"]["logo"]["id"]
logoUrl = "https://apphub.swimlane.io/api/logos/%s" % appid
logodata = requests.get(logoUrl)
if logodata.status_code == 200:
logojson = logodata.json()
try:
logobase64 = logojson["data"]["base64"]
#.split(",")[1]
openapi["info"]["x-logo"] = logobase64
#print(logobase64)
#msg = base64.b64decode(logobase64)
#with io.BytesIO(msg) as buf:
# with Image.open(buf) as tempImg:
# newWidth = 174 / tempImg.width # change this to what ever width you need.
# newHeight = 174 / tempImg.height # change this to what ever height you need.
# newSize = (int(newWidth * tempImg.width), int(newHeight * tempImg.height))
# newImg1 = tempImg.resize(newSize)
# lbl1.IMG = ImageTk.PhotoImage(image=newImg1)
# lbl1.configure(image=lbl1.IMG)
except KeyError:
print("Failed logo parsing for %s" % appid)
pass
dump_data(filename, openapi, category)
innertotal += 1
total += 1
number += 1
print("Created %d openapi specs from Swimlane with category %s" % (innertotal, search_category))
print("\nCreated %d TOTAL openapi specs from Swimlane" % (total))
-7
View File
@@ -1,7 +0,0 @@
# OpenAPI generator
This contains test code that's been moved to shaffuru/backend/go-app/codegen.go
## Todo:
1. Don't use filesystem, but rather store in GCP
2. Add swagger 2.0 to 3.0 converter
3. Fix body / data parsing
@@ -1,29 +0,0 @@
# Base our app image off of the WALKOFF App SDK image
FROM frikky/shuffle:app_sdk as base
# We're going to stage away all of the bloat from the build tools so lets create a builder stage
#FROM base as builder
# Install all alpine build tools needed for our pip installs
#RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev
# Install all of our pip packages in a single directory that we can copy to our base image later
#RUN mkdir /install
#WORKDIR /install
#COPY requirements.txt /requirements.txt
#
## Switch back to our base image and copy in all of our built packages and source code
#FROM base
#COPY --from=builder /install /usr/local
# Install any binary dependencies needed in our final image - this can be a lot of different stuff
#RUN apk --no-cache add --update libmagic
WORKDIR /
COPY requirements.txt /requirements.txt
RUN pip install --prefix="/usr/local" -r /requirements.txt
COPY src /app
# Finally, lets run our app!
WORKDIR /app
CMD python app.py --log-level DEBUG
@@ -1,11 +0,0 @@
# No extra requirements needed
requests==2.32.3
urllib3==2.3.0
liquidpy==0.8.2
MarkupSafe==3.0.2
flask[async]==3.1.0
python-dateutil==2.9.0.post0
PyJWT==2.10.1
cryptography==44.0.2
shufflepy==0.1.0
shuffle-sdk==0.0.25
-387
View File
@@ -1,387 +0,0 @@
package main
import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"github.com/getkin/kin-openapi/openapi3"
"gopkg.in/yaml.v2"
"io"
"io/ioutil"
"log"
"os"
"strings"
)
type WorkflowApp struct {
Name string `json:"name" yaml:"name" required:true datastore:"name"`
IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"`
ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"`
Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"`
AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"`
Description string `json:"description" datastore:"description" required:false yaml:"description"`
Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"`
SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"`
LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false`
ContactInfo struct {
Name string `json:"name" datastore:"name" yaml:"name"`
Url string `json:"url" datastore:"url" yaml:"url"`
} `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false`
Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"`
Authentication Authentication `json:"authentication" yaml:"authentication" required:false datastore:"authentication"`
}
type AuthenticationParams struct {
Description string `json:"description" datastore:"description" yaml:"description"`
ID string `json:"id" datastore:"id" yaml:"id"`
Name string `json:"name" datastore:"name" yaml:"name"`
Example string `json:"example" datastore:"example" yaml:"example"s`
Value string `json:"value,omitempty" datastore:"value" yaml:"value"`
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
Required bool `json:"required" datastore:"required" yaml:"required"`
}
type Authentication struct {
Required bool `json:"required" datastore:"required" yaml:"required" `
Parameters []AuthenticationParams `json:"parameters" datastore:"parameters" yaml:"parameters"`
}
type AuthenticationStore struct {
Key string `json:"key" datastore:"key"`
Value string `json:"value" datastore:"value"`
}
type WorkflowAppActionParameter struct {
Description string `json:"description" datastore:"description" yaml:"description"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Name string `json:"name" datastore:"name" yaml:"name"`
Example string `json:"example" datastore:"example" yaml:"example"`
Value string `json:"value" datastore:"value" yaml:"value,omitempty"`
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"`
Variant string `json:"variant" datastore:"variant" yaml:"variant,omitempty"`
Required bool `json:"required" datastore:"required" yaml:"required"`
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
}
type SchemaDefinition struct {
Type string `json:"type" datastore:"type"`
}
type WorkflowAppAction struct {
Description string `json:"description" datastore:"description"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Name string `json:"name" datastore:"name"`
NodeType string `json:"node_type" datastore:"node_type"`
Environment string `json:"environment" datastore:"environment"`
Authentication []AuthenticationStore `json:"authentication" datastore:"authentication" yaml:"authentication,omitempty"`
Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"`
Returns struct {
Description string `json:"description" datastore:"returns" yaml:"description,omitempty"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
} `json:"returns" datastore:"returns"`
}
func copyFile(fromfile, tofile string) error {
from, err := os.Open(fromfile)
if err != nil {
return err
}
defer from.Close()
to, err := os.OpenFile(tofile, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer to.Close()
_, err = io.Copy(to, from)
if err != nil {
return err
}
return nil
}
// Builds the base structure for the app that we're making
// Returns error if anything goes wrong. This has to work if
// the python code is supposed to be generated
func buildStructure(swagger *openapi3.Swagger, curHash string) (string, error) {
//log.Printf("%#v", swagger)
// adding md5 based on input data to not overwrite earlier data.
generatedPath := "generated"
identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash)
appPath := fmt.Sprintf("%s/%s", generatedPath, identifier)
os.MkdirAll(appPath, os.ModePerm)
os.Mkdir(fmt.Sprintf("%s/src", appPath), os.ModePerm)
err := copyFile("baseline/Dockerfile", fmt.Sprintf("%s/%s", appPath, "Dockerfile"))
if err != nil {
log.Println("Failed to move Dockerfile")
return appPath, err
}
err = copyFile("baseline/requirements.txt", fmt.Sprintf("%s/%s", appPath, "requirements.txt"))
if err != nil {
log.Println("Failed to move requrements.txt")
return appPath, err
}
return appPath, nil
}
func makePythoncode(name, url, method string, parameters, optionalQueries []string) string {
method = strings.ToLower(method)
queryString := ""
queryData := ""
// FIXME - this might break - need to check if ? or & should be set as query
parameterData := ""
if len(optionalQueries) > 0 {
queryString += ", "
for _, query := range optionalQueries {
queryString += fmt.Sprintf("%s=\"\"", query)
queryData += fmt.Sprintf(`
if %s:
url += f"&%s={%s}"`, query, query, query)
}
}
if len(parameters) > 0 {
parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", "))
}
// FIXME - add checks for query data etc
data := fmt.Sprintf(` async def %s_%s(self%s%s):
url=f"%s"
%s
return requests.%s(url).text
`, name, method, parameterData, queryString, url, queryData, method)
return data
}
func generateYaml(swagger *openapi3.Swagger) (WorkflowApp, []string, error) {
api := WorkflowApp{}
log.Printf("%#v", swagger.Info)
if len(swagger.Info.Title) == 0 {
return WorkflowApp{}, []string{}, errors.New("Swagger.Info.Title can't be empty.")
}
if len(swagger.Servers) == 0 {
return WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'")
}
api.Name = swagger.Info.Title
api.Description = swagger.Info.Description
api.IsValid = true
api.Link = swagger.Servers[0].URL // host does not exist lol
api.AppVersion = "1.0.0"
api.Environment = "cloud"
api.ID = ""
api.SmallImage = ""
api.LargeImage = ""
// This is the python code to be generated
// Could just as well be go at this point lol
pythonFunctions := []string{}
for actualPath, path := range swagger.Paths {
//log.Printf("%#v", path)
//log.Printf("%#v", actualPath)
// Find the path name and add it to makeCode() param
firstQuery := true
if path.Get != nil {
// What to do with this, hmm
functionName := strings.ReplaceAll(path.Get.Summary, " ", "_")
functionName = strings.ToLower(functionName)
action := WorkflowAppAction{
Description: path.Get.Description,
Name: path.Get.Summary,
NodeType: "action",
Environment: api.Environment,
Parameters: []WorkflowAppActionParameter{},
}
action.Returns.Schema.Type = "string"
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
//log.Println(path.Parameters)
// Parameters: []WorkflowAppActionParameter{},
// FIXME - add data for POST stuff
firstQuery = true
optionalQueries := []string{}
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(path.Get.Parameters) > 0 {
for _, param := range path.Get.Parameters {
curParam := WorkflowAppActionParameter{
Name: param.Value.Name,
Description: param.Value.Description,
Multiline: false,
Required: param.Value.Required,
Schema: SchemaDefinition{
Type: param.Value.Schema.Value.Type,
},
}
if param.Value.Required {
action.Parameters = append(action.Parameters, curParam)
} else {
optionalParameters = append(optionalParameters, curParam)
}
if param.Value.In == "path" {
log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
log.Printf("QUERY!: %s", param.Value.Name)
if !param.Value.Required {
optionalQueries = append(optionalQueries, param.Value.Name)
continue
}
parameters = append(parameters, param.Value.Name)
if firstQuery {
baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
firstQuery = false
} else {
baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
firstQuery = false
}
}
}
}
// ensuring that they end up last in the specification
// (order is ish important for optional params) - they need to be last.
for _, optionalParam := range optionalParameters {
action.Parameters = append(action.Parameters, optionalParam)
}
curCode := makePythoncode(functionName, baseUrl, "get", parameters, optionalQueries)
pythonFunctions = append(pythonFunctions, curCode)
api.Actions = append(api.Actions, action)
}
}
return api, pythonFunctions, nil
}
func verifyApi(api WorkflowApp) WorkflowApp {
if api.AppVersion == "" {
api.AppVersion = "1.0.0"
}
return api
}
func dumpPython(basePath, name, version string, pythonFunctions []string) error {
//log.Printf("%#v", api)
log.Printf(strings.Join(pythonFunctions, "\n"))
parsedCode := fmt.Sprintf(`import requests
import asyncio
import json
from walkoff_app_sdk.app_base import AppBase
class %s(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "%s"
app_name = "%s"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
%s
if __name__ == "__main__":
asyncio.run(CarbonBlack.run(), debug=True)
`, name, version, name, strings.Join(pythonFunctions, "\n"))
err := ioutil.WriteFile(fmt.Sprintf("%s/src/app.py", basePath), []byte(parsedCode), os.ModePerm)
if err != nil {
return err
}
fmt.Println(parsedCode)
//log.Println(string(data))
return nil
}
func dumpApi(basePath string, api WorkflowApp) error {
//log.Printf("%#v", api)
data, err := yaml.Marshal(api)
if err != nil {
log.Printf("Error with yaml marshal: %s", err)
return err
}
err = ioutil.WriteFile(fmt.Sprintf("%s/api.yaml", basePath), []byte(data), os.ModePerm)
if err != nil {
return err
}
//log.Println(string(data))
return nil
}
func main() {
data := []byte(`{"swagger":"3.0","info":{"title":"hi","description":"you","version":"1.0"},"servers":[{"url":"https://shuffler.io/api/v1"}],"host":"shuffler.io","basePath":"/api/v1","schemes":["https:"],"paths":{"/workflows":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflows","description":"Get workflows","parameters":[]}},"/workflows/{id}":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflow","description":"Get workflow","parameters":[{"in":"query","name":"forgetme","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"query","name":"anotherone","description":"Generated by shuffler.io OpenAPI","required":false,"schema":{"type":"string"}},{"in":"query","name":"hi","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}}]}}},"securityDefinitions":{}}`)
hasher := md5.New()
hasher.Write(data)
newmd5 := hex.EncodeToString(hasher.Sum(nil))
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(data)
if err != nil {
log.Printf("Swagger validation error: %s", err)
os.Exit(3)
}
if strings.Contains(swagger.Info.Title, " ") {
strings.ReplaceAll(swagger.Info.Title, " ", "")
}
basePath, err := buildStructure(swagger, newmd5)
if err != nil {
log.Printf("Failed to build base structure: %s", err)
os.Exit(3)
}
api, pythonfunctions, err := generateYaml(swagger)
if err != nil {
log.Printf("Failed building and generating yaml: %s", err)
os.Exit(3)
}
err = dumpApi(basePath, api)
if err != nil {
log.Printf("Failed dumping yaml: %s", err)
os.Exit(3)
}
err = dumpPython(basePath, swagger.Info.Title, swagger.Info.Version, pythonfunctions)
if err != nil {
log.Printf("Failed dumping python: %s", err)
os.Exit(3)
}
}
-499
View File
@@ -1,499 +0,0 @@
package main
/*
Code used to generate apps from OpenAPI JSON data
Any function ending with GCP doesn't use local fileIO, but rather
google cloud storage, and also has a normal filesystem version of the
same code (doesn't required client as first argument).
This code is used in the backend to generate apps on the fly for users.
All new code is appended to backend/go-app/codegen.go
*/
import (
"context"
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"cloud.google.com/go/storage"
"github.com/getkin/kin-openapi/openapi3"
"gopkg.in/yaml.v2"
)
var bucketName = "shuffler.appspot.com"
type WorkflowApp struct {
Name string `json:"name" yaml:"name" required:true datastore:"name"`
IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"`
ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"`
Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"`
AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"`
Description string `json:"description" datastore:"description" required:false yaml:"description"`
Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"`
SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"`
LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false`
ContactInfo struct {
Name string `json:"name" datastore:"name" yaml:"name"`
Url string `json:"url" datastore:"url" yaml:"url"`
} `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false`
Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"`
Authentication Authentication `json:"authentication" yaml:"authentication" required:false datastore:"authentication"`
}
type AuthenticationParams struct {
Description string `json:"description" datastore:"description" yaml:"description"`
ID string `json:"id" datastore:"id" yaml:"id"`
Name string `json:"name" datastore:"name" yaml:"name"`
Example string `json:"example" datastore:"example" yaml:"example"s`
Value string `json:"value,omitempty" datastore:"value" yaml:"value"`
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
Required bool `json:"required" datastore:"required" yaml:"required"`
}
type Authentication struct {
Required bool `json:"required" datastore:"required" yaml:"required" `
Parameters []AuthenticationParams `json:"parameters" datastore:"parameters" yaml:"parameters"`
}
type AuthenticationStore struct {
Key string `json:"key" datastore:"key"`
Value string `json:"value" datastore:"value"`
}
type WorkflowAppActionParameter struct {
Description string `json:"description" datastore:"description" yaml:"description"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Name string `json:"name" datastore:"name" yaml:"name"`
Example string `json:"example" datastore:"example" yaml:"example"`
Value string `json:"value" datastore:"value" yaml:"value,omitempty"`
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"`
Variant string `json:"variant" datastore:"variant" yaml:"variant,omitempty"`
Required bool `json:"required" datastore:"required" yaml:"required"`
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
}
type SchemaDefinition struct {
Type string `json:"type" datastore:"type"`
}
type WorkflowAppAction struct {
Description string `json:"description" datastore:"description"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Name string `json:"name" datastore:"name"`
NodeType string `json:"node_type" datastore:"node_type"`
Environment string `json:"environment" datastore:"environment"`
Authentication []AuthenticationStore `json:"authentication" datastore:"authentication" yaml:"authentication,omitempty"`
Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"`
Returns struct {
Description string `json:"description" datastore:"returns" yaml:"description,omitempty"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
} `json:"returns" datastore:"returns"`
}
func copyFile(fromfile, tofile string) error {
from, err := os.Open(fromfile)
if err != nil {
return err
}
defer from.Close()
to, err := os.OpenFile(tofile, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer to.Close()
_, err = io.Copy(to, from)
if err != nil {
return err
}
return nil
}
func buildStructureGCP(client *storage.Client, swagger *openapi3.Swagger, curHash string) (string, error) {
ctx := context.Background()
// 1. Have baseline in bucket/generated_apps/baseline
// 2. Copy the baseline to a new folder with identifier name
basePath := "generated_apps"
identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash)
appPath := fmt.Sprintf("%s/%s", basePath, identifier)
fileNames := []string{"Dockerfile", "requirements.txt"}
for _, file := range fileNames {
src := client.Bucket(bucketName).Object(fmt.Sprintf("%s/baseline/%s", basePath, file))
dst := client.Bucket(bucketName).Object(fmt.Sprintf("%s/%s", appPath, file))
if _, err := dst.CopierFrom(src).Run(ctx); err != nil {
return "", err
}
}
return appPath, nil
}
// Builds the base structure for the app that we're making
// Returns error if anything goes wrong. This has to work if
// the python code is supposed to be generated
func buildStructure(swagger *openapi3.Swagger, curHash string) (string, error) {
//log.Printf("%#v", swagger)
// adding md5 based on input data to not overwrite earlier data.
generatedPath := "generated"
identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash)
appPath := fmt.Sprintf("%s/%s", generatedPath, identifier)
os.MkdirAll(appPath, os.ModePerm)
os.Mkdir(fmt.Sprintf("%s/src", appPath), os.ModePerm)
err := copyFile("baseline/Dockerfile", fmt.Sprintf("%s/%s", appPath, "Dockerfile"))
if err != nil {
log.Println("Failed to move Dockerfile")
return appPath, err
}
err = copyFile("baseline/requirements.txt", fmt.Sprintf("%s/%s", appPath, "requirements.txt"))
if err != nil {
log.Println("Failed to move requrements.txt")
return appPath, err
}
return appPath, nil
}
func makePythoncode(name, url, method string, parameters, optionalQueries []string) string {
method = strings.ToLower(method)
queryString := ""
queryData := ""
// FIXME - this might break - need to check if ? or & should be set as query
parameterData := ""
if len(optionalQueries) > 0 {
queryString += ", "
for _, query := range optionalQueries {
queryString += fmt.Sprintf("%s=\"\"", query)
queryData += fmt.Sprintf(`
if %s:
url += f"&%s={%s}"`, query, query, query)
}
}
if len(parameters) > 0 {
parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", "))
}
// FIXME - add checks for query data etc
data := fmt.Sprintf(` async def %s_%s(self%s%s):
url=f"%s"
%s
return requests.%s(url).text
`, name, method, parameterData, queryString, url, queryData, method)
return data
}
func generateYaml(swagger *openapi3.Swagger) (WorkflowApp, []string, error) {
api := WorkflowApp{}
log.Printf("%#v", swagger.Info)
if len(swagger.Info.Title) == 0 {
return WorkflowApp{}, []string{}, errors.New("Swagger.Info.Title can't be empty.")
}
if len(swagger.Servers) == 0 {
return WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'")
}
api.Name = swagger.Info.Title
api.Description = swagger.Info.Description
api.IsValid = true
api.Link = swagger.Servers[0].URL // host does not exist lol
api.AppVersion = "1.0.0"
api.Environment = "cloud"
api.ID = ""
api.SmallImage = ""
api.LargeImage = ""
// This is the python code to be generated
// Could just as well be go at this point lol
pythonFunctions := []string{}
for actualPath, path := range swagger.Paths {
//log.Printf("%#v", path)
//log.Printf("%#v", actualPath)
// Find the path name and add it to makeCode() param
firstQuery := true
if path.Get != nil {
// What to do with this, hmm
functionName := strings.ReplaceAll(path.Get.Summary, " ", "_")
functionName = strings.ToLower(functionName)
action := WorkflowAppAction{
Description: path.Get.Description,
Name: path.Get.Summary,
NodeType: "action",
Environment: api.Environment,
Parameters: []WorkflowAppActionParameter{},
}
action.Returns.Schema.Type = "string"
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
//log.Println(path.Parameters)
// Parameters: []WorkflowAppActionParameter{},
// FIXME - add data for POST stuff
firstQuery = true
optionalQueries := []string{}
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(path.Get.Parameters) > 0 {
for _, param := range path.Get.Parameters {
curParam := WorkflowAppActionParameter{
Name: param.Value.Name,
Description: param.Value.Description,
Multiline: false,
Required: param.Value.Required,
Schema: SchemaDefinition{
Type: param.Value.Schema.Value.Type,
},
}
if param.Value.Required {
action.Parameters = append(action.Parameters, curParam)
} else {
optionalParameters = append(optionalParameters, curParam)
}
if param.Value.In == "path" {
log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
log.Printf("QUERY!: %s", param.Value.Name)
if !param.Value.Required {
optionalQueries = append(optionalQueries, param.Value.Name)
continue
}
parameters = append(parameters, param.Value.Name)
if firstQuery {
baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
firstQuery = false
} else {
baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
firstQuery = false
}
}
}
}
// ensuring that they end up last in the specification
// (order is ish important for optional params) - they need to be last.
for _, optionalParam := range optionalParameters {
action.Parameters = append(action.Parameters, optionalParam)
}
curCode := makePythoncode(functionName, baseUrl, "get", parameters, optionalQueries)
pythonFunctions = append(pythonFunctions, curCode)
api.Actions = append(api.Actions, action)
}
}
return api, pythonFunctions, nil
}
func verifyApi(api WorkflowApp) WorkflowApp {
if api.AppVersion == "" {
api.AppVersion = "1.0.0"
}
return api
}
func dumpPythonGCP(client *storage.Client, basePath, name, version string, pythonFunctions []string) error {
//log.Printf("%#v", api)
log.Printf(strings.Join(pythonFunctions, "\n"))
parsedCode := fmt.Sprintf(`import requests
import asyncio
import json
from walkoff_app_sdk.app_base import AppBase
class %s(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "%s"
app_name = "%s"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
%s
if __name__ == "__main__":
asyncio.run(CarbonBlack.run(), debug=True)
`, name, version, name, strings.Join(pythonFunctions, "\n"))
// Create bucket handle
ctx := context.Background()
bucket := client.Bucket(bucketName)
obj := bucket.Object(fmt.Sprintf("%s/src/app.py", basePath))
w := obj.NewWriter(ctx)
if _, err := fmt.Fprintf(w, parsedCode); err != nil {
return err
}
// Close, just like writing a file.
if err := w.Close(); err != nil {
return err
}
return nil
}
func dumpPython(basePath, name, version string, pythonFunctions []string) error {
//log.Printf("%#v", api)
log.Printf(strings.Join(pythonFunctions, "\n"))
parsedCode := fmt.Sprintf(`import requests
import asyncio
import json
from walkoff_app_sdk.app_base import AppBase
class %s(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "%s"
app_name = "%s"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
%s
if __name__ == "__main__":
asyncio.run(CarbonBlack.run(), debug=True)
`, name, version, name, strings.Join(pythonFunctions, "\n"))
err := ioutil.WriteFile(fmt.Sprintf("%s/src/app.py", basePath), []byte(parsedCode), os.ModePerm)
if err != nil {
return err
}
fmt.Println(parsedCode)
//log.Println(string(data))
return nil
}
func dumpApiGCP(client *storage.Client, basePath string, api WorkflowApp) error {
//log.Printf("%#v", api)
data, err := yaml.Marshal(api)
if err != nil {
log.Printf("Error with yaml marshal: %s", err)
return err
}
// Create bucket handle
ctx := context.Background()
bucket := client.Bucket(bucketName)
obj := bucket.Object(fmt.Sprintf("%s/app.yaml", basePath))
w := obj.NewWriter(ctx)
if _, err := fmt.Fprintf(w, string(data)); err != nil {
return err
}
// Close, just like writing a file.
if err := w.Close(); err != nil {
return err
}
//log.Println(string(data))
return nil
}
func dumpApi(basePath string, api WorkflowApp) error {
//log.Printf("%#v", api)
data, err := yaml.Marshal(api)
if err != nil {
log.Printf("Error with yaml marshal: %s", err)
return err
}
err = ioutil.WriteFile(fmt.Sprintf("%s/api.yaml", basePath), []byte(data), os.ModePerm)
if err != nil {
return err
}
//log.Println(string(data))
return nil
}
func main() {
data := []byte(`{"swagger":"3.0","info":{"title":"hi","description":"you","version":"1.0"},"servers":[{"url":"https://shuffler.io/api/v1"}],"host":"shuffler.io","basePath":"/api/v1","schemes":["https:"],"paths":{"/workflows":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflows","description":"Get workflows","parameters":[]}},"/workflows/{id}":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflow","description":"Get workflow","parameters":[{"in":"query","name":"forgetme","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"query","name":"anotherone","description":"Generated by shuffler.io OpenAPI","required":false,"schema":{"type":"string"}},{"in":"query","name":"hi","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}}]}}},"securityDefinitions":{}}`)
ctx := context.Background()
client, err := storage.NewClient(ctx)
if err != nil {
log.Printf("Failed to create client: %v", err)
os.Exit(3)
}
hasher := md5.New()
hasher.Write(data)
newmd5 := hex.EncodeToString(hasher.Sum(nil))
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(data)
if err != nil {
log.Printf("Swagger validation error: %s", err)
os.Exit(3)
}
if strings.Contains(swagger.Info.Title, " ") {
strings.ReplaceAll(swagger.Info.Title, " ", "")
}
basePath, err := buildStructureGCP(client, swagger, newmd5)
if err != nil {
log.Printf("Failed to build base structure: %s", err)
os.Exit(3)
}
api, pythonfunctions, err := generateYaml(swagger)
if err != nil {
log.Printf("Failed building and generating yaml: %s", err)
os.Exit(3)
}
err = dumpApiGCP(client, basePath, api)
if err != nil {
log.Printf("Failed dumping yaml: %s", err)
os.Exit(3)
}
err = dumpPythonGCP(client, basePath, swagger.Info.Title, swagger.Info.Version, pythonfunctions)
if err != nil {
log.Printf("Failed dumping python: %s", err)
os.Exit(3)
}
}
@@ -1,2 +0,0 @@
cortexutils
requests
@@ -1,43 +0,0 @@
{
"name": "Shuffle",
"version": "1.0",
"author": "@frikkylikeme",
"url": "https://github.com/frikky/shuffle",
"license": "AGPL-V3",
"description": "Execute a workflow in Shuffle",
"dataTypeList": ["thehive:case", "thehive:alert", "thehive:case_artifact"],
"command": "Shuffle/shuffle.py",
"baseConfig": "Shuffle",
"configurationItems": [
{
"name": "url",
"description": "The URL to your shuffle instance",
"type": "string",
"multi": false,
"required": true,
"defaultValue": "https://shuffler.io"
},
{
"name": "api_key",
"description": "The API key to your Shuffle user",
"type": "string",
"multi": false,
"required": true
},
{
"name": "verifyssl",
"description": "Verify SSL certificate",
"type": "boolean",
"multi": false,
"required": true,
"defaultValue": true
},
{
"name": "workflow_id",
"description": "The ID of the workflow to execute",
"type": "string",
"multi": false,
"required": true
}
]
}
@@ -1,27 +0,0 @@
#!/usr/bin/env python3
#encoding: utf-8
from cortexutils.responder import Responder
import requests
class Shuffle(Responder):
def __init__(self):
Responder.__init__(self)
self.api_key = self.get_param("config.api_key", "")
self.url = self.get_param("config.url", "")
self.workflow_id = self.get_param("config.workflow_id", "")
self.verify = self.get_param('config.verifyssl', True, None)
def run(self):
Responder.run(self)
parsed_url = "%s/api/v1/workflows/%s/execute" % (self.url, self.workflow_id)
headers = {
"Authorization": "Bearer %s" % self.api_key,
"User-Agent": "Cortex-Analyzer"
}
requests.post(parsed_url, headers=headers,verify=self.verify)
self.report({'message': 'message sent'})
if __name__ == '__main__':
Shuffle().run()
@@ -1,2 +0,0 @@
cortexutils
requests
@@ -1,28 +0,0 @@
{
"name": "Shuffle_webhook",
"version": "1.0",
"author": "@azgaviperr",
"url": "https://github.com/frikky/shuffle",
"license": "AGPL-V3",
"description": "Execute a webhook in Shuffle",
"dataTypeList": ["thehive:case", "thehive:alert", "thehive:case_artifact"],
"command": "Shuffle_Webhook/shuffle_webhook.py",
"baseConfig": "Shuffle_Webhook",
"configurationItems": [
{
"name": "webhook_url",
"description": "The URL to your shuffle instance",
"type": "string",
"multi": false,
"required": true
},
{
"name": "verifyssl",
"description": "Verify SSL certificate",
"type": "boolean",
"multi": false,
"required": true,
"defaultValue": true
}
]
}
@@ -1,28 +0,0 @@
#!/usr/bin/env python3
#encoding: utf-8
from cortexutils.responder import Responder
import requests
class Shuffle(Responder):
def __init__(self):
Responder.__init__(self)
self.api_key = self.get_param("config.api_key", "")
self.webhook_url = self.get_param("config.webhook_url", "")
self.webhook_id = self.get_param("config.webhook_id", "")
self.verify = self.get_param('config.verifyssl', True, None)
self.data = self.get_param('data')
def run(self):
Responder.run(self)
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "Cortex-Analyzer"
}
requests.post(self.webhook_url, headers=headers,verify=self.verify, json=self.data)
self.report({'message': 'message sent'})
if __name__ == '__main__':
Shuffle().run()
-23
View File
@@ -1,23 +0,0 @@
FROM python:3.9.4-alpine as base
FROM base as builder
RUN mkdir /install
WORKDIR /install
FROM base
RUN apk add g++
COPY --from=builder /install /usr/local
COPY requirements.txt /requirements.txt
RUN pip3 install -r /requirements.txt
RUN mkdir /app
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN python3 -m pip install -r /app/requirements.txt
COPY sub.py /app/sub.py
CMD ["python3", "sub.py"]
@@ -1,9 +0,0 @@
version: '3'
services:
zmq:
image: ghcr.io/frikky/shuffle-zmq:latest
environment:
- ZMQ_HOSTNAME=localhost
- ZMQ_PORT=50000
- ZMQ_FORWARD_URL=https://shuffler.io/api/v1/hooks/webhook_e09bea36-9976-1421-82bc-b8764ca83c1e
restart: unless-stopped
@@ -1,2 +0,0 @@
pyzmq
requests
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
print("Running imports")
import sys
import zmq
import json
import time
import pprint
import os
import sys
import requests
forward_url = os.getenv("ZMQ_FORWARD_URL", "")
print("Checking forward url (ZMQ_FORWARD_URL): %s" % forward_url)
def handle_hook(data):
ret = requests.post(forward_url, json=data)
print(ret.text)
print(ret.status_code)
def main():
host = os.getenv("ZMQ_HOST", "localhost")
port = os.getenv("ZMQ_PORT", "50000")
if len(forward_url) == 0:
print("Failed to start - define ZMQ_FORWARD_URL for webhook forwarder")
exit(0)
print("Starting connection setup to %s:%s" % (host, port))
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect ("tcp://%s:%s" % (host, port))
socket.setsockopt(zmq.SUBSCRIBE, b'')
poller = zmq.Poller()
poller.register(socket, zmq.POLLIN)
print("Starting zmq check for %s:%s" % (host, port))
while True:
socks = dict(poller.poll(timeout=None))
if socket in socks and socks[socket] == zmq.POLLIN:
message = socket.recv()
#print(message)
topic, s, m = message.decode('utf-8').partition(" ")
d = json.loads(m)
try:
# print test if you want status (heartbeat)
test = d["status"]
except KeyError:
handle_hook(d)
time.sleep(1)
if __name__ == "__main__":
print("In init ")
main()
-36
View File
@@ -1,36 +0,0 @@
FROM golang:1.22 as builder
RUN mkdir /app
WORKDIR /app
COPY orborus.go /app/orborus.go
#COPY go.mod /app/go.mod
#COPY go.sum /app/go.sum
RUN go mod init orborus
RUN go get github.com/docker/docker/api/types
RUN go get github.com/docker/docker/api/types/container
#RUN go get github.com/docker/docker/client
RUN go get github.com/mackerelio/go-osstat/cpu
RUN go get github.com/mackerelio/go-osstat/memory
RUN go get github.com/satori/go.uuid
RUN go get github.com/shuffle/shuffle-shared
RUN go get github.com/shirou/gopsutil
# RUN go get k8s.io/client-go
# RUN go get k8s.io/apimachinery
RUN go get
RUN go mod tidy
RUN go build
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus .
FROM alpine:3.15.0
RUN apk add --no-cache bash tzdata
COPY --from=builder /app/ /
ENV ENVIRONMENT_NAME=Shuffle
ENV BASE_URL=http://shuffle-backend:5001
ENV DOCKER_API_VERSION=1.40
ENV SHUFFLE_OPENSEARCH_URL=https://opensearch:9200
CMD ["./orborus"]