Skip to content

Commit

Permalink
feat: enable resource API in the maintenance mode
Browse files Browse the repository at this point in the history
This basically provides `talosctl get --insecure` in maintenance mode.
Only non-sensitive resources are available (equivalent to having
`os:reader` role in the Talos client certificate).

Changes:

* refactored insecure/maintenance client setup in talosctl
* `LinkStatus` is no longer sensitive as it shows only Wireguard public
key, `LinkSpec` still contains private key for obvious reasons
* maintenance mode injects `os:reader` role implicitly

The motivation behind this PR is to deprecate networkd-era interfaces &
routes APIs which are being used in TUI installer, and we need a
replacement.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
  • Loading branch information
smira committed Sep 22, 2021
1 parent 452893c commit 2b52042
Show file tree
Hide file tree
Showing 16 changed files with 188 additions and 144 deletions.
43 changes: 1 addition & 42 deletions cmd/talosctl/cmd/talos/apply-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@ package talos

import (
"context"
"crypto/tls"
"fmt"
"io/ioutil"
"strings"

"github.com/spf13/cobra"
"github.com/talos-systems/crypto/x509"

"github.com/talos-systems/talos/cmd/talosctl/pkg/talos/helpers"
"github.com/talos-systems/talos/internal/pkg/tui/installer"
"github.com/talos-systems/talos/pkg/cli"
machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine"
Expand Down Expand Up @@ -70,45 +67,7 @@ var applyConfigCmd = &cobra.Command{

withClient := func(f func(context.Context, *client.Client) error) error {
if applyConfigCmdFlags.insecure {
ctx := context.Background()

if err := helpers.FailIfMultiNodes(ctx, "apply-config"); err != nil {
return err
}

c, err := client.New(ctx, client.WithTLSConfig(&tls.Config{
InsecureSkipVerify: true,
}), client.WithEndpoints(Nodes...))
if err != nil {
return err
}

//nolint:errcheck
defer c.Close()

tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}

if len(applyConfigCmdFlags.certFingerprints) > 0 {
fingerprints := make([]x509.Fingerprint, len(applyConfigCmdFlags.certFingerprints))

for i, stringFingerprint := range applyConfigCmdFlags.certFingerprints {
fingerprints[i], err = x509.ParseFingerprint(stringFingerprint)
if err != nil {
return fmt.Errorf("error parsing certificate fingerprint %q: %v", stringFingerprint, err)
}
}

tlsConfig.VerifyConnection = x509.MatchSPKIFingerprints(fingerprints...)
}

c, err = client.New(ctx, client.WithTLSConfig(tlsConfig), client.WithEndpoints(Nodes...))
if err != nil {
return err
}

return f(ctx, c)
return WithClientMaintenance(applyConfigCmdFlags.certFingerprints, f)
}

return WithClient(f)
Expand Down
12 changes: 1 addition & 11 deletions cmd/talosctl/cmd/talos/disks.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package talos

import (
"context"
"crypto/tls"
"fmt"
"os"
"strings"
Expand All @@ -29,16 +28,7 @@ var disksCmd = &cobra.Command{
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
if disksCmdFlags.insecure {
ctx := context.Background()

c, err := client.New(ctx, client.WithTLSConfig(&tls.Config{
InsecureSkipVerify: true,
}), client.WithEndpoints(Nodes...))
if err != nil {
return err
}

return printDisks(ctx, c)
return WithClientMaintenance(nil, printDisks)
}

return WithClient(printDisks)
Expand Down
126 changes: 68 additions & 58 deletions cmd/talosctl/cmd/talos/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import (
)

var getCmdFlags struct {
namespace string

output string
insecure bool

watch bool
namespace string
output string
watch bool
}

// getCmd represents the get (resources) command.
Expand All @@ -34,94 +34,104 @@ var getCmd = &cobra.Command{
Long: ``,
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
return WithClient(func(ctx context.Context, c *client.Client) error {
out, err := output.NewWriter(getCmdFlags.output)
if err != nil {
return err
}

resourceType := args[0]
if getCmdFlags.insecure {
return WithClientMaintenance(nil, getResources(args))
}

var resourceID string
return WithClient(getResources(args))
},
}

if len(args) == 2 {
resourceID = args[1]
}
//nolint:gocyclo,cyclop
func getResources(args []string) func(ctx context.Context, c *client.Client) error {
return func(ctx context.Context, c *client.Client) error {
out, err := output.NewWriter(getCmdFlags.output)
if err != nil {
return err
}

defer out.Flush() //nolint:errcheck
resourceType := args[0]

var headerWritten bool
var resourceID string

if getCmdFlags.watch { // get -w <type> OR get -w <type> <id>
watchClient, err := c.Resources.Watch(ctx, getCmdFlags.namespace, resourceType, resourceID)
if err != nil {
return err
}
if len(args) == 2 {
resourceID = args[1]
}

for {
msg, err := watchClient.Recv()
if err != nil {
if err == io.EOF || client.StatusCode(err) == codes.Canceled {
return nil
}
defer out.Flush() //nolint:errcheck

return err
}
var headerWritten bool

if msg.Metadata.GetError() != "" {
fmt.Fprintf(os.Stderr, "%s: %s\n", msg.Metadata.GetHostname(), msg.Metadata.GetError())
if getCmdFlags.watch { // get -w <type> OR get -w <type> <id>
watchClient, err := c.Resources.Watch(ctx, getCmdFlags.namespace, resourceType, resourceID)
if err != nil {
return err
}

continue
for {
msg, err := watchClient.Recv()
if err != nil {
if err == io.EOF || client.StatusCode(err) == codes.Canceled {
return nil
}

if msg.Definition != nil && !headerWritten {
if e := out.WriteHeader(msg.Definition, true); e != nil {
return e
}

headerWritten = true
}
return err
}

if msg.Resource != nil {
if err := out.WriteResource(msg.Metadata.GetHostname(), msg.Resource, msg.EventType); err != nil {
return err
}
if msg.Metadata.GetError() != "" {
fmt.Fprintf(os.Stderr, "%s: %s\n", msg.Metadata.GetHostname(), msg.Metadata.GetError())

if err := out.Flush(); err != nil {
return err
}
}
continue
}
}

// get <type>
// get <type> <id>
printOut := func(parentCtx context.Context, msg client.ResourceResponse) error {
if msg.Definition != nil && !headerWritten {
if e := out.WriteHeader(msg.Definition, false); e != nil {
if e := out.WriteHeader(msg.Definition, true); e != nil {
return e
}

headerWritten = true
}

if msg.Resource != nil {
if err := out.WriteResource(msg.Metadata.GetHostname(), msg.Resource, 0); err != nil {
if err := out.WriteResource(msg.Metadata.GetHostname(), msg.Resource, msg.EventType); err != nil {
return err
}

if err := out.Flush(); err != nil {
return err
}
}
}
}

// get <type>
// get <type> <id>
printOut := func(parentCtx context.Context, msg client.ResourceResponse) error {
if msg.Definition != nil && !headerWritten {
if e := out.WriteHeader(msg.Definition, false); e != nil {
return e
}

return nil
headerWritten = true
}

return helpers.ForEachResource(ctx, c, printOut, getCmdFlags.namespace, args...)
})
},
if msg.Resource != nil {
if err := out.WriteResource(msg.Metadata.GetHostname(), msg.Resource, 0); err != nil {
return err
}
}

return nil
}

return helpers.ForEachResource(ctx, c, printOut, getCmdFlags.namespace, args...)
}
}

func init() {
getCmd.Flags().StringVar(&getCmdFlags.namespace, "namespace", "", "resource namespace (default is to use default namespace per resource)")
getCmd.Flags().StringVarP(&getCmdFlags.output, "output", "o", "table", "output mode (table, yaml)")
getCmd.Flags().BoolVarP(&getCmdFlags.watch, "watch", "w", false, "watch resource changes")
getCmd.Flags().BoolVarP(&getCmdFlags.insecure, "insecure", "i", false, "get resources using the insecure (encrypted with no auth) maintenance service")
addCommand(getCmd)
}
36 changes: 36 additions & 0 deletions cmd/talosctl/cmd/talos/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ package talos

import (
"context"
"crypto/tls"
"fmt"

"github.com/spf13/cobra"
"github.com/talos-systems/crypto/x509"

"github.com/talos-systems/talos/pkg/cli"
"github.com/talos-systems/talos/pkg/machinery/client"
Expand Down Expand Up @@ -81,6 +83,40 @@ func WithClient(action func(context.Context, *client.Client) error) error {
})
}

// WithClientMaintenance wraps common code to initialize Talos client in maintenance (insecure mode).
func WithClientMaintenance(enforceFingerprints []string, action func(context.Context, *client.Client) error) error {
return cli.WithContext(context.Background(), func(ctx context.Context) error {
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}

if len(enforceFingerprints) > 0 {
fingerprints := make([]x509.Fingerprint, len(enforceFingerprints))

for i, stringFingerprint := range enforceFingerprints {
var err error

fingerprints[i], err = x509.ParseFingerprint(stringFingerprint)
if err != nil {
return fmt.Errorf("error parsing certificate fingerprint %q: %v", stringFingerprint, err)
}
}

tlsConfig.VerifyConnection = x509.MatchSPKIFingerprints(fingerprints...)
}

c, err := client.New(ctx, client.WithTLSConfig(tlsConfig), client.WithEndpoints(Nodes...))
if err != nil {
return err
}

//nolint:errcheck
defer c.Close()

return action(ctx, c)
})
}

// Commands is a list of commands published by the package.
var Commands []*cobra.Command

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/talos-systems/talos/internal/app/machined/pkg/system"
networkserver "github.com/talos-systems/talos/internal/app/networkd/pkg/server"
"github.com/talos-systems/talos/internal/app/resources"
storaged "github.com/talos-systems/talos/internal/app/storaged"
"github.com/talos-systems/talos/internal/pkg/configuration"
"github.com/talos-systems/talos/internal/pkg/containers"
Expand Down Expand Up @@ -116,7 +117,7 @@ func (s *Server) Register(obj *grpc.Server) {

machine.RegisterMachineServiceServer(obj, s)
cluster.RegisterClusterServiceServer(obj, s)
resource.RegisterResourceServiceServer(obj, &ResourceServer{server: s})
resource.RegisterResourceServiceServer(obj, &resources.Server{Resources: s.Controller.Runtime().State().V1Alpha2().Resources()})
inspect.RegisterInspectServiceServer(obj, &InspectServer{server: s})
storage.RegisterStorageServiceServer(obj, &storaged.Server{})
timeapi.RegisterTimeServiceServer(obj, &TimeServer{ConfigProvider: s.Controller.Runtime()})
Expand Down
2 changes: 1 addition & 1 deletion internal/app/machined/pkg/controllers/network/link_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ func (ctrl *LinkSpecController) syncLink(ctx context.Context, r controller.Runti

var existingSpec network.WireguardSpec

existingSpec.Decode(wgDev)
existingSpec.Decode(wgDev, false)
existingSpec.Sort()

link.TypedSpec().Wireguard.Sort()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ func (suite *LinkSpecSuite) TestWireguard() {
return suite.assertInterfaces([]string{wgInterface}, func(r *network.LinkStatus) error {
suite.Assert().Equal("wireguard", r.TypedSpec().Kind)

if r.TypedSpec().Wireguard.PrivateKey != priv.String() {
if r.TypedSpec().Wireguard.PublicKey != priv.PublicKey().String() {
return retry.ExpectedErrorf("private key not set")
}

Expand Down Expand Up @@ -617,7 +617,7 @@ func (suite *LinkSpecSuite) TestWireguard() {
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
func() error {
return suite.assertInterfaces([]string{wgInterface}, func(r *network.LinkStatus) error {
if r.TypedSpec().Wireguard.PrivateKey != priv2.String() {
if r.TypedSpec().Wireguard.PublicKey != priv2.PublicKey().String() {
return retry.ExpectedErrorf("private key was not updated")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func (ctrl *LinkStatusController) reconcile(ctx context.Context, r controller.Ru
if err != nil {
logger.Warn("failure getting wireguard attributes", zap.Error(err), zap.String("link", link.Attributes.Name))
} else {
status.Wireguard.Decode(wgDev)
status.Wireguard.Decode(wgDev, true)
}
}

Expand Down
9 changes: 9 additions & 0 deletions internal/app/maintenance/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/talos-systems/talos/internal/app/maintenance/server"
"github.com/talos-systems/talos/pkg/grpc/factory"
"github.com/talos-systems/talos/pkg/grpc/gen"
"github.com/talos-systems/talos/pkg/grpc/middleware/authz"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/resources/network"
)
Expand Down Expand Up @@ -69,6 +70,11 @@ func Run(ctx context.Context, logger *log.Logger, r runtime.Runtime) ([]byte, er

s := server.New(r, logger, cfgCh)

injector := &authz.Injector{
Mode: authz.ReadOnly,
Logger: log.New(logger.Writer(), "machined/authz/injector ", log.Flags()).Printf,
}

// Start the server.
server := factory.NewServer(
s,
Expand All @@ -78,6 +84,9 @@ func Run(ctx context.Context, logger *log.Logger, r runtime.Runtime) ([]byte, er
credentials.NewTLS(tlsConfig),
),
),

factory.WithUnaryInterceptor(injector.UnaryInterceptor()),
factory.WithStreamInterceptor(injector.StreamInterceptor()),
)

listener, err := factory.NewListener(factory.Port(constants.ApidPort))
Expand Down

0 comments on commit 2b52042

Please sign in to comment.