Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions nailgun/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -8084,6 +8084,32 @@ def import_puppetclasses(self, synchronous=True, timeout=None, **kwargs):
client.post(path, **kwargs), self._server_config, synchronous, timeout
)

def add_autosign_entry(self, certname, **kwargs):
"""Add an entry to the puppetserver's autosign file.

:param certname: Name the host is going to register with
"""
kwargs = kwargs.copy()
kwargs.update(self._server_config.get_client_kwargs())
path = f'{self.path()}/autosign'
return _handle_response(
client.post(path, data={'id': certname}, **kwargs),
self._server_config,
)
Comment on lines +8087 to +8098

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 suggestion (security): Consider validating certname before using it in the request.

If certname is externally sourced, ensure it is properly validated or sanitized to prevent injection or malformed requests.

Suggested change
def add_autosign_entry(self, certname, **kwargs):
"""Add an entry to the puppetserver's autosign file.
:param certname: Name the host is going to register with
"""
kwargs = kwargs.copy()
kwargs.update(self._server_config.get_client_kwargs())
path = f'{self.path()}/autosign'
return _handle_response(
client.post(path, data={'id': certname}, **kwargs),
self._server_config,
)
def _validate_certname(self, certname):
"""Validate certname to prevent injection or malformed requests."""
import re
# Only allow alphanumeric, dash, dot, and underscore, 1-255 chars
if not isinstance(certname, str) or not re.match(r'^[A-Za-z0-9_.-]{1,255}$', certname):
raise ValueError(f"Invalid certname: {certname}")
def add_autosign_entry(self, certname, **kwargs):
"""Add an entry to the puppetserver's autosign file.
:param certname: Name the host is going to register with
"""
self._validate_certname(certname)
kwargs = kwargs.copy()
kwargs.update(self._server_config.get_client_kwargs())
path = f'{self.path()}/autosign'
return _handle_response(
client.post(path, data={'id': certname}, **kwargs),
self._server_config,
)


def delete_autosign_entry(self, certname, **kwargs):
"""Delete an entry from the puppetserver's autosign file.

:param certname: Name of the host to be deleted from the autosign file
"""
kwargs = kwargs.copy()
kwargs.update(self._server_config.get_client_kwargs())
path = f'{self.path()}/autosign/{certname}'
return _handle_response(
client.delete(path, **kwargs),
self._server_config,
)

def read(self, entity=None, attrs=None, ignore=None, params=None):
"""Ignore ``download_policy`` field as it's never returned by the server.

Expand Down
33 changes: 33 additions & 0 deletions tests/test_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -3459,6 +3459,39 @@ def test_import_puppetclasses(self):
if 'environment' in param:
self.assertIn('/environments', post.call_args[0][0])

def test_add_autosign_entry(self):
"""Call :meth:`nailgun.entities.SmartProxy.add_autosign_entry`.

Assert that
* correct fqdn is sent,
* proper path is built.
"""
certname = "host.example.com"
with self.subTest():
with mock.patch.object(client, 'post') as post:
self.smart_proxy.add_autosign_entry(certname)
self.assertEqual(post.call_count, 1)
self.assertIn(f'{self.smart_proxy.path()}/autosign', post.call_args[0][0])
self.assertEqual(len(post.call_args[1]), 1)
self.assertEqual(post.call_args[1], {'data': {'id': 'host.example.com'}})

def test_delete_autosign_entry(self):
"""Call :meth:`nailgun.entities.SmartProxy.add_autosign_entry`.

Assert that
* correct fqdn is sent,
* proper path is built.
"""
certname = "host.example.com"
with self.subTest():
with mock.patch.object(client, 'delete') as delete:
self.smart_proxy.delete_autosign_entry(certname)
self.assertEqual(delete.call_count, 1)
self.assertIn(
f'{self.smart_proxy.path()}/autosign/{certname}', delete.call_args[0][0]
)
self.assertEqual(len(delete.call_args[1]), 0)


class SubscriptionTestCase(TestCase):
"""Tests for :class:`nailgun.entities.Subscription`."""
Expand Down