From e40abf7c653665c28404b66ea294bef9bc385b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Sat, 10 Feb 2024 16:48:48 -0500 Subject: [PATCH 1/5] shared/cliconfig: Support SSH encrypted keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- shared/cliconfig/remote.go | 42 +++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/shared/cliconfig/remote.go b/shared/cliconfig/remote.go index 65116df8298..44ee62b162a 100644 --- a/shared/cliconfig/remote.go +++ b/shared/cliconfig/remote.go @@ -1,6 +1,8 @@ package cliconfig import ( + "crypto/ecdsa" + "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" @@ -12,6 +14,7 @@ import ( "strings" "github.com/zitadel/oidc/v2/pkg/oidc" + "golang.org/x/crypto/ssh" "github.com/lxc/incus/client" "github.com/lxc/incus/shared/api" @@ -304,22 +307,47 @@ func (c *Config) getConnectionArgs(name string) (*incus.ConnectionArgs, error) { // Golang has deprecated all methods relating to PEM encryption due to a vulnerability. // However, the weakness does not make PEM unsafe for our purposes as it pertains to password protection on the // key file (client.key is only readable to the user in any case), so we'll ignore deprecation. - if x509.IsEncryptedPEMBlock(pemKey) { //nolint:staticcheck + isEncrypted := x509.IsEncryptedPEMBlock(pemKey) //nolint:staticcheck + isSSH := pemKey.Type == "OPENSSH PRIVATE KEY" + if isEncrypted || isSSH { if c.PromptPassword == nil { return nil, fmt.Errorf("Private key is password protected and no helper was configured") } - password, err := c.PromptPassword("client.crt") + password, err := c.PromptPassword("client.key") if err != nil { return nil, err } - derKey, err := x509.DecryptPEMBlock(pemKey, []byte(password)) //nolint:staticcheck - if err != nil { - return nil, err - } + if isSSH { + sshKey, err := ssh.ParseRawPrivateKeyWithPassphrase(content, []byte(password)) + if err != nil { + return nil, err + } - content = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: derKey}) + ecdsaKey, _ := (sshKey).(*ecdsa.PrivateKey) + rsaKey, _ := (sshKey).(*rsa.PrivateKey) + if ecdsaKey != nil { + derKey, err := x509.MarshalECPrivateKey(ecdsaKey) + if err != nil { + return nil, err + } + + content = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: derKey}) + } else if rsaKey != nil { + derKey := x509.MarshalPKCS1PrivateKey(rsaKey) + content = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: derKey}) + } else { + return nil, fmt.Errorf("Unsupported key type: %T", sshKey) + } + } else { + derKey, err := x509.DecryptPEMBlock(pemKey, []byte(password)) //nolint:staticcheck + if err != nil { + return nil, err + } + + content = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: derKey}) + } } args.TLSClientKey = string(content) From 0861dc32165585ff6d00673d1bda81a9f96e07a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Sat, 10 Feb 2024 17:09:01 -0500 Subject: [PATCH 2/5] tests: Add crypto/ssh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- test/godeps.list | 1 + 1 file changed, 1 insertion(+) diff --git a/test/godeps.list b/test/godeps.list index 0fbe4527fd0..0497b5f642d 100644 --- a/test/godeps.list +++ b/test/godeps.list @@ -16,5 +16,6 @@ github.com/pkg/sftp github.com/zitadel/oidc/v2/pkg/client/rp github.com/zitadel/oidc/v2/pkg/http github.com/zitadel/oidc/v2/pkg/oidc +golang.org/x/crypto/ssh golang.org/x/oauth2 gopkg.in/yaml.v2 From 41fdcbed1cde3a8b3a770591fff4b510df266da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Fri, 9 Feb 2024 15:21:16 -0500 Subject: [PATCH 3/5] doc/remotes: Add mention of keepalive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- doc/remotes.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/doc/remotes.md b/doc/remotes.md index 65c36e280f4..4ad38482d45 100644 --- a/doc/remotes.md +++ b/doc/remotes.md @@ -75,3 +75,25 @@ remotes: protocol: incus public: false ``` + +(remote-keepalive)= +## Enabling `keepalive` + +For those frequently interacting with a particular remote, it's possible to enable a new `keepalive` mode. + +When enabled, Incus will maintain a connection with the target server for up to the configured timeout. +This can significantly reduce the latency when running many `incus` commands. + +To enable, edit your `config.yml` (typically in `~/.config/incus`) and change your remote to look like: + +``` + my-remote: + addr: https://192.0.2.5:8443 + auth_type: tls + project: default + protocol: incus + public: false + keepalive: 30 +``` + +In this example, a timeout of 30 seconds will be used. From 48ec4b79196425de57c0d56d1dc4db5ed816f1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Sat, 10 Feb 2024 17:08:27 -0500 Subject: [PATCH 4/5] doc: Add Ansible to wordlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- doc/.wordlist.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/.wordlist.txt b/doc/.wordlist.txt index 7cb52294613..6e00272f580 100644 --- a/doc/.wordlist.txt +++ b/doc/.wordlist.txt @@ -5,6 +5,8 @@ ACLs AIO allocator AMD +Ansible +Ansible's APIs AppArmor ARMv From 1d4a8f89e950baba402658c48d71b17290a72e81 Mon Sep 17 00:00:00 2001 From: Joseph Price Date: Mon, 5 Feb 2024 22:24:39 +0000 Subject: [PATCH 5/5] doc/authentication: Hints on encrypting client key Signed-off-by: Joseph Price --- doc/authentication.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/doc/authentication.md b/doc/authentication.md index 8bccc2a642e..2b20b614958 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -109,6 +109,28 @@ If the server certificate is valid and signed by the CA, then the connection con Note that the generated certificates are not automatically trusted. You must still add them to the server in one of the ways described in {ref}`authentication-trusted-clients`. +### Encrypting local keys + +The `incus` client also supports encrypted client keys. Keys generated via the methods above can be encrypted with a password, using: + +``` +ssh-keygen -p -o -f .config/incus/client.key +``` + +```{note} +Unless you enable [`keepalive` mode](remote-keepalive), then every single call to Incus will cause the prompt which may get a bit annoying: + + $ incus list remote-host: + Password for client.key: + +------+-------+------+------+------+-----------+ + | NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS | + +------+-------+------+------+------+-----------+ +``` + +```{note} +While the `incus` command line supports encrypted keys, tools such as [Ansible's connection plugin](https://docs.ansible.com/ansible/latest/collections/community/general/incus_connection.html) do not. +``` + (authentication-openid)= ## OpenID Connect authentication