Procházet zdrojové kódy

cmd/kgctl: add output options for showconf

This commit adds several output options to the `showconf` command of the
`kgctl` binary:
* `--as-peer`: this can be used to generate a peer configuration, which
can be used to configure the selected resource as a peer of another
WireGuard interface
* `--output`: this can be used to select the desired output format of
the peer resource, available options are: WireGuard, YAML, and JSON.
Lucas Servén Marín před 7 roky
rodič
revize
90e68c7735

+ 0 - 1
cmd/kgctl/graph.go

@@ -25,7 +25,6 @@ func graph() *cobra.Command {
 	return &cobra.Command{
 		Use:   "graph",
 		Short: "Generates a graph of the Kilo network",
-		Long:  "",
 		RunE:  runGraph,
 	}
 }

+ 157 - 15
cmd/kgctl/showconf.go

@@ -17,16 +17,42 @@ package main
 import (
 	"errors"
 	"fmt"
+	"os"
+	"strings"
 
 	"github.com/spf13/cobra"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/runtime/serializer/json"
+
+	"github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
 	"github.com/squat/kilo/pkg/mesh"
+	"github.com/squat/kilo/pkg/wireguard"
+)
+
+const (
+	outputFormatJSON      = "json"
+	outputFormatWireGuard = "wireguard"
+	outputFormatYAML      = "yaml"
+)
+
+var (
+	availableOutputFormats = strings.Join([]string{
+		outputFormatJSON,
+		outputFormatWireGuard,
+		outputFormatYAML,
+	}, ", ")
+	asPeer     bool
+	output     string
+	serializer *json.Serializer
 )
 
 func showConf() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "showconf",
-		Short: "Show the WireGuard configuration for a node or peer in the Kilo network",
-		Long:  "",
+		Use:               "showconf",
+		Short:             "Show the WireGuard configuration for a node or peer in the Kilo network",
+		PersistentPreRunE: runShowConf,
 	}
 
 	for _, subCmd := range []*cobra.Command{
@@ -35,15 +61,29 @@ func showConf() *cobra.Command {
 	} {
 		cmd.AddCommand(subCmd)
 	}
+	cmd.PersistentFlags().BoolVar(&asPeer, "as-peer", false, "Should the resource be shown as a peer? Useful to configure this resource as a peer of another WireGuard interface.")
+	cmd.PersistentFlags().StringVarP(&output, "output", "o", "wireguard", fmt.Sprintf("The output format of the resource. Only valid when combined with 'as-peer'. Possible values: %s", availableOutputFormats))
 
 	return cmd
 }
 
