Skip to content

Commit

Permalink
feat: provide SideroLink client implementation
Browse files Browse the repository at this point in the history
Related to #4448

The only remaining part is filtering out SideroLink addresses when Talos
looks for a node address.

See also siderolabs/siderolink#2

The way to test it out:

```
$ talosctl cluster create ... --extra-boot-kernel-args
siderolink.api=172.20.0.1:4000
```

(where 172.20.0.1 is the bridge IP)

Run `siderolink-agent` (test implementation):

```
$ sudo _out/siderolink-agent-linux-amd64
```

Now on the host, there should be a `siderolink` Wireguard userspace
tunnel:

```
$ sudo wg
interface: siderolink
  public key: 2aq/V91QyrHAoH24RK0bldukgo2rWk+wqE5Eg6TArCM=
  private key: (hidden)
  listening port: 51821

peer: Tyr6C/F3FFLWtnzqq7Dsm54B40bOPq6++PTiD/zqn2Y=
  endpoint: 172.20.0.1:47857
  allowed ips: fdae:41e4:649b:9303:b6db:d99c:215e:dfc4/128
  latest handshake: 2 minutes, 2 seconds ago
  transfer: 3.62 KiB received, 1012 B sent

...
```

Each Talos node will be registered as a peer, tunnel is established.

You can now ping Talos nodes from the host over the tunnel:

```
$ ping fdae:41e4:649b:9303:b6db:d99c:215e:dfc4
PING fdae:41e4:649b:9303:b6db:d99c:215e:dfc4(fdae:41e4:649b:9303:b6db:d99c:215e:dfc4) 56 data bytes
64 bytes from fdae:41e4:649b:9303:b6db:d99c:215e:dfc4: icmp_seq=1 ttl=64 time=0.352 ms
64 bytes from fdae:41e4:649b:9303:b6db:d99c:215e:dfc4: icmp_seq=2 ttl=64 time=0.437 ms
```

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
  • Loading branch information
smira committed Nov 22, 2021
1 parent 58892cd commit f7d1e77
Show file tree
Hide file tree
Showing 12 changed files with 436 additions and 5 deletions.
11 changes: 11 additions & 0 deletions cmd/talosctl/cmd/mgmt/cluster/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
jsonpatch "github.com/evanphx/json-patch"
"github.com/spf13/cobra"
"github.com/talos-systems/go-blockdevice/blockdevice/encryption"
"github.com/talos-systems/go-procfs/procfs"
talosnet "github.com/talos-systems/net"
"k8s.io/client-go/tools/clientcmd"

Expand Down Expand Up @@ -110,6 +111,7 @@ var (
configPatchControlPlane string
configPatchWorker string
badRTC bool
extraBootKernelArgs string
)

// createCmd represents the cluster up command.
Expand Down Expand Up @@ -494,6 +496,12 @@ func create(ctx context.Context) (err error) {
}
}

var extraKernelArgs *procfs.Cmdline

if extraBootKernelArgs != "" {
extraKernelArgs = procfs.NewCmdline(extraBootKernelArgs)
}

// Add talosconfig to provision options so we'll have it to parse there
provisionOptions = append(provisionOptions, provision.WithTalosConfig(configBundle.TalosConfig()))

Expand All @@ -515,6 +523,7 @@ func create(ctx context.Context) (err error) {
Disks: disks,
SkipInjectingConfig: skipInjectingConfig,
BadRTC: badRTC,
ExtraKernelArgs: extraKernelArgs,
}

if i == 0 {
Expand Down Expand Up @@ -567,6 +576,7 @@ func create(ctx context.Context) (err error) {
Config: cfg,
SkipInjectingConfig: skipInjectingConfig,
BadRTC: badRTC,
ExtraKernelArgs: extraKernelArgs,
})
}

