Skip to content

Rob 1931 bash toolset replaces cli toolsets #855

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ RUN apt-get install -y kubectl

# Microsoft ODBC for Azure SQL. Required for azure/sql toolset
RUN VERSION_ID=$(grep VERSION_ID /etc/os-release | cut -d '"' -f 2 | cut -d '.' -f 1) && \
if ! echo "11 12" | grep -q "$VERSION_ID"; then \
if ! echo "11 12 13" | grep -q "$VERSION_ID"; then \
echo "Debian $VERSION_ID is not currently supported."; \
exit 1; \
fi && \
curl -sSL -O https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb && \
curl -sSL -O https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb && \
dpkg -i packages-microsoft-prod.deb && \
rm packages-microsoft-prod.deb && \
apt-get update && \
Expand Down
42 changes: 42 additions & 0 deletions docs/data-sources/builtin-toolsets/bash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@


The bash toolset replaces the following YAML toolsets:

- kubernetes/logs
- kubernetes/core
- kubectl version
- kubectl describe
- kubectl get
- kubectl events
- kubernetes/live-metrics
- kubectl top
- kubernetes/krew-extras
- kubectl lineage
- kubernetes/kube-lineage-extras
- kube-lineage
- aks/node-health
- az account
- az aks
- az ...
- aks/core
- az aks ...
- az network
- az ...
- argocd/core
- argocd ...
- aws/security
- aws ...
- aws/rds
- aws rds ...
- docker/core
- docker ...
- helm/core
- helm ...


Allowed commands:

- kubectl
- jq
- grep
- awk
3 changes: 3 additions & 0 deletions holmes/common/env_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ def load_bool(env_var, default: Optional[bool]) -> Optional[bool]:
MAX_OUTPUT_TOKEN_RESERVATION = int(
os.environ.get("MAX_OUTPUT_TOKEN_RESERVATION", 16384)
) ## 16k

# When using the bash tool, setting BASH_TOOL_UNSAFE_ALLOW_ALL will skip any command validation and run any command requested by the LLM
BASH_TOOL_UNSAFE_ALLOW_ALL = load_bool("BASH_TOOL_UNSAFE_ALLOW_ALL", False)
126 changes: 126 additions & 0 deletions holmes/plugins/toolsets/bash/argocd/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import argparse
from typing import Any, Optional

from holmes.plugins.toolsets.bash.common.bash_command import BashCommand
from holmes.plugins.toolsets.bash.common.config import BashExecutorConfig
from holmes.plugins.toolsets.bash.common.stringify import escape_shell_args
from holmes.plugins.toolsets.bash.argocd.constants import (
SAFE_ARGOCD_COMMANDS,
BLOCKED_ARGOCD_OPERATIONS,
)


class ArgocdCommand(BashCommand):
def __init__(self):
super().__init__("argocd")

def add_parser(self, parent_parser: Any):
"""Create Argo CD CLI parser with safe command validation."""
argocd_parser = parent_parser.add_parser(
"argocd", help="Argo CD Command Line Interface", exit_on_error=False
)

# Add command subparser
argocd_parser.add_argument(
"command", help="Argo CD command (e.g., app, cluster, proj, repo)"
)

# Capture remaining arguments
argocd_parser.add_argument(
"options",
nargs=argparse.REMAINDER,
default=[],
help="Argo CD CLI subcommands, operations, and options",
)
return argocd_parser

def validate_command(
self, command: Any, original_command: str, config: Optional[BashExecutorConfig]
) -> None:
validate_argocd_command(command)

def stringify_command(
self, command: Any, original_command: str, config: Optional[BashExecutorConfig]
) -> str:
"""Convert parsed Argo CD command back to safe command string."""
parts = ["argocd", command.command]

if hasattr(command, "options") and command.options:
parts.extend(command.options)

return " ".join(escape_shell_args(parts))


def create_argocd_parser(parent_parser: Any):
argocd_command = ArgocdCommand()
return argocd_command.add_parser(parent_parser)


def validate_argocd_command_and_operation(command: str, options: list[str]) -> None:
"""Validate that the Argo CD command and operation combination is safe."""
# Check if this is a top-level command
if command not in SAFE_ARGOCD_COMMANDS:
allowed_commands = ", ".join(sorted(SAFE_ARGOCD_COMMANDS.keys()))
raise ValueError(
f"Argo CD command '{command}' is not in the allowlist. "
f"Allowed commands: {allowed_commands}"
)

