Skip to content

Custom promise type for managing global sshd configuration#128

Merged
larsewi merged 2 commits intocfengine:masterfrom
larsewi:sshd
Mar 31, 2026
Merged

Custom promise type for managing global sshd configuration#128
larsewi merged 2 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

@larsewi larsewi force-pushed the sshd branch 2 times, most recently from 12567d4 to a300170 Compare March 24, 2026 16:04
@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

@olehermanse olehermanse Mar 27, 2026

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? - No. (PermitTTY for example).

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.

Copy link
Copy Markdown
Contributor Author

@larsewi larsewi Mar 31, 2026

Choose a reason for hiding this comment

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

$ man sshd_config | col -b | grep -E '^ {5}[A-Z][a-zA-Z]+$' | sed 's/^ *//'
troff:<standard input>:810: warning [p 8, 7.2i]: cannot break line

That command does not work for me. And parsing a man page sounds very fragile. How about compiling a hardcoded list of accepted keywords? However, this list would vary based on sshd versions and quickly become a maintenance burden.

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.

Ticket: ENT-13797
Signed-off-by: Lars Erik Wik <lars.erik.wik@northern.tech>
@larsewi larsewi merged commit 67e3c03 into cfengine:master Mar 31, 2026
1 check failed
self, promiser: str, attributes: dict[str, object], metadata: dict[str, str]
):
# Check that promiser is a valid sshd keyword
if not re.fullmatch(r"[a-zA-Z0-9]+", promiser):
Copy link
Copy Markdown
Member

@olehermanse olehermanse Mar 31, 2026

Choose a reason for hiding this comment

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

@larsewi Which sshd options can start with a lowercase letter? Which of them can contain numbers? If those exist, please add examples in a comment.

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