Skip to content

Commit 87c27fd

Browse files
authored
Merge pull request #516 from sadsfae/development
feat: support auth via env vars
2 parents 898d400 + 3de528b commit 87c27fd

File tree

8 files changed

+163
-79
lines changed

8 files changed

+163
-79
lines changed

README.md

Lines changed: 82 additions & 59 deletions
Large diffs are not rendered by default.

src/badfish/helpers/parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ def create_parser():
1010
allow_abbrev=False,
1111
)
1212
parser.add_argument("-H", "--host", help="iDRAC host address")
13-
parser.add_argument("-u", help="iDRAC username", required=True)
14-
parser.add_argument("-p", help="iDRAC password", required=True)
13+
parser.add_argument("-u", help="iDRAC username")
14+
parser.add_argument("-p", help="iDRAC password")
1515
parser.add_argument("-i", help="Path to iDRAC interfaces yaml", default=None)
1616
parser.add_argument("-t", help="Type of host as defined on iDRAC interfaces yaml")
1717
parser.add_argument("-l", "--log", help="Optional argument for logging results to a file")

src/badfish/main.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2450,8 +2450,19 @@ async def set_nic_attribute(self, fqdd, attribute, value):
24502450

24512451

24522452
async def execute_badfish(_host, _args, logger, format_handler=None):
2453-
_username = _args["u"]
2454-
_password = _args["p"]
2453+
_username = _args.get("u") or os.environ.get("BADFISH_USERNAME")
2454+
_password = _args.get("p") or os.environ.get("BADFISH_PASSWORD")
2455+
2456+
if _args.get("p"):
2457+
logger.warning(
2458+
"Passing secrets via command line arguments can be unsafe. "
2459+
"Consider using environment variables (BADFISH_USERNAME, BADFISH_PASSWORD)."
2460+
)
2461+
2462+
if not _username or not _password:
2463+
logger.error("Missing credentials. Please provide credentials via CLI arguments or environment variables.")
2464+
return _host, False
2465+
24552466
host_type = _args["t"]
24562467
interfaces_path = _args["i"]
24572468
force = _args["force"]

tests/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,3 +2347,5 @@ def render_device_dict(index, device):
23472347
{RESPONSE_UNSOPPORTED_IDRAC_VERSION}
23482348
{RESPONSE_NIC_ATTR_GET_ERROR}
23492349
"""
2350+
MANAGER_INSTANCE_RESP = '{"Jobs":{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/Jobs"}}'
2351+
JOBS_RESP = '{"Members":[]}'

tests/test_base.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import sys
2+
import os
23
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
34

45
import pytest
@@ -53,21 +54,35 @@ def badfish_call(
5354
mock_host=config.MOCK_HOST,
5455
mock_user=config.MOCK_USER,
5556
mock_pass=config.MOCK_PASS,
57+
use_cli_secrets=False
5658
):
5759
argv = []
60+
env_vars = os.environ.copy()
5861

5962
if mock_host is not None:
6063
argv.extend(("-H", mock_host))
61-
if mock_user is not None:
62-
argv.extend(("-u", mock_user))
63-
if mock_pass is not None:
64-
argv.extend(("-p", mock_pass))
65-
66-
argv.extend(self.args)
67-
try:
68-
main(argv)
69-
except BadfishException:
70-
pass
64+
65+
if use_cli_secrets:
66+
# Legacy behavior: Pass secrets via CLI args to test warning logic
67+
if mock_user is not None:
68+
argv.extend(("-u", mock_user))
69+
if mock_pass is not None:
70+
argv.extend(("-p", mock_pass))
71+
argv.extend(self.args)
72+
else:
73+
# Default behavior for tests: Use Env Vars to suppress warnings
74+
if mock_user is not None:
75+
env_vars["BADFISH_USERNAME"] = mock_user
76+
if mock_pass is not None:
77+
env_vars["BADFISH_PASSWORD"] = mock_pass
78+
argv.extend(self.args)
79+
80+
with patch.dict(os.environ, env_vars):
81+
try:
82+
main(argv)
83+
except BadfishException:
84+
pass
85+
7186
out, err = self._capsys.readouterr()
7287
return out, err
7388

tests/test_execution.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
RESPONSE_INIT_SYSTEMS_RESOURCE_NOT_FOUND,
1414
ROOT_RESP,
1515
SUCCESSFUL_HOST_LIST,
16+
SYS_RESP,
1617
WRONG_BADFISH_EXECUTION,
1718
WRONG_BADFISH_EXECUTION_HOST_LIST,
19+
MANAGER_INSTANCE_RESP,
20+
JOBS_RESP,
1821
)
1922
from tests.test_base import TestBase
2023

@@ -82,6 +85,21 @@ def test_host_list_extras(self, mock_get, mock_post, mock_delete):
8285
class TestInitialization(TestBase):
8386
args = ["--ls-jobs"]
8487

88+
@patch("aiohttp.ClientSession.delete")
89+
@patch("aiohttp.ClientSession.post")
90+
@patch("aiohttp.ClientSession.get")
91+
def test_cli_secrets_warning(self, mock_get, mock_post, mock_delete):
92+
"""Test that passing credentials via CLI triggers a warning."""
93+
responses = [ROOT_RESP] * 4 + [SYS_RESP, MAN_RESP, MANAGER_INSTANCE_RESP, JOBS_RESP]
94+
self.set_mock_response(mock_get, 200, responses)
95+
self.set_mock_response(mock_post, 200, "OK")
96+
self.set_mock_response(mock_delete, 200, "OK")
97+
98+
# Explicitly use CLI secrets to trigger the warning
99+
_, err = self.badfish_call(use_cli_secrets=True)
100+
101+
assert "Passing secrets via command line arguments can be unsafe" in err
102+
85103
@patch("aiohttp.ClientSession.delete")
86104
@patch("aiohttp.ClientSession.post")
87105
@patch("aiohttp.ClientSession.get")

tests/test_hosts_file.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ def test_hosts_good(self):
2424
for call in badfish_mock.await_args_list:
2525
_host, _args, _logger, _fh = call[0]
2626
assert _host == config.MOCK_HOST
27-
2827
assert _args["host_list"] == self.mock_hosts_good_path
29-
assert _args["u"] == config.MOCK_USER
30-
assert _args["p"] == config.MOCK_PASS
3128

3229
def test_hosts_non_existent(self):
3330
self.args = [self.option_arg, "non/existent/file"]
@@ -61,7 +58,4 @@ def test_hosts_bad(self):
6158
for call in badfish_mock.await_args_list:
6259
_host, _args, _logger, _fh = call[0]
6360
assert _host == config.MOCK_HOST
64-
6561
assert _args["host_list"] == self.mock_hosts_garbled_path
66-
assert _args["u"] == config.MOCK_USER
67-
assert _args["p"] == config.MOCK_PASS

tests/test_main_coverage.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import pytest
2+
from unittest.mock import patch, MagicMock
3+
import asyncio
4+
import logging
5+
6+
from src.badfish.main import execute_badfish
7+
8+
@pytest.mark.asyncio
9+
async def test_missing_credentials():
10+
host = "test_host"
11+
args = {} # No 'u' or 'p' in args
12+
logger = MagicMock(spec=logging.Logger)
13+
format_handler = None
14+
15+
with patch('os.environ.get', side_effect=lambda k: None): # Mock env vars as missing
16+
result = await execute_badfish(host, args, logger, format_handler)
17+
18+
assert result == (host, False)
19+
logger.error.assert_called_once_with(
20+
"Missing credentials. Please provide credentials via CLI arguments or environment variables."
21+
)

0 commit comments

Comments
 (0)