Skip to content

Commit 4e13578

Browse files
committed
feat: allow SideroLink unique token in machine config
Allow unique token to be specified in machine config, this way we can workaround a problem with META being non-persistend/wiped. Fixes #10570 Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com> (cherry picked from commit a9109eb)
1 parent eaa575c commit 4e13578

9 files changed

Lines changed: 214 additions & 34 deletions

File tree

internal/app/machined/pkg/controllers/runtime/unique_token.go

Lines changed: 91 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,51 +6,110 @@ package runtime
66

77
import (
88
"context"
9+
"fmt"
910

1011
"github.com/cosi-project/runtime/pkg/controller"
11-
"github.com/cosi-project/runtime/pkg/controller/generic/transform"
1212
"github.com/cosi-project/runtime/pkg/safe"
1313
"github.com/cosi-project/runtime/pkg/state"
1414
"github.com/siderolabs/gen/optional"
1515
"go.uber.org/zap"
1616

1717
"github.com/siderolabs/talos/pkg/machinery/meta"
18+
"github.com/siderolabs/talos/pkg/machinery/resources/config"
1819
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
1920
)
2021

21-
// UniqueMachineTokenController provides a unique token the machine.
22-
type UniqueMachineTokenController = transform.Controller[*runtime.MetaLoaded, *runtime.UniqueMachineToken]
23-
24-
// NewUniqueMachineTokenController instanciates the controller.
25-
func NewUniqueMachineTokenController() *UniqueMachineTokenController {
26-
return transform.NewController(
27-
transform.Settings[*runtime.MetaLoaded, *runtime.UniqueMachineToken]{
28-
Name: "runtime.UniqueMachineTokenController",
29-
MapMetadataFunc: func(in *runtime.MetaLoaded) *runtime.UniqueMachineToken {
30-
return runtime.NewUniqueMachineToken()
31-
},
32-
TransformFunc: func(ctx context.Context, r controller.Reader, logger *zap.Logger, _ *runtime.MetaLoaded, out *runtime.UniqueMachineToken) error {
33-
uniqueToken, err := safe.ReaderGetByID[*runtime.MetaKey](ctx, r, runtime.MetaKeyTagToID(meta.UniqueMachineToken))
34-
if state.IsNotFoundError(err) {
35-
out.TypedSpec().Token = ""
36-
37-
return nil
38-
} else if err != nil {
39-
return err
22+
// UniqueMachineTokenController is a controller that manages SideroLink unique token.
23+
type UniqueMachineTokenController struct{}
24+
25+
// Name implements controller.Controller interface.
26+
func (ctrl *UniqueMachineTokenController) Name() string {
27+
return "runtime.UniqueMachineTokenController"
28+
}
29+
30+
// Inputs implements controller.Controller interface.
31+
func (ctrl *UniqueMachineTokenController) Inputs() []controller.Input {
32+
return []controller.Input{
33+
{
34+
Namespace: runtime.NamespaceName,
35+
Type: runtime.MetaKeyType,
36+
ID: optional.Some(runtime.MetaKeyTagToID(meta.UniqueMachineToken)),
37+
Kind: controller.InputWeak,
38+
},
39+
{
40+
Namespace: runtime.NamespaceName,
41+
Type: runtime.MetaLoadedType,
42+
Kind: controller.InputWeak,
43+
},
44+
{
45+
Namespace: config.NamespaceName,
46+
Type: config.MachineConfigType,
47+
ID: optional.Some(config.ActiveID),
48+
Kind: controller.InputWeak,
49+
},
50+
}
51+
}
52+
53+
// Outputs implements controller.Controller interface.
54+
func (ctrl *UniqueMachineTokenController) Outputs() []controller.Output {
55+
return []controller.Output{
56+
{
57+
Type: runtime.UniqueMachineTokenType,
58+
Kind: controller.OutputExclusive,
59+
},
60+
}
61+
}
62+
63+
// Run implements controller.Controller interface.
64+
//
65+
//nolint:gocyclo
66+
func (ctrl *UniqueMachineTokenController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error {
67+
for {
68+
select {
69+
case <-ctx.Done():
70+
return nil
71+
case <-r.EventCh():
72+
}
73+
74+
r.StartTrackingOutputs()
75+
76+
metaLoaded, err := safe.ReaderGetByID[*runtime.MetaLoaded](ctx, r, runtime.MetaLoadedID)
77+
if err != nil && !state.IsNotFoundError(err) {
78+
return fmt.Errorf("failed to get meta loaded: %w", err)
79+
}
80+
81+
metaKey, err := safe.ReaderGetByID[*runtime.MetaKey](ctx, r, runtime.MetaKeyTagToID(meta.UniqueMachineToken))
82+
if err != nil && !state.IsNotFoundError(err) {
83+
return fmt.Errorf("failed to get unique token meta key: %w", err)
84+
}
85+
86+
cfg, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.ActiveID)
87+
if err != nil && !state.IsNotFoundError(err) {
88+
return fmt.Errorf("failed to get machine config: %w", err)
89+
}
90+
91+
if metaLoaded != nil {
92+
var token string
93+
94+
if metaKey != nil {
95+
token = metaKey.TypedSpec().Value
96+
} else if cfg != nil {
97+
if cfg.Config().SideroLink() != nil {
98+
token = cfg.Config().SideroLink().UniqueToken()
4099
}
100+
}
41101

42-
out.TypedSpec().Token = uniqueToken.TypedSpec().Value
102+
if err = safe.WriterModify(ctx, r, runtime.NewUniqueMachineToken(), func(out *runtime.UniqueMachineToken) error {
103+
out.TypedSpec().Token = token
43104

44105
return nil
45-
},
46-
},
47-
transform.WithExtraInputs(
48-
controller.Input{
49-
Namespace: runtime.NamespaceName,
50-
Type: runtime.MetaKeyType,
51-
ID: optional.Some(runtime.MetaKeyTagToID(meta.UniqueMachineToken)),
52-
Kind: controller.InputWeak,
53-
},
54-
),
55-
)
106+
}); err != nil {
107+
return fmt.Errorf("failed to update unique token: %w", err)
108+
}
109+
}
110+
111+
if err = safe.CleanupOutputs[*runtime.UniqueMachineToken](ctx, r); err != nil {
112+
return fmt.Errorf("failed to cleanup outputs: %w", err)
113+
}
114+
}
56115
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
package runtime_test
6+
7+
import (
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/suite"
13+
14+
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
15+
runtimectrls "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/runtime"
16+
"github.com/siderolabs/talos/pkg/machinery/config/container"
17+
"github.com/siderolabs/talos/pkg/machinery/config/types/siderolink"
18+
"github.com/siderolabs/talos/pkg/machinery/meta"
19+
"github.com/siderolabs/talos/pkg/machinery/resources/config"
20+
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
21+
)
22+
23+
type UniqueMachineTokenSuite struct {
24+
ctest.DefaultSuite
25+
}
26+
27+
func TestUniqueMachineTokenSuite(t *testing.T) {
28+
t.Parallel()
29+
30+
suite.Run(t, &UniqueMachineTokenSuite{
31+
DefaultSuite: ctest.DefaultSuite{
32+
Timeout: 5 * time.Second,
33+
AfterSetup: func(suite *ctest.DefaultSuite) {
34+
suite.Require().NoError(suite.Runtime().RegisterController(&runtimectrls.UniqueMachineTokenController{}))
35+
},
36+
},
37+
})
38+
}
39+
40+
func (suite *UniqueMachineTokenSuite) TestReconcileNoConfig() {
41+
ctest.AssertNoResource[*runtime.UniqueMachineToken](suite, runtime.UniqueMachineTokenID)
42+
43+
suite.Create(runtime.NewMetaLoaded())
44+
45+
ctest.AssertResource(suite, runtime.UniqueMachineTokenID, func(token *runtime.UniqueMachineToken, asrt *assert.Assertions) {
46+
asrt.Empty(token.TypedSpec().Token)
47+
})
48+
49+
metaKey := runtime.NewMetaKey(runtime.NamespaceName, runtime.MetaKeyTagToID(meta.UniqueMachineToken))
50+
metaKey.TypedSpec().Value = "token1"
51+
suite.Create(metaKey)
52+
53+
ctest.AssertResource(suite, runtime.UniqueMachineTokenID, func(token *runtime.UniqueMachineToken, asrt *assert.Assertions) {
54+
asrt.Equal("token1", token.TypedSpec().Token)
55+
})
56+
}
57+
58+
func (suite *UniqueMachineTokenSuite) TestReconcileWithConfig() {
59+
sideroLinkConfig := siderolink.NewConfigV1Alpha1()
60+
sideroLinkConfig.UniqueTokenConfig = "token2"
61+
62+
ctr, err := container.New(sideroLinkConfig)
63+
suite.Require().NoError(err)
64+
65+
cfg := config.NewMachineConfig(ctr)
66+
suite.Create(cfg)
67+
68+
ctest.AssertNoResource[*runtime.UniqueMachineToken](suite, runtime.UniqueMachineTokenID)
69+
70+
suite.Create(runtime.NewMetaLoaded())
71+
72+
ctest.AssertResource(suite, runtime.UniqueMachineTokenID, func(token *runtime.UniqueMachineToken, asrt *assert.Assertions) {
73+
asrt.Equal("token2", token.TypedSpec().Token)
74+
})
75+
76+
metaKey := runtime.NewMetaKey(runtime.NamespaceName, runtime.MetaKeyTagToID(meta.UniqueMachineToken))
77+
metaKey.TypedSpec().Value = "token1"
78+
suite.Create(metaKey)
79+
80+
ctest.AssertResource(suite, runtime.UniqueMachineTokenID, func(token *runtime.UniqueMachineToken, asrt *assert.Assertions) {
81+
asrt.Equal("token1", token.TypedSpec().Token)
82+
})
83+
}

internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
354354
&runtimecontrollers.SecurityStateController{
355355
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
356356
},
357-
runtimecontrollers.NewUniqueMachineTokenController(),
357+
&runtimecontrollers.UniqueMachineTokenController{},
358358
&runtimecontrollers.VersionController{},
359359
&runtimecontrollers.WatchdogTimerConfigController{},
360360
&runtimecontrollers.WatchdogTimerController{},

pkg/machinery/config/config/siderolink.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ import "net/url"
99
// SideroLinkConfig defines the interface to access SideroLink configuration.
1010
type SideroLinkConfig interface {
1111
APIUrl() *url.URL
12+
UniqueToken() string
1213
}

pkg/machinery/config/schemas/config.schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,13 @@
10041004
"description": "SideroLink API URL to connect to.\n",
10051005
"markdownDescription": "SideroLink API URL to connect to.",
10061006
"x-intellij-html-description": "\u003cp\u003eSideroLink API URL to connect to.\u003c/p\u003e\n"
1007+
},
1008+
"uniqueToken": {
1009+
"type": "string",
1010+
"title": "uniqueToken",
1011+
"description": "SideroLink unique token to use for the connection (optional).\n\nThis value is overridden with META key UniqueMachineToken.\n",
1012+
"markdownDescription": "SideroLink unique token to use for the connection (optional).\n\nThis value is overridden with META key UniqueMachineToken.",
1013+
"x-intellij-html-description": "\u003cp\u003eSideroLink unique token to use for the connection (optional).\u003c/p\u003e\n\n\u003cp\u003eThis value is overridden with META key UniqueMachineToken.\u003c/p\u003e\n"
10071014
}
10081015
},
10091016
"additionalProperties": false,

pkg/machinery/config/types/siderolink/siderolink.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ type ConfigV1Alpha1 struct {
6262
// type: string
6363
// pattern: "^(https|grpc)://"
6464
APIUrlConfig meta.URL `yaml:"apiUrl"`
65+
// description: |
66+
// SideroLink unique token to use for the connection (optional).
67+
//
68+
// This value is overridden with META key UniqueMachineToken.
69+
UniqueTokenConfig string `yaml:"uniqueToken,omitempty"`
6570
}
6671

6772
// NewConfigV1Alpha1 creates a new siderolink config document.
@@ -112,6 +117,15 @@ func (s *ConfigV1Alpha1) APIUrl() *url.URL {
112117
return s.APIUrlConfig.URL
113118
}
114119

120+
// UniqueToken implements config.SideroLink interface.
121+
func (s *ConfigV1Alpha1) UniqueToken() string {
122+
if s == nil {
123+
return ""
124+
}
125+
126+
return s.UniqueTokenConfig
127+
}
128+
115129
// Validate implements config.Validator interface.
116130
func (s *ConfigV1Alpha1) Validate(validation.RuntimeMode, ...validation.Option) ([]string, error) {
117131
if s.APIUrlConfig.URL == nil {

pkg/machinery/config/types/siderolink/siderolink_doc.go

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

website/content/v1.10/reference/configuration/siderolink/siderolinkconfig.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ apiUrl: https://siderolink.api/jointoken?token=secret # SideroLink API URL to co
2525
|`apiUrl` |URL |SideroLink API URL to connect to. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
2626
apiUrl: https://siderolink.api/?jointoken=secret
2727
{{< /highlight >}}</details> | |
28+
|`uniqueToken` |string |<details><summary>SideroLink unique token to use for the connection (optional).</summary><br />This value is overridden with META key UniqueMachineToken.</details> | |
2829

2930

3031

website/content/v1.10/schemas/config.schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,13 @@
10041004
"description": "SideroLink API URL to connect to.\n",
10051005
"markdownDescription": "SideroLink API URL to connect to.",
10061006
"x-intellij-html-description": "\u003cp\u003eSideroLink API URL to connect to.\u003c/p\u003e\n"
1007+
},
1008+
"uniqueToken": {
1009+
"type": "string",
1010+
"title": "uniqueToken",
1011+
"description": "SideroLink unique token to use for the connection (optional).\n\nThis value is overridden with META key UniqueMachineToken.\n",
1012+
"markdownDescription": "SideroLink unique token to use for the connection (optional).\n\nThis value is overridden with META key UniqueMachineToken.",
1013+
"x-intellij-html-description": "\u003cp\u003eSideroLink unique token to use for the connection (optional).\u003c/p\u003e\n\n\u003cp\u003eThis value is overridden with META key UniqueMachineToken.\u003c/p\u003e\n"
10071014
}
10081015
},
10091016
"additionalProperties": false,

0 commit comments

Comments
 (0)