mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-09-28 12:34:59 +02:00
feat: add SSH test infrastructure, improve executor temp handling, enhance CLI workflow install
Amp-Thread-ID: https://ampcode.com/threads/T-019c195d-0f3b-724a-946a-a3a93dd7d09b Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
openssh-server \
|
||||
sudo \
|
||||
curl \
|
||||
wget \
|
||||
vim \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir /var/run/sshd
|
||||
|
||||
# Create test user with sudo access
|
||||
RUN useradd -m -s /bin/bash testuser \
|
||||
&& echo "testuser:testpass" | chpasswd \
|
||||
&& usermod -aG sudo testuser \
|
||||
&& echo "testuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
||||
|
||||
# Setup SSH directory for testuser
|
||||
RUN mkdir -p /home/testuser/.ssh \
|
||||
&& chmod 700 /home/testuser/.ssh
|
||||
|
||||
# Copy authorized keys
|
||||
COPY id_ed25519.pub /home/testuser/.ssh/authorized_keys
|
||||
|
||||
# Set proper permissions
|
||||
RUN chmod 600 /home/testuser/.ssh/authorized_keys \
|
||||
&& chown -R testuser:testuser /home/testuser/.ssh
|
||||
|
||||
# Configure SSH
|
||||
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin no/' /etc/ssh/sshd_config \
|
||||
&& sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config \
|
||||
&& sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config
|
||||
|
||||
EXPOSE 22
|
||||
|
||||
CMD ["/usr/sbin/sshd", "-D"]
|
||||
@@ -0,0 +1,33 @@
|
||||
# SSH Test Container
|
||||
|
||||
Ubuntu 22.04 container with SSH server for testing osmedeus SSH runner.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Build and start
|
||||
docker-compose up -d --build
|
||||
|
||||
# Test SSH connection
|
||||
ssh -i id_ed25519 -p 2222 testuser@localhost
|
||||
|
||||
# Stop
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
## Credentials
|
||||
|
||||
- **User**: `testuser`
|
||||
- **Password**: `testpass`
|
||||
- **SSH Key**: `id_ed25519` (in this directory)
|
||||
- **Port**: `2222`
|
||||
|
||||
## SSH Command Examples
|
||||
|
||||
```bash
|
||||
# Using key authentication
|
||||
ssh -i id_ed25519 -p 2222 -o StrictHostKeyChecking=no testuser@localhost
|
||||
|
||||
# Run command
|
||||
ssh -i id_ed25519 -p 2222 -o StrictHostKeyChecking=no testuser@localhost "whoami"
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
ssh-test:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: osmedeus-ssh-test
|
||||
ports:
|
||||
- "2222:22"
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "pgrep", "sshd"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
@@ -0,0 +1,7 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBFkHTIis1TLr0yQE9H+I+Oj5rkuRLmlbTLGTyYFztFogAAAKAlkswGJZLM
|
||||
BgAAAAtzc2gtZWQyNTUxOQAAACBFkHTIis1TLr0yQE9H+I+Oj5rkuRLmlbTLGTyYFztFog
|
||||
AAAECNG9yAIRlP1a223qsQFSig797wA4lcBJzcnjNlQCaWhkWQdMiKzVMuvTJAT0f4j46P
|
||||
muS5EuaVtMsZPJgXO0WiAAAAF29zbWVkZXVzLXRlc3RAbG9jYWxob3N0AQIDBAUG
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEWQdMiKzVMuvTJAT0f4j46PmuS5EuaVtMsZPJgXO0Wi osmedeus-test@localhost
|
||||
@@ -94,6 +94,14 @@ func BuildBuiltinVariables(cfg *config.Config, params map[string]string) map[str
|
||||
runUUID := uuid.New().String()
|
||||
execCtx := core.NewExecutionContext("func-eval", core.KindModule, runUUID, params["target"])
|
||||
exec.injectBuiltinVariables(cfg, params, execCtx)
|
||||
|
||||
// Add temp directory for eval (cleanup is handled by the caller)
|
||||
tempDir, err := os.MkdirTemp("", "osm-tmp-")
|
||||
if err == nil {
|
||||
execCtx.SetVariable("TempDir", tempDir)
|
||||
execCtx.SetVariable("TempFile", filepath.Join(tempDir, "osm-tmp-file"))
|
||||
}
|
||||
|
||||
return execCtx.GetVariables()
|
||||
}
|
||||
|
||||
@@ -170,13 +178,22 @@ func (e *Executor) writeVerboseOutputToLog(output string) {
|
||||
if e.consoleCapture == nil || output == "" {
|
||||
return
|
||||
}
|
||||
// Trim whitespace from output
|
||||
output = strings.TrimSpace(output)
|
||||
if output == "" {
|
||||
return // Don't print [output] if nothing to show
|
||||
}
|
||||
// Format similar to printer.VerboseOutput but write directly to file
|
||||
var sb strings.Builder
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(terminal.Gray("[output]"))
|
||||
sb.WriteString("\n")
|
||||
lines := strings.Split(strings.TrimSuffix(output, "\n"), "\n")
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
// Skip blank lines
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(line)
|
||||
sb.WriteString("\n")
|
||||
@@ -372,6 +389,30 @@ func (e *Executor) injectBuiltinVariables(cfg *config.Config, params map[string]
|
||||
}
|
||||
}
|
||||
|
||||
// setupTempDirectory creates a temporary directory and file for workflow execution
|
||||
// Returns a cleanup function that removes the directory when called
|
||||
func (e *Executor) setupTempDirectory(execCtx *core.ExecutionContext) (cleanup func()) {
|
||||
tempDir, err := os.MkdirTemp("", "osm-tmp-")
|
||||
if err != nil {
|
||||
e.logger.Warn("Failed to create temp directory", zap.Error(err))
|
||||
return func() {} // no-op cleanup
|
||||
}
|
||||
|
||||
execCtx.SetVariable("TempDir", tempDir)
|
||||
|
||||
// Create temp file path within temp dir
|
||||
tempFile := filepath.Join(tempDir, "osm-tmp-file")
|
||||
execCtx.SetVariable("TempFile", tempFile)
|
||||
|
||||
return func() {
|
||||
if err := os.RemoveAll(tempDir); err != nil {
|
||||
e.logger.Warn("Failed to cleanup temp directory",
|
||||
zap.String("path", tempDir),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) debugLogTargetVariables(execCtx *core.ExecutionContext) {
|
||||
if execCtx == nil || execCtx.Logger == nil {
|
||||
return
|
||||
@@ -578,6 +619,122 @@ func printDryRunHeader(workflowName, workflowKind, target, tactic string, stepCo
|
||||
|
||||
// getStepCommand extracts the command/script from a step for display
|
||||
func getStepCommand(step *core.Step) string {
|
||||
// Handle foreach steps specially
|
||||
if step.Type == core.StepTypeForeach && step.Input != "" {
|
||||
parts := []string{fmt.Sprintf("foreach [[%s]] in %s", step.Variable, step.Input)}
|
||||
if step.VariablePreProcess != "" {
|
||||
parts = append(parts, fmt.Sprintf("pre_process: %s", step.VariablePreProcess))
|
||||
}
|
||||
// Add inner step name, type, and command
|
||||
if step.Step != nil && step.Step.Name != "" {
|
||||
innerCmd := getInnerStepCommand(step.Step)
|
||||
if innerCmd != "" {
|
||||
parts = append(parts, fmt.Sprintf("step: %s (%s) cmd: %s", step.Step.Name, step.Step.Type, innerCmd))
|
||||
} else {
|
||||
parts = append(parts, fmt.Sprintf("step: %s (%s)", step.Step.Name, step.Step.Type))
|
||||
}
|
||||
}
|
||||
threads, _ := step.Threads.Int()
|
||||
if threads <= 0 {
|
||||
threads = 1
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("threads: %d", threads))
|
||||
return strings.Join(parts, " | ")
|
||||
}
|
||||
|
||||
if step.Command != "" {
|
||||
return step.Command
|
||||
}
|
||||
if len(step.Commands) > 0 {
|
||||
return step.Commands[0]
|
||||
}
|
||||
if step.Function != "" {
|
||||
return step.Function
|
||||
}
|
||||
if len(step.Functions) > 0 {
|
||||
return step.Functions[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getStepCommandColored returns a colored command string for foreach steps (for console display)
|
||||
func getStepCommandColored(step *core.Step) string {
|
||||
// Handle foreach steps specially with colors
|
||||
if step.Type == core.StepTypeForeach && step.Input != "" {
|
||||
var lines []string
|
||||
|
||||
// First line: foreach [[variable]] in source | threads: N
|
||||
var firstLineParts []string
|
||||
firstLineParts = append(firstLineParts, fmt.Sprintf("foreach %s in %s",
|
||||
terminal.Magenta("[["+step.Variable+"]]"),
|
||||
terminal.Cyan(step.Input)))
|
||||
|
||||
if step.VariablePreProcess != "" {
|
||||
firstLineParts = append(firstLineParts, fmt.Sprintf("pre_process: %s", terminal.Gray(step.VariablePreProcess)))
|
||||
}
|
||||
|
||||
threads, _ := step.Threads.Int()
|
||||
if threads <= 0 {
|
||||
threads = 1
|
||||
}
|
||||
firstLineParts = append(firstLineParts, fmt.Sprintf("threads: %s", terminal.Yellow(fmt.Sprintf("%d", threads))))
|
||||
lines = append(lines, strings.Join(firstLineParts, terminal.Gray(" | ")))
|
||||
|
||||
// Second line: step: name (type)
|
||||
if step.Step != nil && step.Step.Name != "" {
|
||||
lines = append(lines, fmt.Sprintf(" %s %s %s",
|
||||
terminal.Gray("step:"),
|
||||
terminal.HiBlue(step.Step.Name),
|
||||
terminal.Gray("("+string(step.Step.Type)+")")))
|
||||
|
||||
// Add command/commands/function/functions on separate lines
|
||||
innerLines := getInnerStepCommandLines(step.Step)
|
||||
lines = append(lines, innerLines...)
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// For non-foreach steps, return uncolored (will be colored by printer)
|
||||
return getStepCommand(step)
|
||||
}
|
||||
|
||||
// getInnerStepCommandLines returns colored lines for inner step commands/functions
|
||||
func getInnerStepCommandLines(step *core.Step) []string {
|
||||
var lines []string
|
||||
|
||||
if step.Command != "" {
|
||||
lines = append(lines, fmt.Sprintf(" %s %s",
|
||||
terminal.Gray("command:"),
|
||||
terminal.HiGreen(step.Command)))
|
||||
}
|
||||
if len(step.Commands) > 0 {
|
||||
lines = append(lines, fmt.Sprintf(" %s", terminal.Gray("commands:")))
|
||||
for _, cmd := range step.Commands {
|
||||
lines = append(lines, fmt.Sprintf(" %s %s",
|
||||
terminal.Gray("-"),
|
||||
terminal.HiGreen(cmd)))
|
||||
}
|
||||
}
|
||||
if step.Function != "" {
|
||||
lines = append(lines, fmt.Sprintf(" %s %s",
|
||||
terminal.Gray("function:"),
|
||||
terminal.HiCyan(step.Function)))
|
||||
}
|
||||
if len(step.Functions) > 0 {
|
||||
lines = append(lines, fmt.Sprintf(" %s", terminal.Gray("functions:")))
|
||||
for _, fn := range step.Functions {
|
||||
lines = append(lines, fmt.Sprintf(" %s %s",
|
||||
terminal.Gray("-"),
|
||||
terminal.HiCyan(fn)))
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getInnerStepCommand extracts the command/function from an inner step (for logs)
|
||||
func getInnerStepCommand(step *core.Step) string {
|
||||
if step.Command != "" {
|
||||
return step.Command
|
||||
}
|
||||
@@ -823,6 +980,8 @@ func (e *Executor) ExecuteModule(ctx context.Context, module *core.Workflow, par
|
||||
zap.String("tactic", params["tactic"]),
|
||||
)
|
||||
e.injectBuiltinVariables(cfg, params, execCtx)
|
||||
tempCleanup := e.setupTempDirectory(execCtx)
|
||||
defer tempCleanup()
|
||||
e.debugLogTargetVariables(execCtx)
|
||||
|
||||
if !e.dryRun && database.GetDB() != nil {
|
||||
@@ -1438,6 +1597,8 @@ func (e *Executor) ExecuteFlow(ctx context.Context, flow *core.Workflow, params
|
||||
// Inject builtin variables
|
||||
e.logger.Debug("Injecting builtin variables for flow")
|
||||
e.injectBuiltinVariables(cfg, params, execCtx)
|
||||
tempCleanup := e.setupTempDirectory(execCtx)
|
||||
defer tempCleanup()
|
||||
e.debugLogTargetVariables(execCtx)
|
||||
|
||||
if !e.dryRun && database.GetDB() != nil {
|
||||
@@ -2025,15 +2186,17 @@ func (e *Executor) executeStep(ctx context.Context, step *core.Step, execCtx *co
|
||||
stepSymbol := terminal.StepTypeSymbol(string(step.Type), string(step.StepRunner))
|
||||
cmdPrefix := terminal.StepCommandPrefix(string(step.Type))
|
||||
stepCommand := getStepCommand(step)
|
||||
stepCommandColored := getStepCommandColored(step)
|
||||
if stepCommand != "" {
|
||||
stepCommand, _ = e.templateEngine.Render(stepCommand, execCtx.GetVariables())
|
||||
stepCommandColored, _ = e.templateEngine.Render(stepCommandColored, execCtx.GetVariables())
|
||||
}
|
||||
|
||||
// Show step start (skip when progress bar is active)
|
||||
if e.progressBar == nil {
|
||||
e.printer.StepStartWithCommand(step.Name, stepSymbol, stepCommand, cmdPrefix)
|
||||
e.printer.StepStartWithCommand(step.Name, stepSymbol, stepCommandColored, cmdPrefix)
|
||||
} else {
|
||||
// Update progress bar with current step command
|
||||
// Update progress bar with current step command (uncolored for progress bar)
|
||||
e.progressBar.SetCommand(stepCommand)
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,12 @@ func (p *Printer) StepStartWithCommand(stepName, typeSymbol, command, cmdPrefix
|
||||
}
|
||||
_, _ = fmt.Fprintf(os.Stdout, "%s %s %s %s\n", StepStartSymbol(), typeSymbol, Gray("(starting)"), HiBlue(stepName))
|
||||
if command != "" {
|
||||
_, _ = fmt.Fprintf(os.Stdout, " %s\n", HiGreen(formatMultilineCommand(command, cmdPrefix)))
|
||||
// Check if command already contains ANSI color codes (pre-colored)
|
||||
if strings.Contains(command, "\033[") {
|
||||
_, _ = fmt.Fprintf(os.Stdout, " %s %s\n", cmdPrefix, command)
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(os.Stdout, " %s\n", HiGreen(formatMultilineCommand(command, cmdPrefix)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,9 +388,18 @@ func (p *Printer) VerboseOutput(output string) {
|
||||
if output == "" {
|
||||
return
|
||||
}
|
||||
// Trim whitespace from output
|
||||
output = strings.TrimSpace(output)
|
||||
if output == "" {
|
||||
return // Don't print [output] if nothing to show
|
||||
}
|
||||
_, _ = fmt.Fprintf(os.Stdout, " %s\n", Gray("[output]"))
|
||||
lines := strings.Split(strings.TrimSuffix(output, "\n"), "\n")
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
// Skip blank lines
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
_, _ = fmt.Fprintf(os.Stdout, " %s\n", line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ const (
|
||||
// Step type and runner symbols
|
||||
SymbolFunction = "ƒ" // Function step
|
||||
SymbolBash = "$" // Bash/command step
|
||||
SymbolForeach = "∀" // Foreach step (universal quantifier)
|
||||
SymbolDocker = "🐋" // Docker runner
|
||||
SymbolSSH = "❄" // SSH runner
|
||||
|
||||
@@ -146,7 +147,9 @@ func StepTypeSymbol(stepType, runnerType string) string {
|
||||
return Cyan(SymbolFunction)
|
||||
case "remote-bash":
|
||||
return Cyan(SymbolSSH)
|
||||
case "bash", "parallel-steps", "foreach":
|
||||
case "foreach":
|
||||
return Cyan(SymbolForeach)
|
||||
case "bash", "parallel-steps":
|
||||
return Green(SymbolBash)
|
||||
default:
|
||||
return Green(SymbolBash)
|
||||
@@ -160,6 +163,8 @@ func StepCommandPrefix(stepType string) string {
|
||||
return SymbolBowtie // "⋈"
|
||||
case "function":
|
||||
return SymbolFunction // "ƒ"
|
||||
case "foreach":
|
||||
return SymbolForeach // "∀"
|
||||
default:
|
||||
return SymbolBash // "$"
|
||||
}
|
||||
|
||||
@@ -303,6 +303,14 @@ func executeFunctionForTarget(printer *terminal.Printer, script, target string)
|
||||
|
||||
cfg := config.Get()
|
||||
ctx := executor.BuildBuiltinVariables(cfg, params)
|
||||
|
||||
// Defer temp directory cleanup
|
||||
if tempDir, ok := ctx["TempDir"].(string); ok && tempDir != "" {
|
||||
defer func() {
|
||||
_ = os.RemoveAll(tempDir)
|
||||
}()
|
||||
}
|
||||
|
||||
if target != "" {
|
||||
ctx["target"] = target
|
||||
}
|
||||
|
||||
+49
-4
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/j3ssie/osmedeus/v5/internal/config"
|
||||
"github.com/j3ssie/osmedeus/v5/internal/core"
|
||||
"github.com/j3ssie/osmedeus/v5/internal/installer"
|
||||
"github.com/j3ssie/osmedeus/v5/internal/parser"
|
||||
"github.com/j3ssie/osmedeus/v5/internal/terminal"
|
||||
"github.com/j3ssie/osmedeus/v5/public"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -61,7 +62,7 @@ var installWorkflowCmd = &cobra.Command{
|
||||
}
|
||||
return cobra.ExactArgs(1)(cmd, args)
|
||||
},
|
||||
RunE: runInstallWorkflow,
|
||||
RunE: RunInstallWorkflow,
|
||||
}
|
||||
|
||||
// installBaseCmd installs the base folder from a source
|
||||
@@ -141,7 +142,8 @@ This is the primary command for health checks. 'osmedeus health' is an alias for
|
||||
RunE: runInstallValidate,
|
||||
}
|
||||
|
||||
func runInstallWorkflow(cmd *cobra.Command, args []string) error {
|
||||
// RunInstallWorkflow installs workflows from a source (exported for use by workflow install alias)
|
||||
func RunInstallWorkflow(cmd *cobra.Command, args []string) error {
|
||||
cfg := config.Get()
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("configuration not loaded")
|
||||
@@ -183,11 +185,40 @@ func runInstallWorkflow(cmd *cobra.Command, args []string) error {
|
||||
printer.Println(" %s %s", terminal.SymbolBullet, terminal.Gray(workflowURL))
|
||||
}
|
||||
|
||||
return inst.InstallWorkflow(workflowURL)
|
||||
if err := inst.InstallWorkflow(workflowURL); err != nil {
|
||||
return err
|
||||
}
|
||||
printWorkflowSummary(printer, cfg.WorkflowsPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
source := args[0]
|
||||
return inst.InstallWorkflow(source)
|
||||
if err := inst.InstallWorkflow(source); err != nil {
|
||||
return err
|
||||
}
|
||||
printWorkflowSummary(printer, cfg.WorkflowsPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// printWorkflowSummary counts and prints the number of workflows loaded
|
||||
func printWorkflowSummary(printer *terminal.Printer, workflowsPath string) {
|
||||
loader := parser.NewLoader(workflowsPath)
|
||||
|
||||
flows, flowErr := loader.ListFlows()
|
||||
modules, modErr := loader.ListModules()
|
||||
|
||||
if flowErr == nil && modErr == nil {
|
||||
total := len(flows) + len(modules)
|
||||
if total > 0 {
|
||||
printer.Info("Loaded %s workflows (%s flows, %s modules)",
|
||||
terminal.Green(fmt.Sprintf("%d", total)),
|
||||
terminal.Cyan(fmt.Sprintf("%d", len(flows))),
|
||||
terminal.Yellow(fmt.Sprintf("%d", len(modules))))
|
||||
printer.Println(" %s Run %s to see workflow details",
|
||||
terminal.Gray(terminal.SymbolLightning),
|
||||
terminal.Cyan("osmedeus workflow ls"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runInstallBase(cmd *cobra.Command, args []string) error {
|
||||
@@ -261,6 +292,7 @@ func runInstallBase(cmd *cobra.Command, args []string) error {
|
||||
// Continue - workflow installation failure shouldn't block base setup
|
||||
} else {
|
||||
printer.Success("Workflows installed from: %s", terminal.Cyan(workflowURL))
|
||||
printWorkflowSummary(printer, cfg.WorkflowsPath)
|
||||
}
|
||||
printer.Newline()
|
||||
|
||||
@@ -306,6 +338,19 @@ func runInstallBase(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reload config to get updated paths after base installation
|
||||
reloaded, err := config.Load(cfg.BaseFolder)
|
||||
if err == nil {
|
||||
config.Set(reloaded)
|
||||
cfg = reloaded
|
||||
if reloaded.BinariesPath != "" {
|
||||
binariesFolder = reloaded.BinariesPath
|
||||
}
|
||||
}
|
||||
|
||||
// Check if workflows folder exists under the base folder and print stats
|
||||
printWorkflowSummary(printer, cfg.WorkflowsPath)
|
||||
|
||||
ensureBinariesPathInEnv(printer, binariesFolder, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
+28
-2
@@ -170,25 +170,51 @@ func captureExplicitFlags(cmd *cobra.Command) {
|
||||
// Returns true if empty_target preference is set to true, false otherwise
|
||||
// Note: This cannot work for stdin modules (--std-module) since stdin would be consumed
|
||||
func getWorkflowEmptyTargetPreference(cfg *config.Config) bool {
|
||||
log := logger.Get()
|
||||
|
||||
if flowName == "" && len(moduleNames) == 0 {
|
||||
log.Debug("getWorkflowEmptyTargetPreference: no flow or module specified")
|
||||
return false
|
||||
}
|
||||
|
||||
loader := parser.NewLoader(cfg.WorkflowsPath)
|
||||
var workflow *core.Workflow
|
||||
var err error
|
||||
var workflowName string
|
||||
|
||||
if flowName != "" {
|
||||
workflowName = flowName
|
||||
workflow, err = loader.LoadWorkflow(flowName)
|
||||
} else if len(moduleNames) > 0 {
|
||||
workflowName = moduleNames[0]
|
||||
workflow, err = loader.LoadWorkflow(moduleNames[0])
|
||||
}
|
||||
|
||||
if err != nil || workflow == nil || workflow.Preferences == nil {
|
||||
if err != nil {
|
||||
log.Debug("getWorkflowEmptyTargetPreference: failed to load workflow",
|
||||
zap.String("workflow", workflowName),
|
||||
zap.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
return workflow.Preferences.GetEmptyTarget(false)
|
||||
if workflow == nil {
|
||||
log.Debug("getWorkflowEmptyTargetPreference: workflow is nil",
|
||||
zap.String("workflow", workflowName))
|
||||
return false
|
||||
}
|
||||
|
||||
if workflow.Preferences == nil {
|
||||
log.Debug("getWorkflowEmptyTargetPreference: workflow has no preferences",
|
||||
zap.String("workflow", workflowName))
|
||||
return false
|
||||
}
|
||||
|
||||
result := workflow.Preferences.GetEmptyTarget(false)
|
||||
log.Debug("getWorkflowEmptyTargetPreference: checked preference",
|
||||
zap.String("workflow", workflowName),
|
||||
zap.Bool("empty_target", result))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// applyWorkflowPreferences applies workflow preferences to CLI variables
|
||||
|
||||
@@ -1134,6 +1134,23 @@ var workflowShowCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
// workflowInstallCmd installs workflows from a source (alias for install workflow)
|
||||
var workflowInstallCmd = &cobra.Command{
|
||||
Use: "install [source]",
|
||||
Short: "Install workflows from git URL, zip URL, local zip file, or local folder",
|
||||
Long: `Install workflows from various sources. This is an alias for 'osmedeus install workflow'.`,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if workflowPreset {
|
||||
if len(args) != 0 {
|
||||
return fmt.Errorf("no source argument is allowed with --preset")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return cobra.ExactArgs(1)(cmd, args)
|
||||
},
|
||||
RunE: RunInstallWorkflow,
|
||||
}
|
||||
|
||||
// workflowValidateCmd validates a workflow
|
||||
var workflowValidateCmd = &cobra.Command{
|
||||
Use: "validate [name|path|folder]",
|
||||
@@ -1211,9 +1228,11 @@ func init() {
|
||||
workflowValidateCmd.Flags().StringVar(&lintFormat, "format", "pretty", "output format: pretty, json, github")
|
||||
workflowValidateCmd.Flags().StringSliceVar(&lintDisable, "disable", []string{}, "disable specific rules (comma-separated)")
|
||||
workflowValidateCmd.Flags().StringVar(&lintSeverity, "severity", "info", "minimum severity level: info, warning, error")
|
||||
workflowInstallCmd.Flags().BoolVar(&workflowPreset, "preset", false, "install from OSM_WORKFLOW_URL environment variable (default: DEFAULT_WORKFLOW_REPO)")
|
||||
workflowCmd.AddCommand(workflowListCmd)
|
||||
workflowCmd.AddCommand(workflowShowCmd)
|
||||
workflowCmd.AddCommand(workflowValidateCmd)
|
||||
workflowCmd.AddCommand(workflowInstallCmd)
|
||||
}
|
||||
|
||||
// hasMatchingTags checks if a workflow has any of the specified tags
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"_last_update_at": "2026-01-21T10:08:20.876322+00:00",
|
||||
"_last_update_at": "2026-02-01T03:52:26.051205+00:00",
|
||||
"amass": {
|
||||
"desc": "In-depth attack surface mapping and asset discovery",
|
||||
"repo_link": "https://github.com/owasp-amass/amass",
|
||||
@@ -42,9 +42,9 @@
|
||||
}
|
||||
},
|
||||
"dnsx": {
|
||||
"desc": "fast and multi-purpose DNS toolkit allow to run multiple DNS queries of your choice with a list of user-supplied resolvers",
|
||||
"desc": "dnsx is a fast and multi-purpose DNS toolkit allow to run multiple DNS queries of your choice with a list of user-supplied resolvers.",
|
||||
"repo_link": "https://github.com/projectdiscovery/dnsx",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"package-manager": "github-release",
|
||||
"tags": [
|
||||
"recon",
|
||||
@@ -54,12 +54,12 @@
|
||||
],
|
||||
"valide-command": "",
|
||||
"linux": {
|
||||
"amd64": "https://github.com/projectdiscovery/dnsx/releases/download/v1.2.2/dnsx_1.2.2_linux_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/dnsx/releases/download/v1.2.2/dnsx_1.2.2_linux_arm64.zip"
|
||||
"amd64": "https://github.com/projectdiscovery/dnsx/releases/download/v1.2.3/dnsx_1.2.3_linux_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/dnsx/releases/download/v1.2.3/dnsx_1.2.3_linux_arm64.zip"
|
||||
},
|
||||
"darwin": {
|
||||
"amd64": "https://github.com/projectdiscovery/dnsx/releases/download/v1.2.2/dnsx_1.2.2_macOS_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/dnsx/releases/download/v1.2.2/dnsx_1.2.2_macOS_arm64.zip"
|
||||
"amd64": "https://github.com/projectdiscovery/dnsx/releases/download/v1.2.3/dnsx_1.2.3_macOS_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/dnsx/releases/download/v1.2.3/dnsx_1.2.3_macOS_arm64.zip"
|
||||
}
|
||||
},
|
||||
"assetfinder": {
|
||||
@@ -84,7 +84,7 @@
|
||||
"nuclei": {
|
||||
"desc": "Nuclei is a fast, customizable vulnerability scanner powered by the global security community and built on a simple YAML-based DSL, enabling collaboration to tackle trending vulnerabilities on the internet. It helps you find vulnerabilities in your applications, APIs, networks, DNS, and cloud configurations.",
|
||||
"repo_link": "https://github.com/projectdiscovery/nuclei",
|
||||
"version": "3.6.2",
|
||||
"version": "3.7.0",
|
||||
"package-manager": "github-release",
|
||||
"tags": [
|
||||
"vulnerability-scanner",
|
||||
@@ -93,12 +93,12 @@
|
||||
],
|
||||
"valide-command": "",
|
||||
"linux": {
|
||||
"amd64": "https://github.com/projectdiscovery/nuclei/releases/download/v3.6.2/nuclei_3.6.2_linux_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/nuclei/releases/download/v3.6.2/nuclei_3.6.2_linux_arm64.zip"
|
||||
"amd64": "https://github.com/projectdiscovery/nuclei/releases/download/v3.7.0/nuclei_3.7.0_linux_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/nuclei/releases/download/v3.7.0/nuclei_3.7.0_linux_arm64.zip"
|
||||
},
|
||||
"darwin": {
|
||||
"amd64": "https://github.com/projectdiscovery/nuclei/releases/download/v3.6.2/nuclei_3.6.2_macOS_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/nuclei/releases/download/v3.6.2/nuclei_3.6.2_macOS_arm64.zip"
|
||||
"amd64": "https://github.com/projectdiscovery/nuclei/releases/download/v3.7.0/nuclei_3.7.0_macOS_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/nuclei/releases/download/v3.7.0/nuclei_3.7.0_macOS_arm64.zip"
|
||||
}
|
||||
},
|
||||
"nuclei-templates": {
|
||||
@@ -122,7 +122,7 @@
|
||||
"httpx": {
|
||||
"desc": "httpx is a fast and multi-purpose HTTP toolkit that allows running multiple probes using the retryablehttp library.",
|
||||
"repo_link": "https://github.com/projectdiscovery/httpx",
|
||||
"version": "1.7.4",
|
||||
"version": "1.8.1",
|
||||
"package-manager": "github-release",
|
||||
"tags": [
|
||||
"recon",
|
||||
@@ -132,12 +132,12 @@
|
||||
],
|
||||
"valide-command": "",
|
||||
"linux": {
|
||||
"amd64": "https://github.com/projectdiscovery/httpx/releases/download/v1.7.4/httpx_1.7.4_linux_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/httpx/releases/download/v1.7.4/httpx_1.7.4_linux_arm64.zip"
|
||||
"amd64": "https://github.com/projectdiscovery/httpx/releases/download/v1.8.1/httpx_1.8.1_linux_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/httpx/releases/download/v1.8.1/httpx_1.8.1_linux_arm64.zip"
|
||||
},
|
||||
"darwin": {
|
||||
"amd64": "https://github.com/projectdiscovery/httpx/releases/download/v1.7.4/httpx_1.7.4_macOS_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/httpx/releases/download/v1.7.4/httpx_1.7.4_macOS_arm64.zip"
|
||||
"amd64": "https://github.com/projectdiscovery/httpx/releases/download/v1.8.1/httpx_1.8.1_macOS_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/httpx/releases/download/v1.8.1/httpx_1.8.1_macOS_arm64.zip"
|
||||
}
|
||||
},
|
||||
"katana": {
|
||||
@@ -164,7 +164,7 @@
|
||||
"naabu": {
|
||||
"desc": "A fast port scanner written in go with a focus on reliability and simplicity. Designed to be used in combination with other tools for attack surface discovery in bug bounties and pentests",
|
||||
"repo_link": "https://github.com/projectdiscovery/naabu",
|
||||
"version": "2.3.7",
|
||||
"version": "2.4.0",
|
||||
"package-manager": "github-release",
|
||||
"tags": [
|
||||
"port-scanner",
|
||||
@@ -173,12 +173,12 @@
|
||||
],
|
||||
"valide-command": "",
|
||||
"linux": {
|
||||
"amd64": "https://github.com/projectdiscovery/naabu/releases/download/v2.3.7/naabu_2.3.7_linux_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/naabu/releases/download/v2.3.7/naabu_2.3.7_linux_arm64.zip"
|
||||
"amd64": "https://github.com/projectdiscovery/naabu/releases/download/v2.4.0/naabu_2.4.0_linux_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/naabu/releases/download/v2.4.0/naabu_2.4.0_linux_arm64.zip"
|
||||
},
|
||||
"darwin": {
|
||||
"amd64": "https://github.com/projectdiscovery/naabu/releases/download/v2.3.7/naabu_2.3.7_macOS_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/naabu/releases/download/v2.3.7/naabu_2.3.7_macOS_arm64.zip"
|
||||
"amd64": "https://github.com/projectdiscovery/naabu/releases/download/v2.4.0/naabu_2.4.0_macOS_amd64.zip",
|
||||
"arm64": "https://github.com/projectdiscovery/naabu/releases/download/v2.4.0/naabu_2.4.0_macOS_arm64.zip"
|
||||
}
|
||||
},
|
||||
"ffuf": {
|
||||
@@ -223,6 +223,27 @@
|
||||
"arm64": "https://github.com/trufflesecurity/trufflehog/releases/download/v3.90.3/trufflehog_3.90.3_darwin_arm64.tar.gz"
|
||||
}
|
||||
},
|
||||
"kingfisher": {
|
||||
"desc": "Kingfisher is a blazingly fast and highly accurate tool for secret detection and live validation across files, Git repos, GitHub, GitLab, Azure Repos, BitBucket, Gitea, AWS S3, Docker images, Jira, Slack, and Confluence",
|
||||
"repo_link": "https://github.com/mongodb/kingfisher",
|
||||
"version": "1.76.0",
|
||||
"package-manager": "github-release",
|
||||
"tags": [
|
||||
"secret-detection",
|
||||
"security",
|
||||
"scanning",
|
||||
"credentials"
|
||||
],
|
||||
"valide-command": "",
|
||||
"linux": {
|
||||
"amd64": "https://github.com/mongodb/kingfisher/releases/download/v1.76.0/kingfisher-linux-x64.tgz",
|
||||
"arm64": "https://github.com/mongodb/kingfisher/releases/download/v1.76.0/kingfisher-linux-arm64.tgz"
|
||||
},
|
||||
"darwin": {
|
||||
"amd64": "https://github.com/mongodb/kingfisher/releases/download/v1.76.0/kingfisher-darwin-x64.tgz",
|
||||
"arm64": "https://github.com/mongodb/kingfisher/releases/download/v1.76.0/kingfisher-darwin-arm64.tgz"
|
||||
}
|
||||
},
|
||||
"semgrep": {
|
||||
"desc": "Static analysis tool for code scanning",
|
||||
"repo_link": "https://github.com/returntocorp/semgrep",
|
||||
@@ -271,7 +292,8 @@
|
||||
"tags": [
|
||||
"utility",
|
||||
"file-transfer",
|
||||
"sync"
|
||||
"sync",
|
||||
"optional"
|
||||
],
|
||||
"valide-command": "",
|
||||
"multi-commands-linux": [
|
||||
@@ -519,7 +541,7 @@
|
||||
}
|
||||
},
|
||||
"puredns": {
|
||||
"desc": "Fast domain resolver and subdomain bruteforcing tool with accurate wildcard filtering",
|
||||
"desc": "Puredns is a fast domain resolver and subdomain bruteforcing tool that can accurately filter out wildcard subdomains and DNS poisoned entries.",
|
||||
"repo_link": "https://github.com/d3mondev/puredns",
|
||||
"version": "2.1.1",
|
||||
"package-manager": "github-release",
|
||||
@@ -703,7 +725,7 @@
|
||||
"trivy": {
|
||||
"desc": "Find vulnerabilities, misconfigurations, secrets, SBOM in containers, Kubernetes, code repositories, clouds and more",
|
||||
"repo_link": "https://github.com/aquasecurity/trivy",
|
||||
"version": "0.68.2",
|
||||
"version": "0.69.0",
|
||||
"package-manager": "github-release",
|
||||
"tags": [
|
||||
"vulnerability-scanner",
|
||||
@@ -793,7 +815,7 @@
|
||||
"interactsh": {
|
||||
"desc": "An OOB interaction gathering server and client library",
|
||||
"repo_link": "https://github.com/projectdiscovery/interactsh",
|
||||
"version": "1.2.4",
|
||||
"version": "1.3.0",
|
||||
"package-manager": "go-getter",
|
||||
"tags": [
|
||||
"oob-testing",
|
||||
|
||||
Reference in New Issue
Block a user