Skip to content

Centralize SSH/clush command building and extract host resolution uti…#2149

Open
berendt wants to merge 1 commit intomainfrom
refactor-ssh-clush
Open

Centralize SSH/clush command building and extract host resolution uti…#2149
berendt wants to merge 1 commit intomainfrom
refactor-ssh-clush

Conversation

@berendt
Copy link
Member

@berendt berendt commented Mar 17, 2026

…lities

Move host resolution functions (resolve_hostname_to_ip, get_primary_ipv4_from_netbox, resolve_host_with_fallback, get_hosts_from_group, select_host_from_list) from console.py into new osism/utils/hosts.py module. Add build_ssh_command() and build_clush_command() builder functions to osism/utils/ssh.py to eliminate duplicated SSH option boilerplate across console, log, report, compose, and container commands. This also removes shell=True subprocess calls in compose, container, and log commands.

AI-assisted: Claude Code

…lities

Move host resolution functions (resolve_hostname_to_ip, get_primary_ipv4_from_netbox,
resolve_host_with_fallback, get_hosts_from_group, select_host_from_list) from
console.py into new osism/utils/hosts.py module. Add build_ssh_command() and
build_clush_command() builder functions to osism/utils/ssh.py to eliminate
duplicated SSH option boilerplate across console, log, report, compose, and
container commands. This also removes shell=True subprocess calls in compose,
container, and log commands.

AI-assisted: Claude Code

Signed-off-by: Christian Berendt <berendt@osism.tech>
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 15 security issues, and 1 other issue

Security issues:

  • Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="osism/utils/ssh.py" line_range="74-71" />
