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:
parent
875f67a6e1
commit
fe858041bd
@ -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, "")
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user