Andrey Smirnov caee24bf61
feat: implement KubeSpan identity controller
Fixes #4138

When KubeSpan is enabled, Talos automatically generates or loads
KubeSpan identity which consists of Wireguard key pair. ULA address is
calculated based on ClusterID and first NIC MAC address.

Some code was borrowed from #3577.

Example:

```
$ talosctl -n 172.20.0.2 get ksi
NODE         NAMESPACE   TYPE               ID      VERSION   ADDRESS                                       PUBLICKEY
172.20.0.2   kubespan    KubeSpanIdentity   local   1         fd71:6e1d:86be:6302:e871:1bff:feb2:ccee/128   Oak2fBEWngBhwslBxDVgnRNHXs88OAp4kjroSX0uqUE=
```

Additional changes:

* `--with-kubespan` flag for `talosctl cluster create` for quick testing
* validate that cluster discovery (and KubeSpan) requires ClusterID and
ClusterSecret.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
Signed-off-by: Seán C McCord <ulexus@gmail.com>
Co-authored-by: Seán C McCord <ulexus@gmail.com>
2021-08-27 18:49:15 +03:00

142 lines
4.7 KiB
Go

// 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 kubespan_test
import (
"net"
"os"
"path/filepath"
"testing"
"time"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/stretchr/testify/suite"
"github.com/talos-systems/go-retry/retry"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
kubespanctrl "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/kubespan"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/resources/config"
"github.com/talos-systems/talos/pkg/resources/kubespan"
"github.com/talos-systems/talos/pkg/resources/network"
runtimeres "github.com/talos-systems/talos/pkg/resources/runtime"
"github.com/talos-systems/talos/pkg/resources/v1alpha1"
)
type IdentitySuite struct {
KubeSpanSuite
statePath string
}
func (suite *IdentitySuite) TestGenerate() {
suite.statePath = suite.T().TempDir()
suite.Require().NoError(suite.runtime.RegisterController(&kubespanctrl.IdentityController{
StatePath: suite.statePath,
}))
suite.startRuntime()
stateMount := runtimeres.NewMountStatus(v1alpha1.NamespaceName, constants.StatePartitionLabel)
suite.Assert().NoError(suite.state.Create(suite.ctx, stateMount))
cfg := kubespan.NewConfig(config.NamespaceName, kubespan.ConfigID)
cfg.TypedSpec().Enabled = true
cfg.TypedSpec().ClusterID = "8XuV9TZHW08DOk3bVxQjH9ih_TBKjnh-j44tsCLSBzo="
suite.Require().NoError(suite.state.Create(suite.ctx, cfg))
firstMac := network.NewHardwareAddr(network.NamespaceName, network.FirstHardwareAddr)
mac, err := net.ParseMAC("ea:71:1b:b2:cc:ee")
suite.Require().NoError(err)
firstMac.TypedSpec().HardwareAddr = nethelpers.HardwareAddr(mac)
suite.Require().NoError(suite.state.Create(suite.ctx, firstMac))
specMD := resource.NewMetadata(kubespan.NamespaceName, kubespan.IdentityType, kubespan.LocalIdentity, resource.VersionUndefined)
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
suite.assertResource(
specMD,
func(res resource.Resource) error {
spec := res.(*kubespan.Identity).TypedSpec()
_, err := wgtypes.ParseKey(spec.PrivateKey)
suite.Assert().NoError(err)
_, err = wgtypes.ParseKey(spec.PublicKey)
suite.Assert().NoError(err)
suite.Assert().Equal("fd7f:175a:b97c:5602:e871:1bff:feb2:ccee/128", spec.Address.String())
suite.Assert().Equal("fd7f:175a:b97c:5602::/64", spec.Subnet.String())
return nil
},
),
))
}
func (suite *IdentitySuite) TestLoad() {
// using verbatim data here to make sure nodeId representation is supported in future version fo Talos
const identityYaml = `address: ""
subnet: ""
privateKey: sF45u5ePau58WeeCUY3T8D9foEKaQ8Opx4cGC8g4XE4=
publicKey: Oak2fBEWngBhwslBxDVgnRNHXs88OAp4kjroSX0uqUE=
`
suite.statePath = suite.T().TempDir()
suite.Require().NoError(suite.runtime.RegisterController(&kubespanctrl.IdentityController{
StatePath: suite.statePath,
}))
suite.startRuntime()
suite.Require().NoError(os.WriteFile(filepath.Join(suite.statePath, constants.KubeSpanIdentityFilename), []byte(identityYaml), 0o600))
stateMount := runtimeres.NewMountStatus(v1alpha1.NamespaceName, constants.StatePartitionLabel)
suite.Assert().NoError(suite.state.Create(suite.ctx, stateMount))
cfg := kubespan.NewConfig(config.NamespaceName, kubespan.ConfigID)
cfg.TypedSpec().Enabled = true
cfg.TypedSpec().ClusterID = "8XuV9TZHW08DOk3bVxQjH9ih_TBKjnh-j44tsCLSBzo="
suite.Require().NoError(suite.state.Create(suite.ctx, cfg))
firstMac := network.NewHardwareAddr(network.NamespaceName, network.FirstHardwareAddr)
mac, err := net.ParseMAC("ea:71:1b:b2:cc:ee")
suite.Require().NoError(err)
firstMac.TypedSpec().HardwareAddr = nethelpers.HardwareAddr(mac)
suite.Require().NoError(suite.state.Create(suite.ctx, firstMac))
specMD := resource.NewMetadata(kubespan.NamespaceName, kubespan.IdentityType, kubespan.LocalIdentity, resource.VersionUndefined)
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
suite.assertResource(
specMD,
func(res resource.Resource) error {
spec := res.(*kubespan.Identity).TypedSpec()
suite.Assert().Equal("sF45u5ePau58WeeCUY3T8D9foEKaQ8Opx4cGC8g4XE4=", spec.PrivateKey)
suite.Assert().Equal("Oak2fBEWngBhwslBxDVgnRNHXs88OAp4kjroSX0uqUE=", spec.PublicKey)
suite.Assert().Equal("fd7f:175a:b97c:5602:e871:1bff:feb2:ccee/128", spec.Address.String())
suite.Assert().Equal("fd7f:175a:b97c:5602::/64", spec.Subnet.String())
return nil
},
),
))
}
func TestIdentitySuite(t *testing.T) {
suite.Run(t, new(IdentitySuite))
}