Skip to content

Custom promise type for managing global sshd configuration#128

Open
larsewi wants to merge 4 commits intocfengine:masterfrom
larsewi:sshd
Open

Custom promise type for managing global sshd configuration#128
larsewi wants to merge 4 commits intocfengine:masterfrom
larsewi:sshd

Conversation

@larsewi
Copy link
Copy Markdown
Contributor

@larsewi larsewi commented Mar 12, 2026

No description provided.

Signed-off-by: Lars Erik Wik <lars.erik.wik@northern.tech>
@larsewi larsewi force-pushed the sshd branch 5 times, most recently from 57e9aac to 8156d8c Compare March 12, 2026 22:59
@cf-bottom
Copy link
Copy Markdown

Thanks for submitting a PR! Maybe @craigcomstock can review this?

@larsewi larsewi force-pushed the sshd branch 3 times, most recently from dbc704a to f9ee09c Compare March 16, 2026 15:59
@larsewi larsewi marked this pull request as ready for review March 16, 2026 16:20
Copy link
Copy Markdown
Member

@olehermanse olehermanse left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +139 to +144
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")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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>
@larsewi larsewi requested a review from olehermanse March 24, 2026 18:09
Copy link
Copy Markdown
Member

@olehermanse olehermanse left a comment

Choose a reason for hiding this comment

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

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

Comment on lines +116 to +126
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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

@olehermanse olehermanse Mar 30, 2026

Choose a reason for hiding this comment

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

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.

Comment on lines +9 to +13
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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment on lines +7 to +8
# When run by CFEngine, the module library is installed. For local development
# and testing, fall back to the in-tree copy.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could maybe add a docstring / comment here (or in README) about how to run tests.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants