Question on LXD Token Auth

According to the docs, the recommended way to authenticate with the LXD server is to add the client’s TLS certificate to the server’s trust store through a trust token.

I would be authenticating using PHP, is there a better way to automate adding the certificate, correct me if I am wrong.

On the server, I would have first to do:

lxc config trust add --name my_name

which would generate a token, then the client can pass that to the password key when authenticating, my question, is there a better way to generate the trust token, could you give me a high level of how I can achieve this, between, it is not practical to generate the trust token by hand on each lxd server, and I do not want to use the trust password, I want the flexibility of LXD doing that for me

Anyone?

You can also do it via API

POST /1.0/certificates

{
  "type": "client",
  "token": true,
  "name": "somename"
}

You’ll get something like this

{
  "type": "async",
  "status": "Operation created",
  "status_code": 100,
  "operation": "/1.0/operations/12867e47-c16e-4229-a223-150f2f95aba0",
  "error_code": 0,
  "error": "",
  "metadata": {
    "id": "12867e47-c16e-4229-a223-150f2f95aba0",
    "class": "token",
    "description": "Executing operation",
    "created_at": "2023-03-23T15:25:02.545007512Z",
    "updated_at": "2023-03-23T15:25:02.545007512Z",
    "status": "Running",
    "status_code": 103,
    "resources": null,
    "metadata": {
      "addresses": [
        "127.0.0.1:8443"
      ],
      "fingerprint": "8b04c0f07109eff9a10648fa330b0d4b5ddea8077df751ceab109353234b4f6e",
      "request": {
        "name": "oneeight",
        "type": "client",
        "restricted": false,
        "projects": [],
        "certificate": "",
        "password": "",
        "token": true
      },
      "secret": "somesecret"
    },
    "may_cancel": true,
    "err": "",
    "location": "uplink-lite-demo-01"
  }
}

We can then create a json with the following structure and run Base.encode64 to produce the join token for the client.

{
  "client_name": "thename",
  "fingerprint": "fingerprint",
  "addresses": ["address"],
  "secret": "secret"
}

You should then get the trust token.

2 Likes

LXD uses mutual TLS. This means that the certificate and private key must be generated on the client and its certificate then be added to the LXD server’s trust store (and vice versa for mutual trust).

See https://linuxcontainers.org/lxd/docs/latest/authentication/#trusted-tls-clients for more information.

2 Likes

Thanks, guys, I ended up using the trust password, but I have stumbled upon another error, no matter what I do, I always get:

Invalid certificate type

.

I don’t know what I am doing wrong, here is how I am generating the pem cert and key in PHP:

        $days = $config['days'] ?? 365;
        $dn = [
            "organizationName" => AppConfig::getAppName(),
            "commonName" => AppConfig::getAppName(),
            "emailAddress" => MailConfig::getMailReplyTo()
        ];

        // Generate certificate
        $privateKey = openssl_pkey_new();
        $cert = openssl_csr_new($dn, $privateKey);
        $cert = openssl_csr_sign($cert, null, $privateKey, $days);

        // Generate strings
        openssl_x509_export($cert, $certString);
        openssl_pkey_export($privateKey, $privateKeyString);

Yes, it successfully connects to the server, the way I knew that was if I changed the password, I get unauthorized access, the only error I have is adding the cert, here is my payload (don’t worry, this is a fake pass):

{"name":"devsrealm","password":"epbBUvhO3laWWQAUTOuK6k0xlP2Cc8S\/UC+ZWY6aoFHw=="}

According to the doc:

The certificate field can be omitted in which case the TLS client
certificate in use for the connection will be retrieved and added to the
trust store.

even when I added the pem content, I still got the same error, here is the curl config (shorten it so I can get my point across):

       $post_fields = json_encode($opts);

        $curl_info = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_FOLLOWLOCATION => false,
            CURLOPT_HTTPHEADER => ['Content-Type:application/json'],
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_SSLCERT_BLOB => $this->getCertificateString(),
            CURLOPT_SSLKEY_BLOB => $this->getPrivateKeyString(),
            CURLOPT_POSTFIELDS => $post_fields,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
        ];

The getCertificateString() and getPrivateKeyString() method is the ones generated above.

Between, I have verified that the pem file can be read by OpenSSL using openssl_x509_read, and it did so with no error, so, I don’t know where the error is coming from.

Can anyone spot any errors?

Edit:
I generated the cert and key manually just to satisfy my curiosity, and I had the same error, where is the log file for a remote connection like this stored in lxd?

Edit1:
When I used an incorrect password, I can see that in the log, It shows:
“msg=Bad trust password”

so, correcting the password do not show any error but I still got the usual: “Invalid Certificate Type”

What is the contents of opts?

Thanks for your reply Thomas, the content of the opts is the payload I posted above, here is it again

{"name":"devsrealm","password":"epbBUvhO3laWWQAUTOuK6k0xlP2Cc8S\/UC+ZWY6aoFHw=="}

The reason, I did not add the certificate is because according to the doc, it can be omitted in which case the TLS client certificate in use for the connection will be retrieved, however, even when I added the certificate I still got the same error, here is an example of the payload including the cert:

