From 4fa4e2f77250a1f829d875a37f37289026f43850 Mon Sep 17 00:00:00 2001 From: Volodymyr Boiko Date: Fri, 28 Mar 2025 10:50:36 +0200 Subject: [PATCH] added API token based auth Change-Id: I2ea749bcbcf816cdbdd6b9df7d463ac3a758d080 --- .../drivers/vastdata_driver.rst | 1 + .../configuration/tables/manila-vastdata.inc | 2 + manila/share/drivers/vastdata/driver.py | 32 +++++++++++++--- manila/share/drivers/vastdata/rest.py | 37 ++++++++++++++++--- .../share/drivers/vastdata/test_driver.py | 12 +++++- .../tests/share/drivers/vastdata/test_rest.py | 25 +++++++++++-- ...api-token-based-auth-f6ee3fdce1ba6450.yaml | 6 +++ 7 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/vastdata-add-api-token-based-auth-f6ee3fdce1ba6450.yaml diff --git a/doc/source/configuration/shared-file-systems/drivers/vastdata_driver.rst b/doc/source/configuration/shared-file-systems/drivers/vastdata_driver.rst index 5aec382d26..95fffdcb44 100644 --- a/doc/source/configuration/shared-file-systems/drivers/vastdata_driver.rst +++ b/doc/source/configuration/shared-file-systems/drivers/vastdata_driver.rst @@ -69,6 +69,7 @@ other parameters that are not specific to VAST Share Driver. vast_mgmt_port = {vms_port} vast_mgmt_user = {mgmt_user} vast_mgmt_password = {mgmt_password} + vast_api_token = {vast_api_token} vast_vippool_name = {vip_pool} vast_root_export = {root_export} diff --git a/doc/source/configuration/tables/manila-vastdata.inc b/doc/source/configuration/tables/manila-vastdata.inc index 9ac4503717..02ffa56be9 100644 --- a/doc/source/configuration/tables/manila-vastdata.inc +++ b/doc/source/configuration/tables/manila-vastdata.inc @@ -30,3 +30,5 @@ - (String) Username for VAST management API. * - ``vast_mgmt_password`` = - (String) Password for VAST management API. + * - ``vast_api_token`` = + - (String) API token for accessing VAST mgmt. If provided, it will be used instead of 'vast_mgmt_user' and 'vast_mgmt_password'. diff --git a/manila/share/drivers/vastdata/driver.py b/manila/share/drivers/vastdata/driver.py index e115c2e456..99ab911116 100644 --- a/manila/share/drivers/vastdata/driver.py +++ b/manila/share/drivers/vastdata/driver.py @@ -79,6 +79,16 @@ help="Password for VAST management", secret=True ), + cfg.StrOpt( + "vast_api_token", + default="", + secret=True, + help=( + "API token for accessing VAST mgmt. " + "If provided, it will be used instead " + "of 'san_login' and 'san_password'." + ) + ), ] CONF = cfg.CONF @@ -116,19 +126,29 @@ def do_setup(self, context): username = self.configuration.safe_get("vast_mgmt_user") password = self.configuration.safe_get("vast_mgmt_password") + api_token = self.configuration.safe_get("vast_api_token") host = self.configuration.safe_get("vast_mgmt_host") port = self.configuration.safe_get("vast_mgmt_port") - if not all((username, password, port)): + if not host: + raise exception.VastDriverException( + reason="`vast_mgmt_host` must be set in manila.conf." + ) + # Require either (username & password) OR (API token) + if not ((username and password) or api_token): raise exception.VastDriverException( - reason="Not all required parameters are present." - " Make sure you specified `vast_mgmt_host`," - " `vast_mgmt_port`, and `vast_mgmt_user` " - "in manila.conf." + reason="Authentication failed: You must specify either " + "`vast_mgmt_user` and `vast_mgmt_password`, " + "or provide `vast_api_token` in manila.conf." ) if port: host = f"{host}:{port}" self.rest = vast_rest.RestApi( - host, username, password, False, self.VERSION + host=host, + username=username, + password=password, + api_token=api_token, + ssl_verify=False, + plugin_version=self.VERSION, ) LOG.debug("VAST Data driver setup is complete.") diff --git a/manila/share/drivers/vastdata/rest.py b/manila/share/drivers/vastdata/rest.py index 0b46f170a4..f70db98e0b 100644 --- a/manila/share/drivers/vastdata/rest.py +++ b/manila/share/drivers/vastdata/rest.py @@ -32,20 +32,37 @@ class Session(requests.Session): - def __init__(self, host, username, password, ssl_verify, plugin_version): + def __init__( + self, + host, + username, + password, + api_token, + ssl_verify, + plugin_version, + ): super().__init__() self.base_url = f"https://{host.strip('/')}/api" self.ssl_verify = ssl_verify self.username = username self.password = password + self.token = api_token self.headers["Accept"] = "application/json" self.headers["Content-Type"] = "application/json" self.headers["User-Agent"] = ( f"manila/v{plugin_version}" f" ({requests.utils.default_user_agent()})" ) - # will be updated on first request - self.headers["authorization"] = "Bearer" + if self.token: + LOG.info("VMS session is using API token authentication.") + self.headers["authorization"] = f"Api-Token {self.token}" + else: + # Will be updated on the first request + LOG.info( + "VMS session is using username/password authentication" + " (Bearer token will be acquired)." + ) + self.headers["authorization"] = "Bearer" if not ssl_verify: import urllib3 @@ -95,7 +112,8 @@ def request( ret = super().request( verb, url, verify=self.ssl_verify, params=params, **kwargs ) - if ret.status_code == 403 and "Token is invalid" in ret.text: + # No refresh for token based auth. Token should be long-lived. + if ret.status_code == 403 and not self.token: self.refresh_auth_token() raise exception.VastApiRetry(reason="Token is invalid or expired.") @@ -307,11 +325,20 @@ def delete(self, path): class RestApi: - def __init__(self, host, username, password, ssl_verify, plugin_version): + def __init__( + self, + host, + username, + password, + api_token, + ssl_verify, + plugin_version, + ): self.session = Session( host=host, username=username, password=password, + api_token=api_token, ssl_verify=ssl_verify, plugin_version=plugin_version, ) diff --git a/manila/tests/share/drivers/vastdata/test_driver.py b/manila/tests/share/drivers/vastdata/test_driver.py index b406da125b..e923a0f52a 100644 --- a/manila/tests/share/drivers/vastdata/test_driver.py +++ b/manila/tests/share/drivers/vastdata/test_driver.py @@ -94,7 +94,7 @@ def test_do_setup(self): self.assertFalse(session.ssl_verify) self.assertEqual(session.base_url, "https://test:443/api") - @ddt.data("vast_mgmt_user", "vast_vippool_name") + @ddt.data("vast_mgmt_user", "vast_vippool_name", "vast_mgmt_host") def test_do_setup_missing_required_fields(self, missing_field): self.fake_conf.set_default(missing_field, None) _driver = driver.VASTShareDriver( @@ -103,6 +103,16 @@ def test_do_setup_missing_required_fields(self, missing_field): with self.assertRaises(exception.VastDriverException): _driver.do_setup(self._context) + def test_do_setup_with_api_token(self): + self.fake_conf.set_default("vast_mgmt_user", None) + self.fake_conf.set_default("vast_mgmt_password", None) + self.fake_conf.set_default("vast_api_token", "test_token") + _driver = driver.VASTShareDriver( + execute=mock.MagicMock(), configuration=self.fake_conf + ) + _driver.do_setup(self._context) + self.assertEqual(_driver.rest.session.token, "test_token") + @mock.patch( "manila.share.drivers.vastdata.rest.Session.get", mock.MagicMock(return_value=fake_metrics), diff --git a/manila/tests/share/drivers/vastdata/test_rest.py b/manila/tests/share/drivers/vastdata/test_rest.py index f76d3db9c0..5235023966 100644 --- a/manila/tests/share/drivers/vastdata/test_rest.py +++ b/manila/tests/share/drivers/vastdata/test_rest.py @@ -101,8 +101,12 @@ class TestSession(unittest.TestCase): def setUp(self): self.session = vast_rest.Session( - "host", "username", - "password", False, "1.0" + "host", + "username", + "password", + "", + False, + "1.0", ) @mock.patch("requests.Session.request") @@ -274,6 +278,7 @@ def test_view_create(self): "host", "username", "password", + "", True, "1.0" ) @@ -324,6 +329,7 @@ def test_capacity_metrics(self): "host", "username", "password", + "", True, "1.0" ) @@ -344,7 +350,12 @@ class TestFolders(unittest.TestCase): ) def setUp(self): self.rest_api = vast_rest.RestApi( - "host", "username", "password", True, "1.0" + "host", + "username", + "password", + "", + True, + "1.0", ) @ddt.data( @@ -446,6 +457,7 @@ def setUp(self): "host", "username", "password", + "", True, "1.0" ) @@ -506,7 +518,12 @@ def test_get_sw_version(self, mock_session): mock.MagicMock(sys_version="1.0") ] rest_api = vast_rest.RestApi( - "host", "username", "password", True, "1.0" + "host", + "username", + "password", + "", + True, + "1.0", ) version = rest_api.get_sw_version() self.assertEqual(version, "1.0") diff --git a/releasenotes/notes/vastdata-add-api-token-based-auth-f6ee3fdce1ba6450.yaml b/releasenotes/notes/vastdata-add-api-token-based-auth-f6ee3fdce1ba6450.yaml new file mode 100644 index 0000000000..ff8c90278d --- /dev/null +++ b/releasenotes/notes/vastdata-add-api-token-based-auth-f6ee3fdce1ba6450.yaml @@ -0,0 +1,6 @@ +--- +features: + - Added support for authentication using an API token in the VAST Manila driver. + Introduced the `vast_api_token` configuration option, allowing users to + authenticate with a pre-generated API token instead of using `vast_mgmt_user` + and `vast_mgmt_password`.