+func runShowConf(c *cobra.Command, args []string) error {
+	switch output {
+	case outputFormatJSON:
+		serializer = json.NewSerializer(json.DefaultMetaFactory, peerCreatorTyper{}, peerCreatorTyper{}, true)
+	case outputFormatWireGuard:
+	case outputFormatYAML:
+		serializer = json.NewYAMLSerializer(json.DefaultMetaFactory, peerCreatorTyper{}, peerCreatorTyper{})
+	default:
+		return fmt.Errorf("output format %v unknown; posible values are: %s", output, availableOutputFormats)
+	}
+	return runRoot(c, args)
+}
+
 func showConfNode() *cobra.Command {
 	return &cobra.Command{
-		Use:   "node",
+		Use:   "node [name]",
 		Short: "Show the WireGuard configuration for a node in the Kilo network",
-		Long:  "",
 		RunE:  runShowConfNode,
 		Args:  cobra.ExactArgs(1),
 	}
@@ -51,9 +91,8 @@ func showConfNode() *cobra.Command {
 
 func showConfPeer() *cobra.Command {
 	return &cobra.Command{
-		Use:   "peer",
+		Use:   "peer [name]",
 		Short: "Show the WireGuard configuration for a peer in the Kilo network",
-		Long:  "",
 		RunE:  runShowConfPeer,
 		Args:  cobra.ExactArgs(1),
 	}
@@ -93,11 +132,35 @@ func runShowConfNode(_ *cobra.Command, args []string) error {
 	if err != nil {
 		return fmt.Errorf("failed to create topology: %v", err)
 	}
-	c, err := t.Conf().Bytes()
-	if err != nil {
-		return fmt.Errorf("failed to generate configuration: %v", err)
+
+	if !asPeer {
+		c, err := t.Conf().Bytes()
+		if err != nil {
+			return fmt.Errorf("failed to generate configuration: %v", err)
+		}
+		_, err = os.Stdout.Write(c)
+		return err
+	}
+
+	switch output {
+	case outputFormatJSON:
+		fallthrough
+	case outputFormatYAML:
+		p := translatePeer(t.AsPeer())
+		p.Name = hostname
+		return serializer.Encode(p, os.Stdout)
+	case outputFormatWireGuard:
+		c, err := (&wireguard.Conf{
+			Peers: []*wireguard.Peer{
+				t.AsPeer(),
+			},
+		}).Bytes()
+		if err != nil {
+			return fmt.Errorf("failed to generate configuration: %v", err)
+		}
+		_, err = os.Stdout.Write(c)
+		return err
 	}
-	fmt.Printf(string(c))
 	return nil
 }
 
@@ -137,10 +200,89 @@ func runShowConfPeer(_ *cobra.Command, args []string) error {
 	if err != nil {
 		return fmt.Errorf("failed to create topology: %v", err)
 	}
-	c, err := t.PeerConf(peer).Bytes()
-	if err != nil {
-		return fmt.Errorf("failed to generate configuration: %v", err)
+	if !asPeer {
+		c, err := t.PeerConf(peer).Bytes()
+		if err != nil {
+			return fmt.Errorf("failed to generate configuration: %v", err)
+		}
+		_, err = os.Stdout.Write(c)
+		return err
+	}
+
+	switch output {
+	case outputFormatJSON:
+		fallthrough
+	case outputFormatYAML:
+		p := translatePeer(t.AsPeer())
+		p.Name = peer
+		return serializer.Encode(p, os.Stdout)
+	case outputFormatWireGuard:
+		c, err := (&wireguard.Conf{
+			Peers: []*wireguard.Peer{
+				&peers[peer].Peer,
+			},
+		}).Bytes()
+		if err != nil {
+			return fmt.Errorf("failed to generate configuration: %v", err)
+		}
+		_, err = os.Stdout.Write(c)
+		return err
 	}
-	fmt.Printf(string(c))
 	return nil
 }
+
+// translatePeer translates a wireguard.Peer to a Peer CRD.
+func translatePeer(peer *wireguard.Peer) *v1alpha1.Peer {
+	if peer == nil {
+		return &v1alpha1.Peer{}
+	}
+	var aips []string
+	for _, aip := range peer.AllowedIPs {
+		// Skip any invalid IPs.
+		if aip == nil {
+			continue
+		}
+		aips = append(aips, aip.String())
+	}
+	var endpoint *v1alpha1.PeerEndpoint
+	if peer.Endpoint != nil && peer.Endpoint.Port > 0 && peer.Endpoint.IP != nil {
+		endpoint = &v1alpha1.PeerEndpoint{
+			IP:   peer.Endpoint.IP.String(),
+			Port: peer.Endpoint.Port,
+		}
+	}
+	var key string
+	if len(peer.PublicKey) > 0 {
+		key = string(peer.PublicKey)
+	}
+	var pka int
+	if peer.PersistentKeepalive > 0 {
+		pka = peer.PersistentKeepalive
+	}
+	return &v1alpha1.Peer{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       v1alpha1.PeerKind,
+			APIVersion: v1alpha1.SchemeGroupVersion.String(),
+		},
+		Spec: v1alpha1.PeerSpec{
+			AllowedIPs:          aips,
+			Endpoint:            endpoint,
+			PublicKey:           key,
+			PersistentKeepalive: pka,
+		},
+	}
+}
+
+type peerCreatorTyper struct{}
+
+func (p peerCreatorTyper) New(_ schema.GroupVersionKind) (runtime.Object, error) {
+	return &v1alpha1.Peer{}, nil
+}
+
+func (p peerCreatorTyper) ObjectKinds(_ runtime.Object) ([]schema.GroupVersionKind, bool, error) {
+	return []schema.GroupVersionKind{v1alpha1.PeerGVK}, false, nil
+}
+
+func (p peerCreatorTyper) Recognizes(_ schema.GroupVersionKind) bool {
+	return true
+}

+ 1 - 1
pkg/k8s/apis/kilo/v1alpha1/register.go

@@ -31,7 +31,7 @@ var (
 const GroupName = "kilo.squat.ai"
 
 // SchemeGroupVersion is the group version used to register these objects.
-var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
+var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: Version}
 
 // Resource takes an unqualified resource and returns a Group-qualified GroupResource.
 func Resource(resource string) schema.GroupResource {

+ 8 - 0
pkg/k8s/apis/kilo/v1alpha1/types.go

@@ -21,15 +21,23 @@ import (
 	"net"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 )
 
 const (
+	// Version is the version of this API.
+	Version = "v1alpha1"
 	// PeerKind is the API kind for the peer resource.
 	PeerKind = "Peer"
 	// PeerPlural is the plural name for the peer resource.
 	PeerPlural = "peers"
 )
 
+var (
+	// PeerGVK is the GroupVersionKind for Peers.
+	PeerGVK = schema.GroupVersionKind{Group: GroupName, Version: Version, Kind: PeerKind}
+)
+
 // PeerShortNames are convenient shortnames for the peer resource.
 var PeerShortNames = []string{"peer"}
 

+ 19 - 0
pkg/mesh/topology.go

@@ -338,6 +338,25 @@ func (t *Topology) Conf() *wireguard.Conf {
 	return c
 }
 
+// AsPeer generates the WireGuard peer configuration for the local location of the given Topology.
+// This configuration can be used to configure this location as a peer of another WireGuard interface.
+func (t *Topology) AsPeer() *wireguard.Peer {
+	for _, s := range t.segments {
+		if s.location != t.location {
+			continue
+		}
+		return &wireguard.Peer{
+			AllowedIPs: s.allowedIPs,
+			Endpoint: &wireguard.Endpoint{
+				IP:   s.endpoint,
+				Port: uint32(t.port),
+			},
+			PublicKey: s.key,
+		}
+	}
+	return nil
+}
+
 // PeerConf generates a WireGuard configuration file for a given peer in a Topology.
 func (t *Topology) PeerConf(name string) *wireguard.Conf {
 	c := &wireguard.Conf{}