From a120b65b2a18f22031da8808e8ec8b8a0b728046 Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 16 Jul 2024 10:06:51 -0300 Subject: [PATCH 01/11] contrib_testing: Remove deprecated `utcnow` Signed-off-by: hamistao --- contrib_testing/local-http-test.py | 2 +- contrib_testing/local-unix-test.py | 2 +- contrib_testing/remote-test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib_testing/local-http-test.py b/contrib_testing/local-http-test.py index d3c0bba0..63f2823a 100755 --- a/contrib_testing/local-http-test.py +++ b/contrib_testing/local-http-test.py @@ -12,7 +12,7 @@ def log(s): - now = datetime.datetime.utcnow() + now = datetime.datetime.now(datetime.UTC) print(f"{now} - {s}") diff --git a/contrib_testing/local-unix-test.py b/contrib_testing/local-unix-test.py index 25608ccd..976f2c59 100755 --- a/contrib_testing/local-unix-test.py +++ b/contrib_testing/local-unix-test.py @@ -12,7 +12,7 @@ def log(s): - now = datetime.datetime.utcnow() + now = datetime.datetime.now(datetime.UTC) print(f"{now} - {s}") diff --git a/contrib_testing/remote-test.py b/contrib_testing/remote-test.py index a000889e..9f36c69b 100755 --- a/contrib_testing/remote-test.py +++ b/contrib_testing/remote-test.py @@ -12,7 +12,7 @@ def log(s): - now = datetime.datetime.utcnow() + now = datetime.datetime.now(datetime.UTC) print(f"{now} - {s}") From ec1b3ee10400bf2724e7818940a6320fe2319952 Mon Sep 17 00:00:00 2001 From: hamistao Date: Mon, 15 Jul 2024 09:13:11 -0300 Subject: [PATCH 02/11] pylxd/models: Support `trust_token` field on certificate Signed-off-by: hamistao --- pylxd/models/certificate.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pylxd/models/certificate.py b/pylxd/models/certificate.py index 87dbafc4..eb616cf3 100644 --- a/pylxd/models/certificate.py +++ b/pylxd/models/certificate.py @@ -53,7 +53,7 @@ def all(cls, client): def create( cls, client, - password, + secret, cert_data, cert_type="client", name="", @@ -68,11 +68,14 @@ def create( data = { "type": cert_type, "certificate": base64_cert, - "password": password, "name": name, "restricted": restricted, "projects": projects, } + if client.has_api_extension("explicit_trust_token"): + data["trust_token"] = secret + else: + data["password"] = secret response = client.api.certificates.post(json=data) location = response.headers["Location"] fingerprint = location.split("/")[-1] From a4dbc295dc6aa22962de12d9e5967eca83b59194 Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 16 Jul 2024 12:20:53 -0300 Subject: [PATCH 03/11] pylxd/tests: Include api_extensions on mock response This change avoids the unit tests breaking due to the introduced API extension on certificates.create Signed-off-by: hamistao --- pylxd/tests/test_client.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pylxd/tests/test_client.py b/pylxd/tests/test_client.py index e11cd0b3..9d572cca 100644 --- a/pylxd/tests/test_client.py +++ b/pylxd/tests/test_client.py @@ -191,7 +191,12 @@ def test_authenticate(self): """A client is authenticated.""" response = mock.MagicMock(status_code=200) response.json.side_effect = [ - {"metadata": {"auth": "untrusted"}}, + { + "metadata": { + "auth": "untrusted", + "api_extensions": ["explicit_trust_token"], + } + }, { "metadata": { "type": "client", From e1886c454e222b55b93e221e26aa02484d4fad33 Mon Sep 17 00:00:00 2001 From: hamistao Date: Mon, 15 Jul 2024 09:10:06 -0300 Subject: [PATCH 04/11] integration: Export token if supported Signed-off-by: hamistao --- integration/run-integration-tests | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/integration/run-integration-tests b/integration/run-integration-tests index 3f18af38..f096f531 100755 --- a/integration/run-integration-tests +++ b/integration/run-integration-tests @@ -18,10 +18,13 @@ fi # Make sure a client.{crt,key} exist by trying to add a bogus remote lxc remote add foo 127.0.0.1:1234 2>/dev/null || true -if ! lxc info | grep -qwF explicit_trust_token; then +lxc config set core.https_address 127.0.0.1 +if lxc info | grep -qwF explicit_trust_token; then + LXD_TOKEN="$(lxc config trust add --name pylxd --quiet)" + export LXD_TOKEN +else lxc config set core.trust_password password fi -lxc config set core.https_address 127.0.0.1 if ! lxc storage show default >/dev/null 2>&1; then lxc storage create default dir From a0f7864f9f7f57bafbf6a654ddbd40c165e02448 Mon Sep 17 00:00:00 2001 From: hamistao Date: Wed, 17 Jul 2024 17:25:35 -0300 Subject: [PATCH 05/11] setup.cfg: Pass env variables for integration tests This is needed to make LXD_TOKEN visible inside the tox environment and be used for the client authentication tests. Passing all the variables starting with "LXD_" in case future tests also require env variables. Signed-off-by: hamistao --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 7c8361e6..1b0aa295 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,6 +82,7 @@ commands = pytest {posargs:pylxd} [testenv:integration] +passenv = LXD_* commands = pytest integration {posargs} From 981dea874d1342f522343e202e39eced3ea40890 Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 16 Jul 2024 09:23:37 -0300 Subject: [PATCH 06/11] integration: Use token over password if needed Signed-off-by: hamistao --- integration/test_client.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/integration/test_client.py b/integration/test_client.py index 01dc9c65..e0de35cf 100644 --- a/integration/test_client.py +++ b/integration/test_client.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + import pylxd from integration.testing import IntegrationTestCase from pylxd import exceptions @@ -21,16 +23,16 @@ class TestClient(IntegrationTestCase): """Tests for `Client`.""" def test_authenticate(self): - if self.client.has_api_extension("explicit_trust_token"): - self.skipTest( - "Required LXD support for password authentication not available!" - ) - client = pylxd.Client("https://127.0.0.1:8443/") + if client.has_api_extension("explicit_trust_token"): + secret = os.getenv("LXD_TOKEN") + else: + secret = "password" + self.assertFalse(client.trusted) - client.authenticate("password") + client.authenticate(secret) self.assertTrue(client.trusted) @@ -48,5 +50,10 @@ def test_authenticate_with_project(self): self.skipTest(message) raise - client.authenticate("password") + if client.has_api_extension("explicit_trust_token"): + secret = os.getenv("LXD_TOKEN") + else: + secret = "password" + + client.authenticate(secret) self.assertEqual(client.host_info["environment"]["project"], "test-project") From 4b4ac6fccf26453171dddd670ca3e9ca200669bb Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 16 Jul 2024 09:25:01 -0300 Subject: [PATCH 07/11] contrib_testing: Use token over password if needed Signed-off-by: hamistao --- contrib_testing/local-http-test.py | 8 +++++++- contrib_testing/local-unix-test.py | 8 +++++++- contrib_testing/remote-test.py | 8 +++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/contrib_testing/local-http-test.py b/contrib_testing/local-http-test.py index 63f2823a..e53db56a 100755 --- a/contrib_testing/local-http-test.py +++ b/contrib_testing/local-http-test.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import datetime +import os import time import requests @@ -49,6 +50,11 @@ def create_and_update(client): if __name__ == "__main__": client = pylxd.Client("https://127.0.0.1:8443/", verify=False) log("Authenticating...") - client.authenticate("password") + if client.has_api_extension("explicit_trust_token"): + secret = os.getenv("LXD_TOKEN") + else: + secret = "password" + + client.authenticate(secret) create_and_update(client) diff --git a/contrib_testing/local-unix-test.py b/contrib_testing/local-unix-test.py index 976f2c59..1a3f956f 100755 --- a/contrib_testing/local-unix-test.py +++ b/contrib_testing/local-unix-test.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import datetime +import os import time import requests @@ -49,6 +50,11 @@ def create_and_update(client): if __name__ == "__main__": client = pylxd.Client() log("Authenticating...") - client.authenticate("password") + if client.has_api_extension("explicit_trust_token"): + secret = os.getenv("LXD_TOKEN") + else: + secret = "password" + + client.authenticate(secret) create_and_update(client) diff --git a/contrib_testing/remote-test.py b/contrib_testing/remote-test.py index 9f36c69b..8e6c4887 100755 --- a/contrib_testing/remote-test.py +++ b/contrib_testing/remote-test.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import datetime +import os import time import requests @@ -50,6 +51,11 @@ def create_and_update(client): if __name__ == "__main__": client = pylxd.Client("https://10.245.162.33:8443/", verify=False) log("Authenticating...") - client.authenticate("password") + if client.has_api_extension("explicit_trust_token"): + secret = os.getenv("LXD_TOKEN") + else: + secret = "password" + + client.authenticate(secret) create_and_update(client) From f47cb9324d605d20a3fa294ab3f86f0be42f8c75 Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 16 Jul 2024 09:27:39 -0300 Subject: [PATCH 08/11] migration: Export trust token if needed Signed-off-by: hamistao --- migration/run_migration_integration_tests-18-04 | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/migration/run_migration_integration_tests-18-04 b/migration/run_migration_integration_tests-18-04 index 68a7f9ce..c1bd636b 100755 --- a/migration/run_migration_integration_tests-18-04 +++ b/migration/run_migration_integration_tests-18-04 @@ -28,8 +28,13 @@ lxc start "$CONTAINER_ONE_NAME" sleep 5 # Wait for the network to come up lxc exec "$CONTAINER_ONE_NAME" -- apt update lxc exec "$CONTAINER_ONE_NAME" -- apt install -y tox python3-dev libssl-dev libffi-dev build-essential criu -lxc exec "$CONTAINER_ONE_NAME" -- lxc config set core.trust_password password lxc exec "$CONTAINER_ONE_NAME" -- lxc config set core.https_address "[::]" +if lxc exec "$CONTAINER_ONE_NAME" -- lxc info | grep -qwF explicit_trust_token; then + LXD_TOKEN_ONE="$(lxc exec "$CONTAINER_ONE_NAME" -- lxc config trust add --name pylxd --quiet)" + export LXD_TOKEN_ONE +else + lxc exec "$CONTAINER_ONE_NAME" -- lxc config set core.trust_password password +fi lxc exec "$CONTAINER_ONE_NAME" -- mkdir -p /root/.config/lxc lxc exec "$CONTAINER_ONE_NAME" -- openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 \ -sha384 -keyout /root/.config/lxc/client.key -out /root/.config/lxc/client.crt -nodes \ @@ -51,8 +56,13 @@ lxc start "$CONTAINER_TWO_NAME" sleep 5 # Wait for the network to come up lxc exec "$CONTAINER_TWO_NAME" -- apt update lxc exec "$CONTAINER_TWO_NAME" -- apt install -y tox python3-dev libssl-dev libffi-dev build-essential criu -lxc exec "$CONTAINER_TWO_NAME" -- lxc config set core.trust_password password lxc exec "$CONTAINER_TWO_NAME" -- lxc config set core.https_address "[::]:8443" +if lxc exec "$CONTAINER_TWO_NAME" -- lxc info | grep -qwF explicit_trust_token; then + LXD_TOKEN_TWO="$(lxc exec "$CONTAINER_TWO_NAME" -- lxc config trust add --name pylxd --quiet)" + export LXD_TOKEN_TWO +else + lxc exec "$CONTAINER_TWO_NAME" -- lxc config set core.trust_password password +fi lxc exec "$CONTAINER_ONE_NAME" -- mkdir -p /root/.config/lxc lxc exec "$CONTAINER_TWO_NAME" -- openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 \ -sha384 -keyout /root/.config/lxc/client.key -out /root/.config/lxc/client.crt -nodes \ From a628fdd9d69d6dcccc8ee247058da586093b9f8a Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 16 Jul 2024 09:28:51 -0300 Subject: [PATCH 09/11] migration: Use token over password if needed Signed-off-by: hamistao --- migration/test_containers.py | 37 +++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/migration/test_containers.py b/migration/test_containers.py index c2dc9cbc..2c1a2124 100644 --- a/migration/test_containers.py +++ b/migration/test_containers.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from integration.testing import IntegrationTestCase @@ -35,10 +37,20 @@ def test_migrate_running(self): second_host = "https://10.0.3.222:8443/" client1 = Client(endpoint=first_host, verify=False) - client1.authenticate("password") + if client1.has_api_extension("explicit_trust_token"): + secret = os.getenv("LXD_TOKEN_ONE") + else: + secret = "password" + + client1.authenticate(secret) client2 = Client(endpoint=second_host, verify=False) - client2.authenticate("password") + if client2.has_api_extension("explicit_trust_token"): + secret = os.getenv("LXD_TOKEN_TWO") + else: + secret = "password" + + client2.authenticate(secret) an_container = client1.containers.get(self.container.name) an_container.start(wait=True) an_container.sync() @@ -53,7 +65,12 @@ def test_migrate_local_client(self): second_host = "https://10.0.3.222:8443/" client2 = Client(endpoint=second_host, verify=False) - client2.authenticate("password") + if client2.has_api_extension("explicit_trust_token"): + secret = os.getenv("LXD_TOKEN_TWO") + else: + secret = "password" + + client2.authenticate(secret) self.assertRaises(ValueError, self.container.migrate, client2) @@ -65,10 +82,20 @@ def test_migrate_stopped(self): second_host = "https://10.0.3.222:8443/" client1 = Client(endpoint=first_host, verify=False) - client1.authenticate("password") + if client1.has_api_extension("explicit_trust_token"): + secret = os.getenv("LXD_TOKEN_ONE") + else: + secret = "password" + + client1.authenticate(secret) client2 = Client(endpoint=second_host, verify=False) - client2.authenticate("password") + if client2.has_api_extension("explicit_trust_token"): + secret = os.getenv("LXD_TOKEN_TWO") + else: + secret = "password" + + client2.authenticate(secret) an_container = client1.containers.get(self.container.name) an_migrated_container = an_container.migrate(client2, wait=True) From 149a84057de914b3b9643151e1a03cbdbba7934b Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 16 Jul 2024 23:35:18 -0300 Subject: [PATCH 10/11] pylxd: Rename `password` parameter Signed-off-by: hamistao --- pylxd/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylxd/client.py b/pylxd/client.py index cb2a5932..c5c5c1eb 100644 --- a/pylxd/client.py +++ b/pylxd/client.py @@ -478,11 +478,11 @@ def assert_has_api_extension(self, name): if not self.has_api_extension(name): raise exceptions.LXDAPIExtensionNotAvailable(name) - def authenticate(self, password): + def authenticate(self, secret): if self.trusted: return cert = open(self.api.session.cert[0]).read().encode("utf-8") - self.certificates.create(password, cert) + self.certificates.create(secret, cert) # Refresh the host info response = self.api.get() From 708794241675a18caa5f4dd80d9a154d9aea6a3b Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 16 Jul 2024 23:42:24 -0300 Subject: [PATCH 11/11] doc/source: Update client authentication description Signed-off-by: hamistao --- doc/source/authentication.rst | 11 ++++++----- doc/source/certificates.rst | 7 ++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/source/authentication.rst b/doc/source/authentication.rst index 3d4a423a..f085a844 100644 --- a/doc/source/authentication.rst +++ b/doc/source/authentication.rst @@ -3,8 +3,9 @@ Client Authentication ===================== When using LXD over https, LXD uses an asymmetric keypair for authentication. -The keypairs are added to the authentication database after entering the LXD -instance's "trust password". +The keypairs are added to the authentication database after entering a secret. +The secret can be the LXD trust password, when using LXD 5.0 or older, or a +trust token otherwise. Generate a certificate @@ -35,11 +36,11 @@ essentially meaning that the authentication has not yet occurred. >>> client.trusted False -In order to authenticate the client, pass the lxd instance's trust -password to `Client.authenticate` +In order to authenticate the client, pass the LXD instance's trust +password or token to `Client.authenticate` .. code-block:: python - >>> client.authenticate('a-secret-trust-password') + >>> client.authenticate('a-secret') >>> client.trusted >>> True diff --git a/doc/source/certificates.rst b/doc/source/certificates.rst index 0927617c..34166986 100644 --- a/doc/source/certificates.rst +++ b/doc/source/certificates.rst @@ -13,9 +13,10 @@ methods: - `all()` - Retrieve all certificates. - `get()` - Get a specifit certificate, by its fingerprint. - - `create()` - Create a new certificate. This method requires - a first argument that is the LXD trust password, and the cert - data, in binary format. + - `create()` - Create a new certificate. This method requires a first argument + that is a secret and a second containing the cert data, in binary format. + The secret can be the LXD trust password, when using LXD 5.0 or older, + or a trust token otherwise. Certificate attributes