Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to enable/disable netapi clients #63050

Merged
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
5 changes: 5 additions & 0 deletions changelog/63050.changed
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
netapi_enable_clients option to allow enabling/disabling of clients in salt-api.
By default all clients will now be disabled. Users of salt-api will need
to update their master config to enable the clients that they use. Not adding
the netapi_enable_clients option with required clients to the master config will
disable salt-api.
3 changes: 3 additions & 0 deletions conf/master
Original file line number Diff line number Diff line change
Expand Up @@ -1340,3 +1340,6 @@
############################################
# Allow the raw_shell parameter to be used when calling Salt SSH client via API
#netapi_allow_raw_shell: True

# Set a list of clients to enable in in the API
#netapi_enable_clients: []
35 changes: 35 additions & 0 deletions doc/ref/configuration/master.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5099,6 +5099,41 @@ Used by ``salt-api`` for the master requests timeout.

rest_timeout: 300

.. conf_master:: netapi_disable_clients

``netapi_enable_clients``
--------------------------

barneysowood marked this conversation as resolved.
Show resolved Hide resolved
.. versionadded:: 3006.0

Default: ``[]``

Used by ``salt-api`` to enable access to the listed clients. Unless a
client is addded to this list, requests will be rejected before
authentication is attempted or processing of the low state occurs.

This can be used to only expose the required functionality via
``salt-api``.

Configuration with all possible clients enabled:

.. code-block:: yaml

netapi_enable_clients:
- local
- local_async
- local_batch
- local_subset
- runner
- runner_async
- ssh
- wheel
- wheel_async

.. note::

Enabling all clients is not recommended - only enable the
clients that provide the functionality required.

.. _syndic-server-settings:

Expand Down
12 changes: 12 additions & 0 deletions doc/topics/netapi/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ values that are mapped to function arguments. This allows calling functions
simply by creating a data structure. (And this is exactly how much of Salt's
own internals work!)

The :conf_master:`netapi_enable_clients` list in the master config sets which
clients are available. It is recommended to only enable the clients required
to complete the tasks needed to reduce the amount of Salt functionality exposed
via the netapi. Enabling the local clients will provide the same functionality as
the :command:`salt` command.

.. admonition:: :conf_master:`netapi_enable_clients`

Prior to Salt's 3006.0 release all clients were enabled and it was not possible
to disable clients individually.


.. autoclass:: salt.netapi.NetapiClient
:members: local, local_async, local_subset, ssh, runner, runner_async,
wheel, wheel_async
Expand Down
12 changes: 12 additions & 0 deletions doc/topics/releases/3006.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ supported versions. See
for more information.


All salt-api functionality disabled by default
----------------------------------------------

All netapi clients, which provide the functionality to salt-api, will now
be disabled by default. If you use the salt-api you must add the new
`netapi_enable_clients` option to your salt master config. This is
a breaking change and the salt-api will not function without this
new configuration option.
See `Netapi module <https://docs.saltproject.io/en/latest/topics/netapi/index.html#introduction-to-netapi-modules>`
for more information.


How do I migrate to the onedir packages?
----------------------------------------
The migration path from the classic, non-onedir packages to the onedir packages
Expand Down
3 changes: 3 additions & 0 deletions salt/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,8 @@ def _gather_buffer_space():
# Allow raw_shell option when using the ssh
# client via the Salt API
"netapi_allow_raw_shell": bool,
# Enable clients in the Salt API
"netapi_enable_clients": list,
"disabled_requisites": (str, list),
"global_state_conditions": (type(None), dict),
# Feature flag config
Expand Down Expand Up @@ -1618,6 +1620,7 @@ def _gather_buffer_space():
"pass_strict_fetch": False,
"pass_gnupghome": "",
"pass_dir": "",
"netapi_enable_clients": [],
}
)

Expand Down
7 changes: 7 additions & 0 deletions salt/netapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ def run(self, low):
"Invalid client specified: '{}'".format(low.get("client"))
)