command_config = SAFE_ARGOCD_COMMANDS[command]

# Handle commands with no subcommands (like version, context)
if isinstance(command_config, set) and len(command_config) == 0:
# This command has no subcommands, only flags are allowed
return

# If no options provided, this might be just showing command help
if not options:
return

# Extract the operation from options
operation_parts = []

# Find where the actual flags start
for i, option in enumerate(options):
if option.startswith("-"):
break
operation_parts.append(option)
else:
# No flags found, all are operation parts
operation_parts = options

# For commands with subcommands, validate the operation
if isinstance(command_config, set) and len(command_config) > 0:
if operation_parts:
operation = operation_parts[0]
if operation not in command_config:
allowed_ops = ", ".join(sorted(command_config))
raise ValueError(
f"Operation '{operation}' not allowed for command '{command}'. "
f"Allowed operations: {allowed_ops}"
)

# Check for blocked operations in the full command
full_command = " ".join([command] + operation_parts)
for blocked_op in BLOCKED_ARGOCD_OPERATIONS:
if blocked_op in full_command:
raise ValueError(
f"Argo CD command contains blocked operation '{blocked_op}': {full_command}"
)


def validate_argocd_command(cmd: Any) -> None:
"""
Validate Argo CD command to prevent injection attacks and ensure safety.
Raises ValueError if validation fails.
"""
# Validate command and operation
if hasattr(cmd, "options") and cmd.options:
validate_argocd_command_and_operation(cmd.command, cmd.options)


def stringify_argocd_command(
command: Any, original_command: str, config: Optional[BashExecutorConfig]
) -> str:
argocd_command = ArgocdCommand()
return argocd_command.stringify_command(command, original_command, config)
114 changes: 114 additions & 0 deletions holmes/plugins/toolsets/bash/argocd/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Safe Argo CD CLI commands and their allowed operations
SAFE_ARGOCD_COMMANDS = {
# Application management (read-only)
"app": {
"list",
"get",
"resources",
"diff",
"history",
"manifests",
"logs",
"wait", # Wait for app to reach desired state (read-only monitoring)
},
# Cluster management (read-only)
"cluster": {
"list",
"get",
},
# Project management (read-only)
"proj": {
"list",
"get",
},
# Repository management (read-only)
"repo": {
"list",
"get",
},
# Context management (read-only operations)
"context": set(), # No subcommands, just lists contexts
# Version information (completely safe)
"version": set(), # No subcommands, just shows version
# Account information (read-only)
"account": {
"list",
"get",
"get-user-info",
"can-i",
},
# Administrative commands (limited read-only)
"admin": {
"dashboard", # Starts read-only web UI
"settings", # Shows/validates settings (read-only)
},
}

# Blocked Argo CD operations (state-modifying or sensitive)
BLOCKED_ARGOCD_OPERATIONS = {
# Authentication operations (sensitive)
"login",
"logout",
"relogin",
# Application lifecycle operations (state-modifying)
"create",
"delete",
"sync",
"rollback",
"edit",
"set",
"unset",
"patch",
"delete-resource",
"terminate-op",
"actions", # Custom resource actions
# Account management (sensitive)
"generate-token",
"delete-token",
"update-password",
"bcrypt",
# Cluster management (state-modifying)
"add",
"rm",
"set",
"rotate-auth",
# Project management (state-modifying)
"create",
"delete",
"edit",
"add-source",
"remove-source",
"add-destination",
"remove-destination",
"add-cluster-resource-whitelist",
"remove-cluster-resource-whitelist",
"add-namespace-resource-blacklist",
"remove-namespace-resource-blacklist",
"add-cluster-resource-blacklist",
"remove-cluster-resource-blacklist",
"add-namespace-resource-whitelist",
"remove-namespace-resource-whitelist",
"add-orphaned-ignore",
"remove-orphaned-ignore",
"add-signature-key",
"remove-signature-key",
"set-orphaned-ignore",
"add-role",
"remove-role",
"add-role-token",
"delete-role-token",
"set-role",
# Repository management (state-modifying)
"add",
"rm",
# Context management (state-modifying)
"context", # When used with arguments to switch contexts
# Certificate management
"cert",
# GPG key management
"gpg",
# Application set operations
"appset",
# Notification operations
"notifications",
}
Loading
Loading