From a3c4550ce16c20326e0f6def141d5304e1f88374 Mon Sep 17 00:00:00 2001 From: Din Music Date: Fri, 20 Sep 2024 12:45:44 +0000 Subject: [PATCH 1/4] provider-config: Do not blindly accept remote certificate when using trust token Signed-off-by: Din Music --- internal/provider-config/config.go | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/internal/provider-config/config.go b/internal/provider-config/config.go index 9d136b72..b6bcab09 100644 --- a/internal/provider-config/config.go +++ b/internal/provider-config/config.go @@ -320,7 +320,7 @@ func (p *LxdProviderConfig) server(remoteName string) (lxd.Server, error) { } // Try to accept the remote certificate. - err := p.acceptRemoteCertificate(remoteName, remote.Address) + err := p.acceptRemoteCertificate(remoteName, remote.Token, remote.Address) if err != nil { return nil, fmt.Errorf("Failed to accept server certificate for remote %q: %v", remoteName, err) } @@ -428,12 +428,14 @@ func (p *LxdProviderConfig) setRemote(remoteName string, remote LxdRemote) error // acceptRemoteCertificate retrieves the unverified peer certificate found at // the remote address and stores it locally. -func (p *LxdProviderConfig) acceptRemoteCertificate(remoteName string, url string) error { - // Check if we are allowed to accept the remote certificate. - if !p.acceptServerCertificate { +func (p *LxdProviderConfig) acceptRemoteCertificate(remoteName string, token string, url string) error { + // Check if we are allowed to blindly accept the remote certificate. + // When the trust token is used, the fingerprint contained in the token + // is used to ensure we get the right certificate. + if token == "" && !p.acceptServerCertificate { return errors.New("Unable to communicate with remote server. " + - `Either set "accept_remote_certificate" to true or add ` + - "the remote out of band of Terraform and try again.") + `You can set "accept_remote_certificate" to true, add ` + + "the remote out of band of Terraform, or use the trust token.") } // Try to retrieve server's certificate. @@ -442,6 +444,20 @@ func (p *LxdProviderConfig) acceptRemoteCertificate(remoteName string, url strin return err } + if token != "" { + // Decode token. + decodedToken, err := shared.CertificateTokenDecode(token) + if err != nil { + return fmt.Errorf("Failed decoding trust token for remote %q: %v", remoteName, err) + } + + // Compare token and certificate fingerprints. + certFingerprint := shared.CertFingerprint(cert) + if certFingerprint != decodedToken.Fingerprint { + return fmt.Errorf("Fingerprint mismatch between trust token and certificate from remote %q", remoteName) + } + } + certPath := p.config.ServerCertPath(remoteName) certDir := filepath.Dir(certPath) From cc04d7952bb6e5eecc41ba6ba7f72e7badc66736 Mon Sep 17 00:00:00 2001 From: Din Music Date: Fri, 20 Sep 2024 17:29:58 +0000 Subject: [PATCH 2/4] provider: Remove redundant env reset in test Reset of environment variables is already called in a defer Signed-off-by: Din Music --- internal/provider/provider_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 3ef6f079..d1296bd8 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -32,7 +32,6 @@ func TestAccProvider_configDir(t *testing.T) { }, }, }) - resetLXDRemoteEnvVars() } func TestAccProvider_trustToken(t *testing.T) { From f54d032fac6e7b6bfbcf70e1cf8527869cc081f4 Mon Sep 17 00:00:00 2001 From: Din Music Date: Fri, 20 Sep 2024 16:07:05 +0000 Subject: [PATCH 3/4] provider: Update initial auth tests Signed-off-by: Din Music --- internal/provider/provider_test.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index d1296bd8..56b42193 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -40,6 +40,13 @@ func TestAccProvider_trustToken(t *testing.T) { token := acctest.ConfigureTrustToken(t) configDir := t.TempDir() + invalidToken := ` +ewogICJjbGllbnRfbmFtZSI6ICJ0bXBfdG9rZW4iLAogICJmaW5nZXJwcmludCI6ICJZb3VfaGF2 +ZV9kZWNvZGVkX2FfdGVtcG9yYXJ5X3Rva2VuLkNvbmdyYXR1bGF0aW9ucyEiLAogICJhZGRyZXNz +ZXMiOiBbCiAgICAiMTI3LjAuMC4xOjg0NDMiCiAgXSwKICAic2VjcmV0IjogIlRoaXNfaXNfYV90 +b3Bfc2VjcmV0LkRvX25vdF90ZWxsX2l0X3RvX2FueW9uZSEiLAogICJleHBpcmVzX2F0IjogIjAw +MDEtMDEtMDFUMDA6MDA6MDBaIgp9Cg==` + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) @@ -49,8 +56,8 @@ func TestAccProvider_trustToken(t *testing.T) { Steps: []resource.TestStep{ { // Ensure authentication fails with incorrect token. - Config: testAccProvider_remoteServer(configDir, "", "invalid", true), - ExpectError: regexp.MustCompile(`not authorized`), + Config: testAccProvider_remoteServer(configDir, "", invalidToken, true), + ExpectError: regexp.MustCompile(`mismatch between trust token and certificate`), }, { // Ensure authentication succeeds with correct token. @@ -153,7 +160,7 @@ func TestAccProvider_generateClientCertificate(t *testing.T) { } func TestAccProvider_acceptRemoteCertificate(t *testing.T) { - token := acctest.ConfigureTrustToken(t) + password := acctest.ConfigureTrustPassword(t) configDir := t.TempDir() resource.Test(t, resource.TestCase{ @@ -165,12 +172,12 @@ func TestAccProvider_acceptRemoteCertificate(t *testing.T) { Steps: []resource.TestStep{ { // Ensure authentication fails if remote server is not accepted. - Config: testAccProvider_remoteServer(configDir, "", token, false), + Config: testAccProvider_remoteServer(configDir, password, "", false), ExpectError: regexp.MustCompile(`Failed to accept server certificate`), }, { // Ensure authentication succeeds if remote server is accepted. - Config: testAccProvider_remoteServer(configDir, "", token, true), + Config: testAccProvider_remoteServer(configDir, password, "", true), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("lxd_noop.noop", "remote", "tf-remote"), resource.TestCheckResourceAttr("lxd_noop.noop", "project", "default"), From d82682d75607d4f67cda8e2fb3c34fd9888750c0 Mon Sep 17 00:00:00 2001 From: Din Music Date: Mon, 23 Sep 2024 16:17:18 +0000 Subject: [PATCH 4/4] docs: Update docs to reflect token verification Signed-off-by: Din Music --- docs/index.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/index.md b/docs/index.md index f6977fe2..d0fe47ee 100644 --- a/docs/index.md +++ b/docs/index.md @@ -63,15 +63,13 @@ The following arguments are supported: configuration. Defaults to `$HOME/snap/lxd/common/config` (and fallbacks to `$HOME/.config/lxc`). * `generate_client_certificates` - *Optional* - Automatically generate the LXD - client certificate if it does not exist. Valid values are `true` and `false`. - This can also be set with the `LXD_GENERATE_CLIENT_CERTS` Environment - variable. Defaults to `false`. + client certificate if it does not exist. Defaults to `false`. * `accept_remote_certificate` - *Optional* - Automatically accept the LXD - remote's certificate. Valid values are `true` and `false`. If this is not set - to `true`, you must accept the certificate out of band of Terraform. This can - also be set with the `LXD_ACCEPT_SERVER_CERTIFICATE` environment variable. - Defaults to `false` + remote's certificate during initial authentication. If this is not set + to `true`, you must accept the certificate out of band of Terraform or + use a trust token instead (recommended, see `token` in `remote`). + Defaults to `false`. The `remote` block supports: @@ -85,7 +83,6 @@ The `remote` block supports: * `default` - *Optional* - Whether this should be the default remote. This remote will then be used when one is not specified in a resource. - Valid values are `true` and `false`. If you choose to _not_ set default=true on a `remote` and do not specify a remote in a resource, this provider will attempt to connect to an LXD server running on the same host through the UNIX socket. See `Undefined Remote` @@ -108,12 +105,13 @@ socket. ## Environment Variable Remote It is possible to define a single `remote` through environment variables. -The required variables are: - +The supported variables are: * `LXD_REMOTE` - The name of the remote. * `LXD_ADDR` - The address of the LXD remote. * `LXD_PASSWORD` - The password of the LXD remote. * `LXD_TOKEN` - The trust token of the LXD remote. +* `LXD_GENERATE_CLIENT_CERTS` - Automatically generate the LXD client certificate if missing. +* `LXD_ACCEPT_SERVER_CERTIFICATE` - Automatically accept the LXD remote's certificate during initial authentication. ## PKI Support