{"name":"tonics","certificate":"-----BEGIN CERTIFICATE-----\nMIIDsDCCApigAwIBAgIBADANBgkqhkiG9w0BAQUFADBxMQ8wDQYDVQQKDAZUb25p\nY3MxDzANBgNVBAMMBlRvbmljczErMCkGCSqGSIb3DQEJARYcZmFydXFAZXhjbHVz\naXZlbXVzaWNwbHVzLmNvbTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh\ndGUwHhcNMjMwNTI2MDk1NTQ4WhcNMjQwNTI1MDk1NTQ4WjBxMQ8wDQYDVQQKDAZU\nb25pY3MxDzANBgNVBAMMBlRvbmljczErMCkGCSqGSIb3DQEJARYcZmFydXFAZXhj\nbHVzaXZlbXVzaWNwbHVzLmNvbTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUt\nU3RhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEt9BxvL0c6A35\nAoxIvMw\/aI+mC8ycsyzMBMH90CdLQUtNJF0GGgEXGi8hSl8pHdMdUCOVk2bIAyLH\nDsX9R1c7nExmX\/09peg0WOStMKcuvcDFZZgdEWouwlQieSWxkycmOYFtVYILirC5\nfWA7\/BNa16k1drgxwp03dfM9vB2w66I4BR\/BbXEppHp0WbN9E78KrxVAIj6gIRp0\nCRFiiBi4n+cmIwh8jUugktV7yl+MevJaq30Gl\/8ozyF85JvlxJ\/8DuNjwBfJqAA\/\n1+mYeB6KrlcGb568CNuRqqxJcKQjmUb\/Ag8bk\/V+TKXYkRYL9KGk\/rs8D5T5nwn1\nxNduJRo7AgMBAAGjUzBRMB0GA1UdDgQWBBR78A2Y0gdR9EQLFtAkg4cIqygjlDAf\nBgNVHSMEGDAWgBR78A2Y0gdR9EQLFtAkg4cIqygjlDAPBgNVHRMBAf8EBTADAQH\/\nMA0GCSqGSIb3DQEBBQUAA4IBAQBjdTjGkZgxJMWvs0J2DTvd9J74FJ1ZkIAmNRdE\n6WEvJx4ax867ldkwEEl6ZZgysf6yxomURJRMk2P3N7t2Lm9GsgqXwdlFp2s8b\/rU\nnicu8N5YskcdjH8A3WSy59vWq7Xlpy2PYlDP82a7dbwT0b77PUqMwGentIo63AHV\n2bmckL+DwxQb40c2vvoi95lLlZ1qFdEymzkeO\/6begj1F3JvgDHw2kak14bpwIvL\nGxivYpT5m6ykPam7u7VLxqR3a\/a6Q7KylT4hWQIAJRwUOCxbqDRJsT02qK5KHEvX\nDxDlrHL0X7eEsZhh13qzH2TtX3QhCHTBa0zqjpIZsDCjYpvZ\n-----END CERTIFICATE-----\n","password":"epbBUvhO3laWWNfHOZMyPqa4Bt3ncOj92rOu7hCTOTiUXIqmIZGebhK8JJPRzk5iFvsVQAUTOuK6k0xlPFglol3mlVSrgxaJNWT9eXF6dDMVCGlV6Nk12Cc8S\/UC+ZWY6aoFHw=="}

I have also tried combining the private key + the pem cert, which did not work either, I know the X509 pem cert is the only thing needed.

Edit:
Here is the log to show the connection was indeed successful:

*   Trying xx.xx.xx.xxx:xxx...
* Connected to xx.xx.xx.xxx (xx.xx.xx.xxx) port xxx (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: O=linuxcontainers.org; CN=root@localhost
*  start date: May 27 08:01:10 2023 GMT
*  expire date: May 24 08:01:10 2033 GMT
*  issuer: O=linuxcontainers.org; CN=root@localhost
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x55b1459073f0)
> POST /1.0/certificates?public HTTP/2
Host: xx.xx.xx.xxx:xxxx
accept: */*
content-type:application/json
content-length: 1481

* We are completely uploaded and fine
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 400 
< content-type: application/json
< x-content-type-options: nosniff
< content-length: 129
< date: Sat, 27 May 2023 08:47:34 GMT
< 
* Connection #0 to host xx.xx.xx.xxx left intact

I just ran a quick test by manually generating a token with lxc config trust add and then replacing the password with the token, it works and added the same pem certificate I have been trying to add with the trust password method, I didn’t change the payload, it is the same cert, so, it means, the error is from lxd server end.

What is the solution in this scenario?

I did not change anything in the above code, and now, I can add a certificate, I don’t understand what is wrong, though, I still can’t add a certificate directly from the post_field, it retrieves it from the connection, while trying to add a certificate directly I get:

{#560 ▼
  +"type": "error"
  +"status": ""
  +"status_code": 0
  +"operation": ""
  +"error_code": 400
  +"error": "illegal base64 data at input byte 0"
  +"metadata": null
}

Okay, I have been able to reproduce why I was initially getting the invalid certificate type, this is because I did not include the client type, it should not be like so:

{
  "certificate": "X509 PEM certificate",
  "name": "castiana",
  "password": "blah",
}

but like so:

{
  "certificate": "X509 PEM certificate",
  "name": "castiana",
  "password": "blah",
  "type": "client"
}

The "type": "client" should be included, now, the next error is, the illegal base64 data at input byte 0

Edit:

Fixed, it turns out, the pem certificate needs to be commented out, so, here is what I did:

$pemCert =  trim(str_replace([
                "-----BEGIN CERTIFICATE-----",
                "-----END CERTIFICATE-----"
            ], null, $pemCert]));

and now, I can add and update a cert

1 Like

You may find this latest video useful:

https://www.youtube.com/watch?v=YvGbvspXObI

2 Likes