Skip to content

Commit e3cb882

Browse files
authored
Implement authentication (#8)
* Implement registration and host authentication Signed-off-by: Andrea Mazzotti <[email protected]>
1 parent 9952df2 commit e3cb882

34 files changed

+1463
-231
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ You can use it with any OpenAPI compliant tool, for example the online [Swagger
4848

4949
This API is consumed by the `elemental-agent` and is meant for **Internal** use only.
5050

51+
### Authentication & Authorization
52+
53+
The Elemental API uses two different authorization header.
54+
The `Registration-Authorization` should contain a valid JWT formatted `Bearer` token when fetching registration information or registering a new host.
55+
When registering a new host and modifying a host resource, the `Authorization` header should also contain a valid JWT formatted `Bearer` token that identifies the host.
56+
For more information, you can find more details in the [documentation](doc/AUTH.md).
57+
5158
## Rancher Integration
5259

5360
[Rancher Turtles](https://docs.rancher-turtles.com/) is an extension to Rancher that brings increased integration with Cluster API.

api/v1beta1/elementalhost_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ type ElementalHostSpec struct {
3131
// using this host.
3232
// +optional
3333
MachineRef *corev1.ObjectReference `json:"machineRef,omitempty"`
34+
// PubKey is the host public key to verify when authenticating
35+
// Elemental API requests for this host.
36+
PubKey string `json:"pubKey,omitempty"`
3437
}
3538

3639
// ElementalHostStatus defines the observed state of ElementalHost.

api/v1beta1/elementalregistration_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package v1beta1
1919
import (
2020
"time"
2121

22+
corev1 "k8s.io/api/core/v1"
2223
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2324
runtime "k8s.io/apimachinery/pkg/runtime"
2425
)
@@ -34,6 +35,8 @@ type ElementalRegistrationSpec struct {
3435
// Config points to Elemental machine configuration.
3536
// +optional
3637
Config Config `json:"config,omitempty"`
38+
// PrivateKeyRef is a reference to a secret containing the private key used to generate registration tokens
39+
PrivateKeyRef *corev1.ObjectReference `json:"privateKeyRef,omitempty"`
3740
}
3841

3942
// ElementalRegistrationStatus defines the observed state of ElementalRegistration.
@@ -78,6 +81,10 @@ type Registration struct {
7881
URI string `json:"uri,omitempty" yaml:"uri,omitempty" mapstructure:"uri"`
7982
// +optional
8083
CACert string `json:"caCert,omitempty" yaml:"caCert,omitempty" mapstructure:"caCert"`
84+
// +optional
85+
TokenDuration time.Duration `json:"tokenDuration,omitempty" yaml:"tokenDuration,omitempty" mapstructure:"tokenDuration"`
86+
// +optional
87+
Token string `json:"token,omitempty" yaml:"token,omitempty" mapstructure:"token"`
8188
}
8289

8390
type Agent struct {

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/agent/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ registration:
5757
WhfJrSPzvfWPO73w0MFMBRXZ74Tc24SN6QPBin5LaAIhAM9hidFQ71SZQnPY3Y1I
5858
JZPkAoVeIOoFDgXvl9MkHBuk
5959
-----END CERTIFICATE-----
60+
# A valid JWT token to use during registration
61+
token: eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJFbGVtZW50YWxSZWdpc3RyYXRpb25SZWNvbmNpbGVyIiwic3ViIjoiaHR0cDovLzE5Mi4xNjguMTIyLjEwOjMwMDA5L2VsZW1lbnRhbC92MS9uYW1lc3BhY2VzL2RlZmF1bHQvcmVnaXN0cmF0aW9ucy9teS1yZWdpc3RyYXRpb24iLCJhdWQiOlsiaHR0cDovLzE5Mi4xNjguMTIyLjEwOjMwMDA5L2VsZW1lbnRhbC92MS9uYW1lc3BhY2VzL2RlZmF1bHQvcmVnaXN0cmF0aW9ucy9teS1yZWdpc3RyYXRpb24iXSwibmJmIjoxNjk5ODY0NzIwLCJpYXQiOjE2OTk4NjQ3MjB9.YQsYZoaZ3tGV6z5aXo1e9LmGdA-wQOtmmpi4yAAfXcqh6_S6iIjgblXqw6koQJCzhBMy2-APPQL0ANEBcAljBQ
6062
agent:
6163
# Work directory
6264
workDir: /var/lib/elemental/agent
@@ -77,7 +79,7 @@ agent:
7779
# Enable agent debug logs
7880
debug: false
7981
# Which OS plugin to use
80-
osPlugin: "/usr/lib/elemental/plugins/elemental.so"
82+
osPlugin: /usr/lib/elemental/plugins/elemental.so
8183
# The period used by the agent to sync with the Elemental API
8284
reconciliation: 1m
8385
# Allow 'http' scheme

cmd/agent/main.go

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import (
1010
"github.com/rancher-sandbox/cluster-api-provider-elemental/internal/agent/client"
1111
"github.com/rancher-sandbox/cluster-api-provider-elemental/internal/agent/config"
1212
"github.com/rancher-sandbox/cluster-api-provider-elemental/internal/agent/hostname"
13-
"github.com/rancher-sandbox/cluster-api-provider-elemental/internal/agent/identity"
1413
log "github.com/rancher-sandbox/cluster-api-provider-elemental/internal/agent/log"
1514
"github.com/rancher-sandbox/cluster-api-provider-elemental/internal/agent/utils"
1615
"github.com/rancher-sandbox/cluster-api-provider-elemental/internal/api"
16+
"github.com/rancher-sandbox/cluster-api-provider-elemental/internal/identity"
1717
"github.com/rancher-sandbox/cluster-api-provider-elemental/internal/version"
1818
"github.com/rancher-sandbox/cluster-api-provider-elemental/pkg/agent/osplugin"
1919
"github.com/spf13/cobra"
@@ -50,7 +50,7 @@ var (
5050
func main() {
5151
fs := vfs.OSFS
5252
osPluginLoader := osplugin.NewLoader()
53-
client := client.NewClient()
53+
client := client.NewClient(version.Version)
5454
commandRunner := utils.NewCommandRunner()
5555
cmd := newCommand(fs, osPluginLoader, commandRunner, client)
5656
if err := cmd.Execute(); err != nil {
@@ -103,13 +103,13 @@ func newCommand(fs vfs.FS, pluginLoader osplugin.Loader, commandRunner utils.Com
103103
return fmt.Errorf("Initializing plugin: %w", err)
104104
}
105105
// Initialize Identity
106-
identityManager := identity.NewDummyManager(fs, conf.Agent.WorkDir)
107-
signingKey, err := identityManager.LoadSigningKeyOrCreateNew()
106+
identityManager := identity.NewManager(fs, conf.Agent.WorkDir)
107+
identity, err := identityManager.LoadSigningKeyOrCreateNew()
108108
if err != nil {
109109
return fmt.Errorf("initializing identity: %w", err)
110110
}
111111
// Initialize Elemental API Client
112-
if err := client.Init(fs, signingKey, conf); err != nil {
112+
if err := client.Init(fs, identity, conf); err != nil {
113113
return fmt.Errorf("initializing Elemental API client: %w", err)
114114
}
115115
// Get current hostname
@@ -120,10 +120,14 @@ func newCommand(fs vfs.FS, pluginLoader osplugin.Loader, commandRunner utils.Com
120120
// Register
121121
if registerFlag {
122122
log.Info("Registering Elemental Host")
123+
pubKey, err := identity.MarshalPublic()
124+
if err != nil {
125+
return fmt.Errorf("marshalling host public key: %w", err)
126+
}
123127
var registration *api.RegistrationResponse
124-
hostname, registration = handleRegistration(client, osPlugin, conf.Agent.Reconciliation)
128+
hostname, registration = handleRegistration(client, osPlugin, pubKey, conf.Registration.Token, conf.Agent.Reconciliation)
125129
log.Infof("Successfully registered as '%s'", hostname)
126-
if err := handlePostRegistration(osPlugin, hostname, signingKey, registration); err != nil {
130+
if err := handlePostRegistration(osPlugin, hostname, identity, registration); err != nil {
127131
return fmt.Errorf("handling post registration: %w", err)
128132
}
129133
// Exit program if --install was not called
@@ -134,7 +138,7 @@ func newCommand(fs vfs.FS, pluginLoader osplugin.Loader, commandRunner utils.Com
134138
// Install
135139
if installFlag {
136140
log.Info("Installing Elemental")
137-
handleInstall(client, osPlugin, hostname, conf.Agent.Reconciliation)
141+
handleInstall(client, osPlugin, hostname, conf.Registration.Token, conf.Agent.Reconciliation)
138142
log.Info("Installation successful")
139143
handlePost(osPlugin, conf.Agent.PostInstall.PowerOff, conf.Agent.PostInstall.Reboot)
140144
return nil
@@ -143,7 +147,7 @@ func newCommand(fs vfs.FS, pluginLoader osplugin.Loader, commandRunner utils.Com
143147
// Reset
144148
if resetFlag {
145149
log.Info("Resetting Elemental")
146-
handleReset(client, osPlugin, conf.Agent.Reconciliation, hostname)
150+
handleReset(client, osPlugin, hostname, conf.Registration.Token, conf.Agent.Reconciliation)
147151
log.Info("Reset successful")
148152
handlePost(osPlugin, conf.Agent.PostReset.PowerOff, conf.Agent.PostReset.Reboot)
149153
return nil
@@ -221,7 +225,7 @@ func getConfig(fs vfs.FS) (config.Config, error) {
221225
return conf, nil
222226
}
223227

224-
func handleRegistration(client client.Client, osPlugin osplugin.Plugin, registrationRecoveryPeriod time.Duration) (string, *api.RegistrationResponse) {
228+
func handleRegistration(client client.Client, osPlugin osplugin.Plugin, pubKey []byte, registrationToken string, registrationRecoveryPeriod time.Duration) (string, *api.RegistrationResponse) {
225229
hostnameFormatter := hostname.NewFormatter(osPlugin)
226230
var newHostname string
227231
var registration *api.RegistrationResponse
@@ -235,7 +239,7 @@ func handleRegistration(client client.Client, osPlugin osplugin.Plugin, registra
235239
}
236240
// Fetch remote Registration
237241
log.Debug("Fetching remote registration")
238-
registration, err = client.GetRegistration()
242+
registration, err = client.GetRegistration(registrationToken)
239243
if err != nil {
240244
log.Error(err, "getting remote Registration")
241245
registrationError = true
@@ -257,7 +261,8 @@ func handleRegistration(client client.Client, osPlugin osplugin.Plugin, registra
257261
Name: newHostname,
258262
Annotations: registration.HostAnnotations,
259263
Labels: registration.HostLabels,
260-
}); err != nil {
264+
PubKey: string(pubKey),
265+
}, registrationToken); err != nil {
261266
log.Error(err, "registering new ElementalHost")
262267
registrationError = true
263268
continue
@@ -267,7 +272,7 @@ func handleRegistration(client client.Client, osPlugin osplugin.Plugin, registra
267272
return newHostname, registration
268273
}
269274

270-
func handlePostRegistration(osPlugin osplugin.Plugin, hostnameToSet string, signingKey []byte, registration *api.RegistrationResponse) error {
275+
func handlePostRegistration(osPlugin osplugin.Plugin, hostnameToSet string, id identity.Identity, registration *api.RegistrationResponse) error {
271276
// Persist registered hostname
272277
if err := osPlugin.PersistHostname(hostnameToSet); err != nil {
273278
return fmt.Errorf("persisting hostname '%s': %w", hostnameToSet, err)
@@ -282,14 +287,18 @@ func handlePostRegistration(osPlugin osplugin.Plugin, hostnameToSet string, sign
282287
return fmt.Errorf("persisting agent config file '%s': %w", configPath, err)
283288
}
284289
// Persist identity file
290+
identityBytes, err := id.Marshal()
291+
if err != nil {
292+
return fmt.Errorf("marshalling identity: %w", err)
293+
}
285294
privateKeyPath := fmt.Sprintf("%s/%s", agentConfig.Agent.WorkDir, identity.PrivateKeyFile)
286-
if err := osPlugin.PersistFile(signingKey, privateKeyPath, 0640, 0, 0); err != nil {
295+
if err := osPlugin.PersistFile(identityBytes, privateKeyPath, 0640, 0, 0); err != nil {
287296
return fmt.Errorf("persisting private key file '%s': %w", privateKeyPath, err)
288297
}
289298
return nil
290299
}
291300

292-
func handleInstall(client client.Client, osPlugin osplugin.Plugin, hostname string, installationRecoveryPeriod time.Duration) {
301+
func handleInstall(client client.Client, osPlugin osplugin.Plugin, hostname string, registrationToken string, installationRecoveryPeriod time.Duration) {
293302
cloudConfigAlreadyApplied := false
294303
alreadyInstalled := false
295304
installationError := false
@@ -304,7 +313,7 @@ func handleInstall(client client.Client, osPlugin osplugin.Plugin, hostname stri
304313
var err error
305314
if !cloudConfigAlreadyApplied || !alreadyInstalled {
306315
log.Debug("Fetching remote registration")
307-
registration, err = client.GetRegistration()
316+
registration, err = client.GetRegistration(registrationToken)
308317
if err != nil {
309318
log.Error(err, "getting remote Registration")
310319
installationError = true
@@ -354,7 +363,7 @@ func handleInstall(client client.Client, osPlugin osplugin.Plugin, hostname stri
354363
}
355364
}
356365

357-
func handleReset(client client.Client, osPlugin osplugin.Plugin, resetRecoveryPeriod time.Duration, hostname string) {
366+
func handleReset(client client.Client, osPlugin osplugin.Plugin, hostname string, registrationToken string, resetRecoveryPeriod time.Duration) {
358367
resetError := false
359368
alreadyReset := false
360369
for {
@@ -375,7 +384,7 @@ func handleReset(client client.Client, osPlugin osplugin.Plugin, resetRecoveryPe
375384
if !alreadyReset {
376385
// Fetch remote Registration
377386
log.Debug("Fetching remote registration")
378-
registration, err := client.GetRegistration()
387+
registration, err := client.GetRegistration(registrationToken)
379388
if err != nil {
380389
log.Error(err, "getting remote Registration")
381390
resetError = true

0 commit comments

Comments
 (0)