if low.get("client") not in self.opts.get("netapi_enable_clients"):
Ch3LL marked this conversation as resolved.
Show resolved Hide resolved
raise salt.exceptions.SaltInvocationError(
"Client disabled: '{}'. Add to 'netapi_enable_clients' master config option to enable.".format(
low.get("client")
)
)

if not ("token" in low or "eauth" in low):
raise salt.exceptions.EauthAuthenticationError(
"No authentication credentials given"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@pytest.fixture
def client_config(client_config, netapi_port):
client_config["rest_cherrypy"] = {"port": netapi_port, "debug": True}
client_config["netapi_enable_clients"] = ["local"]
return client_config


Expand Down
6 changes: 6 additions & 0 deletions tests/pytests/functional/netapi/rest_tornado/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
@pytest.fixture
def client_config(client_config, netapi_port):
client_config["rest_tornado"] = {"port": netapi_port}
client_config["netapi_enable_clients"] = [
"local",
"local_async",
"runner",
"runner_async",
]
return client_config


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
@pytest.fixture
def client_config(client_config, netapi_port):
client_config["rest_cherrypy"] = {"port": netapi_port, "debug": True}
client_config["netapi_enable_clients"] = ["local", "runner"]
return client_config


Expand Down
6 changes: 6 additions & 0 deletions tests/pytests/integration/netapi/rest_tornado/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
@pytest.fixture
def client_config(client_config, netapi_port):
client_config["rest_tornado"] = {"port": netapi_port}
client_config["netapi_enable_clients"] = [
"local",
"local_async",
"runner",
"runner_async",
]
return client_config


Expand Down
101 changes: 89 additions & 12 deletions tests/pytests/integration/netapi/test_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import pytest

import salt.netapi
from salt.exceptions import EauthAuthenticationError
from salt.exceptions import EauthAuthenticationError, SaltInvocationError
from tests.support.mock import patch


@pytest.fixture
Expand All @@ -13,15 +14,17 @@ def client(salt_minion, salt_sub_minion, client_config):
def test_local(client, auth_creds, salt_minion, salt_sub_minion):
low = {"client": "local", "tgt": "*", "fun": "test.ping", **auth_creds}

ret = client.run(low)
with patch.dict(client.opts, {"netapi_enable_clients": ["local"]}):
ret = client.run(low)
assert ret == {salt_minion.id: True, salt_sub_minion.id: True}


@pytest.mark.slow_test
def test_local_batch(client, auth_creds, salt_minion, salt_sub_minion):
low = {"client": "local_batch", "tgt": "*", "fun": "test.ping", **auth_creds}

ret = client.run(low)
with patch.dict(client.opts, {"netapi_enable_clients": ["local_batch"]}):
ret = client.run(low)
assert ret
# local_batch returns a generator
ret = list(ret)
Expand All @@ -33,23 +36,63 @@ def test_local_batch(client, auth_creds, salt_minion, salt_sub_minion):
def test_local_async(client, auth_creds, salt_minion, salt_sub_minion):
low = {"client": "local_async", "tgt": "*", "fun": "test.ping", **auth_creds}

ret = client.run(low)
with patch.dict(client.opts, {"netapi_enable_clients": ["local_async"]}):
ret = client.run(low)

assert "jid" in ret
assert sorted(ret["minions"]) == sorted([salt_minion.id, salt_sub_minion.id])


def test_local_unauthenticated(client):
low = {"client": "local", "tgt": "*", "fun": "test.ping"}
with pytest.raises(EauthAuthenticationError):
client.run(low)

with patch.dict(client.opts, {"netapi_enable_clients": ["local"]}):
with pytest.raises(EauthAuthenticationError):
client.run(low)


def test_local_disabled(client, auth_creds):
low = {"client": "local", "tgt": "*", "fun": "test.ping", **auth_creds}

ret = None
with pytest.raises(SaltInvocationError):
ret = client.run(low)

assert ret is None


def test_local_batch_disabled(client, auth_creds):
low = {"client": "local_batch", "tgt": "*", "fun": "test.ping", **auth_creds}

ret = None
with pytest.raises(SaltInvocationError):
ret = client.run(low)

assert ret is None


def test_local_subset_disabled(client, auth_creds):
low = {
"client": "local_subset",
"tgt": "*",
"fun": "test.ping",
"subset": 1,
**auth_creds,
}

ret = None
with pytest.raises(SaltInvocationError):
ret = client.run(low)

assert ret is None


@pytest.mark.slow_test
def test_wheel(client, auth_creds):
low = {"client": "wheel", "fun": "key.list_all", **auth_creds}

ret = client.run(low)
with patch.dict(client.opts, {"netapi_enable_clients": ["wheel"]}):
ret = client.run(low)

assert "tag" in ret
assert "data" in ret
Expand All @@ -64,19 +107,53 @@ def test_wheel(client, auth_creds):
def test_wheel_async(client, auth_creds):
low = {"client": "wheel_async", "fun": "key.list_all", **auth_creds}

ret = client.run(low)
with patch.dict(client.opts, {"netapi_enable_clients": ["wheel_async"]}):
ret = client.run(low)
assert "jid" in ret
assert "tag" in ret


def test_wheel_unauthenticated(client):
low = {"client": "wheel", "tgt": "*", "fun": "test.ping"}

with pytest.raises(EauthAuthenticationError):
client.run(low)
with patch.dict(client.opts, {"netapi_enable_clients": ["wheel"]}):
with pytest.raises(EauthAuthenticationError):
client.run(low)


def test_wheel_disabled(client, auth_creds):
low = {"client": "wheel", "tgt": "*", "fun": "test.ping", **auth_creds}

ret = None
with pytest.raises(SaltInvocationError):
ret = client.run(low)

assert ret is None


def test_wheel_async_disabled(client, auth_creds):
low = {"client": "wheel_async", "tgt": "*", "fun": "test.ping", **auth_creds}

ret = None
with pytest.raises(SaltInvocationError):
ret = client.run(low)

assert ret is None


def test_runner_unauthenticated(client):
low = {"client": "runner", "tgt": "*", "fun": "test.ping"}
with pytest.raises(EauthAuthenticationError):
client.run(low)

with patch.dict(client.opts, {"netapi_enable_clients": ["runner"]}):
with pytest.raises(EauthAuthenticationError):
client.run(low)


def test_runner_disabled(client, auth_creds):
low = {"client": "runner", "tgt": "*", "fun": "test.ping", **auth_creds}

ret = None
with pytest.raises(SaltInvocationError):
ret = client.run(low)

assert ret is None
20 changes: 19 additions & 1 deletion tests/pytests/integration/netapi/test_ssh_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

import salt.netapi
from salt.exceptions import EauthAuthenticationError
from salt.exceptions import EauthAuthenticationError, SaltInvocationError
from tests.support.helpers import SaveRequestsPostHandler, Webserver
from tests.support.mock import patch

Expand All @@ -11,6 +11,12 @@
]


@pytest.fixture
def client_config(client_config):
client_config["netapi_enable_clients"] = ["ssh"]
return client_config


@pytest.fixture
def client(client_config, salt_minion):
return salt.netapi.NetapiClient(client_config)
Expand Down Expand Up @@ -77,6 +83,7 @@ def test_ssh(client, auth_creds, salt_ssh_roster_file, rosters_dir, ssh_priv_key

def test_ssh_unauthenticated(client):
low = {"client": "ssh", "tgt": "localhost", "fun": "test.ping"}

with pytest.raises(EauthAuthenticationError):
client.run(low)

Expand Down Expand Up @@ -117,6 +124,17 @@ def test_ssh_authenticated_raw_shell_disabled(client, tmp_path):
assert badfile.exists() is False


def test_ssh_disabled(client, auth_creds):
low = {"client": "ssh", "tgt": "localhost", "fun": "test.ping", **auth_creds}

ret = None
with patch.dict(client.opts, {"netapi_enable_clients": []}):
with pytest.raises(SaltInvocationError):
ret = client.run(low)

assert ret is None


def test_shell_inject_ssh_priv(
client, salt_ssh_roster_file, rosters_dir, tmp_path, salt_auto_account
):
Expand Down