Skip to content

Commit

Permalink
Merge pull request netenglabs#937 from Sepehr-A/issue-845
Browse files Browse the repository at this point in the history
Make username also an env var for poller
  • Loading branch information
ddutt authored May 21, 2024
2 parents ced4eb6 + 22adb0a commit c41bba9
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 8 deletions.
27 changes: 23 additions & 4 deletions docs/inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@ auths:
- name: suzieq-user-04
key-passphrase: ask
keyfile: path/to/key

- name: suzieq-user-05
username: ask
password: ask

- name: suzieq-user-06
username: env:USERNAME_ENV_VAR
password: ask

- name: suzieq-user-07
username: env:USERNAME_ENV_VAR
password: env:PASSWORD_ENV_VAR

- name: suzieq-user-08
username: ask
password: env:PASSWORD_ENV_VAR

namespaces:
- name: testing
Expand All @@ -80,7 +96,7 @@ namespaces:
!!! warning
Some observations on the YAML file above:
- **This is just an example** that covers all the possible combinations, **not an real life inventory**
- **This is just an example** that covers most of the possible combinations, **not an real life inventory**
- **Do not specify device type unless you're using REST**. SuzieQ automatically determines device type with SSH
- Most environments require setting the `ignore-known-hosts` option in the device section
- The auths section shows all the different authorization methods supported by SuzieQ
Expand All @@ -95,7 +111,8 @@ For this reason, SuzieQ inventory now supports three different options to store
- `env:<ENV_VARIABLE>`: the sensitive information is stored in an environment variable
- `ask`: the user can write the sensitive information on the stdin

Currently this method is used to specify passwords, passphrases and tokens.
This method is currently utilized for specifying usernames, passwords,
passphrases, and tokens.

## <a name='inventory-sources'></a>Sources

Expand Down Expand Up @@ -323,8 +340,10 @@ In case a private key is used to authenticate:

Where `key-passphrase` is the passphrase of the private key.

Both `passoword` and `key-passphrase` are considered [sensitive data](#sensitive-data).
For this reason they can be set as plaintext, env variable or asked to the user via stdin.
`Password`, `key-passphrase` and `username` are considered [sensitive
data](#sensitive-data).
For this reason they can be set as plaintext, env variable or
asked to the user via stdin.

### <a name='cred-file'></a>Credential file

Expand Down
12 changes: 10 additions & 2 deletions suzieq/poller/controller/credential_loader/static.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

from suzieq.poller.controller.credential_loader.base_credential_loader import \
CredentialLoader, CredentialLoaderModel, check_credentials
from suzieq.shared.utils import get_sensitive_data
from suzieq.shared.exceptions import SensitiveLoadError, InventorySourceError
from suzieq.shared.utils import get_sensitive_data


class StaticModel(CredentialLoaderModel):
Expand All @@ -18,7 +18,7 @@ class StaticModel(CredentialLoaderModel):
keyfile: Optional[str]
enable_password: Optional[str] = Field(alias='enable-password')

@validator('password', 'key_passphrase', 'enable_password')
@validator('username', 'password', 'key_passphrase', 'enable_password')
def validate_sens_field(cls, field):
"""Validate if the sensitive var was passed correctly
"""
Expand Down Expand Up @@ -57,6 +57,14 @@ def init(self, init_data: dict):
init_data['key_passphrase'] = init_data.pop('key-passphrase', None)
super().init(init_data)

if self._data.username == 'ask':
try:
self._data.username = get_sensitive_data(
self._data.username,
f'{self.name} Username to login to device: ')
except SensitiveLoadError as e:
raise InventorySourceError(f'{self.name} {e}')

if self._data.password == 'ask':
try:
self._data.password = get_sensitive_data(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Dict
from pydantic import ValidationError

import pytest
from pydantic import ValidationError

from suzieq.poller.controller.credential_loader.static import (StaticLoader,
StaticModel)
from tests.unit.poller.shared.utils import read_yaml_file
Expand Down Expand Up @@ -84,30 +85,38 @@ def test_variables_init(monkeypatch):
"""
ask_password = 'ask_password'
env_password = 'env_password'
ask_username = 'ask_username'
env_username = 'env_username'
plain_passphrase = 'my-pass'

# 'env' and 'plain' values
init_data = {
'name': 'n',
'username': 'env:SUZIEQ_ENV_USERNAME',
'key-passphrase': f'plain:{plain_passphrase}',
'password': 'env:SUZIEQ_ENV_PASSWORD'
}
monkeypatch.setenv('SUZIEQ_ENV_PASSWORD', env_password)
monkeypatch.setenv('SUZIEQ_ENV_USERNAME', env_username)
sl = StaticModel(**init_data)

assert sl.key_passphrase == plain_passphrase
assert sl.password == env_password
assert sl.username == env_username

# 'ask' values
init_data = {
'name': 'n',
'username': 'ask',
'password': 'ask'
}
valid_data = StaticModel(**init_data).dict(by_alias=True)
monkeypatch.setattr('getpass.getpass', lambda x: ask_password)
mock_get_pass = MockGetPass([ask_username, ask_password])
monkeypatch.setattr('getpass.getpass', mock_get_pass)
sl = StaticLoader(valid_data)

assert sl._data.password == ask_password
assert sl._data.username == ask_username

# unknown parameter
init_data = {
Expand All @@ -117,3 +126,40 @@ def test_variables_init(monkeypatch):
}
with pytest.raises(ValidationError):
StaticModel(**init_data)


class MockGetPass:
"""
Mocks `getpass.getpass` for testing, cycling through a list of predefined
responses.
Attributes:
responses (list): A list of responses to simulate sequential user inputs.
call_count (int): Tracks calls to provide the next response in the list.
"""

def __init__(self, responses: list):
"""
Initializes the mock with responses and resets call count.
Args:
responses: Simulated user inputs.
"""
self.responses = responses
self.call_count = 0

def __call__(self, prompt=''):
"""
Returns the next response from the list, mimicking user input.
Args:
prompt: Unused, present for compatibility.
Returns:
The next simulated user input.
"""
response = self.responses[self.call_count]
self.call_count += 1
if self.call_count >= len(self.responses):
self.call_count = 0
return response

0 comments on commit c41bba9

Please sign in to comment.