<code_context>
+def build_clush_command(
</code_context>
<issue_to_address>
**issue (bug_risk):** Explicitly validate that either `hosts` or `group` is provided (and not both) when building the clush command.

If both `hosts` and `group` are `None`, `build_clush_command` returns `[CLUSH_BINARY, "-l", user]`, which will likely fail at runtime in a non-obvious way. If both are provided, `hosts` silently wins. Please have the function: (1) raise `ValueError` when neither is provided, and (2) either raise or otherwise clearly enforce/document that `hosts` and `group` are mutually exclusive, so misuse fails fast and call-site behavior is predictable.
</issue_to_address>

### Comment 2
<location path="osism/commands/compose.py" line_range="44" />
<code_context>
        subprocess.call(build_ssh_command(host, remote_command=remote_command))
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 3
<location path="osism/commands/console.py" line_range="73" />
<code_context>
            subprocess.call(build_clush_command(group=host))
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 4
<location path="osism/commands/console.py" line_range="89" />
<code_context>
            subprocess.call(build_ssh_command(resolved_host))
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 5
<location path="osism/commands/console.py" line_range="100-102" />
<code_context>
                subprocess.call(
                    build_ssh_command(resolved_host, remote_command=ssh_command)
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 6
<location path="osism/commands/console.py" line_range="113-119" />
<code_context>
            subprocess.call(
                build_ssh_command(
                    resolved_target_host,
                    remote_command=ssh_command,
                    request_tty=True,
                )
            )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 7
<location path="osism/commands/container.py" line_range="47" />
<code_context>
                subprocess.call(build_ssh_command(host, remote_command=remote_command))
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 8
<location path="osism/commands/container.py" line_range="52" />
<code_context>
            subprocess.call(build_ssh_command(host, remote_command=remote_command))
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 9
<location path="osism/commands/log.py" line_range="73" />
<code_context>
        subprocess.call(build_ssh_command(host, remote_command=remote_command))
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 10
<location path="osism/commands/log.py" line_range="136-138" />
<code_context>
            rc = subprocess.call(
                build_clush_command(hosts=group_hosts, remote_command=tail_command)
            )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 11
<location path="osism/commands/log.py" line_range="156-158" />
<code_context>
            rc = subprocess.call(
                build_ssh_command(resolved_host, remote_command=tail_command)
            )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 12
<location path="osism/commands/report.py" line_range="83-91" />
<code_context>
                memory_result = subprocess.run(
                    build_ssh_command(
                        resolved_host,
                        remote_command=dmidecode_command,
                        connect_timeout=10,
                    ),
                    capture_output=True,
                    text=True,
                    timeout=30,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 13
<location path="osism/commands/report.py" line_range="101-109" />
<code_context>
                uuid_result = subprocess.run(
                    build_ssh_command(
                        resolved_host,
                        remote_command=uuid_command,
                        connect_timeout=10,
                    ),
                    capture_output=True,
                    text=True,
                    timeout=30,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 14
<location path="osism/commands/report.py" line_range="203-211" />
<code_context>
                lldp_result = subprocess.run(
                    build_ssh_command(
                        resolved_host,
                        remote_command=lldp_command,
                        connect_timeout=10,
                    ),
                    capture_output=True,
                    text=True,
                    timeout=30,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 15
<location path="osism/commands/report.py" line_range="359-367" />
<code_context>
                bgp_result = subprocess.run(
                    build_ssh_command(
                        resolved_host,
                        remote_command=bgp_command,
                        connect_timeout=10,
                    ),
                    capture_output=True,
                    text=True,
                    timeout=30,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 16
<location path="osism/commands/report.py" line_range="527-535" />
<code_context>
                fact_result = subprocess.run(
                    build_ssh_command(
                        resolved_host,
                        remote_command=fact_command,
                        connect_timeout=10,
                    ),
                    capture_output=True,
                    text=True,
                    timeout=30,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

if remote_command:
cmd.append(remote_command)

return cmd
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Explicitly validate that either hosts or group is provided (and not both) when building the clush command.

If both hosts and group are None, build_clush_command returns [CLUSH_BINARY, "-l", user], which will likely fail at runtime in a non-obvious way. If both are provided, hosts silently wins. Please have the function: (1) raise ValueError when neither is provided, and (2) either raise or otherwise clearly enforce/document that hosts and group are mutually exclusive, so misuse fails fast and call-site behavior is predictable.

f"/usr/bin/ssh -i /ansible/secrets/id_rsa.operator {ssh_options} {settings.OPERATOR_USER}@{host} '{ssh_command}'",
shell=True,
)
subprocess.call(build_ssh_command(host, remote_command=remote_command))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

host,
]
)
subprocess.call(build_clush_command(group=host))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

f"{settings.OPERATOR_USER}@{resolved_host}",
]
)
subprocess.call(build_ssh_command(resolved_host))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Comment on lines 100 to 102
subprocess.call(
[
"/usr/bin/ssh",
"-i",
"/ansible/secrets/id_rsa.operator",
*ssh_options,
f"{settings.OPERATOR_USER}@{resolved_host}",
ssh_command,
]
build_ssh_command(resolved_host, remote_command=ssh_command)
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'call' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Comment on lines 83 to 91
memory_result = subprocess.run(
[
*ssh_base,
f"{settings.OPERATOR_USER}@{resolved_host}",
dmidecode_command,
],
build_ssh_command(
resolved_host,
remote_command=dmidecode_command,
connect_timeout=10,
),
capture_output=True,
text=True,
timeout=30,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Comment on lines 101 to 109
uuid_result = subprocess.run(
[
*ssh_base,
f"{settings.OPERATOR_USER}@{resolved_host}",
uuid_command,
],
build_ssh_command(
resolved_host,
remote_command=uuid_command,
connect_timeout=10,
),
capture_output=True,
text=True,
timeout=30,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Comment on lines 203 to 211
lldp_result = subprocess.run(
[
*ssh_base,
f"{settings.OPERATOR_USER}@{resolved_host}",
lldp_command,
],
build_ssh_command(
resolved_host,
remote_command=lldp_command,
connect_timeout=10,
),
capture_output=True,
text=True,
timeout=30,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Comment on lines 359 to 367
bgp_result = subprocess.run(
[
*ssh_base,
f"{settings.OPERATOR_USER}@{resolved_host}",
bgp_command,
],
build_ssh_command(
resolved_host,
remote_command=bgp_command,
connect_timeout=10,
),
capture_output=True,
text=True,
timeout=30,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Comment on lines 527 to 535
fact_result = subprocess.run(
[
*ssh_base,
f"{settings.OPERATOR_USER}@{resolved_host}",
fact_command,
],
build_ssh_command(
resolved_host,
remote_command=fact_command,
connect_timeout=10,
),
capture_output=True,
text=True,
timeout=30,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant