Custom promise type for managing global sshd configuration#128
Custom promise type for managing global sshd configuration#128larsewi wants to merge 4 commits intocfengine:masterfrom
Conversation
Signed-off-by: Lars Erik Wik <lars.erik.wik@northern.tech>
57e9aac to
8156d8c
Compare
|
Thanks for submitting a PR! Maybe @craigcomstock can review this? |
dbc704a to
f9ee09c
Compare
olehermanse
left a comment
There was a problem hiding this comment.
I think it's good enough for an experimental / first iteration, but there's many things we could consider doing differently. Left some comments in the code.
promise-types/sshd/sshd.py
Outdated
| def validate_promise( # pyright: ignore[reportImplicitOverride] | ||
| self, promiser: str, attributes: dict[str, object], metadata: dict[str, str] | ||
| ): | ||
| for attr, value in attributes.items(): | ||
| if not isinstance(value, (str, list)): | ||
| raise ValidationError(f"Attribute '{attr}' must be a string or slist") |
There was a problem hiding this comment.
I think we should do a better attempt at validating the promise - is it possible to get a list of all valid configuration options from sshd and/or write to a temporary file and use sshd to validate this file?
There was a problem hiding this comment.
Could not find any nice way to do it
Ticket: ENT-13797 Signed-off-by: Lars Erik Wik <lars.erik.wik@northern.tech>
Instead of passing sshd directives as named attributes on a single promise, each directive is now its own promise where the promiser is the sshd keyword and "value" is the sole attribute. This simplifies validation and aligns with a one-promise-per-directive model. Ticket: ENT-13797 Signed-off-by: Lars Erik Wik <lars.erik.wik@northern.tech> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ticket: ENT-13797 Signed-off-by: Lars Erik Wik <lars.erik.wik@northern.tech>
olehermanse
left a comment
There was a problem hiding this comment.
Looks good. Some small comments / suggestions.
| if not isinstance(value, (str, list)): | ||
| raise ValidationError("Attribute 'value' must be a string or an slist") | ||
|
|
||
| # Make sure 'value' attribute is not empty |
There was a problem hiding this comment.
Can check some things about promiser too, right? IIRC it should:
- Not contain whitespace,
#or/. - Not be empty
- First letter is always uppercase?
- Last letter is always lowercase?
| def validate_config(self, filename: str) -> bool: | ||
| """Validate the sshd syntax on a file""" | ||
| r = subprocess.run( | ||
| ["/usr/sbin/sshd", "-t", "-f", filename], | ||
| capture_output=True, | ||
| text=True, | ||
| ) | ||
| if r.returncode != 0: | ||
| self.log_error(f"Configuration validation failed: {r.stderr.strip()}") | ||
| return False | ||
| return True |
There was a problem hiding this comment.
Why can't we do this as part of validate? With a temporary file.
From your code it looks like it should be possible / easy, if not, please explain in a comment.
There was a problem hiding this comment.
Validation should not fail if you don't have sshd installed for example. Also this command does a sanity of the keys, which do not belong here.
There was a problem hiding this comment.
Does man sshd_config give all the possible options?
man sshd_config | col -b | grep -E '^ {5}[A-Z][a-zA-Z]+$' | sed 's/^ *//'I really think it would be nice if we could do proper validation.
| try: | ||
| from cfengine_module_library import PromiseModule, ValidationError, Result | ||
| except ImportError: | ||
| sys.path.append(os.path.join(os.path.dirname(__file__), "../../libraries/python")) | ||
| from cfengine_module_library import PromiseModule, ValidationError, Result |
There was a problem hiding this comment.
I could easily see this becoming confusing, if I have it installed, and they are different. On the other hand, we change the library so rarely that I guess it's okay.
| # When run by CFEngine, the module library is installed. For local development | ||
| # and testing, fall back to the in-tree copy. |
There was a problem hiding this comment.
Could maybe add a docstring / comment here (or in README) about how to run tests.
No description provided.