From ea416866bb0bb1994a061b1c248184dcdf82bde1 Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Mon, 10 Nov 2025 09:41:44 +0100 Subject: [PATCH 01/13] Add: Credential stores support --- gvm/protocols/gmp/requests/next/__init__.py | 5 +- .../gmp/requests/next/_credential_stores.py | 126 +++++ .../gmp/requests/next/_credentials.py | 469 ++++++++++++++++++ 3 files changed, 598 insertions(+), 2 deletions(-) create mode 100644 gvm/protocols/gmp/requests/next/_credential_stores.py create mode 100644 gvm/protocols/gmp/requests/next/_credentials.py diff --git a/gvm/protocols/gmp/requests/next/__init__.py b/gvm/protocols/gmp/requests/next/__init__.py index 109199a8..f7691bc3 100644 --- a/gvm/protocols/gmp/requests/next/__init__.py +++ b/gvm/protocols/gmp/requests/next/__init__.py @@ -5,6 +5,8 @@ from gvm.protocols.gmp.requests.next._agent_groups import AgentGroups from gvm.protocols.gmp.requests.next._agent_installers import AgentInstallers from gvm.protocols.gmp.requests.next._agents import Agents +from gvm.protocols.gmp.requests.next._credentials import Credentials, CredentialType +from gvm.protocols.gmp.requests.next._credential_stores import CredentialStores from gvm.protocols.gmp.requests.next._oci_image_targets import OCIImageTargets from gvm.protocols.gmp.requests.next._tasks import Tasks @@ -24,8 +26,6 @@ CertBundAdvisories, Cpes, CredentialFormat, - Credentials, - CredentialType, Cves, DfnCertAdvisories, EntityType, @@ -98,6 +98,7 @@ "Credentials", "CredentialFormat", "CredentialType", + "CredentialStores", "Cves", "DfnCertAdvisories", "EntityID", diff --git a/gvm/protocols/gmp/requests/next/_credential_stores.py b/gvm/protocols/gmp/requests/next/_credential_stores.py new file mode 100644 index 00000000..d47f565b --- /dev/null +++ b/gvm/protocols/gmp/requests/next/_credential_stores.py @@ -0,0 +1,126 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import Optional + +from gvm.errors import RequiredArgument +from gvm.protocols.core import Request +from gvm.utils import to_bool +from gvm.xml import XmlCommand + +from .._entity_id import EntityID + + +class CredentialStores: + @classmethod + def get_credential_stores(cls, + *, + credential_store_id: Optional[EntityID] = None, + filter_string: Optional[str] = None, + filter_id: Optional[EntityID] = None, + details: Optional[bool] = None, + ) -> Request: + """Request a list of credential stores + + Args: + credential_store_id: ID of credential store to fetch + filter_string: Filter term to use for the query + filter_id: UUID of an existing filter to use for the query + details: Whether to exclude results + """ + + cmd = XmlCommand("get_credential_stores") + cmd.add_filter(filter_string, filter_id) + + if details: + cmd.set_attribute("details", to_bool(details)) + + if credential_store_id: + cmd.add_element("credential_store_id", credential_store_id) + + return cmd + + @classmethod + def modify_credential_store(cls, + credential_store_id: EntityID, + *, + active: Optional[bool] = None, + host: Optional[str] = None, + port: Optional[int] = None, + path: Optional[str] = None, + app_id: Optional[str] = None, + client_cert: Optional[str] = None, + client_key: Optional[str] = None, + client_pkcs12_file: Optional[str] = None, + passphrase: Optional[str] = None, + server_ca_cert: Optional[str] = None, + comment: Optional[str] = None, + ) -> Request: + """Modify a credential store + + Args: + credential_store_id: ID of credential store to fetch + active: Whether the credential store is active + host: The host to use for reaching the credential store + port: The port to use for reaching the credential store + path: The URI path the credential store is using + app_id: Depends on the credential store used. Usually called the same in the credential store + client_cert: The client certificate to use for authorization, as a plain string + client_key: The client key to use for authorization, as a plain string + client_pkcs12_file: The pkcs12 file contents to use for authorization, as a plain string + (alternative to using client_cert and client_key) + passphrase: The passphrase to use to decrypt client_pkcs12_file or client_key file + server_ca_cert: The server certificate, so the credential store can be trusted + comment: An optional comment to store alongside the credential store + """ + + cmd = XmlCommand("modify_credential_store") + cmd.set_attribute("credential_store_id", credential_store_id) + + if active: + cmd.add_element("active", to_bool(active)) + if host: + cmd.add_element("host", host) + if port: + cmd.add_element("port", str(port)) + if path: + cmd.add_element("path", path) + if comment: + cmd.add_element("comment", comment) + + preferences = cmd.add_element("preferences") + + if app_id: + preferences.add_element("app_id", app_id) + if client_cert: + preferences.add_element("client_cert", client_cert) + if client_key: + preferences.add_element("client_key", client_key) + if client_pkcs12_file: + preferences.add_element("client_pkcs12_file", client_pkcs12_file) + if passphrase: + preferences.add_element("passphrase", passphrase) + if server_ca_cert: + preferences.add_element("server_ca_cert", server_ca_cert) + + return cmd + + @classmethod + def verify_credential_store(cls, + credential_store_id: EntityID, + ) -> Request: + """Verify that the connection to a credential store works + + Args: + credential_store_id: The uuid of the credential store to verify + """ + if not credential_store_id: + raise RequiredArgument( + function=cls.verify_credential_store.__name__, + argument="credential_store_id", + ) + + cmd = XmlCommand("verify_credential_store") + cmd.add_element("credential_store_id", credential_store_id) + return cmd diff --git a/gvm/protocols/gmp/requests/next/_credentials.py b/gvm/protocols/gmp/requests/next/_credentials.py new file mode 100644 index 00000000..aec3d1fe --- /dev/null +++ b/gvm/protocols/gmp/requests/next/_credentials.py @@ -0,0 +1,469 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import Optional, Union, override + +from gvm._enum import Enum +from gvm.errors import RequiredArgument +from gvm.protocols.core import Request +from gvm.utils import to_bool +from gvm.xml import XmlCommand + +from .._entity_id import EntityID + +from ..v224._credentials import Credentials as CredentialsV224, SnmpAuthAlgorithm, \ + SnmpPrivacyAlgorithm + + +class CredentialType(Enum): + """Enum for credential types""" + + CLIENT_CERTIFICATE = "cc" + SNMP = "snmp" + USERNAME_PASSWORD = "up" + USERNAME_SSH_KEY = "usk" + SMIME_CERTIFICATE = "smime" + PGP_ENCRYPTION_KEY = "pgp" + PASSWORD_ONLY = "pw" + + CREDENTIAL_STORE_CLIENT_CERTIFICATE = "cs_cc" + CREDENTIAL_STORE_SNMP = "cs_snmp" + CREDENTIAL_STORE_USERNAME_PASSWORD = "cs_up" + CREDENTIAL_STORE_USERNAME_SSH_KEY = "cs_usk" + CREDENTIAL_STORE_SMIME_CERTIFICATE = "cs_smime" + CREDENTIAL_STORE_PGP_ENCRYPTION_KEY = "cs_pgp" + CREDENTIAL_STORE_PASSWORD_ONLY = "cs_pw" + + +class Credentials(CredentialsV224): + @override + @classmethod + def create_credential( + cls, + name: str, + credential_type: Union[CredentialType, str], + *, + comment: Optional[str] = None, + allow_insecure: Optional[bool] = None, + certificate: Optional[str] = None, + key_phrase: Optional[str] = None, + private_key: Optional[str] = None, + login: Optional[str] = None, + password: Optional[str] = None, + credential_store_id: Optional[EntityID] = None, + vault_id: Optional[str] = None, + host_identifier: Optional[str] = None, + auth_algorithm: Optional[Union[SnmpAuthAlgorithm, str]] = None, + community: Optional[str] = None, + privacy_algorithm: Optional[Union[SnmpPrivacyAlgorithm, str]] = None, + privacy_password: Optional[str] = None, + public_key: Optional[str] = None, + ) -> Request: + """Create a new credential + + Create a new credential e.g. to be used in the method of an alert. + + Currently the following credential types are supported: + + - Username + Password + - Username + SSH-Key + - Client Certificates + - SNMPv1 or SNMPv2c protocol + - S/MIME Certificate + - OpenPGP Key + - Password only + + Arguments: + name: Name of the new credential + credential_type: The credential type. + comment: Comment for the credential + allow_insecure: Whether to allow insecure use of the credential + certificate: Certificate for the credential. + Required for client-certificate and smime credential types. + key_phrase: Key passphrase for the private key. + Used for the username+ssh-key credential type. + private_key: Private key to use for login. Required + for usk credential type. Also used for the cc credential type. + The supported key types (dsa, rsa, ecdsa, ...) and formats (PEM, + PKC#12, OpenSSL, ...) depend on your installed GnuTLS version. + login: Username for the credential. Required for username+password, + username+ssh-key and snmp credential type. + password: Password for the credential. Used for username+password + and snmp credential types. + credential_store_id: The id of the credential store to use + (defaults to '94e74cbe-0504-4ab1-b96f-0739f786f57c') + vault_id: Vault-ID used to access the secret in credential store + host_identifier: Host-Identifier used to access the secret in credential store + community: The SNMP community + auth_algorithm: The SNMP authentication algorithm. Required for snmp + credential type. + privacy_algorithm: The SNMP privacy algorithm + privacy_password: The SNMP privacy password + public_key: PGP public key in *armor* plain text format. Required + for pgp credential type. + + Examples: + Creating a Username + Password credential + + .. code-block:: python + + request = Credentials.create_credential( + name='UP Credential', + credential_type=CredentialType.USERNAME_PASSWORD, + login='foo', + password='bar', + ) + + Creating a Username + SSH Key credential + + .. code-block:: python + + with open('path/to/private-ssh-key') as f: + key = f.read() + + request = Credentials.create_credential( + name='USK Credential', + credential_type=CredentialType.USERNAME_SSH_KEY, + login='foo', + key_phrase='foobar', + private_key=key, + ) + + Creating a PGP credential + + .. note:: + + A compatible public pgp key file can be exported with GnuPG via + :: + + $ gpg --armor --export alice@cyb.org > alice.asc + + .. code-block:: python + + with open('path/to/pgp.key.asc') as f: + key = f.read() + + request = Credentials.create_credential( + name='PGP Credential', + credential_type=CredentialType.PGP_ENCRYPTION_KEY, + public_key=key, + ) + + Creating a S/MIME credential + + .. code-block:: python + + with open('path/to/smime-cert') as f: + cert = f.read() + + request = Credentials.create_credential( + name='SMIME Credential', + credential_type=CredentialType.SMIME_CERTIFICATE, + certificate=cert, + ) + + Creating a Password-Only credential + + .. code-block:: python + + request = Credentials.create_credential( + name='Password-Only Credential', + credential_type=CredentialType.PASSWORD_ONLY, + password='foo', + ) + + Creating an auto-generated password + + .. code-block:: python + + request = Credentials.create_credential( + name='UP Credential', + credential_type=CredentialType.USERNAME_PASSWORD, + login='foo', + ) + + Creating an auto-generated SSH-Key credential + + .. code-block:: python + + request = Credentials.create_credential( + name='USK Credential', + credential_type=CredentialType.USERNAME_SSH_KEY, + login='foo', + ) + + Creating a Password-Only credential stored in a Credential Store + + .. code-block:: python + + request = Credentials.create_credential( + name='Credential-Store Password-Only Credential', + credential_type=CredentialType.CREDENTIAL_STOREPASSWORD_ONLY, + vault_id='a5f84dd4-da18-447c-a9fb-b77b5df49076', + host_identifier='/My/Secret', + ) + """ + if not name: + raise RequiredArgument( + function=cls.create_credential.__name__, argument="name" + ) + + if not credential_type: + raise RequiredArgument( + function=cls.create_credential.__name__, + argument="credential_type", + ) + + if not isinstance(credential_type, CredentialType): + credential_type = CredentialType(credential_type) + + cmd = XmlCommand("create_credential") + cmd.add_element("name", name) + + cmd.add_element("type", credential_type.value) + + if comment: + cmd.add_element("comment", comment) + + if allow_insecure is not None: + cmd.add_element("allow_insecure", to_bool(allow_insecure)) + + if ( + credential_type == CredentialType.CLIENT_CERTIFICATE + or credential_type == CredentialType.SMIME_CERTIFICATE + ): + if not certificate: + raise RequiredArgument( + function=cls.create_credential.__name__, + argument="certificate", + ) + + cmd.add_element("certificate", certificate) + + if ( + credential_type == CredentialType.USERNAME_PASSWORD + or credential_type == CredentialType.USERNAME_SSH_KEY + or credential_type == CredentialType.SNMP + ): + if not login: + raise RequiredArgument( + function=cls.create_credential.__name__, argument="login" + ) + + cmd.add_element("login", login) + + if credential_type == CredentialType.PASSWORD_ONLY and not password: + raise RequiredArgument( + function=cls.create_credential.__name__, argument="password" + ) + + if ( + credential_type == CredentialType.USERNAME_PASSWORD + or credential_type == CredentialType.SNMP + or credential_type == CredentialType.PASSWORD_ONLY + ) and password: + cmd.add_element("password", password) + + if ( + credential_type == CredentialType.USERNAME_SSH_KEY + and private_key is not None + ): + if not private_key: + raise RequiredArgument( + function=cls.create_credential.__name__, + argument="private_key", + ) + + xml_key = cmd.add_element("key") + xml_key.add_element("private", private_key) + + if key_phrase: + xml_key.add_element("phrase", key_phrase) + + if credential_type == CredentialType.CLIENT_CERTIFICATE and private_key: + xml_key = cmd.add_element("key") + xml_key.add_element("private", private_key) + + if credential_type == CredentialType.SNMP: + if not auth_algorithm: + raise RequiredArgument( + function=cls.create_credential.__name__, + argument="auth_algorithm", + ) + if not isinstance(auth_algorithm, SnmpAuthAlgorithm): + auth_algorithm = SnmpAuthAlgorithm(auth_algorithm) + + cmd.add_element("auth_algorithm", auth_algorithm.value) + + if community: + cmd.add_element("community", community) + + if privacy_algorithm is not None or privacy_password: + xml_privacy = cmd.add_element("privacy") + + if privacy_algorithm is not None: + if not isinstance(privacy_algorithm, SnmpPrivacyAlgorithm): + privacy_algorithm = SnmpPrivacyAlgorithm( + privacy_algorithm + ) + + xml_privacy.add_element( + "algorithm", privacy_algorithm.value + ) + + if privacy_password: + xml_privacy.add_element("password", privacy_password) + + if credential_type == CredentialType.PGP_ENCRYPTION_KEY: + if not public_key: + raise RequiredArgument( + function=cls.create_credential.__name__, + argument="public_key", + ) + + xml_key = cmd.add_element("key") + xml_key.add_element("public", public_key) + + if credential_type == CredentialType.CREDENTIAL_STORE_CLIENT_CERTIFICATE \ + or credential_type == CredentialType.CREDENTIAL_STORE_SNMP \ + or credential_type == CredentialType.CREDENTIAL_STORE_USERNAME_PASSWORD \ + or credential_type == CredentialType.CREDENTIAL_STORE_USERNAME_SSH_KEY \ + or credential_type == CredentialType.CREDENTIAL_STORE_SMIME_CERTIFICATE \ + or credential_type == CredentialType.CREDENTIAL_STORE_PGP_ENCRYPTION_KEY \ + or credential_type == CredentialType.CREDENTIAL_STORE_PASSWORD_ONLY: + if not vault_id: + raise RequiredArgument( + function=cls.create_credential.__name__, + argument="vault_id", + ) + if not host_identifier: + raise RequiredArgument( + function=cls.create_credential.__name__, + argument="host_identifier", + ) + + if credential_store_id: + cmd.add_element("credential_store_id", credential_store_id) + + cmd.add_element("vault_id", vault_id) + cmd.add_element("host_identifier", host_identifier) + + return cmd + + @override + @classmethod + def modify_credential( + cls, + credential_id: EntityID, + *, + name: Optional[str] = None, + comment: Optional[str] = None, + allow_insecure: Optional[bool] = None, + certificate: Optional[str] = None, + key_phrase: Optional[str] = None, + private_key: Optional[str] = None, + login: Optional[str] = None, + password: Optional[str] = None, + credential_store_id: Optional[EntityID] = None, + vault_id: Optional[str] = None, + host_identifier: Optional[str] = None, + auth_algorithm: Optional[Union[SnmpAuthAlgorithm, str]] = None, + community: Optional[str] = None, + privacy_algorithm: Optional[Union[SnmpPrivacyAlgorithm, str]] = None, + privacy_password: Optional[str] = None, + public_key: Optional[str] = None, + ) -> Request: + """Modifies an existing credential. + + Arguments: + credential_id: UUID of the credential + name: Name of the credential + comment: Comment for the credential + allow_insecure: Whether to allow insecure use of the credential + certificate: Certificate for the credential + key_phrase: Key passphrase for the private key + private_key: Private key to use for login + login: Username for the credential + password: Password for the credential + credential_store_id: The id of the credential store to use + (defaults to '94e74cbe-0504-4ab1-b96f-0739f786f57c') + vault_id: Vault-ID used to access the secret in credential store + host_identifier: Host-Identifier used to access the secret in credential store + auth_algorithm: The authentication algorithm for SNMP + community: The SNMP community + privacy_algorithm: The privacy algorithm for SNMP + privacy_password: The SNMP privacy password + public_key: PGP public key in *armor* plain text format + """ + if not credential_id: + raise RequiredArgument( + function=cls.modify_credential.__name__, + argument="credential_id", + ) + + cmd = XmlCommand("modify_credential") + cmd.set_attribute("credential_id", str(credential_id)) + + if comment: + cmd.add_element("comment", comment) + + if name: + cmd.add_element("name", name) + + if allow_insecure is not None: + cmd.add_element("allow_insecure", to_bool(allow_insecure)) + + if certificate: + cmd.add_element("certificate", certificate) + + if key_phrase and private_key: + xml_key = cmd.add_element("key") + xml_key.add_element("phrase", key_phrase) + xml_key.add_element("private", private_key) + elif (not key_phrase and private_key) or ( + key_phrase and not private_key + ): + raise RequiredArgument( + function=cls.modify_credential.__name__, + argument="key_phrase and private_key", + ) + + if login: + cmd.add_element("login", login) + + if password: + cmd.add_element("password", password) + + if auth_algorithm: + if not isinstance(auth_algorithm, SnmpAuthAlgorithm): + auth_algorithm = SnmpAuthAlgorithm(auth_algorithm) + + cmd.add_element("auth_algorithm", auth_algorithm.value) + + if community: + cmd.add_element("community", community) + + if privacy_algorithm is not None or privacy_password is not None: + xml_privacy = cmd.add_element("privacy") + + if privacy_algorithm is not None: + if not isinstance(privacy_algorithm, SnmpPrivacyAlgorithm): + privacy_algorithm = SnmpPrivacyAlgorithm(privacy_algorithm) + xml_privacy.add_element("algorithm", privacy_algorithm.value) + + if privacy_password is not None: + xml_privacy.add_element("password", privacy_password) + + if public_key: + xml_key = cmd.add_element("key") + xml_key.add_element("public", public_key) + + if credential_store_id: + cmd.add_element("credential_store_id", credential_store_id) + if vault_id: + cmd.add_element("vault_id", vault_id) + if host_identifier: + cmd.add_element("host_identifier", host_identifier) + + return cmd From 2c14d8e877e0a7f85680916cad314785c9271eec Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Mon, 10 Nov 2025 10:03:54 +0100 Subject: [PATCH 02/13] fixed formatting and linter errors --- gvm/protocols/gmp/requests/next/__init__.py | 5 +- .../gmp/requests/next/_credential_stores.py | 59 ++++---- .../gmp/requests/next/_credentials.py | 134 ++++++++++-------- 3 files changed, 106 insertions(+), 92 deletions(-) diff --git a/gvm/protocols/gmp/requests/next/__init__.py b/gvm/protocols/gmp/requests/next/__init__.py index f7691bc3..f4132589 100644 --- a/gvm/protocols/gmp/requests/next/__init__.py +++ b/gvm/protocols/gmp/requests/next/__init__.py @@ -5,7 +5,10 @@ from gvm.protocols.gmp.requests.next._agent_groups import AgentGroups from gvm.protocols.gmp.requests.next._agent_installers import AgentInstallers from gvm.protocols.gmp.requests.next._agents import Agents -from gvm.protocols.gmp.requests.next._credentials import Credentials, CredentialType +from gvm.protocols.gmp.requests.next._credentials import ( + Credentials, + CredentialType, +) from gvm.protocols.gmp.requests.next._credential_stores import CredentialStores from gvm.protocols.gmp.requests.next._oci_image_targets import OCIImageTargets from gvm.protocols.gmp.requests.next._tasks import Tasks diff --git a/gvm/protocols/gmp/requests/next/_credential_stores.py b/gvm/protocols/gmp/requests/next/_credential_stores.py index d47f565b..ffa4d1f0 100644 --- a/gvm/protocols/gmp/requests/next/_credential_stores.py +++ b/gvm/protocols/gmp/requests/next/_credential_stores.py @@ -14,13 +14,14 @@ class CredentialStores: @classmethod - def get_credential_stores(cls, - *, - credential_store_id: Optional[EntityID] = None, - filter_string: Optional[str] = None, - filter_id: Optional[EntityID] = None, - details: Optional[bool] = None, - ) -> Request: + def get_credential_stores( + cls, + *, + credential_store_id: Optional[EntityID] = None, + filter_string: Optional[str] = None, + filter_id: Optional[EntityID] = None, + details: Optional[bool] = None, + ) -> Request: """Request a list of credential stores Args: @@ -37,26 +38,27 @@ def get_credential_stores(cls, cmd.set_attribute("details", to_bool(details)) if credential_store_id: - cmd.add_element("credential_store_id", credential_store_id) + cmd.add_element("credential_store_id", str(credential_store_id)) return cmd @classmethod - def modify_credential_store(cls, - credential_store_id: EntityID, - *, - active: Optional[bool] = None, - host: Optional[str] = None, - port: Optional[int] = None, - path: Optional[str] = None, - app_id: Optional[str] = None, - client_cert: Optional[str] = None, - client_key: Optional[str] = None, - client_pkcs12_file: Optional[str] = None, - passphrase: Optional[str] = None, - server_ca_cert: Optional[str] = None, - comment: Optional[str] = None, - ) -> Request: + def modify_credential_store( + cls, + credential_store_id: EntityID, + *, + active: Optional[bool] = None, + host: Optional[str] = None, + port: Optional[int] = None, + path: Optional[str] = None, + app_id: Optional[str] = None, + client_cert: Optional[str] = None, + client_key: Optional[str] = None, + client_pkcs12_file: Optional[str] = None, + passphrase: Optional[str] = None, + server_ca_cert: Optional[str] = None, + comment: Optional[str] = None, + ) -> Request: """Modify a credential store Args: @@ -76,7 +78,7 @@ def modify_credential_store(cls, """ cmd = XmlCommand("modify_credential_store") - cmd.set_attribute("credential_store_id", credential_store_id) + cmd.set_attribute("credential_store_id", str(credential_store_id)) if active: cmd.add_element("active", to_bool(active)) @@ -107,9 +109,10 @@ def modify_credential_store(cls, return cmd @classmethod - def verify_credential_store(cls, - credential_store_id: EntityID, - ) -> Request: + def verify_credential_store( + cls, + credential_store_id: EntityID, + ) -> Request: """Verify that the connection to a credential store works Args: @@ -122,5 +125,5 @@ def verify_credential_store(cls, ) cmd = XmlCommand("verify_credential_store") - cmd.add_element("credential_store_id", credential_store_id) + cmd.add_element("credential_store_id", str(credential_store_id)) return cmd diff --git a/gvm/protocols/gmp/requests/next/_credentials.py b/gvm/protocols/gmp/requests/next/_credentials.py index aec3d1fe..fa044502 100644 --- a/gvm/protocols/gmp/requests/next/_credentials.py +++ b/gvm/protocols/gmp/requests/next/_credentials.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Optional, Union, override +from typing import Optional, Union from gvm._enum import Enum from gvm.errors import RequiredArgument @@ -12,8 +12,11 @@ from .._entity_id import EntityID -from ..v224._credentials import Credentials as CredentialsV224, SnmpAuthAlgorithm, \ - SnmpPrivacyAlgorithm +from ..v224._credentials import ( + Credentials as CredentialsV224, + SnmpAuthAlgorithm, + SnmpPrivacyAlgorithm, +) class CredentialType(Enum): @@ -37,28 +40,27 @@ class CredentialType(Enum): class Credentials(CredentialsV224): - @override @classmethod def create_credential( - cls, - name: str, - credential_type: Union[CredentialType, str], - *, - comment: Optional[str] = None, - allow_insecure: Optional[bool] = None, - certificate: Optional[str] = None, - key_phrase: Optional[str] = None, - private_key: Optional[str] = None, - login: Optional[str] = None, - password: Optional[str] = None, - credential_store_id: Optional[EntityID] = None, - vault_id: Optional[str] = None, - host_identifier: Optional[str] = None, - auth_algorithm: Optional[Union[SnmpAuthAlgorithm, str]] = None, - community: Optional[str] = None, - privacy_algorithm: Optional[Union[SnmpPrivacyAlgorithm, str]] = None, - privacy_password: Optional[str] = None, - public_key: Optional[str] = None, + cls, + name: str, + credential_type: Union[CredentialType, str], + *, + comment: Optional[str] = None, + allow_insecure: Optional[bool] = None, + certificate: Optional[str] = None, + key_phrase: Optional[str] = None, + private_key: Optional[str] = None, + login: Optional[str] = None, + password: Optional[str] = None, + credential_store_id: Optional[EntityID] = None, + vault_id: Optional[str] = None, + host_identifier: Optional[str] = None, + auth_algorithm: Optional[Union[SnmpAuthAlgorithm, str]] = None, + community: Optional[str] = None, + privacy_algorithm: Optional[Union[SnmpPrivacyAlgorithm, str]] = None, + privacy_password: Optional[str] = None, + public_key: Optional[str] = None, ) -> Request: """Create a new credential @@ -230,8 +232,8 @@ def create_credential( cmd.add_element("allow_insecure", to_bool(allow_insecure)) if ( - credential_type == CredentialType.CLIENT_CERTIFICATE - or credential_type == CredentialType.SMIME_CERTIFICATE + credential_type == CredentialType.CLIENT_CERTIFICATE + or credential_type == CredentialType.SMIME_CERTIFICATE ): if not certificate: raise RequiredArgument( @@ -242,9 +244,9 @@ def create_credential( cmd.add_element("certificate", certificate) if ( - credential_type == CredentialType.USERNAME_PASSWORD - or credential_type == CredentialType.USERNAME_SSH_KEY - or credential_type == CredentialType.SNMP + credential_type == CredentialType.USERNAME_PASSWORD + or credential_type == CredentialType.USERNAME_SSH_KEY + or credential_type == CredentialType.SNMP ): if not login: raise RequiredArgument( @@ -259,15 +261,15 @@ def create_credential( ) if ( - credential_type == CredentialType.USERNAME_PASSWORD - or credential_type == CredentialType.SNMP - or credential_type == CredentialType.PASSWORD_ONLY + credential_type == CredentialType.USERNAME_PASSWORD + or credential_type == CredentialType.SNMP + or credential_type == CredentialType.PASSWORD_ONLY ) and password: cmd.add_element("password", password) if ( - credential_type == CredentialType.USERNAME_SSH_KEY - and private_key is not None + credential_type == CredentialType.USERNAME_SSH_KEY + and private_key is not None ): if not private_key: raise RequiredArgument( @@ -325,13 +327,20 @@ def create_credential( xml_key = cmd.add_element("key") xml_key.add_element("public", public_key) - if credential_type == CredentialType.CREDENTIAL_STORE_CLIENT_CERTIFICATE \ - or credential_type == CredentialType.CREDENTIAL_STORE_SNMP \ - or credential_type == CredentialType.CREDENTIAL_STORE_USERNAME_PASSWORD \ - or credential_type == CredentialType.CREDENTIAL_STORE_USERNAME_SSH_KEY \ - or credential_type == CredentialType.CREDENTIAL_STORE_SMIME_CERTIFICATE \ - or credential_type == CredentialType.CREDENTIAL_STORE_PGP_ENCRYPTION_KEY \ - or credential_type == CredentialType.CREDENTIAL_STORE_PASSWORD_ONLY: + if ( + credential_type + == CredentialType.CREDENTIAL_STORE_CLIENT_CERTIFICATE + or credential_type == CredentialType.CREDENTIAL_STORE_SNMP + or credential_type + == CredentialType.CREDENTIAL_STORE_USERNAME_PASSWORD + or credential_type + == CredentialType.CREDENTIAL_STORE_USERNAME_SSH_KEY + or credential_type + == CredentialType.CREDENTIAL_STORE_SMIME_CERTIFICATE + or credential_type + == CredentialType.CREDENTIAL_STORE_PGP_ENCRYPTION_KEY + or credential_type == CredentialType.CREDENTIAL_STORE_PASSWORD_ONLY + ): if not vault_id: raise RequiredArgument( function=cls.create_credential.__name__, @@ -344,35 +353,34 @@ def create_credential( ) if credential_store_id: - cmd.add_element("credential_store_id", credential_store_id) + cmd.add_element("credential_store_id", str(credential_store_id)) cmd.add_element("vault_id", vault_id) cmd.add_element("host_identifier", host_identifier) return cmd - @override @classmethod def modify_credential( - cls, - credential_id: EntityID, - *, - name: Optional[str] = None, - comment: Optional[str] = None, - allow_insecure: Optional[bool] = None, - certificate: Optional[str] = None, - key_phrase: Optional[str] = None, - private_key: Optional[str] = None, - login: Optional[str] = None, - password: Optional[str] = None, - credential_store_id: Optional[EntityID] = None, - vault_id: Optional[str] = None, - host_identifier: Optional[str] = None, - auth_algorithm: Optional[Union[SnmpAuthAlgorithm, str]] = None, - community: Optional[str] = None, - privacy_algorithm: Optional[Union[SnmpPrivacyAlgorithm, str]] = None, - privacy_password: Optional[str] = None, - public_key: Optional[str] = None, + cls, + credential_id: EntityID, + *, + name: Optional[str] = None, + comment: Optional[str] = None, + allow_insecure: Optional[bool] = None, + certificate: Optional[str] = None, + key_phrase: Optional[str] = None, + private_key: Optional[str] = None, + login: Optional[str] = None, + password: Optional[str] = None, + credential_store_id: Optional[EntityID] = None, + vault_id: Optional[str] = None, + host_identifier: Optional[str] = None, + auth_algorithm: Optional[Union[SnmpAuthAlgorithm, str]] = None, + community: Optional[str] = None, + privacy_algorithm: Optional[Union[SnmpPrivacyAlgorithm, str]] = None, + privacy_password: Optional[str] = None, + public_key: Optional[str] = None, ) -> Request: """Modifies an existing credential. @@ -422,7 +430,7 @@ def modify_credential( xml_key.add_element("phrase", key_phrase) xml_key.add_element("private", private_key) elif (not key_phrase and private_key) or ( - key_phrase and not private_key + key_phrase and not private_key ): raise RequiredArgument( function=cls.modify_credential.__name__, @@ -460,7 +468,7 @@ def modify_credential( xml_key.add_element("public", public_key) if credential_store_id: - cmd.add_element("credential_store_id", credential_store_id) + cmd.add_element("credential_store_id", str(credential_store_id)) if vault_id: cmd.add_element("vault_id", vault_id) if host_identifier: From a1fef2ec196aaaa5737e04537023ee9b9d224b42 Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Mon, 10 Nov 2025 10:14:32 +0100 Subject: [PATCH 03/13] had to remove inhertiance due to argument type change in `create_credential` --- gvm/protocols/gmp/requests/next/__init__.py | 2 +- .../gmp/requests/next/_credentials.py | 117 +++++++++++++++++- 2 files changed, 115 insertions(+), 4 deletions(-) diff --git a/gvm/protocols/gmp/requests/next/__init__.py b/gvm/protocols/gmp/requests/next/__init__.py index f4132589..2ffd2d47 100644 --- a/gvm/protocols/gmp/requests/next/__init__.py +++ b/gvm/protocols/gmp/requests/next/__init__.py @@ -5,11 +5,11 @@ from gvm.protocols.gmp.requests.next._agent_groups import AgentGroups from gvm.protocols.gmp.requests.next._agent_installers import AgentInstallers from gvm.protocols.gmp.requests.next._agents import Agents +from gvm.protocols.gmp.requests.next._credential_stores import CredentialStores from gvm.protocols.gmp.requests.next._credentials import ( Credentials, CredentialType, ) -from gvm.protocols.gmp.requests.next._credential_stores import CredentialStores from gvm.protocols.gmp.requests.next._oci_image_targets import OCIImageTargets from gvm.protocols.gmp.requests.next._tasks import Tasks diff --git a/gvm/protocols/gmp/requests/next/_credentials.py b/gvm/protocols/gmp/requests/next/_credentials.py index fa044502..014d87aa 100644 --- a/gvm/protocols/gmp/requests/next/_credentials.py +++ b/gvm/protocols/gmp/requests/next/_credentials.py @@ -11,9 +11,8 @@ from gvm.xml import XmlCommand from .._entity_id import EntityID - from ..v224._credentials import ( - Credentials as CredentialsV224, + CredentialFormat, SnmpAuthAlgorithm, SnmpPrivacyAlgorithm, ) @@ -39,7 +38,24 @@ class CredentialType(Enum): CREDENTIAL_STORE_PASSWORD_ONLY = "cs_pw" -class Credentials(CredentialsV224): +class Credentials: + @classmethod + def clone_credential(cls, credential_id: EntityID) -> Request: + """Clone a credential + + Args: + credential_id: The ID of the credential to clone + """ + if not credential_id: + raise RequiredArgument( + function=cls.clone_credential.__name__, + argument="credential_id", + ) + + cmd = XmlCommand("create_credential") + cmd.add_element("copy", str(credential_id)) + return cmd + @classmethod def create_credential( cls, @@ -360,6 +376,101 @@ def create_credential( return cmd + @classmethod + def delete_credential( + cls, credential_id: EntityID, *, ultimate: Optional[bool] = False + ) -> Request: + """Delete a credential + + Args: + credential_id: The ID of the credential to delete + ultimate: Whether to remove entirely, or to the trashcan. + """ + if not credential_id: + raise RequiredArgument( + function=cls.delete_credential.__name__, + argument="credential_id", + ) + + cmd = XmlCommand("delete_credential") + cmd.set_attribute("credential_id", str(credential_id)) + cmd.set_attribute("ultimate", to_bool(ultimate)) + return cmd + + @staticmethod + def get_credentials( + *, + filter_string: Optional[str] = None, + filter_id: Optional[EntityID] = None, + scanners: Optional[bool] = None, + trash: Optional[bool] = None, + targets: Optional[bool] = None, + ) -> Request: + """Request a list of credentials + + Arguments: + filter_string: Filter term to use for the query + filter_id: UUID of an existing filter to use for the query + scanners: Whether to include a list of scanners using the + credentials + trash: Whether to get the trashcan credentials instead + targets: Whether to include a list of targets using the credentials + """ + cmd = XmlCommand("get_credentials") + + cmd.add_filter(filter_string, filter_id) + + if scanners is not None: + cmd.set_attribute("scanners", to_bool(scanners)) + + if trash is not None: + cmd.set_attribute("trash", to_bool(trash)) + + if targets is not None: + cmd.set_attribute("targets", to_bool(targets)) + + return cmd + + @classmethod + def get_credential( + cls, + credential_id: EntityID, + *, + scanners: Optional[bool] = None, + targets: Optional[bool] = None, + credential_format: Optional[Union[CredentialFormat, str]] = None, + ) -> Request: + """Request a single credential + + Arguments: + credential_id: UUID of an existing credential + scanners: Whether to include a list of scanners using the + credentials + targets: Whether to include a list of targets using the credentials + credential_format: One of "key", "rpm", "deb", "exe" or "pem" + """ + if not credential_id: + raise RequiredArgument( + function=cls.get_credential.__name__, argument="credential_id" + ) + + cmd = XmlCommand("get_credentials") + cmd.set_attribute("credential_id", str(credential_id)) + + if credential_format: + if not isinstance(credential_format, CredentialFormat): + credential_format = CredentialFormat(credential_format) + + cmd.set_attribute("format", credential_format.value) + + if scanners is not None: + cmd.set_attribute("scanners", to_bool(scanners)) + + if targets is not None: + cmd.set_attribute("targets", to_bool(targets)) + + return cmd + @classmethod def modify_credential( cls, From 1a21fb615bbd67991558d9bca34dd41d95e3ee76 Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Wed, 12 Nov 2025 18:18:33 +0100 Subject: [PATCH 04/13] refactor: credential store credentials got their own type enum and methods --- gvm/protocols/gmp/_gmpnext.py | 163 ++++++- gvm/protocols/gmp/requests/next/__init__.py | 4 +- .../gmp/requests/next/_credentials.py | 451 +----------------- 3 files changed, 188 insertions(+), 430 deletions(-) diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py index fae99d99..5cdad942 100644 --- a/gvm/protocols/gmp/_gmpnext.py +++ b/gvm/protocols/gmp/_gmpnext.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Any, Mapping, Optional, Sequence +from typing import Any, Mapping, Optional, Sequence, Union from gvm.protocols.gmp.requests import EntityID @@ -13,6 +13,9 @@ AgentGroups, AgentInstallers, Agents, + Credentials, + CredentialStoreCredentialType, + CredentialStores, OCIImageTargets, Tasks, ) @@ -297,6 +300,164 @@ def clone_agent_group( AgentGroups.clone_agent_group(agent_group_id) ) + def create_credential_store_credential( + self, + name: str, + credential_type: Union[CredentialStoreCredentialType, str], + *, + comment: Optional[str] = None, + allow_insecure: Optional[bool] = None, + credential_store_id: Optional[EntityID] = None, + vault_id: Optional[str] = None, + host_identifier: Optional[str] = None, + ) -> T: + """Create a new credential store type credential + + Args: + name: Name of the credential + credential_type: Type of the credential + comment: Optional comment for the credential object + allow_insecure: Whether to allow insecure usage of credential + credential_store_id: Optional credential store id to fetch the credential from + vault_id: Vault id used to fetch the credential from credential store + host_identifier: Host identifier used to fetch the credential from credential store + """ + return self._send_request_and_transform_response( + Credentials.create_credential_store_credential( + name=name, + credential_type=credential_type, + comment=comment, + allow_insecure=allow_insecure, + credential_store_id=credential_store_id, + vault_id=vault_id, + host_identifier=host_identifier, + ) + ) + + def modify_credential_store_credential( + self, + credential_id: EntityID, + *, + name: Optional[str] = None, + comment: Optional[str] = None, + allow_insecure: Optional[bool] = None, + credential_store_id: Optional[EntityID] = None, + vault_id: Optional[str] = None, + host_identifier: Optional[str] = None, + ) -> T: + """Modify an existing credential stored in a credential store + + Args: + credential_id: UUID of the credential to modify + name: Name of the credential + comment: Optional comment for the credential object + allow_insecure: Whether to allow insecure usage of credential + credential_store_id: Optional credential store id to fetch the credential from + vault_id: Vault id used to fetch the credential from credential store + host_identifier: Host identifier used to fetch the credential from credential store + """ + self._send_request_and_transform_response( + Credentials.modify_credential_store_credential( + credential_id=credential_id, + name=name, + comment=comment, + allow_insecure=allow_insecure, + credential_store_id=credential_store_id, + vault_id=vault_id, + host_identifier=host_identifier, + ) + ) + + def get_credential_stores( + self, + *, + credential_store_id: Optional[EntityID] = None, + filter_string: Optional[str] = None, + filter_id: Optional[EntityID] = None, + details: Optional[bool] = None, + ) -> T: + """Request a list of credential stores + + Args: + credential_store_id: ID of credential store to fetch + filter_string: Filter term to use for the query + filter_id: UUID of an existing filter to use for the query + details: Whether to exclude results + """ + return self._send_request_and_transform_response( + CredentialStores.get_credential_stores( + credential_store_id=credential_store_id, + filter_string=filter_string, + filter_id=filter_id, + details=details, + ) + ) + + def modify_credential_store( + self, + credential_store_id: EntityID, + *, + active: Optional[bool] = None, + host: Optional[str] = None, + port: Optional[int] = None, + path: Optional[str] = None, + app_id: Optional[str] = None, + client_cert: Optional[str] = None, + client_key: Optional[str] = None, + client_pkcs12_file: Optional[str] = None, + passphrase: Optional[str] = None, + server_ca_cert: Optional[str] = None, + comment: Optional[str] = None, + ) -> T: + """Modify an existing credential store + + Args: + credential_store_id: ID of credential store to fetch + active: Whether the credential store is active + host: The host to use for reaching the credential store + port: The port to use for reaching the credential store + path: The URI path the credential store is using + app_id: Depends on the credential store used. Usually called the same in the credential store + client_cert: The client certificate to use for authorization, as a plain string + client_key: The client key to use for authorization, as a plain string + client_pkcs12_file: The pkcs12 file contents to use for authorization, as a plain string + (alternative to using client_cert and client_key) + passphrase: The passphrase to use to decrypt client_pkcs12_file or client_key file + server_ca_cert: The server certificate, so the credential store can be trusted + comment: An optional comment to store alongside the credential store + """ + self._send_request_and_transform_response( + CredentialStores.modify_credential_store( + credential_store_id=credential_store_id, + active=active, + host=host, + port=port, + path=path, + app_id=app_id, + client_cert=client_cert, + client_key=client_key, + client_pkcs12_file=client_pkcs12_file, + passphrase=passphrase, + server_ca_cert=server_ca_cert, + comment=comment, + ) + ) + + def verify_credential_store( + self, + credential_store_id: EntityID, + ) -> T: + """Verify that the connection to an existing credential store works + + Args: + credential_store_id: The uuid of the credential store to verify + """ + self._send_request_and_transform_response( + CredentialStores.verify_credential_store( + credential_store_id=credential_store_id, + ) + ) + def create_oci_image_target( self, name: str, diff --git a/gvm/protocols/gmp/requests/next/__init__.py b/gvm/protocols/gmp/requests/next/__init__.py index 2ffd2d47..2ab0b671 100644 --- a/gvm/protocols/gmp/requests/next/__init__.py +++ b/gvm/protocols/gmp/requests/next/__init__.py @@ -8,7 +8,7 @@ from gvm.protocols.gmp.requests.next._credential_stores import CredentialStores from gvm.protocols.gmp.requests.next._credentials import ( Credentials, - CredentialType, + CredentialStoreCredentialType, ) from gvm.protocols.gmp.requests.next._oci_image_targets import OCIImageTargets from gvm.protocols.gmp.requests.next._tasks import Tasks @@ -100,7 +100,7 @@ "Cpes", "Credentials", "CredentialFormat", - "CredentialType", + "CredentialStoreCredentialType", "CredentialStores", "Cves", "DfnCertAdvisories", diff --git a/gvm/protocols/gmp/requests/next/_credentials.py b/gvm/protocols/gmp/requests/next/_credentials.py index 014d87aa..61187dde 100644 --- a/gvm/protocols/gmp/requests/next/_credentials.py +++ b/gvm/protocols/gmp/requests/next/_credentials.py @@ -12,73 +12,36 @@ from .._entity_id import EntityID from ..v224._credentials import ( - CredentialFormat, - SnmpAuthAlgorithm, - SnmpPrivacyAlgorithm, + Credentials as CredentialsV224, ) -class CredentialType(Enum): - """Enum for credential types""" +class CredentialStoreCredentialType(Enum): + """Enum for credential store credential types""" - CLIENT_CERTIFICATE = "cc" - SNMP = "snmp" - USERNAME_PASSWORD = "up" - USERNAME_SSH_KEY = "usk" - SMIME_CERTIFICATE = "smime" - PGP_ENCRYPTION_KEY = "pgp" - PASSWORD_ONLY = "pw" + CLIENT_CERTIFICATE = "cs_cc" + SNMP = "cs_snmp" + USERNAME_PASSWORD = "cs_up" + USERNAME_SSH_KEY = "cs_usk" + SMIME_CERTIFICATE = "cs_smime" + PGP_ENCRYPTION_KEY = "cs_pgp" + PASSWORD_ONLY = "cs_pw" - CREDENTIAL_STORE_CLIENT_CERTIFICATE = "cs_cc" - CREDENTIAL_STORE_SNMP = "cs_snmp" - CREDENTIAL_STORE_USERNAME_PASSWORD = "cs_up" - CREDENTIAL_STORE_USERNAME_SSH_KEY = "cs_usk" - CREDENTIAL_STORE_SMIME_CERTIFICATE = "cs_smime" - CREDENTIAL_STORE_PGP_ENCRYPTION_KEY = "cs_pgp" - CREDENTIAL_STORE_PASSWORD_ONLY = "cs_pw" - -class Credentials: +class Credentials(CredentialsV224): @classmethod - def clone_credential(cls, credential_id: EntityID) -> Request: - """Clone a credential - - Args: - credential_id: The ID of the credential to clone - """ - if not credential_id: - raise RequiredArgument( - function=cls.clone_credential.__name__, - argument="credential_id", - ) - - cmd = XmlCommand("create_credential") - cmd.add_element("copy", str(credential_id)) - return cmd - - @classmethod - def create_credential( + def create_credential_store_credential( cls, name: str, - credential_type: Union[CredentialType, str], + credential_type: Union[CredentialStoreCredentialType, str], *, comment: Optional[str] = None, allow_insecure: Optional[bool] = None, - certificate: Optional[str] = None, - key_phrase: Optional[str] = None, - private_key: Optional[str] = None, - login: Optional[str] = None, - password: Optional[str] = None, credential_store_id: Optional[EntityID] = None, vault_id: Optional[str] = None, host_identifier: Optional[str] = None, - auth_algorithm: Optional[Union[SnmpAuthAlgorithm, str]] = None, - community: Optional[str] = None, - privacy_algorithm: Optional[Union[SnmpPrivacyAlgorithm, str]] = None, - privacy_password: Optional[str] = None, - public_key: Optional[str] = None, ) -> Request: - """Create a new credential + """Create a new credential that is fetched from a credential store Create a new credential e.g. to be used in the method of an alert. @@ -97,120 +60,12 @@ def create_credential( credential_type: The credential type. comment: Comment for the credential allow_insecure: Whether to allow insecure use of the credential - certificate: Certificate for the credential. - Required for client-certificate and smime credential types. - key_phrase: Key passphrase for the private key. - Used for the username+ssh-key credential type. - private_key: Private key to use for login. Required - for usk credential type. Also used for the cc credential type. - The supported key types (dsa, rsa, ecdsa, ...) and formats (PEM, - PKC#12, OpenSSL, ...) depend on your installed GnuTLS version. - login: Username for the credential. Required for username+password, - username+ssh-key and snmp credential type. - password: Password for the credential. Used for username+password - and snmp credential types. credential_store_id: The id of the credential store to use (defaults to '94e74cbe-0504-4ab1-b96f-0739f786f57c') vault_id: Vault-ID used to access the secret in credential store host_identifier: Host-Identifier used to access the secret in credential store - community: The SNMP community - auth_algorithm: The SNMP authentication algorithm. Required for snmp - credential type. - privacy_algorithm: The SNMP privacy algorithm - privacy_password: The SNMP privacy password - public_key: PGP public key in *armor* plain text format. Required - for pgp credential type. Examples: - Creating a Username + Password credential - - .. code-block:: python - - request = Credentials.create_credential( - name='UP Credential', - credential_type=CredentialType.USERNAME_PASSWORD, - login='foo', - password='bar', - ) - - Creating a Username + SSH Key credential - - .. code-block:: python - - with open('path/to/private-ssh-key') as f: - key = f.read() - - request = Credentials.create_credential( - name='USK Credential', - credential_type=CredentialType.USERNAME_SSH_KEY, - login='foo', - key_phrase='foobar', - private_key=key, - ) - - Creating a PGP credential - - .. note:: - - A compatible public pgp key file can be exported with GnuPG via - :: - - $ gpg --armor --export alice@cyb.org > alice.asc - - .. code-block:: python - - with open('path/to/pgp.key.asc') as f: - key = f.read() - - request = Credentials.create_credential( - name='PGP Credential', - credential_type=CredentialType.PGP_ENCRYPTION_KEY, - public_key=key, - ) - - Creating a S/MIME credential - - .. code-block:: python - - with open('path/to/smime-cert') as f: - cert = f.read() - - request = Credentials.create_credential( - name='SMIME Credential', - credential_type=CredentialType.SMIME_CERTIFICATE, - certificate=cert, - ) - - Creating a Password-Only credential - - .. code-block:: python - - request = Credentials.create_credential( - name='Password-Only Credential', - credential_type=CredentialType.PASSWORD_ONLY, - password='foo', - ) - - Creating an auto-generated password - - .. code-block:: python - - request = Credentials.create_credential( - name='UP Credential', - credential_type=CredentialType.USERNAME_PASSWORD, - login='foo', - ) - - Creating an auto-generated SSH-Key credential - - .. code-block:: python - - request = Credentials.create_credential( - name='USK Credential', - credential_type=CredentialType.USERNAME_SSH_KEY, - login='foo', - ) - Creating a Password-Only credential stored in a Credential Store .. code-block:: python @@ -233,8 +88,8 @@ def create_credential( argument="credential_type", ) - if not isinstance(credential_type, CredentialType): - credential_type = CredentialType(credential_type) + if not isinstance(credential_type, CredentialStoreCredentialType): + credential_type = CredentialStoreCredentialType(credential_type) cmd = XmlCommand("create_credential") cmd.add_element("name", name) @@ -248,114 +103,16 @@ def create_credential( cmd.add_element("allow_insecure", to_bool(allow_insecure)) if ( - credential_type == CredentialType.CLIENT_CERTIFICATE - or credential_type == CredentialType.SMIME_CERTIFICATE - ): - if not certificate: - raise RequiredArgument( - function=cls.create_credential.__name__, - argument="certificate", - ) - - cmd.add_element("certificate", certificate) - - if ( - credential_type == CredentialType.USERNAME_PASSWORD - or credential_type == CredentialType.USERNAME_SSH_KEY - or credential_type == CredentialType.SNMP - ): - if not login: - raise RequiredArgument( - function=cls.create_credential.__name__, argument="login" - ) - - cmd.add_element("login", login) - - if credential_type == CredentialType.PASSWORD_ONLY and not password: - raise RequiredArgument( - function=cls.create_credential.__name__, argument="password" - ) - - if ( - credential_type == CredentialType.USERNAME_PASSWORD - or credential_type == CredentialType.SNMP - or credential_type == CredentialType.PASSWORD_ONLY - ) and password: - cmd.add_element("password", password) - - if ( - credential_type == CredentialType.USERNAME_SSH_KEY - and private_key is not None - ): - if not private_key: - raise RequiredArgument( - function=cls.create_credential.__name__, - argument="private_key", - ) - - xml_key = cmd.add_element("key") - xml_key.add_element("private", private_key) - - if key_phrase: - xml_key.add_element("phrase", key_phrase) - - if credential_type == CredentialType.CLIENT_CERTIFICATE and private_key: - xml_key = cmd.add_element("key") - xml_key.add_element("private", private_key) - - if credential_type == CredentialType.SNMP: - if not auth_algorithm: - raise RequiredArgument( - function=cls.create_credential.__name__, - argument="auth_algorithm", - ) - if not isinstance(auth_algorithm, SnmpAuthAlgorithm): - auth_algorithm = SnmpAuthAlgorithm(auth_algorithm) - - cmd.add_element("auth_algorithm", auth_algorithm.value) - - if community: - cmd.add_element("community", community) - - if privacy_algorithm is not None or privacy_password: - xml_privacy = cmd.add_element("privacy") - - if privacy_algorithm is not None: - if not isinstance(privacy_algorithm, SnmpPrivacyAlgorithm): - privacy_algorithm = SnmpPrivacyAlgorithm( - privacy_algorithm - ) - - xml_privacy.add_element( - "algorithm", privacy_algorithm.value - ) - - if privacy_password: - xml_privacy.add_element("password", privacy_password) - - if credential_type == CredentialType.PGP_ENCRYPTION_KEY: - if not public_key: - raise RequiredArgument( - function=cls.create_credential.__name__, - argument="public_key", - ) - - xml_key = cmd.add_element("key") - xml_key.add_element("public", public_key) - - if ( - credential_type - == CredentialType.CREDENTIAL_STORE_CLIENT_CERTIFICATE - or credential_type == CredentialType.CREDENTIAL_STORE_SNMP + credential_type == CredentialStoreCredentialType.CLIENT_CERTIFICATE + or credential_type == CredentialStoreCredentialType.SNMP or credential_type - == CredentialType.CREDENTIAL_STORE_USERNAME_PASSWORD + == CredentialStoreCredentialType.USERNAME_PASSWORD + or credential_type == CredentialStoreCredentialType.USERNAME_SSH_KEY or credential_type - == CredentialType.CREDENTIAL_STORE_USERNAME_SSH_KEY + == CredentialStoreCredentialType.SMIME_CERTIFICATE or credential_type - == CredentialType.CREDENTIAL_STORE_SMIME_CERTIFICATE - or credential_type - == CredentialType.CREDENTIAL_STORE_PGP_ENCRYPTION_KEY - or credential_type == CredentialType.CREDENTIAL_STORE_PASSWORD_ONLY + == CredentialStoreCredentialType.PGP_ENCRYPTION_KEY + or credential_type == CredentialStoreCredentialType.PASSWORD_ONLY ): if not vault_id: raise RequiredArgument( @@ -377,121 +134,16 @@ def create_credential( return cmd @classmethod - def delete_credential( - cls, credential_id: EntityID, *, ultimate: Optional[bool] = False - ) -> Request: - """Delete a credential - - Args: - credential_id: The ID of the credential to delete - ultimate: Whether to remove entirely, or to the trashcan. - """ - if not credential_id: - raise RequiredArgument( - function=cls.delete_credential.__name__, - argument="credential_id", - ) - - cmd = XmlCommand("delete_credential") - cmd.set_attribute("credential_id", str(credential_id)) - cmd.set_attribute("ultimate", to_bool(ultimate)) - return cmd - - @staticmethod - def get_credentials( - *, - filter_string: Optional[str] = None, - filter_id: Optional[EntityID] = None, - scanners: Optional[bool] = None, - trash: Optional[bool] = None, - targets: Optional[bool] = None, - ) -> Request: - """Request a list of credentials - - Arguments: - filter_string: Filter term to use for the query - filter_id: UUID of an existing filter to use for the query - scanners: Whether to include a list of scanners using the - credentials - trash: Whether to get the trashcan credentials instead - targets: Whether to include a list of targets using the credentials - """ - cmd = XmlCommand("get_credentials") - - cmd.add_filter(filter_string, filter_id) - - if scanners is not None: - cmd.set_attribute("scanners", to_bool(scanners)) - - if trash is not None: - cmd.set_attribute("trash", to_bool(trash)) - - if targets is not None: - cmd.set_attribute("targets", to_bool(targets)) - - return cmd - - @classmethod - def get_credential( - cls, - credential_id: EntityID, - *, - scanners: Optional[bool] = None, - targets: Optional[bool] = None, - credential_format: Optional[Union[CredentialFormat, str]] = None, - ) -> Request: - """Request a single credential - - Arguments: - credential_id: UUID of an existing credential - scanners: Whether to include a list of scanners using the - credentials - targets: Whether to include a list of targets using the credentials - credential_format: One of "key", "rpm", "deb", "exe" or "pem" - """ - if not credential_id: - raise RequiredArgument( - function=cls.get_credential.__name__, argument="credential_id" - ) - - cmd = XmlCommand("get_credentials") - cmd.set_attribute("credential_id", str(credential_id)) - - if credential_format: - if not isinstance(credential_format, CredentialFormat): - credential_format = CredentialFormat(credential_format) - - cmd.set_attribute("format", credential_format.value) - - if scanners is not None: - cmd.set_attribute("scanners", to_bool(scanners)) - - if targets is not None: - cmd.set_attribute("targets", to_bool(targets)) - - return cmd - - @classmethod - def modify_credential( + def modify_credential_store_credential( cls, credential_id: EntityID, *, name: Optional[str] = None, comment: Optional[str] = None, allow_insecure: Optional[bool] = None, - certificate: Optional[str] = None, - key_phrase: Optional[str] = None, - private_key: Optional[str] = None, - login: Optional[str] = None, - password: Optional[str] = None, credential_store_id: Optional[EntityID] = None, vault_id: Optional[str] = None, host_identifier: Optional[str] = None, - auth_algorithm: Optional[Union[SnmpAuthAlgorithm, str]] = None, - community: Optional[str] = None, - privacy_algorithm: Optional[Union[SnmpPrivacyAlgorithm, str]] = None, - privacy_password: Optional[str] = None, - public_key: Optional[str] = None, ) -> Request: """Modifies an existing credential. @@ -500,20 +152,10 @@ def modify_credential( name: Name of the credential comment: Comment for the credential allow_insecure: Whether to allow insecure use of the credential - certificate: Certificate for the credential - key_phrase: Key passphrase for the private key - private_key: Private key to use for login - login: Username for the credential - password: Password for the credential credential_store_id: The id of the credential store to use (defaults to '94e74cbe-0504-4ab1-b96f-0739f786f57c') vault_id: Vault-ID used to access the secret in credential store host_identifier: Host-Identifier used to access the secret in credential store - auth_algorithm: The authentication algorithm for SNMP - community: The SNMP community - privacy_algorithm: The privacy algorithm for SNMP - privacy_password: The SNMP privacy password - public_key: PGP public key in *armor* plain text format """ if not credential_id: raise RequiredArgument( @@ -533,51 +175,6 @@ def modify_credential( if allow_insecure is not None: cmd.add_element("allow_insecure", to_bool(allow_insecure)) - if certificate: - cmd.add_element("certificate", certificate) - - if key_phrase and private_key: - xml_key = cmd.add_element("key") - xml_key.add_element("phrase", key_phrase) - xml_key.add_element("private", private_key) - elif (not key_phrase and private_key) or ( - key_phrase and not private_key - ): - raise RequiredArgument( - function=cls.modify_credential.__name__, - argument="key_phrase and private_key", - ) - - if login: - cmd.add_element("login", login) - - if password: - cmd.add_element("password", password) - - if auth_algorithm: - if not isinstance(auth_algorithm, SnmpAuthAlgorithm): - auth_algorithm = SnmpAuthAlgorithm(auth_algorithm) - - cmd.add_element("auth_algorithm", auth_algorithm.value) - - if community: - cmd.add_element("community", community) - - if privacy_algorithm is not None or privacy_password is not None: - xml_privacy = cmd.add_element("privacy") - - if privacy_algorithm is not None: - if not isinstance(privacy_algorithm, SnmpPrivacyAlgorithm): - privacy_algorithm = SnmpPrivacyAlgorithm(privacy_algorithm) - xml_privacy.add_element("algorithm", privacy_algorithm.value) - - if privacy_password is not None: - xml_privacy.add_element("password", privacy_password) - - if public_key: - xml_key = cmd.add_element("key") - xml_key.add_element("public", public_key) - if credential_store_id: cmd.add_element("credential_store_id", str(credential_store_id)) if vault_id: From f1b01164d853635f5bfe75fbc0b1020392bb166e Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Wed, 12 Nov 2025 18:20:12 +0100 Subject: [PATCH 05/13] added missing return statements --- gvm/protocols/gmp/_gmpnext.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py index 5cdad942..c7288cd9 100644 --- a/gvm/protocols/gmp/_gmpnext.py +++ b/gvm/protocols/gmp/_gmpnext.py @@ -356,7 +356,7 @@ def modify_credential_store_credential( vault_id: Vault id used to fetch the credential from credential store host_identifier: Host identifier used to fetch the credential from credential store """ - self._send_request_and_transform_response( + return self._send_request_and_transform_response( Credentials.modify_credential_store_credential( credential_id=credential_id, name=name, @@ -426,7 +426,7 @@ def modify_credential_store( server_ca_cert: The server certificate, so the credential store can be trusted comment: An optional comment to store alongside the credential store """ - self._send_request_and_transform_response( + return self._send_request_and_transform_response( CredentialStores.modify_credential_store( credential_store_id=credential_store_id, active=active, @@ -452,7 +452,7 @@ def verify_credential_store( Args: credential_store_id: The uuid of the credential store to verify """ - self._send_request_and_transform_response( + return self._send_request_and_transform_response( CredentialStores.verify_credential_store( credential_store_id=credential_store_id, ) From 5952d7ef6417150387136fedbbac646b9234ef61 Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Wed, 12 Nov 2025 18:22:22 +0100 Subject: [PATCH 06/13] import 'CredentialType' from gmp224 --- gvm/protocols/gmp/requests/next/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gvm/protocols/gmp/requests/next/__init__.py b/gvm/protocols/gmp/requests/next/__init__.py index 2ab0b671..b6541fcf 100644 --- a/gvm/protocols/gmp/requests/next/__init__.py +++ b/gvm/protocols/gmp/requests/next/__init__.py @@ -29,6 +29,7 @@ CertBundAdvisories, Cpes, CredentialFormat, + CredentialType, Cves, DfnCertAdvisories, EntityType, @@ -100,6 +101,7 @@ "Cpes", "Credentials", "CredentialFormat", + "CredentialType", "CredentialStoreCredentialType", "CredentialStores", "Cves", From 4c852eff30ee5bb7591b3055899120c11087fc54 Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Wed, 12 Nov 2025 18:25:16 +0100 Subject: [PATCH 07/13] clarification in doc string --- gvm/protocols/gmp/requests/next/_credentials.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gvm/protocols/gmp/requests/next/_credentials.py b/gvm/protocols/gmp/requests/next/_credentials.py index 61187dde..9769efce 100644 --- a/gvm/protocols/gmp/requests/next/_credentials.py +++ b/gvm/protocols/gmp/requests/next/_credentials.py @@ -60,8 +60,8 @@ def create_credential_store_credential( credential_type: The credential type. comment: Comment for the credential allow_insecure: Whether to allow insecure use of the credential - credential_store_id: The id of the credential store to use - (defaults to '94e74cbe-0504-4ab1-b96f-0739f786f57c') + credential_store_id: Optional id of the credential store to use + (gvmd will pick default one if none is provided) vault_id: Vault-ID used to access the secret in credential store host_identifier: Host-Identifier used to access the secret in credential store @@ -152,8 +152,8 @@ def modify_credential_store_credential( name: Name of the credential comment: Comment for the credential allow_insecure: Whether to allow insecure use of the credential - credential_store_id: The id of the credential store to use - (defaults to '94e74cbe-0504-4ab1-b96f-0739f786f57c') + credential_store_id: Optional id of the credential store to use + (gvmd will pick default one if none is provided) vault_id: Vault-ID used to access the secret in credential store host_identifier: Host-Identifier used to access the secret in credential store """ From 08ca27b2ba09c25c9815a9cd35f99bffe4704641 Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Fri, 14 Nov 2025 09:13:24 +0100 Subject: [PATCH 08/13] base64 encode cert files before adding them to xml --- .../gmp/requests/next/_credential_stores.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/gvm/protocols/gmp/requests/next/_credential_stores.py b/gvm/protocols/gmp/requests/next/_credential_stores.py index ffa4d1f0..e441eb5d 100644 --- a/gvm/protocols/gmp/requests/next/_credential_stores.py +++ b/gvm/protocols/gmp/requests/next/_credential_stores.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later +from base64 import b64encode from typing import Optional from gvm.errors import RequiredArgument @@ -96,15 +97,27 @@ def modify_credential_store( if app_id: preferences.add_element("app_id", app_id) if client_cert: - preferences.add_element("client_cert", client_cert) + preferences.add_element( + "client_cert", + b64encode(client_cert.encode("ascii")).decode("ascii"), + ) if client_key: - preferences.add_element("client_key", client_key) + preferences.add_element( + "client_key", + b64encode(client_key.encode("ascii")).decode("ascii"), + ) if client_pkcs12_file: - preferences.add_element("client_pkcs12_file", client_pkcs12_file) + preferences.add_element( + "client_pkcs12_file", + b64encode(client_pkcs12_file.encode("ascii")).decode("ascii"), + ) if passphrase: preferences.add_element("passphrase", passphrase) if server_ca_cert: - preferences.add_element("server_ca_cert", server_ca_cert) + preferences.add_element( + "server_ca_cert", + b64encode(server_ca_cert.encode("ascii")).decode("ascii"), + ) return cmd From 2671b7980ab3d9ca835b24912ae239d42ffdd9ed Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Sun, 16 Nov 2025 19:59:47 +0100 Subject: [PATCH 09/13] added tests --- .../gmp/requests/next/_credential_stores.py | 10 +- .../entities/credential_stores/__init__.py | 13 + .../test_get_credential_stores.py | 34 +++ .../test_modify_credential_stores.py | 219 ++++++++++++++++ .../test_verify_credential_stores.py | 22 ++ .../gmpnext/entities/credentials/__init__.py | 15 ++ ...test_create_credential_store_credential.py | 242 ++++++++++++++++++ ...test_modify_credential_store_credential.py | 100 ++++++++ .../entities/test_credential_stores.py | 27 ++ .../gmpnext/entities/test_credentials.py | 18 +- 10 files changed, 695 insertions(+), 5 deletions(-) create mode 100644 tests/protocols/gmpnext/entities/credential_stores/__init__.py create mode 100644 tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py create mode 100644 tests/protocols/gmpnext/entities/credential_stores/test_modify_credential_stores.py create mode 100644 tests/protocols/gmpnext/entities/credential_stores/test_verify_credential_stores.py create mode 100644 tests/protocols/gmpnext/entities/credentials/__init__.py create mode 100644 tests/protocols/gmpnext/entities/credentials/test_create_credential_store_credential.py create mode 100644 tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py create mode 100644 tests/protocols/gmpnext/entities/test_credential_stores.py diff --git a/gvm/protocols/gmp/requests/next/_credential_stores.py b/gvm/protocols/gmp/requests/next/_credential_stores.py index e441eb5d..42808749 100644 --- a/gvm/protocols/gmp/requests/next/_credential_stores.py +++ b/gvm/protocols/gmp/requests/next/_credential_stores.py @@ -78,10 +78,16 @@ def modify_credential_store( comment: An optional comment to store alongside the credential store """ + if not credential_store_id: + raise RequiredArgument( + function=cls.verify_credential_store.__name__, + argument="credential_store_id", + ) + cmd = XmlCommand("modify_credential_store") cmd.set_attribute("credential_store_id", str(credential_store_id)) - if active: + if active is not None: cmd.add_element("active", to_bool(active)) if host: cmd.add_element("host", host) @@ -138,5 +144,5 @@ def verify_credential_store( ) cmd = XmlCommand("verify_credential_store") - cmd.add_element("credential_store_id", str(credential_store_id)) + cmd.set_attribute("credential_store_id", str(credential_store_id)) return cmd diff --git a/tests/protocols/gmpnext/entities/credential_stores/__init__.py b/tests/protocols/gmpnext/entities/credential_stores/__init__.py new file mode 100644 index 00000000..ca5fe222 --- /dev/null +++ b/tests/protocols/gmpnext/entities/credential_stores/__init__.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from .test_get_credential_stores import GmpGetCredentialStoresTestMixin +from .test_modify_credential_stores import GmpModifyCredentialStoreTestMixin +from .test_verify_credential_stores import GmpVerifyCredentialStoreTestMixin + +__all__ = ( + "GmpGetCredentialStoresTestMixin", + "GmpModifyCredentialStoreTestMixin", + "GmpVerifyCredentialStoreTestMixin", +) diff --git a/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py b/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py new file mode 100644 index 00000000..f8301e69 --- /dev/null +++ b/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + + +class GmpGetCredentialStoresTestMixin: + def test_get_credential_stores(self): + self.gmp.get_credential_stores() + + self.connection.send.has_been_called_with(b"") + + def test_get_credential_stores_with_filter_string(self): + self.gmp.get_credential_stores(filter_string="foo=bar") + + self.connection.send.has_been_called_with( + b'' + ) + + def test_get_credential_stores_with_filter_id(self): + self.gmp.get_credential_stores(filter_id="f1") + + self.connection.send.has_been_called_with( + b'' + ) + + def test_get_credential_stores_with_id(self): + self.gmp.get_credential_stores(credential_store_id="cs1") + + self.connection.send.has_been_called_with( + b"" + b"cs1" + b"" + ) diff --git a/tests/protocols/gmpnext/entities/credential_stores/test_modify_credential_stores.py b/tests/protocols/gmpnext/entities/credential_stores/test_modify_credential_stores.py new file mode 100644 index 00000000..0210b505 --- /dev/null +++ b/tests/protocols/gmpnext/entities/credential_stores/test_modify_credential_stores.py @@ -0,0 +1,219 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from gvm.errors import RequiredArgument + + +class GmpModifyCredentialStoreTestMixin: + def test_modify_credential_store(self): + self.gmp.modify_credential_store(credential_store_id="cs1") + + self.connection.send.has_been_called_with( + b'' + b"" + b"" + ) + + def test_modify_credential_store_without_id(self): + with self.assertRaises(RequiredArgument): + self.gmp.modify_credential_store(credential_store_id=None) + + with self.assertRaises(RequiredArgument): + self.gmp.modify_credential_store(credential_store_id="") + + def test_modify_credential_store_with_active(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + active=True, + ) + + self.connection.send.has_been_called_with( + b'' + b"1" + b"" + b"" + ) + + self.gmp.modify_credential_store( + credential_store_id="cs1", + active=False, + ) + + self.connection.send.has_been_called_with( + b'' + b"0" + b"" + b"" + ) + + def test_modify_credential_store_with_host(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + host="foo", + ) + + self.connection.send.has_been_called_with( + b'' + b"foo" + b"" + b"" + ) + + def test_modify_credential_store_with_port(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + port=1234, + ) + + self.connection.send.has_been_called_with( + b'' + b"1234" + b"" + b"" + ) + + def test_modify_credential_store_with_path(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + path="/foo/bar", + ) + + self.connection.send.has_been_called_with( + b'' + b"/foo/bar" + b"" + b"" + ) + + def test_modify_credential_store_with_comment(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + comment="ahoi", + ) + + self.connection.send.has_been_called_with( + b'' + b"ahoi" + b"" + b"" + ) + + def test_modify_credential_store_with_app_id(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + app_id="foo", + ) + + self.connection.send.has_been_called_with( + b'' + b"" + b"foo" + b"" + b"" + ) + + def test_modify_credential_store_with_client_cert(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + client_cert="foo", + ) + + self.connection.send.has_been_called_with( + b'' + b"" + b"Zm9v" + b"" + b"" + ) + + def test_modify_credential_store_with_client_key(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + client_key="foo", + ) + + self.connection.send.has_been_called_with( + b'' + b"" + b"Zm9v" + b"" + b"" + ) + + def test_modify_credential_store_with_client_pkcs12_file(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + client_pkcs12_file="foo", + ) + + self.connection.send.has_been_called_with( + b'' + b"" + b"Zm9v" + b"" + b"" + ) + + def test_modify_credential_store_with_passphrase(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + passphrase="foo", + ) + + self.connection.send.has_been_called_with( + b'' + b"" + b"foo" + b"" + b"" + ) + + def test_modify_credential_store_with_server_ca_cert(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + server_ca_cert="foo", + ) + + self.connection.send.has_been_called_with( + b'' + b"" + b"Zm9v" + b"" + b"" + ) + + def test_modify_credential_store_with_all(self): + self.gmp.modify_credential_store( + credential_store_id="cs1", + active=False, + host="localhost", + port="80", + path="/api", + comment="why was 6 afraid of 7? because 7 8 9", + app_id="appId", + client_cert="clientCert", + client_key="clientKey", + client_pkcs12_file="clientPkcs12File", + passphrase="secret", + server_ca_cert="serverCaCert", + ) + + self.connection.send.has_been_called_with( + b'' + b"0" + b"localhost" + b"80" + b"/api" + b"why was 6 afraid of 7? because 7 8 9" + b"" + b"appId" + b"Y2xpZW50Q2VydA==" + b"Y2xpZW50S2V5" + b"Y2xpZW50UGtjczEyRmlsZQ==" + b"secret" + b"c2VydmVyQ2FDZXJ0" + b"" + b"" + ) diff --git a/tests/protocols/gmpnext/entities/credential_stores/test_verify_credential_stores.py b/tests/protocols/gmpnext/entities/credential_stores/test_verify_credential_stores.py new file mode 100644 index 00000000..d1c3d298 --- /dev/null +++ b/tests/protocols/gmpnext/entities/credential_stores/test_verify_credential_stores.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from gvm.errors import RequiredArgument + + +class GmpVerifyCredentialStoreTestMixin: + def test_verify_credential_store(self): + self.gmp.verify_credential_store(credential_store_id="cs1") + + self.connection.send.has_been_called_with( + b'' + ) + + def test_verify_credential_store_without_id(self): + with self.assertRaises(RequiredArgument): + self.gmp.verify_credential_store(credential_store_id=None) + + with self.assertRaises(RequiredArgument): + self.gmp.verify_credential_store(credential_store_id="") diff --git a/tests/protocols/gmpnext/entities/credentials/__init__.py b/tests/protocols/gmpnext/entities/credentials/__init__.py new file mode 100644 index 00000000..4a49c76e --- /dev/null +++ b/tests/protocols/gmpnext/entities/credentials/__init__.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from .test_create_credential_store_credential import ( + GmpCreateCredentialStoreCredentialTestMixin, +) +from .test_modify_credential_store_credential import ( + GmpModifyCredentialStoreCredentialTestMixin, +) + +__all__ = ( + "GmpCreateCredentialStoreCredentialTestMixin", + "GmpModifyCredentialStoreCredentialTestMixin", +) diff --git a/tests/protocols/gmpnext/entities/credentials/test_create_credential_store_credential.py b/tests/protocols/gmpnext/entities/credentials/test_create_credential_store_credential.py new file mode 100644 index 00000000..d46a634f --- /dev/null +++ b/tests/protocols/gmpnext/entities/credentials/test_create_credential_store_credential.py @@ -0,0 +1,242 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from gvm.errors import InvalidArgument, RequiredArgument +from gvm.protocols.gmp.requests.next import CredentialStoreCredentialType + + +class GmpCreateCredentialStoreCredentialTestMixin: + def test_create_cs_up_credential_missing_name(self): + with self.assertRaises(RequiredArgument): + self.gmp.create_credential_store_credential( + name="", + credential_type=CredentialStoreCredentialType.USERNAME_PASSWORD, + vault_id="foo", + host_identifier="bar", + ) + + with self.assertRaises(RequiredArgument): + self.gmp.create_credential_store_credential( + name=None, + credential_type=CredentialStoreCredentialType.USERNAME_PASSWORD, + vault_id="foo", + host_identifier="bar", + ) + + def test_create_cs_up_credential(self): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.USERNAME_PASSWORD, + comment="bar", + vault_id="123", + host_identifier="456", + ) + + self.connection.send.has_been_called_with( + b"" + b"foo" + b"cs_up" + b"bar" + b"123" + b"456" + b"" + ) + + def test_create_cs_up_credential_with_credential_store_id(self): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.USERNAME_PASSWORD, + comment="bar", + credential_store_id="abc", + vault_id="123", + host_identifier="456", + ) + + self.connection.send.has_been_called_with( + b"" + b"foo" + b"cs_up" + b"bar" + b"abc" + b"123" + b"456" + b"" + ) + + def test_create_cs_up_credential_with_missing_vault_id(self): + with self.assertRaises(RequiredArgument): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.USERNAME_PASSWORD, + comment="bar", + host_identifier="456", + ) + + def test_create_cs_up_credential_with_missing_host_identifier(self): + with self.assertRaises(RequiredArgument): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.USERNAME_PASSWORD, + comment="bar", + vault_id="123", + ) + + def test_create_cs_up_credential_with_allow_insecure(self): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.USERNAME_PASSWORD, + comment="bar", + vault_id="123", + host_identifier="456", + allow_insecure=True, + ) + + self.connection.send.has_been_called_with( + b"" + b"foo" + b"cs_up" + b"bar" + b"1" + b"123" + b"456" + b"" + ) + + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.USERNAME_PASSWORD, + comment="bar", + vault_id="123", + host_identifier="456", + allow_insecure=False, + ) + + self.connection.send.has_been_called_with( + b"" + b"foo" + b"cs_up" + b"bar" + b"0" + b"123" + b"456" + b"" + ) + + def test_create_cs_cc_credential(self): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.CLIENT_CERTIFICATE, + vault_id="123", + host_identifier="456", + ) + + self.connection.send.has_been_called_with( + b"" + b"foo" + b"cs_cc" + b"123" + b"456" + b"" + ) + + def test_create_cs_usk_credential(self): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.USERNAME_SSH_KEY, + vault_id="123", + host_identifier="456", + ) + + self.connection.send.has_been_called_with( + b"" + b"foo" + b"cs_usk" + b"123" + b"456" + b"" + ) + + def test_create_cs_snmp_credential(self): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.SNMP, + vault_id="123", + host_identifier="456", + ) + + self.connection.send.has_been_called_with( + b"" + b"foo" + b"cs_snmp" + b"123" + b"456" + b"" + ) + + def test_create_cs_smime_credential(self): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.SMIME_CERTIFICATE, + vault_id="123", + host_identifier="456", + ) + + self.connection.send.has_been_called_with( + b"" + b"foo" + b"cs_smime" + b"123" + b"456" + b"" + ) + + def test_create_cs_pgp_credential(self): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.PGP_ENCRYPTION_KEY, + vault_id="123", + host_identifier="456", + ) + + self.connection.send.has_been_called_with( + b"" + b"foo" + b"cs_pgp" + b"123" + b"456" + b"" + ) + + def test_create_cs_credential_invalid_credential_type(self): + with self.assertRaises(RequiredArgument): + self.gmp.create_credential_store_credential( + name="foo", credential_type=None + ) + + with self.assertRaises(RequiredArgument): + self.gmp.create_credential_store_credential( + name="foo", credential_type="" + ) + + with self.assertRaises(InvalidArgument): + self.gmp.create_credential_store_credential( + name="foo", credential_type="bar" + ) + + def test_create_cs_pw_credential(self): + self.gmp.create_credential_store_credential( + name="foo", + credential_type=CredentialStoreCredentialType.PASSWORD_ONLY, + vault_id="123", + host_identifier="456", + ) + self.connection.send.has_been_called_with( + b"" + b"foo" + b"cs_pw" + b"123" + b"456" + b"" + ) diff --git a/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py b/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py new file mode 100644 index 00000000..d3d72b79 --- /dev/null +++ b/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from gvm.errors import RequiredArgument + + +class GmpModifyCredentialStoreCredentialTestMixin: + def test_modify_cs_credential(self): + self.gmp.modify_credential_store_credential(credential_id="c1") + + self.connection.send.has_been_called_with( + b'' + ) + + def test_modify_cs_credential_missing_credential_id(self): + with self.assertRaises(RequiredArgument): + self.gmp.modify_credential_store_credential(None) + + with self.assertRaises(RequiredArgument): + self.gmp.modify_credential_store_credential("") + + with self.assertRaises(RequiredArgument): + self.gmp.modify_credential_store_credential(credential_id="") + + def test_modify_cs_credential_with_name(self): + self.gmp.modify_credential_store_credential( + credential_id="c1", name="foo" + ) + + self.connection.send.has_been_called_with( + b'' + b"foo" + b"" + ) + + def test_modify_cs_credential_with_comment(self): + self.gmp.modify_credential_store_credential( + credential_id="c1", comment="foo" + ) + + self.connection.send.has_been_called_with( + b'' + b"foo" + b"" + ) + + def test_modify_cs_credential_with_credential_store_id(self): + self.gmp.modify_credential_store_credential( + credential_id="c1", credential_store_id="foo" + ) + + self.connection.send.has_been_called_with( + b'' + b"foo" + b"" + ) + + def test_modify_cs_credential_with_vault_id(self): + self.gmp.modify_credential_store_credential( + credential_id="c1", vault_id="foo" + ) + + self.connection.send.has_been_called_with( + b'' + b"foo" + b"" + ) + + def test_modify_cs_credential_with_host_identifier(self): + self.gmp.modify_credential_store_credential( + credential_id="c1", host_identifier="foo" + ) + + self.connection.send.has_been_called_with( + b'' + b"foo" + b"" + ) + + def test_modify_cs_credential_with_all(self): + self.gmp.modify_credential_store_credential( + credential_id="c1", + name="foo_name", + comment="foo_comment", + credential_store_id="foo_csid", + vault_id="foo_vid", + host_identifier="foo_hid", + ) + + self.connection.send.has_been_called_with( + b'' + b"foo_comment" + b"foo_name" + b"foo_csid" + b"foo_vid" + b"foo_hid" + b"" + ) diff --git a/tests/protocols/gmpnext/entities/test_credential_stores.py b/tests/protocols/gmpnext/entities/test_credential_stores.py new file mode 100644 index 00000000..1c3628e6 --- /dev/null +++ b/tests/protocols/gmpnext/entities/test_credential_stores.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from ...gmpnext import GMPTestCase +from ..entities.credential_stores import ( + GmpGetCredentialStoresTestMixin, + GmpModifyCredentialStoreTestMixin, + GmpVerifyCredentialStoreTestMixin, +) + + +class GMPGetCredentialStoresTest(GmpGetCredentialStoresTestMixin, GMPTestCase): + pass + + +class GMPModifyCredentialStoreTest( + GmpModifyCredentialStoreTestMixin, GMPTestCase +): + pass + + +class GMPVerifyCredentialStoreTest( + GmpVerifyCredentialStoreTestMixin, GMPTestCase +): + pass diff --git a/tests/protocols/gmpnext/entities/test_credentials.py b/tests/protocols/gmpnext/entities/test_credentials.py index 5843ed24..6fd67390 100644 --- a/tests/protocols/gmpnext/entities/test_credentials.py +++ b/tests/protocols/gmpnext/entities/test_credentials.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later # +from ...gmpnext import GMPTestCase from ...gmpv224.entities.credentials import ( GmpCloneCredentialTestMixin, GmpCreateCredentialTestMixin, @@ -11,14 +12,21 @@ GmpGetCredentialTestMixin, GmpModifyCredentialTestMixin, ) -from ...gmpv227 import GMPTestCase +from ..entities.credentials import ( + GmpCreateCredentialStoreCredentialTestMixin, + GmpModifyCredentialStoreCredentialTestMixin, +) class GMPCloneCredentialTestCase(GmpCloneCredentialTestMixin, GMPTestCase): pass -class GMPCreateCredentialTestCase(GmpCreateCredentialTestMixin, GMPTestCase): +class GMPCreateCredentialTestCase( + GmpCreateCredentialTestMixin, + GmpCreateCredentialStoreCredentialTestMixin, + GMPTestCase, +): pass @@ -34,5 +42,9 @@ class GMPGetCredentialsTestCase(GmpGetCredentialsTestMixin, GMPTestCase): pass -class GMPModifyCredentialTestCase(GmpModifyCredentialTestMixin, GMPTestCase): +class GMPModifyCredentialTestCase( + GmpModifyCredentialTestMixin, + GmpModifyCredentialStoreCredentialTestMixin, + GMPTestCase, +): pass From 9bf85331cbdc137ecc6546aa7d793633dd97345a Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Sun, 16 Nov 2025 20:10:18 +0100 Subject: [PATCH 10/13] fixed test coverage --- .../gmp/requests/next/_credential_stores.py | 2 +- .../gmp/requests/next/_credentials.py | 56 ++++++++++--------- .../test_get_credential_stores.py | 13 +++++ ...test_modify_credential_store_credential.py | 21 +++++++ 4 files changed, 66 insertions(+), 26 deletions(-) diff --git a/gvm/protocols/gmp/requests/next/_credential_stores.py b/gvm/protocols/gmp/requests/next/_credential_stores.py index 42808749..a266ef6a 100644 --- a/gvm/protocols/gmp/requests/next/_credential_stores.py +++ b/gvm/protocols/gmp/requests/next/_credential_stores.py @@ -35,7 +35,7 @@ def get_credential_stores( cmd = XmlCommand("get_credential_stores") cmd.add_filter(filter_string, filter_id) - if details: + if details is not None: cmd.set_attribute("details", to_bool(details)) if credential_store_id: diff --git a/gvm/protocols/gmp/requests/next/_credentials.py b/gvm/protocols/gmp/requests/next/_credentials.py index 9769efce..cceb9939 100644 --- a/gvm/protocols/gmp/requests/next/_credentials.py +++ b/gvm/protocols/gmp/requests/next/_credentials.py @@ -5,7 +5,7 @@ from typing import Optional, Union from gvm._enum import Enum -from gvm.errors import RequiredArgument +from gvm.errors import InvalidArgument, RequiredArgument from gvm.protocols.core import Request from gvm.utils import to_bool from gvm.xml import XmlCommand @@ -103,33 +103,39 @@ def create_credential_store_credential( cmd.add_element("allow_insecure", to_bool(allow_insecure)) if ( - credential_type == CredentialStoreCredentialType.CLIENT_CERTIFICATE - or credential_type == CredentialStoreCredentialType.SNMP - or credential_type - == CredentialStoreCredentialType.USERNAME_PASSWORD - or credential_type == CredentialStoreCredentialType.USERNAME_SSH_KEY - or credential_type - == CredentialStoreCredentialType.SMIME_CERTIFICATE - or credential_type - == CredentialStoreCredentialType.PGP_ENCRYPTION_KEY - or credential_type == CredentialStoreCredentialType.PASSWORD_ONLY + credential_type != CredentialStoreCredentialType.CLIENT_CERTIFICATE + and credential_type != CredentialStoreCredentialType.SNMP + and credential_type + != CredentialStoreCredentialType.USERNAME_PASSWORD + and credential_type + != CredentialStoreCredentialType.USERNAME_SSH_KEY + and credential_type + != CredentialStoreCredentialType.SMIME_CERTIFICATE + and credential_type + != CredentialStoreCredentialType.PGP_ENCRYPTION_KEY + and credential_type != CredentialStoreCredentialType.PASSWORD_ONLY ): - if not vault_id: - raise RequiredArgument( - function=cls.create_credential.__name__, - argument="vault_id", - ) - if not host_identifier: - raise RequiredArgument( - function=cls.create_credential.__name__, - argument="host_identifier", - ) + raise InvalidArgument( + function=cls.create_credential.__name__, + argument="credential_type", + ) + + if not vault_id: + raise RequiredArgument( + function=cls.create_credential.__name__, + argument="vault_id", + ) + if not host_identifier: + raise RequiredArgument( + function=cls.create_credential.__name__, + argument="host_identifier", + ) - if credential_store_id: - cmd.add_element("credential_store_id", str(credential_store_id)) + if credential_store_id: + cmd.add_element("credential_store_id", str(credential_store_id)) - cmd.add_element("vault_id", vault_id) - cmd.add_element("host_identifier", host_identifier) + cmd.add_element("vault_id", vault_id) + cmd.add_element("host_identifier", host_identifier) return cmd diff --git a/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py b/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py index f8301e69..5bb29d37 100644 --- a/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py +++ b/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py @@ -24,6 +24,19 @@ def test_get_credential_stores_with_filter_id(self): b'' ) + def test_get_credential_stores_with_details(self): + self.gmp.get_credential_stores(details=True) + + self.connection.send.has_been_called_with( + b'' + ) + + self.gmp.get_credential_stores(details=False) + + self.connection.send.has_been_called_with( + b'' + ) + def test_get_credential_stores_with_id(self): self.gmp.get_credential_stores(credential_store_id="cs1") diff --git a/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py b/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py index d3d72b79..1882cc13 100644 --- a/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py +++ b/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py @@ -46,6 +46,27 @@ def test_modify_cs_credential_with_comment(self): b"" ) + def test_modify_cs_credential_with_allow_insecure(self): + self.gmp.modify_credential_store_credential( + credential_id="c1", allow_insecure=True + ) + + self.connection.send.has_been_called_with( + b'' + b"1" + b"" + ) + + self.gmp.modify_credential_store_credential( + credential_id="c1", allow_insecure=False + ) + + self.connection.send.has_been_called_with( + b'' + b"0" + b"" + ) + def test_modify_cs_credential_with_credential_store_id(self): self.gmp.modify_credential_store_credential( credential_id="c1", credential_store_id="foo" From 43a876771a34b2f503ad055dd0b36b56441e20fc Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Thu, 27 Nov 2025 09:20:52 +0100 Subject: [PATCH 11/13] removed 'allow_insecure' --- gvm/protocols/gmp/_gmpnext.py | 6 --- .../gmp/requests/next/_credentials.py | 16 +------- ...test_create_credential_store_credential.py | 41 ------------------- ...test_modify_credential_store_credential.py | 23 +---------- 4 files changed, 3 insertions(+), 83 deletions(-) diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py index c7288cd9..4b00616f 100644 --- a/gvm/protocols/gmp/_gmpnext.py +++ b/gvm/protocols/gmp/_gmpnext.py @@ -306,7 +306,6 @@ def create_credential_store_credential( credential_type: Union[CredentialStoreCredentialType, str], *, comment: Optional[str] = None, - allow_insecure: Optional[bool] = None, credential_store_id: Optional[EntityID] = None, vault_id: Optional[str] = None, host_identifier: Optional[str] = None, @@ -317,7 +316,6 @@ def create_credential_store_credential( name: Name of the credential credential_type: Type of the credential comment: Optional comment for the credential object - allow_insecure: Whether to allow insecure usage of credential credential_store_id: Optional credential store id to fetch the credential from vault_id: Vault id used to fetch the credential from credential store host_identifier: Host identifier used to fetch the credential from credential store @@ -327,7 +325,6 @@ def create_credential_store_credential( name=name, credential_type=credential_type, comment=comment, - allow_insecure=allow_insecure, credential_store_id=credential_store_id, vault_id=vault_id, host_identifier=host_identifier, @@ -340,7 +337,6 @@ def modify_credential_store_credential( *, name: Optional[str] = None, comment: Optional[str] = None, - allow_insecure: Optional[bool] = None, credential_store_id: Optional[EntityID] = None, vault_id: Optional[str] = None, host_identifier: Optional[str] = None, @@ -351,7 +347,6 @@ def modify_credential_store_credential( credential_id: UUID of the credential to modify name: Name of the credential comment: Optional comment for the credential object - allow_insecure: Whether to allow insecure usage of credential credential_store_id: Optional credential store id to fetch the credential from vault_id: Vault id used to fetch the credential from credential store host_identifier: Host identifier used to fetch the credential from credential store @@ -361,7 +356,6 @@ def modify_credential_store_credential( credential_id=credential_id, name=name, comment=comment, - allow_insecure=allow_insecure, credential_store_id=credential_store_id, vault_id=vault_id, host_identifier=host_identifier, diff --git a/gvm/protocols/gmp/requests/next/_credentials.py b/gvm/protocols/gmp/requests/next/_credentials.py index cceb9939..a8e9d454 100644 --- a/gvm/protocols/gmp/requests/next/_credentials.py +++ b/gvm/protocols/gmp/requests/next/_credentials.py @@ -7,7 +7,6 @@ from gvm._enum import Enum from gvm.errors import InvalidArgument, RequiredArgument from gvm.protocols.core import Request -from gvm.utils import to_bool from gvm.xml import XmlCommand from .._entity_id import EntityID @@ -36,7 +35,6 @@ def create_credential_store_credential( credential_type: Union[CredentialStoreCredentialType, str], *, comment: Optional[str] = None, - allow_insecure: Optional[bool] = None, credential_store_id: Optional[EntityID] = None, vault_id: Optional[str] = None, host_identifier: Optional[str] = None, @@ -59,7 +57,6 @@ def create_credential_store_credential( name: Name of the new credential credential_type: The credential type. comment: Comment for the credential - allow_insecure: Whether to allow insecure use of the credential credential_store_id: Optional id of the credential store to use (gvmd will pick default one if none is provided) vault_id: Vault-ID used to access the secret in credential store @@ -99,9 +96,6 @@ def create_credential_store_credential( if comment: cmd.add_element("comment", comment) - if allow_insecure is not None: - cmd.add_element("allow_insecure", to_bool(allow_insecure)) - if ( credential_type != CredentialStoreCredentialType.CLIENT_CERTIFICATE and credential_type != CredentialStoreCredentialType.SNMP @@ -146,7 +140,6 @@ def modify_credential_store_credential( *, name: Optional[str] = None, comment: Optional[str] = None, - allow_insecure: Optional[bool] = None, credential_store_id: Optional[EntityID] = None, vault_id: Optional[str] = None, host_identifier: Optional[str] = None, @@ -157,7 +150,6 @@ def modify_credential_store_credential( credential_id: UUID of the credential name: Name of the credential comment: Comment for the credential - allow_insecure: Whether to allow insecure use of the credential credential_store_id: Optional id of the credential store to use (gvmd will pick default one if none is provided) vault_id: Vault-ID used to access the secret in credential store @@ -172,14 +164,10 @@ def modify_credential_store_credential( cmd = XmlCommand("modify_credential") cmd.set_attribute("credential_id", str(credential_id)) - if comment: - cmd.add_element("comment", comment) - if name: cmd.add_element("name", name) - - if allow_insecure is not None: - cmd.add_element("allow_insecure", to_bool(allow_insecure)) + if comment: + cmd.add_element("comment", comment) if credential_store_id: cmd.add_element("credential_store_id", str(credential_store_id)) diff --git a/tests/protocols/gmpnext/entities/credentials/test_create_credential_store_credential.py b/tests/protocols/gmpnext/entities/credentials/test_create_credential_store_credential.py index d46a634f..0a17c452 100644 --- a/tests/protocols/gmpnext/entities/credentials/test_create_credential_store_credential.py +++ b/tests/protocols/gmpnext/entities/credentials/test_create_credential_store_credential.py @@ -83,47 +83,6 @@ def test_create_cs_up_credential_with_missing_host_identifier(self): vault_id="123", ) - def test_create_cs_up_credential_with_allow_insecure(self): - self.gmp.create_credential_store_credential( - name="foo", - credential_type=CredentialStoreCredentialType.USERNAME_PASSWORD, - comment="bar", - vault_id="123", - host_identifier="456", - allow_insecure=True, - ) - - self.connection.send.has_been_called_with( - b"" - b"foo" - b"cs_up" - b"bar" - b"1" - b"123" - b"456" - b"" - ) - - self.gmp.create_credential_store_credential( - name="foo", - credential_type=CredentialStoreCredentialType.USERNAME_PASSWORD, - comment="bar", - vault_id="123", - host_identifier="456", - allow_insecure=False, - ) - - self.connection.send.has_been_called_with( - b"" - b"foo" - b"cs_up" - b"bar" - b"0" - b"123" - b"456" - b"" - ) - def test_create_cs_cc_credential(self): self.gmp.create_credential_store_credential( name="foo", diff --git a/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py b/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py index 1882cc13..b8dbc860 100644 --- a/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py +++ b/tests/protocols/gmpnext/entities/credentials/test_modify_credential_store_credential.py @@ -46,27 +46,6 @@ def test_modify_cs_credential_with_comment(self): b"" ) - def test_modify_cs_credential_with_allow_insecure(self): - self.gmp.modify_credential_store_credential( - credential_id="c1", allow_insecure=True - ) - - self.connection.send.has_been_called_with( - b'' - b"1" - b"" - ) - - self.gmp.modify_credential_store_credential( - credential_id="c1", allow_insecure=False - ) - - self.connection.send.has_been_called_with( - b'' - b"0" - b"" - ) - def test_modify_cs_credential_with_credential_store_id(self): self.gmp.modify_credential_store_credential( credential_id="c1", credential_store_id="foo" @@ -112,8 +91,8 @@ def test_modify_cs_credential_with_all(self): self.connection.send.has_been_called_with( b'' - b"foo_comment" b"foo_name" + b"foo_comment" b"foo_csid" b"foo_vid" b"foo_hid" From ec551126e3aa9fafa811a2480e074b4233f79fa6 Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Thu, 27 Nov 2025 09:53:25 +0100 Subject: [PATCH 12/13] added 'get_credential_store' --- gvm/protocols/gmp/_gmpnext.py | 18 ++++++++++--- .../gmp/requests/next/_credential_stores.py | 27 +++++++++++++++---- .../test_get_credential_stores.py | 27 ++++++++++++------- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py index 4b00616f..a831da24 100644 --- a/gvm/protocols/gmp/_gmpnext.py +++ b/gvm/protocols/gmp/_gmpnext.py @@ -362,10 +362,24 @@ def modify_credential_store_credential( ) ) + def get_credential_store( + self, + credential_store_id: EntityID, + ) -> T: + """Request a credential store + + Args: + credential_store_id: ID of credential store to fetch + """ + return self._send_request_and_transform_response( + CredentialStores.get_credential_store( + credential_store_id=credential_store_id, + ) + ) + def get_credential_stores( self, *, - credential_store_id: Optional[EntityID] = None, filter_string: Optional[str] = None, filter_id: Optional[EntityID] = None, details: Optional[bool] = None, @@ -373,14 +387,12 @@ def get_credential_stores( """Request a list of credential stores Args: - credential_store_id: ID of credential store to fetch filter_string: Filter term to use for the query filter_id: UUID of an existing filter to use for the query details: Whether to exclude results """ return self._send_request_and_transform_response( CredentialStores.get_credential_stores( - credential_store_id=credential_store_id, filter_string=filter_string, filter_id=filter_id, details=details, diff --git a/gvm/protocols/gmp/requests/next/_credential_stores.py b/gvm/protocols/gmp/requests/next/_credential_stores.py index a266ef6a..aea9cff6 100644 --- a/gvm/protocols/gmp/requests/next/_credential_stores.py +++ b/gvm/protocols/gmp/requests/next/_credential_stores.py @@ -14,11 +14,32 @@ class CredentialStores: + @classmethod + def get_credential_store( + cls, + credential_store_id: EntityID, + ) -> Request: + """Request a credential store + + Args: + credential_store_id: ID of credential store to fetch + """ + + if not credential_store_id: + raise RequiredArgument( + function=cls.get_credential_store.__name__, + argument="credential_store_id", + ) + + cmd = XmlCommand("get_credential_stores") + cmd.add_element("credential_store_id", str(credential_store_id)) + + return cmd + @classmethod def get_credential_stores( cls, *, - credential_store_id: Optional[EntityID] = None, filter_string: Optional[str] = None, filter_id: Optional[EntityID] = None, details: Optional[bool] = None, @@ -26,7 +47,6 @@ def get_credential_stores( """Request a list of credential stores Args: - credential_store_id: ID of credential store to fetch filter_string: Filter term to use for the query filter_id: UUID of an existing filter to use for the query details: Whether to exclude results @@ -38,9 +58,6 @@ def get_credential_stores( if details is not None: cmd.set_attribute("details", to_bool(details)) - if credential_store_id: - cmd.add_element("credential_store_id", str(credential_store_id)) - return cmd @classmethod diff --git a/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py b/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py index 5bb29d37..5a2c94d5 100644 --- a/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py +++ b/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py @@ -3,8 +3,26 @@ # SPDX-License-Identifier: GPL-3.0-or-later # +from gvm.errors import RequiredArgument + class GmpGetCredentialStoresTestMixin: + def test_get_credential_store(self): + self.gmp.get_credential_store(credential_store_id="cs1") + + self.connection.send.has_been_called_with( + b"" + b"cs1" + b"" + ) + + def test_get_credential_store_without_id(self): + with self.assertRaises(RequiredArgument): + self.gmp.get_credential_store(credential_store_id=None) + + with self.assertRaises(RequiredArgument): + self.gmp.get_credential_store(credential_store_id="") + def test_get_credential_stores(self): self.gmp.get_credential_stores() @@ -36,12 +54,3 @@ def test_get_credential_stores_with_details(self): self.connection.send.has_been_called_with( b'' ) - - def test_get_credential_stores_with_id(self): - self.gmp.get_credential_stores(credential_store_id="cs1") - - self.connection.send.has_been_called_with( - b"" - b"cs1" - b"" - ) From b8ce2978b6c22da3955136e8a27499aad2abec34 Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Thu, 27 Nov 2025 11:08:29 +0100 Subject: [PATCH 13/13] added details parameter to 'get_credential_store' --- gvm/protocols/gmp/_gmpnext.py | 6 +++++- .../gmp/requests/next/_credential_stores.py | 8 +++++++- .../test_get_credential_stores.py | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py index a831da24..3ec405eb 100644 --- a/gvm/protocols/gmp/_gmpnext.py +++ b/gvm/protocols/gmp/_gmpnext.py @@ -365,15 +365,19 @@ def modify_credential_store_credential( def get_credential_store( self, credential_store_id: EntityID, + *, + details: Optional[bool] = None, ) -> T: """Request a credential store Args: credential_store_id: ID of credential store to fetch + details: True to request all details """ return self._send_request_and_transform_response( CredentialStores.get_credential_store( credential_store_id=credential_store_id, + details=details, ) ) @@ -389,7 +393,7 @@ def get_credential_stores( Args: filter_string: Filter term to use for the query filter_id: UUID of an existing filter to use for the query - details: Whether to exclude results + details: True to request all details """ return self._send_request_and_transform_response( CredentialStores.get_credential_stores( diff --git a/gvm/protocols/gmp/requests/next/_credential_stores.py b/gvm/protocols/gmp/requests/next/_credential_stores.py index aea9cff6..67ae0db6 100644 --- a/gvm/protocols/gmp/requests/next/_credential_stores.py +++ b/gvm/protocols/gmp/requests/next/_credential_stores.py @@ -18,11 +18,14 @@ class CredentialStores: def get_credential_store( cls, credential_store_id: EntityID, + *, + details: Optional[bool] = None, ) -> Request: """Request a credential store Args: credential_store_id: ID of credential store to fetch + details: True to request all details """ if not credential_store_id: @@ -34,6 +37,9 @@ def get_credential_store( cmd = XmlCommand("get_credential_stores") cmd.add_element("credential_store_id", str(credential_store_id)) + if details is not None: + cmd.set_attribute("details", to_bool(details)) + return cmd @classmethod @@ -49,7 +55,7 @@ def get_credential_stores( Args: filter_string: Filter term to use for the query filter_id: UUID of an existing filter to use for the query - details: Whether to exclude results + details: True to request all details """ cmd = XmlCommand("get_credential_stores") diff --git a/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py b/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py index 5a2c94d5..6d460585 100644 --- a/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py +++ b/tests/protocols/gmpnext/entities/credential_stores/test_get_credential_stores.py @@ -16,6 +16,23 @@ def test_get_credential_store(self): b"" ) + def test_get_credential_store_with_details(self): + self.gmp.get_credential_store(credential_store_id="cs1", details=True) + + self.connection.send.has_been_called_with( + b'' + b"cs1" + b"" + ) + + self.gmp.get_credential_store(credential_store_id="cs1", details=False) + + self.connection.send.has_been_called_with( + b'' + b"cs1" + b"" + ) + def test_get_credential_store_without_id(self): with self.assertRaises(RequiredArgument): self.gmp.get_credential_store(credential_store_id=None)