1
0
mirror of https://github.com/lxc/incus.git synced 2026-02-05 09:46:19 +01:00

incus/remote: Add support for PFX generation

Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
This commit is contained in:
Stéphane Graber
2025-11-25 12:47:33 -05:00
parent b1c7652a34
commit 6a73c64c94
3 changed files with 72 additions and 4 deletions

View File

@@ -20,6 +20,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/spf13/cobra"
"software.sslmate.com/src/go-pkcs12"
incus "github.com/lxc/incus/v6/client"
"github.com/lxc/incus/v6/internal/i18n"
@@ -733,14 +734,19 @@ func (c *cmdRemoteGetDefault) Command() *cobra.Command {
type cmdRemoteGetClientCertificate struct {
global *cmdGlobal
remote *cmdRemote
flagFormat string
}
// Command returns a cobra.Command for get-client-certificate.
func (c *cmdRemoteGetClientCertificate) Command() *cobra.Command {
cmd := &cobra.Command{}
cmd.Use = cli.Usage("get-client-certificate")
cmd.Short = i18n.G("Print the client certificate used by this Incus client")
cmd.Use = cli.Usage("get-client-certificate [<target>]")
cmd.Short = i18n.G("Print or retrieve the client certificate used by this Incus client")
cmd.RunE = c.Run
cmd.Flags().StringVarP(&c.flagFormat, "format", "f", "pem", i18n.G("Format (pem|pfx)")+"``")
return cmd
}
@@ -749,11 +755,19 @@ func (c *cmdRemoteGetClientCertificate) Run(cmd *cobra.Command, args []string) e
conf := c.global.conf
// Quick checks.
exit, err := c.global.checkArgs(cmd, args, 0, 0)
exit, err := c.global.checkArgs(cmd, args, 0, 1)
if exit {
return err
}
if !slices.Contains([]string{"pem", "pfx"}, c.flagFormat) {
return fmt.Errorf(i18n.G("Invalid certificate format %q"), c.flagFormat)
}
if c.flagFormat == "pfx" && len(args) == 0 {
return errors.New("PFX export requires a filename")
}
// Check if we need to generate a new certificate.
if !conf.HasClientCertificate() {
if !c.global.flagQuiet {
@@ -767,12 +781,63 @@ func (c *cmdRemoteGetClientCertificate) Run(cmd *cobra.Command, args []string) e
}
// Read the certificate.
tlsClientCert, _, _, err := conf.GetClientCertificate("")
tlsClientCert, tlsClientKey, _, err := conf.GetClientCertificate("")
if err != nil {
return fmt.Errorf("Failed to get certificate: %w", err)
}
if len(args) > 0 {
// Create the file.
w, err := os.Create(args[0])
if err != nil {
return err
}
defer func() { _ = w.Close() }()
switch c.flagFormat {
case "pem":
_, err = fmt.Fprint(w, tlsClientCert)
if err != nil {
return err
}
case "pfx":
// Restrict the permission as it includes the key.
err = w.Chmod(0o600)
if err != nil {
return err
}
// Get a password.
password := c.global.asker.AskPasswordOnce(fmt.Sprintf(i18n.G("Password for %s: "), args[0]))
if err != nil {
return err
}
// Load the cert and key.
cert, err := tls.X509KeyPair([]byte(tlsClientCert), []byte(tlsClientKey))
if err != nil {
return err
}
// Get the PKCS12.
pfx, err := pkcs12.Modern2023.Encode(cert.PrivateKey, cert.Leaf, nil, password)
if err != nil {
return err
}
_, err = fmt.Fprint(w, string(pfx))
if err != nil {
return err
}
}
return nil
}
fmt.Print(tlsClientCert)
return nil
}

1
go.mod
View File

@@ -175,4 +175,5 @@ require (
google.golang.org/grpc v1.77.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
moul.io/http2curl/v2 v2.3.0 // indirect
software.sslmate.com/src/go-pkcs12 v0.6.0 // indirect
)

2
go.sum
View File

@@ -1021,3 +1021,5 @@ moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHc
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU=
software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=