From 74e528f7ec14852a3d6efb5d31414d1353b97be4 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 3 Sep 2026 13:37:35 +0200 Subject: [PATCH 1/4] Adopt machine console to new metal-console --- cmd/admin/v2/machine.go | 39 +------------------------------------ cmd/api/v2/machine.go | 40 +++++++++++++++++++++++++++++++++++++- pkg/helpers/ssh.go | 43 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 39 deletions(-) create mode 100644 pkg/helpers/ssh.go diff --git a/cmd/admin/v2/machine.go b/cmd/admin/v2/machine.go index c8cb0a3..9b742e3 100644 --- a/cmd/admin/v2/machine.go +++ b/cmd/admin/v2/machine.go @@ -452,7 +452,7 @@ func (c *machine) console(ctx context.Context, args []string) error { return err } - err = sshClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), &c.c.Context.Token, true) + err = helpers.SShClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, c.c.GetProject()) if err != nil { return fmt.Errorf("machine console error:%w", err) } @@ -592,40 +592,3 @@ func (c *machine) firewallSSH(ctx context.Context, args []string) (err error) { } return s.Connect(nil) } - -// sshClient opens an interactive ssh session to the host on port with user, authenticated by the key. -func sshClient(user, keyfile, host string, port int, idToken *string, passwordAuth bool) error { - var opts []metalssh.ConnectOpt - - if passwordAuth { - opts = append(opts, metalssh.ConnectOptOutputPassword(*idToken)) - } else { - if keyfile == "" { - var err error - keyfile, err = helpers.SearchSSHKey() - if err != nil { - return err - } - } - - privateKey, err := os.ReadFile(keyfile) - if err != nil { - return err - } - - opts = append(opts, metalssh.ConnectOptOutputPrivateKey(privateKey)) - } - - s, err := metalssh.NewClient(user, host, port, opts...) - if err != nil { - return err - } - - var env *metalssh.Env - - if idToken != nil { - env = &metalssh.Env{"LC_METAL_STACK_OIDC_TOKEN": *idToken} - } - - return s.Connect(env) -} diff --git a/cmd/api/v2/machine.go b/cmd/api/v2/machine.go index cc7845e..3a148da 100644 --- a/cmd/api/v2/machine.go +++ b/cmd/api/v2/machine.go @@ -1,6 +1,10 @@ package v2 import ( + "context" + "fmt" + "net/url" + "github.com/metal-stack/api/go/errorutil" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" @@ -8,6 +12,7 @@ import ( "github.com/metal-stack/cli/pkg/helpers" "github.com/metal-stack/metal-lib/pkg/genericcli" "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -72,7 +77,21 @@ If ~/.ssh/[id_ed25519.pub | id_rsa.pub | id_dsa.pub] is present it will be picke ValidArgsFn: c.Completion.Machine, } - return genericcli.NewCmds(cmdsConfig) + consoleCmd := &cobra.Command{ + Use: "console", + Short: "establishes a connection to the serial console of a machine. for authentication at the metal-console it uses the token such that no machine ssh key is required for access (unlike the corresponding user API command).", + RunE: func(cmd *cobra.Command, args []string) error { + return w.console(cmd.Context(), args) + }, + ValidArgsFunction: c.Completion.AdminMachine, + } + consoleCmd.Flags().Bool("ipmi", false, "if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present)") + consoleCmd.Flags().Int("metal-console-port", 5222, "port open on our control-plane to connect via ssh to get machine console access") + consoleCmd.Flags().StringP("project", "p", "", "project of the machine") + genericcli.Must(consoleCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) + genericcli.Must(consoleCmd.MarkFlagRequired("project")) + + return genericcli.NewCmds(cmdsConfig, consoleCmd) } func (c *machine) Create(rq *apiv2.MachineServiceCreateRequest) (*apiv2.Machine, error) { @@ -176,3 +195,22 @@ func (c *machine) Convert(r *apiv2.Machine) (string, *apiv2.MachineServiceCreate return helpers.EncodeProject(r.Uuid, r.Allocation.Project), create, update, err } + +func (c *machine) console(ctx context.Context, args []string) error { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return err + } + + parsedurl, err := url.Parse(pointer.SafeDeref(c.c.Context.ApiURL)) + if err != nil { + return err + } + + err = helpers.SShClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, viper.GetString("project")) + if err != nil { + return fmt.Errorf("machine console error:%w", err) + } + + return nil +} diff --git a/pkg/helpers/ssh.go b/pkg/helpers/ssh.go new file mode 100644 index 0000000..36fd964 --- /dev/null +++ b/pkg/helpers/ssh.go @@ -0,0 +1,43 @@ +package helpers + +import ( + "os" + + metalssh "github.com/metal-stack/metal-lib/pkg/ssh" +) + +// sshClient opens an interactive ssh session to the host on port with user, authenticated by the key. +func SShClient(user, keyfile, host string, port int, idToken, project string) error { + var opts []metalssh.ConnectOpt + + opts = append(opts, metalssh.ConnectOptOutputPassword(idToken)) + + if keyfile == "" { + var err error + keyfile, err = SearchSSHKey() + if err != nil { + return err + } + } + + privateKey, err := os.ReadFile(keyfile) + if err != nil { + return err + } + + opts = append(opts, metalssh.ConnectOptOutputPrivateKey(privateKey)) + + s, err := metalssh.NewClient(user, host, port, opts...) + if err != nil { + return err + } + + env := map[string]string{ + "LC_METAL_STACK_OIDC_TOKEN": idToken, + "LC_METAL_STACK_PROJECT": project, + } + + sshEnv := metalssh.Env(env) + + return s.Connect(&sshEnv) +} From 308afa936f7006b9b3485cc44b84cb853def4d45 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 9 Sep 2026 11:56:29 +0200 Subject: [PATCH 2/4] Use exposed env variable --- cmd/admin/v2/machine.go | 2 +- cmd/api/v2/machine.go | 2 +- go.mod | 11 ++++++----- go.sum | 30 ++++++++++++++++-------------- pkg/helpers/ssh.go | 11 +++++++---- 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/cmd/admin/v2/machine.go b/cmd/admin/v2/machine.go index 9b742e3..35a3ade 100644 --- a/cmd/admin/v2/machine.go +++ b/cmd/admin/v2/machine.go @@ -452,7 +452,7 @@ func (c *machine) console(ctx context.Context, args []string) error { return err } - err = helpers.SShClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, c.c.GetProject()) + err = helpers.SShClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, nil) if err != nil { return fmt.Errorf("machine console error:%w", err) } diff --git a/cmd/api/v2/machine.go b/cmd/api/v2/machine.go index 3a148da..19bc412 100644 --- a/cmd/api/v2/machine.go +++ b/cmd/api/v2/machine.go @@ -207,7 +207,7 @@ func (c *machine) console(ctx context.Context, args []string) error { return err } - err = helpers.SShClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, viper.GetString("project")) + err = helpers.SShClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, new(viper.GetString("project"))) if err != nil { return fmt.Errorf("machine console error:%w", err) } diff --git a/go.mod b/go.mod index eeb669c..12fadf8 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,8 @@ require ( github.com/fatih/color v1.19.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/metal-stack/api v0.5.1 + github.com/metal-stack/api v0.5.5 + github.com/metal-stack/metal-console v0.7.7-0.20260909095012-75ec3d1e3e1a github.com/metal-stack/metal-lib v0.26.3 github.com/metal-stack/v v1.0.3 github.com/spf13/afero v1.15.0 @@ -22,7 +23,7 @@ require ( ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect buf.build/go/protovalidate v1.3.0 // indirect buf.build/go/protoyaml v0.7.0 // indirect cel.dev/expr v0.25.3 // indirect @@ -43,7 +44,7 @@ require ( github.com/gaissmai/bart v0.29.0 // indirect github.com/go-json-experiment/json v0.0.0-20260820222146-c27c302e5fc3 // indirect github.com/go-openapi/errors v0.22.8 // indirect - github.com/go-openapi/strfmt v0.27.0 // indirect + github.com/go-openapi/strfmt v0.27.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/goccy/go-json v0.10.6 // indirect @@ -57,7 +58,7 @@ require ( github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jsimonetti/rtnetlink v1.4.2 // indirect - github.com/klauspost/compress v1.19.2 // indirect + github.com/klauspost/compress v1.20.0 // indirect github.com/klauspost/connect-compress/v2 v2.1.1 // indirect github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.24 // indirect @@ -91,7 +92,7 @@ require ( go.yaml.in/yaml/v3 v3.0.5 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect go4.org/netipx v0.0.0-20260823151212-3075585bcbeb // indirect - golang.org/x/crypto v0.55.0 // indirect + golang.org/x/crypto v0.56.0 // indirect golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa // indirect golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect diff --git a/go.sum b/go.sum index 2ffa8a5..09c16e9 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ 9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f h1:1C7nZuxUMNz7eiQALRfiqNOm04+m3edWlRff/BYHf0Q= 9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.1 h1:Slv0uGxx219srASyiaI5C9cDlyG8kNDcXpTSYcuAeE4= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.1/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= buf.build/go/protovalidate v1.3.0 h1:8ITcnZGkAHx6TyhZvro+iET/AyqU8gEWQJK2WsT62ms= buf.build/go/protovalidate v1.3.0/go.mod h1:82s5g+rFRj1CZPiLv6OTA31jBu2fpq7mLXHwa9mZfEs= buf.build/go/protoyaml v0.7.0 h1:z4oVoFicbpPefhT7WAykxUdfp0yEQlhMQ2mCZOY5V38= @@ -113,10 +113,10 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= -github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= -github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= -github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= -github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/strfmt v0.27.2 h1:SG32SlbwNy92s0KJiVxt2joJeFdqIYHvwrA0OU6HqzQ= +github.com/go-openapi/strfmt v0.27.2/go.mod h1:M4CKsMO0Fb8qR10+1Ra75wCKNNquy+Vj+4LWZrhTo2E= +github.com/go-openapi/testify/v2 v2.7.0 h1:bycOreEj6wfBvijg3YFogZ/sFjTCDmQnwSodSzHa3X8= +github.com/go-openapi/testify/v2 v2.7.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= @@ -165,8 +165,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90= github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM= -github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8= -github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= +github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/klauspost/connect-compress/v2 v2.1.1 h1:ycZNp4rWOZBodVE2Ls5AzK4aHkyK+GteEfzRZgKNs+c= github.com/klauspost/connect-compress/v2 v2.1.1/go.mod h1:9oilsPHJMzGKkjafSBk9J7iVo4mO+dw0G0KSdVpnlVE= github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= @@ -191,8 +191,10 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A= github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg= -github.com/metal-stack/api v0.5.1 h1:cA4i56RYXwcda/EkEWb2MG/YGQAEqJ4RvwxS7RtdUaA= -github.com/metal-stack/api v0.5.1/go.mod h1:QbDG4YRFIkDf1z6cGmeu0m/791IAMyk8/9U/lsrxH8E= +github.com/metal-stack/api v0.5.5 h1:AzPjTKi8Y4XNiJaEcRDCAjuOmIaLuuOEhKXVWliTtWc= +github.com/metal-stack/api v0.5.5/go.mod h1:U8c+awSMxXaRJjzWpo1IMcvlNxOi1ewu/fk1BzDM7hI= +github.com/metal-stack/metal-console v0.7.7-0.20260909095012-75ec3d1e3e1a h1:zFB1a0PMyzgBGtJU3HID75pEsD6l694BViuxJxKDdLg= +github.com/metal-stack/metal-console v0.7.7-0.20260909095012-75ec3d1e3e1a/go.mod h1:ZkselOaxF/iELFL9Tku3maXgpX4DM0CHjoG7CGuky6E= github.com/metal-stack/metal-lib v0.26.3 h1:K5gLoD65m6p3l6qCPrfavIdvNdWfmF2QdXrvU2URaZs= github.com/metal-stack/metal-lib v0.26.3/go.mod h1:cNXjPBs8SFnjqfBobuSbm5mDk6E/jS8PVeIOrV/7POE= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= @@ -300,8 +302,8 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4 go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20260823151212-3075585bcbeb h1:XBM4hvfwGAttkkiTIFfeigdfcL1xIfdKXqFdgiHGtDs= go4.org/netipx v0.0.0-20260823151212-3075585bcbeb/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= -golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa h1:QSyA8ishJCyT21kER9KwNt0b7BM3iRK4x9QXhjN5Fdk= golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= @@ -342,8 +344,8 @@ google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsok google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/helpers/ssh.go b/pkg/helpers/ssh.go index 36fd964..e8c3542 100644 --- a/pkg/helpers/ssh.go +++ b/pkg/helpers/ssh.go @@ -3,11 +3,12 @@ package helpers import ( "os" + consoleapi "github.com/metal-stack/metal-console/api" metalssh "github.com/metal-stack/metal-lib/pkg/ssh" ) -// sshClient opens an interactive ssh session to the host on port with user, authenticated by the key. -func SShClient(user, keyfile, host string, port int, idToken, project string) error { +// SShClient opens an interactive ssh session to the host on port with user, authenticated by the key. +func SShClient(user, keyfile, host string, port int, idToken string, project *string) error { var opts []metalssh.ConnectOpt opts = append(opts, metalssh.ConnectOptOutputPassword(idToken)) @@ -33,8 +34,10 @@ func SShClient(user, keyfile, host string, port int, idToken, project string) er } env := map[string]string{ - "LC_METAL_STACK_OIDC_TOKEN": idToken, - "LC_METAL_STACK_PROJECT": project, + consoleapi.OidcTokenEnv: idToken, + } + if project != nil { + env[consoleapi.ProjectEnv] = *project } sshEnv := metalssh.Env(env) From 199e89b096f70c8936ee80d59a8e16498513e5bf Mon Sep 17 00:00:00 2001 From: Gerrit Date: Wed, 9 Sep 2026 13:46:29 +0200 Subject: [PATCH 3/4] Fix missing flag. --- cmd/api/v2/machine.go | 8 ++--- docs/admin/metalctlv2_admin_component_list.md | 2 +- docs/metalctlv2_machine.md | 1 + docs/metalctlv2_machine_console.md | 35 +++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 docs/metalctlv2_machine_console.md diff --git a/cmd/api/v2/machine.go b/cmd/api/v2/machine.go index 19bc412..1150927 100644 --- a/cmd/api/v2/machine.go +++ b/cmd/api/v2/machine.go @@ -1,7 +1,6 @@ package v2 import ( - "context" "fmt" "net/url" @@ -81,13 +80,14 @@ If ~/.ssh/[id_ed25519.pub | id_rsa.pub | id_dsa.pub] is present it will be picke Use: "console", Short: "establishes a connection to the serial console of a machine. for authentication at the metal-console it uses the token such that no machine ssh key is required for access (unlike the corresponding user API command).", RunE: func(cmd *cobra.Command, args []string) error { - return w.console(cmd.Context(), args) + return w.console(args) }, ValidArgsFunction: c.Completion.AdminMachine, } consoleCmd.Flags().Bool("ipmi", false, "if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present)") consoleCmd.Flags().Int("metal-console-port", 5222, "port open on our control-plane to connect via ssh to get machine console access") consoleCmd.Flags().StringP("project", "p", "", "project of the machine") + consoleCmd.Flags().StringP("sshidentity", "i", "", "the ssh private key used when creating the machine") genericcli.Must(consoleCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) genericcli.Must(consoleCmd.MarkFlagRequired("project")) @@ -196,7 +196,7 @@ func (c *machine) Convert(r *apiv2.Machine) (string, *apiv2.MachineServiceCreate return helpers.EncodeProject(r.Uuid, r.Allocation.Project), create, update, err } -func (c *machine) console(ctx context.Context, args []string) error { +func (c *machine) console(args []string) error { id, err := genericcli.GetExactlyOneArg(args) if err != nil { return err @@ -209,7 +209,7 @@ func (c *machine) console(ctx context.Context, args []string) error { err = helpers.SShClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, new(viper.GetString("project"))) if err != nil { - return fmt.Errorf("machine console error:%w", err) + return fmt.Errorf("machine console error: %w", err) } return nil diff --git a/docs/admin/metalctlv2_admin_component_list.md b/docs/admin/metalctlv2_admin_component_list.md index 197f61c..1b6ca32 100644 --- a/docs/admin/metalctlv2_admin_component_list.md +++ b/docs/admin/metalctlv2_admin_component_list.md @@ -11,7 +11,7 @@ metalctlv2 admin component list [flags] ``` -h, --help help for list --identifier string lists only component with this identifier - --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: identifier|started|type + --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: expiration|identifier|started|type --type string lists only component of this type --uuid string lists only component with this uuid ``` diff --git a/docs/metalctlv2_machine.md b/docs/metalctlv2_machine.md index 8df768c..4ede4a4 100644 --- a/docs/metalctlv2_machine.md +++ b/docs/metalctlv2_machine.md @@ -29,6 +29,7 @@ allocate a machine * [metalctlv2](metalctlv2.md) - cli for managing entities in metal-stack * [metalctlv2 machine apply](metalctlv2_machine_apply.md) - applies one or more machines from a given file +* [metalctlv2 machine console](metalctlv2_machine_console.md) - establishes a connection to the serial console of a machine. for authentication at the metal-console it uses the token such that no machine ssh key is required for access (unlike the corresponding user API command). * [metalctlv2 machine create](metalctlv2_machine_create.md) - creates the machine * [metalctlv2 machine delete](metalctlv2_machine_delete.md) - deletes the machine * [metalctlv2 machine describe](metalctlv2_machine_describe.md) - describes the machine diff --git a/docs/metalctlv2_machine_console.md b/docs/metalctlv2_machine_console.md new file mode 100644 index 0000000..1a11825 --- /dev/null +++ b/docs/metalctlv2_machine_console.md @@ -0,0 +1,35 @@ +## metalctlv2 machine console + +establishes a connection to the serial console of a machine. for authentication at the metal-console it uses the token such that no machine ssh key is required for access (unlike the corresponding user API command). + +``` +metalctlv2 machine console [flags] +``` + +### Options + +``` + -h, --help help for console + --ipmi if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present) + --metal-console-port int port open on our control-plane to connect via ssh to get machine console access (default 5222) + -p, --project string project of the machine + -i, --sshidentity string the ssh private key used when creating the machine +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities + From 9af35093758758d491196522e2dde48e2f028ae0 Mon Sep 17 00:00:00 2001 From: Gerrit Date: Wed, 9 Sep 2026 14:40:36 +0200 Subject: [PATCH 4/4] Texts. --- cmd/admin/v2/machine.go | 6 +++--- cmd/api/v2/machine.go | 10 +++++----- docs/admin/metalctlv2_admin_machine_console.md | 4 ++-- docs/metalctlv2_machine.md | 2 +- docs/metalctlv2_machine_console.md | 6 +++--- pkg/helpers/ssh.go | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cmd/admin/v2/machine.go b/cmd/admin/v2/machine.go index 35a3ade..7f37594 100644 --- a/cmd/admin/v2/machine.go +++ b/cmd/admin/v2/machine.go @@ -203,8 +203,8 @@ If ~/.ssh/[id_ed25519.pub | id_rsa.pub | id_dsa.pub] is present it will be picke }, ValidArgsFunction: c.Completion.AdminMachine, } - consoleCmd.Flags().Bool("ipmi", false, "if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present)") - consoleCmd.Flags().Int("metal-console-port", 5222, "port open on our control-plane to connect via ssh to get machine console access") + consoleCmd.Flags().Bool("ipmi", false, "if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present and the machine bmc being accessible from the local machine)") + consoleCmd.Flags().Int("metal-console-port", 5222, "the metal-console tcp port in the control-plane to connect via ssh to get machine console access") consolePasswordCmd := &cobra.Command{ Use: "consolepassword", @@ -452,7 +452,7 @@ func (c *machine) console(ctx context.Context, args []string) error { return err } - err = helpers.SShClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, nil) + err = helpers.SSHClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, nil) if err != nil { return fmt.Errorf("machine console error:%w", err) } diff --git a/cmd/api/v2/machine.go b/cmd/api/v2/machine.go index 1150927..b073687 100644 --- a/cmd/api/v2/machine.go +++ b/cmd/api/v2/machine.go @@ -78,14 +78,14 @@ If ~/.ssh/[id_ed25519.pub | id_rsa.pub | id_dsa.pub] is present it will be picke consoleCmd := &cobra.Command{ Use: "console", - Short: "establishes a connection to the serial console of a machine. for authentication at the metal-console it uses the token such that no machine ssh key is required for access (unlike the corresponding user API command).", + Short: "establishes a connection to the serial console of a machine. for authentication at the metal-console it uses the token and the machine ssh key that was used when creating the machine.", RunE: func(cmd *cobra.Command, args []string) error { return w.console(args) }, - ValidArgsFunction: c.Completion.AdminMachine, + ValidArgsFunction: c.Completion.Machine, } - consoleCmd.Flags().Bool("ipmi", false, "if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present)") - consoleCmd.Flags().Int("metal-console-port", 5222, "port open on our control-plane to connect via ssh to get machine console access") + consoleCmd.Flags().Bool("ipmi", false, "if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present and the machine bmc being accessible from the local machine)") + consoleCmd.Flags().Int("metal-console-port", 5222, "the metal-console tcp port in the control-plane to connect via ssh to get machine console access") consoleCmd.Flags().StringP("project", "p", "", "project of the machine") consoleCmd.Flags().StringP("sshidentity", "i", "", "the ssh private key used when creating the machine") genericcli.Must(consoleCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) @@ -207,7 +207,7 @@ func (c *machine) console(args []string) error { return err } - err = helpers.SShClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, new(viper.GetString("project"))) + err = helpers.SSHClient(id, viper.GetString("sshidentity"), parsedurl.Host, viper.GetInt("metal-console-port"), c.c.Context.Token, new(viper.GetString("project"))) if err != nil { return fmt.Errorf("machine console error: %w", err) } diff --git a/docs/admin/metalctlv2_admin_machine_console.md b/docs/admin/metalctlv2_admin_machine_console.md index 95c011a..54642a3 100644 --- a/docs/admin/metalctlv2_admin_machine_console.md +++ b/docs/admin/metalctlv2_admin_machine_console.md @@ -10,8 +10,8 @@ metalctlv2 admin machine console [flags] ``` -h, --help help for console - --ipmi if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present) - --metal-console-port int port open on our control-plane to connect via ssh to get machine console access (default 5222) + --ipmi if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present and the machine bmc being accessible from the local machine) + --metal-console-port int the metal-console tcp port in the control-plane to connect via ssh to get machine console access (default 5222) ``` ### Options inherited from parent commands diff --git a/docs/metalctlv2_machine.md b/docs/metalctlv2_machine.md index 4ede4a4..7f58ae5 100644 --- a/docs/metalctlv2_machine.md +++ b/docs/metalctlv2_machine.md @@ -29,7 +29,7 @@ allocate a machine * [metalctlv2](metalctlv2.md) - cli for managing entities in metal-stack * [metalctlv2 machine apply](metalctlv2_machine_apply.md) - applies one or more machines from a given file -* [metalctlv2 machine console](metalctlv2_machine_console.md) - establishes a connection to the serial console of a machine. for authentication at the metal-console it uses the token such that no machine ssh key is required for access (unlike the corresponding user API command). +* [metalctlv2 machine console](metalctlv2_machine_console.md) - establishes a connection to the serial console of a machine. for authentication at the metal-console it uses the token and the machine ssh key that was used when creating the machine. * [metalctlv2 machine create](metalctlv2_machine_create.md) - creates the machine * [metalctlv2 machine delete](metalctlv2_machine_delete.md) - deletes the machine * [metalctlv2 machine describe](metalctlv2_machine_describe.md) - describes the machine diff --git a/docs/metalctlv2_machine_console.md b/docs/metalctlv2_machine_console.md index 1a11825..41a65cc 100644 --- a/docs/metalctlv2_machine_console.md +++ b/docs/metalctlv2_machine_console.md @@ -1,6 +1,6 @@ ## metalctlv2 machine console -establishes a connection to the serial console of a machine. for authentication at the metal-console it uses the token such that no machine ssh key is required for access (unlike the corresponding user API command). +establishes a connection to the serial console of a machine. for authentication at the metal-console it uses the token and the machine ssh key that was used when creating the machine. ``` metalctlv2 machine console [flags] @@ -10,8 +10,8 @@ metalctlv2 machine console [flags] ``` -h, --help help for console - --ipmi if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present) - --metal-console-port int port open on our control-plane to connect via ssh to get machine console access (default 5222) + --ipmi if set to true, the serial console will be opened using ipmitool (requires ipmitool to be present and the machine bmc being accessible from the local machine) + --metal-console-port int the metal-console tcp port in the control-plane to connect via ssh to get machine console access (default 5222) -p, --project string project of the machine -i, --sshidentity string the ssh private key used when creating the machine ``` diff --git a/pkg/helpers/ssh.go b/pkg/helpers/ssh.go index e8c3542..b0c09e9 100644 --- a/pkg/helpers/ssh.go +++ b/pkg/helpers/ssh.go @@ -7,8 +7,8 @@ import ( metalssh "github.com/metal-stack/metal-lib/pkg/ssh" ) -// SShClient opens an interactive ssh session to the host on port with user, authenticated by the key. -func SShClient(user, keyfile, host string, port int, idToken string, project *string) error { +// SSHClient opens an interactive ssh session to the host on port with user, authenticated by the key. +func SSHClient(user, keyfile, host string, port int, idToken string, project *string) error { var opts []metalssh.ConnectOpt opts = append(opts, metalssh.ConnectOptOutputPassword(idToken))