feat: enable version API in maintenance mode

Version API is only available over SideroLink connection.

This is useful to find Talos version as it got booted (e.g. to generate
proper machine configuration).

There's a security concern that version API might return sensitive
information via public API. At the same time Talos version can be
guessed by looking at the output of other APIs, e.g. resource type list
(`talosctl get rd`), which changes with every minor version.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
Andrey Smirnov 2022-05-26 16:46:58 +04:00
parent 875f67a6e1
commit fe858041bd
No known key found for this signature in database
GPG Key ID: 7B26396447AB6DFD
3 changed files with 113 additions and 46 deletions

View File

@ -24,6 +24,7 @@ var versionCmdFlags struct {
clientOnly bool
shortVersion bool
json bool
insecure bool
}
// versionCmd represents the `talosctl version` command.
@ -49,56 +50,65 @@ var versionCmd = &cobra.Command{
fmt.Println("Server:")
}
return WithClient(func(ctx context.Context, c *client.Client) error {
var remotePeer peer.Peer
if versionCmdFlags.insecure {
return WithClientMaintenance(nil, cmdVersion)
}
resp, err := c.Version(ctx, grpc.Peer(&remotePeer))
if err != nil {
if resp == nil {
return fmt.Errorf("error getting version: %s", err)
}
cli.Warning("%s", err)
}
defaultNode := client.AddrFromPeer(&remotePeer)
for _, msg := range resp.Messages {
node := defaultNode
if msg.Metadata != nil {
node = msg.Metadata.Hostname
}
if !versionCmdFlags.json {
fmt.Printf("\t%s: %s\n", "NODE", node)
version.PrintLongVersionFromExisting(msg.Version)
var enabledFeatures []string
if msg.Features.GetRbac() {
enabledFeatures = append(enabledFeatures, "RBAC")
}
fmt.Printf("\tEnabled: %s\n", strings.Join(enabledFeatures, ", "))
continue
}
b, err := protojson.Marshal(msg)
if err != nil {
return err
}
fmt.Printf("%s\n", b)
}
return nil
})
return WithClient(cmdVersion)
},
}
func cmdVersion(ctx context.Context, c *client.Client) error {
var remotePeer peer.Peer
resp, err := c.Version(ctx, grpc.Peer(&remotePeer))
if err != nil {
if resp == nil {
return fmt.Errorf("error getting version: %s", err)
}
cli.Warning("%s", err)
}
defaultNode := client.AddrFromPeer(&remotePeer)
for _, msg := range resp.Messages {
node := defaultNode
if msg.Metadata != nil {
node = msg.Metadata.Hostname
}
if !versionCmdFlags.json {
fmt.Printf("\t%s: %s\n", "NODE", node)
version.PrintLongVersionFromExisting(msg.Version)
var enabledFeatures []string
if msg.Features.GetRbac() {
enabledFeatures = append(enabledFeatures, "RBAC")
}
fmt.Printf("\tEnabled: %s\n", strings.Join(enabledFeatures, ", "))
continue
}
b, err := protojson.Marshal(msg)
if err != nil {
return err
}
fmt.Printf("%s\n", b)
}
return nil
}
func init() {
versionCmd.Flags().BoolVar(&versionCmdFlags.shortVersion, "short", false, "Print the short version")
versionCmd.Flags().BoolVar(&versionCmdFlags.clientOnly, "client", false, "Print client version only")
versionCmd.Flags().BoolVarP(&versionCmdFlags.insecure, "insecure", "i", false, "use Talos maintenance mode API")
// TODO remove when https://github.com/talos-systems/talos/issues/907 is implemented
versionCmd.Flags().BoolVar(&versionCmdFlags.json, "json", false, "")

View File

@ -8,11 +8,15 @@ import (
"context"
"fmt"
"log"
"net"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"inet.af/netaddr"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/resources"
@ -23,6 +27,8 @@ import (
"github.com/talos-systems/talos/pkg/machinery/api/storage"
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
v1alpha1machine "github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
"github.com/talos-systems/talos/pkg/version"
)
// Server implements machine.MachineService, network.NetworkService, and storage.StorageService.
@ -116,3 +122,53 @@ func (s *Server) GenerateConfiguration(ctx context.Context, in *machine.Generate
func (s *Server) GenerateClientConfiguration(ctx context.Context, in *machine.GenerateClientConfigurationRequest) (*machine.GenerateClientConfigurationResponse, error) {
return nil, status.Error(codes.Unimplemented, "client configuration (talosconfig) can't be generated in the maintenance mode")
}
func verifyPeer(ctx context.Context, condition func(netaddr.IP) bool) bool {
remotePeer, ok := peer.FromContext(ctx)
if !ok {
return false
}
if remotePeer.Addr.Network() != "tcp" {
return false
}
ip, _, err := net.SplitHostPort(remotePeer.Addr.String())
if err != nil {
return false
}
addr, err := netaddr.ParseIP(ip)
if err != nil {
return false
}
return condition(addr)
}
// Version implements the machine.MachineServer interface.
func (s *Server) Version(ctx context.Context, in *emptypb.Empty) (*machine.VersionResponse, error) {
if !verifyPeer(ctx, func(addr netaddr.IP) bool {
return network.IsULA(addr, network.ULASideroLink)
}) {
return nil, status.Error(codes.Unimplemented, "Version API is not implemented in maintenance mode")
}
var platform *machine.PlatformInfo
if s.runtime.State().Platform() != nil {
platform = &machine.PlatformInfo{
Name: s.runtime.State().Platform().Name(),
Mode: s.runtime.State().Platform().Mode().String(),
}
}
return &machine.VersionResponse{
Messages: []*machine.Version{
{
Version: version.NewVersion(),
Platform: platform,
},
},
}, nil
}

View File

@ -2094,9 +2094,10 @@ talosctl version [flags]
### Options
```
--client Print client version only
-h, --help help for version
--short Print the short version
--client Print client version only
-h, --help help for version
-i, --insecure use Talos maintenance mode API
--short Print the short version
```
### Options inherited from parent commands