Expand Down Expand Up @@ -837,6 +847,7 @@ func init() {
createCmd.Flags().StringVar(&configPatchControlPlane, "config-patch-control-plane", "", "patch generated machineconfigs (applied to 'init' and 'controlplane' types)")
createCmd.Flags().StringVar(&configPatchWorker, "config-patch-worker", "", "patch generated machineconfigs (applied to 'worker' type)")
createCmd.Flags().BoolVar(&badRTC, "bad-rtc", false, "launch VM with bad RTC state (QEMU only)")
createCmd.Flags().StringVar(&extraBootKernelArgs, "extra-boot-kernel-args", "", "add extra kernel args to the initial boot from vmlinuz and initramfs (QEMU only)")

Cmd.AddCommand(createCmd)
}
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ require (
github.com/talos-systems/go-loadbalancer v0.1.1
github.com/talos-systems/go-procfs v0.1.0
github.com/talos-systems/go-retry v0.3.1
github.com/talos-systems/go-smbios v0.1.0
github.com/talos-systems/go-smbios v0.1.1-0.20211122130416-fd5ec8ce4873
github.com/talos-systems/grpc-proxy v0.2.0
github.com/talos-systems/net v0.3.1-0.20211112122313-0abe5bdae8f8
github.com/talos-systems/talos/pkg/machinery v0.0.0-00010101000000-000000000000
github.com/talos-systems/siderolink v0.0.0-20211119180852-0755b24d4682
github.com/talos-systems/talos/pkg/machinery v0.14.0-alpha.1.0.20211118180932-1ffa8e048008
github.com/u-root/u-root v7.0.0+incompatible
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
github.com/vmware-tanzu/sonobuoy v0.55.0
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1078,12 +1078,14 @@ github.com/talos-systems/go-retry v0.1.0/go.mod h1:HiXQqyVStZ35uSY/MTLWVvQVmC3lI
github.com/talos-systems/go-retry v0.1.1-0.20201113203059-8c63d290a688/go.mod h1:HiXQqyVStZ35uSY/MTLWVvQVmC3lIW2MS5VdDaMtoKM=
github.com/talos-systems/go-retry v0.3.1 h1:GjjyHB8i1CJpb1O5qYPMljq74cRQ5uiDoyMaWddA5FA=
github.com/talos-systems/go-retry v0.3.1/go.mod h1:HiXQqyVStZ35uSY/MTLWVvQVmC3lIW2MS5VdDaMtoKM=
github.com/talos-systems/go-smbios v0.1.0 h1:C6ooNSKyw2bzJxDTPeNbom5sri53h6bJgTWDSf9EAn8=
github.com/talos-systems/go-smbios v0.1.0/go.mod h1:HxhrzAoTZ7ed5Z5VvtCvnCIrOxyXDS7V2B5hCetAMW8=
github.com/talos-systems/go-smbios v0.1.1-0.20211122130416-fd5ec8ce4873 h1:YXgD3A1kdxpP+yEp0vED/IQrwMLyIy7HFRNfGYQgcUs=
github.com/talos-systems/go-smbios v0.1.1-0.20211122130416-fd5ec8ce4873/go.mod h1:vk76naUSZaWE8Z95wbDn51FgH0goECM4oK3KY2hYSMU=
github.com/talos-systems/grpc-proxy v0.2.0 h1:DN75bLfaW4xfhq0r0mwFRnfGhSB+HPhK1LNzuMEs9Pw=
github.com/talos-systems/grpc-proxy v0.2.0/go.mod h1:sm97Vc/z2cok3pu6ruNeszQej4KDxFrDgfWs4C1mtC4=
github.com/talos-systems/net v0.3.1-0.20211112122313-0abe5bdae8f8 h1:oT2MASZ8V3DuZbhaJWJ8oZ373zfmgXpvw2xLHM5cOYk=
github.com/talos-systems/net v0.3.1-0.20211112122313-0abe5bdae8f8/go.mod h1:zhcGixNJz9dgwFiUwc7gkkAqdVqXagU1SNNoIVXYKGo=
github.com/talos-systems/siderolink v0.0.0-20211119180852-0755b24d4682 h1:0rXuO5pwwqXnNR/q33iB8vaAaSPNLKbS/d9PwkCFQkE=
github.com/talos-systems/siderolink v0.0.0-20211119180852-0755b24d4682/go.mod h1:e3PMKivoq1tpggh8esTxLQrKiZvPgPe060/ukWcJOJw=
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
Expand Down
6 changes: 5 additions & 1 deletion internal/app/machined/internal/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ func RunInstallerContainer(disk, platform, ref string, configBytes []byte, reg c
}

for _, arg := range options.ExtraKernelArgs {
args = append(args, []string{"--extra-kernel-arg", arg}...)
args = append(args, "--extra-kernel-arg", arg)
}

if c := procfs.ProcCmdline().Get(constants.KernelParamSideroLink).First(); c != nil {
args = append(args, "--extra-kernel-arg", fmt.Sprintf("%s=%s", constants.KernelParamSideroLink, *c))
}

specOpts := []oci.SpecOpts{
Expand Down
198 changes: 198 additions & 0 deletions internal/app/machined/pkg/controllers/siderolink/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package siderolink

import (
"bytes"
"context"
"fmt"

"github.com/AlekSi/pointer"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/state"
"github.com/talos-systems/go-procfs/procfs"
"github.com/talos-systems/go-smbios/smbios"
pb "github.com/talos-systems/siderolink/api/siderolink"
"go.uber.org/zap"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc"
"inet.af/netaddr"

"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
)

// ManagerController interacts with SideroLink API and brings up the SideroLink Wireguard interface.
type ManagerController struct {
Cmdline *procfs.Cmdline

nodeKey wgtypes.Key
}

// Name implements controller.Controller interface.
func (ctrl *ManagerController) Name() string {
return "siderolink.ManagerController"
}

// Inputs implements controller.Controller interface.
func (ctrl *ManagerController) Inputs() []controller.Input {
return []controller.Input{
{
Namespace: network.NamespaceName,
Type: network.StatusType,
ID: pointer.ToString(network.StatusID),
Kind: controller.InputWeak,
},
}
}

// Outputs implements controller.Controller interface.
func (ctrl *ManagerController) Outputs() []controller.Output {
return []controller.Output{
{
Type: network.AddressSpecType,
Kind: controller.OutputShared,
},
{
Type: network.LinkSpecType,
Kind: controller.OutputShared,
},
}
}

// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *ManagerController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
if ctrl.Cmdline == nil || ctrl.Cmdline.Get(constants.KernelParamSideroLink).First() == nil {
// no SideroLink command line argument, skip controller
return nil
}

s, err := smbios.New()
if err != nil {
return fmt.Errorf("error reading node UUID: %w", err)
}

nodeUUID, err := s.SystemInformation().UUID()
if err != nil {
return fmt.Errorf("error getting node UUID: %w", err)
}

var zeroKey wgtypes.Key

if bytes.Equal(ctrl.nodeKey[:], zeroKey[:]) {
ctrl.nodeKey, err = wgtypes.GeneratePrivateKey()
if err != nil {
return fmt.Errorf("error generating Wireguard key: %w", err)
}
}

apiEndpoint := *ctrl.Cmdline.Get(constants.KernelParamSideroLink).First()

conn, err := grpc.DialContext(ctx, apiEndpoint, grpc.WithInsecure())
if err != nil {
return fmt.Errorf("error dialing SideroLink endpoint %q: %w", apiEndpoint, err)
}

sideroLinkClient := pb.NewProvisionServiceClient(conn)

for {
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
}

netStatus, err := r.Get(ctx, resource.NewMetadata(network.NamespaceName, network.StatusType, network.StatusID, resource.VersionUndefined))
if err != nil {
if state.IsNotFoundError(err) {
// no network state yet
continue
}

return fmt.Errorf("error reading network status: %w", err)
}

if !netStatus.(*network.Status).TypedSpec().AddressReady {
// wait for address
continue
}

resp, err := sideroLinkClient.Provision(ctx, &pb.ProvisionRequest{
NodeUuid: nodeUUID.String(),
NodePublicKey: ctrl.nodeKey.PublicKey().String(),
})
if err != nil {
return fmt.Errorf("error accessing SideroLink API: %w", err)
}

serverAddress, err := netaddr.ParseIP(resp.ServerAddress)
if err != nil {
return fmt.Errorf("error parsing server address: %w", err)
}

nodeAddress, err := netaddr.ParseIPPrefix(resp.NodeAddressPrefix)
if err != nil {
return fmt.Errorf("error parsing node address: %w", err)
}

if err = r.Modify(ctx,
network.NewLinkSpec(network.ConfigNamespaceName, network.LayeredID(network.ConfigOperator, network.LinkID(constants.SideroLinkName))),
func(r resource.Resource) error {
spec := r.(*network.LinkSpec).TypedSpec()

spec.ConfigLayer = network.ConfigOperator
spec.Name = constants.SideroLinkName
spec.Type = nethelpers.LinkNone
spec.Kind = "wireguard"
spec.Up = true
spec.Logical = true

spec.Wireguard = network.WireguardSpec{
PrivateKey: ctrl.nodeKey.String(),
Peers: []network.WireguardPeer{
{
PublicKey: resp.ServerPublicKey,
Endpoint: resp.ServerEndpoint,
AllowedIPs: []netaddr.IPPrefix{
netaddr.IPPrefixFrom(serverAddress, serverAddress.BitLen()),
},
// make sure Talos pings SideroLink endpoint, so that tunnel is established:
// SideroLink doesn't know Talos endpoint.
PersistentKeepaliveInterval: constants.SideroLinkDefaultPeerKeepalive,
},
},
}
spec.Wireguard.Sort()

return nil
}); err != nil {
return fmt.Errorf("error creating siderolink spec: %w", err)
}

if err = r.Modify(ctx,
network.NewAddressSpec(network.ConfigNamespaceName, network.LayeredID(network.ConfigOperator, network.AddressID(constants.SideroLinkName, nodeAddress))),
func(r resource.Resource) error {
spec := r.(*network.AddressSpec).TypedSpec()

spec.ConfigLayer = network.ConfigOperator
spec.Address = nodeAddress
spec.Family = nethelpers.FamilyInet6
spec.Flags = nethelpers.AddressFlags(nethelpers.AddressPermanent)
spec.LinkName = constants.SideroLinkName
spec.Scope = nethelpers.ScopeGlobal

return nil
}); err != nil {
return fmt.Errorf("error creating address spec: %w", err)
}

// all done, terminate controller
return nil
}
}

0 comments on commit f7d1e77

Please sign in to comment.