Skip to content

Commit

Permalink
Return registration errors to client (#301)
Browse files Browse the repository at this point in the history
* Return registration errors to client

Introduces two new message-types (MsgError and MsgConfig).

MsgError is sent when an error is encountered during the registration
process.

MsgConfig is used to send the elemental configuration to the client,
before this was just a raw message with no type so we need to check in
the server if the client supports the message, otherwise fallback to the
raw message.

Signed-off-by: Fredrik Lönnegren <fredrik.lonnegren@suse.com>

* Change registration error message

unknown -> unexpected

Co-authored-by: Francesco Giudici <francesco.giudici@gmail.com>
Signed-off-by: Fredrik Lönnegren <fredrik.lonnegren@gmail.com>

* Remove InventoryServer receiver argument

From writeError method

Co-authored-by: Francesco Giudici <francesco.giudici@gmail.com>
Signed-off-by: Fredrik Lönnegren <fredrik.lonnegren@suse.com>
  • Loading branch information
frelon and fgiudici committed Dec 20, 2022
1 parent 4658997 commit 2003655
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 49 deletions.
15 changes: 9 additions & 6 deletions cmd/register/main.go
Expand Up @@ -24,10 +24,6 @@ import (
"time"

"github.com/mudler/yip/pkg/schema"
elementalv1 "github.com/rancher/elemental-operator/api/v1beta1"
"github.com/rancher/elemental-operator/pkg/elementalcli"
"github.com/rancher/elemental-operator/pkg/register"
"github.com/rancher/elemental-operator/pkg/version"
agent "github.com/rancher/system-agent/pkg/config"
"github.com/sanity-io/litter"
"github.com/sirupsen/logrus"
Expand All @@ -37,6 +33,11 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"

elementalv1 "github.com/rancher/elemental-operator/api/v1beta1"
"github.com/rancher/elemental-operator/pkg/elementalcli"
"github.com/rancher/elemental-operator/pkg/register"
"github.com/rancher/elemental-operator/pkg/version"
)

const (
Expand Down Expand Up @@ -133,8 +134,10 @@ func run(config elementalv1.Config) {
logrus.Fatal("Registration URL is empty")
}

var err error
var data, caCert []byte
var (
err error
data, caCert []byte
)

/* Here we can have a file path or the cert data itself */
_, err = os.Stat(registration.CACert)
Expand Down
30 changes: 25 additions & 5 deletions pkg/register/register.go
Expand Up @@ -30,11 +30,13 @@ import (
"github.com/gorilla/websocket"
"github.com/jaypipes/ghw"
"github.com/pkg/errors"
"github.com/sanity-io/litter"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v1"

"github.com/rancher/elemental-operator/pkg/dmidecode"
"github.com/rancher/elemental-operator/pkg/hostinfo"
"github.com/rancher/elemental-operator/pkg/tpm"
"github.com/sanity-io/litter"
"github.com/sirupsen/logrus"
)

func Register(url string, caCert []byte, smbios bool, emulateTPM bool, emulatedSeed int64) ([]byte, error) {
Expand Down Expand Up @@ -100,11 +102,29 @@ func Register(url string, caCert []byte, smbios bool, emulateTPM bool, emulatedS
if err := WriteMessage(conn, MsgGet, []byte{}); err != nil {
return nil, fmt.Errorf("request elemental configuration: %w", err)
}
_, r, err := conn.NextReader()

msgType, data, err := ReadMessage(conn)
if err != nil {
return nil, fmt.Errorf("read elemental configuration: %w", err)
return nil, fmt.Errorf("read configuration response: %w", err)
}

logrus.Debugf("Got configuration response: %s", msgType.String())

switch msgType {
case MsgError:
msg := &ErrorMessage{}
if err = yaml.Unmarshal(data, &msg); err != nil {
return nil, errors.Wrap(err, "unable to unmarshal error-message")
}

return nil, errors.New(msg.Message)

case MsgConfig:
return data, nil

default:
return nil, fmt.Errorf("unexpected response message: %s", msgType)
}
return io.ReadAll(r)
}

func initWebsocketConn(url string, caCert []byte, tpmAuth *tpm.AuthClient) (*websocket.Conn, error) {
Expand Down
24 changes: 19 additions & 5 deletions pkg/register/websocket.go
Expand Up @@ -36,7 +36,9 @@ const (
MsgGet
MsgVersion
MsgSystemData
MsgLast = MsgSystemData // MsgLast must point to the last message
MsgConfig
MsgError
MsgLast = MsgError // MsgLast must point to the last message
)

func (mt MessageType) String() string {
Expand All @@ -55,11 +57,25 @@ func (mt MessageType) String() string {
return "Version"
case MsgSystemData:
return "System"
case MsgConfig:
return "Config"
case MsgError:
return "Error"
default:
return "Unknown"
}
}

type ErrorMessage struct {
Message string `json:"message,omitempty" yaml:"message"`
}

func NewErrorMessage(err error) ErrorMessage {
return ErrorMessage{
Message: err.Error(),
}
}

// ReadMessage reads from the websocket connection returning the MessageType
// and the actual data
func ReadMessage(conn *websocket.Conn) (MessageType, []byte, error) {
Expand Down Expand Up @@ -89,10 +105,8 @@ func WriteMessage(conn *websocket.Conn, msgType MessageType, data []byte) error
defer w.Close()

buf := insertMessageType(msgType, data)
if _, err := w.Write(buf); err != nil {
return err
}
return nil
_, err = w.Write(buf)
return err
}

// SendJSONData transmits json encoded data to the websocket connection, attaching to
Expand Down
103 changes: 78 additions & 25 deletions pkg/server/register.go
Expand Up @@ -29,14 +29,15 @@ import (

"github.com/google/uuid"
"github.com/gorilla/websocket"
elementalv1 "github.com/rancher/elemental-operator/api/v1beta1"
"github.com/rancher/elemental-operator/pkg/hostinfo"
"github.com/rancher/elemental-operator/pkg/register"
values "github.com/rancher/wrangler/pkg/data"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"

elementalv1 "github.com/rancher/elemental-operator/api/v1beta1"
"github.com/rancher/elemental-operator/pkg/hostinfo"
"github.com/rancher/elemental-operator/pkg/register"
)

var (
Expand Down Expand Up @@ -144,7 +145,6 @@ func (i *InventoryServer) unauthenticatedResponse(registration *elementalv1.Mach
}

func (i *InventoryServer) createMachineInventory(inventory *elementalv1.MachineInventory) (*elementalv1.MachineInventory, error) {

if inventory.Spec.TPMHash == "" {
return nil, fmt.Errorf("machine inventory TPMHash is empty")
}
Expand Down Expand Up @@ -225,7 +225,7 @@ func (i *InventoryServer) getMachineRegistration(req *http.Request) (*elementalv
return mRegistration, nil
}

func (i *InventoryServer) writeMachineInventoryCloudConfig(conn *websocket.Conn, inventory *elementalv1.MachineInventory, registration *elementalv1.MachineRegistration) error {
func (i *InventoryServer) writeMachineInventoryCloudConfig(conn *websocket.Conn, protoVersion register.MessageType, inventory *elementalv1.MachineInventory, registration *elementalv1.MachineRegistration) error {
sa := &corev1.ServiceAccount{}

if err := i.Get(i, types.NamespacedName{
Expand Down Expand Up @@ -254,17 +254,11 @@ func (i *InventoryServer) writeMachineInventoryCloudConfig(conn *websocket.Conn,
return fmt.Errorf("failed to get server-url: %w", err)
}

writer, err := conn.NextWriter(websocket.BinaryMessage)
if err != nil {
return err
}
defer writer.Close()

if registration.Spec.Config == nil {
registration.Spec.Config = &elementalv1.Config{}
}

return yaml.NewEncoder(writer).Encode(elementalv1.Config{
config := elementalv1.Config{
Elemental: elementalv1.Elemental{
Registration: elementalv1.Registration{
URL: registration.Status.RegistrationURL,
Expand All @@ -279,7 +273,32 @@ func (i *InventoryServer) writeMachineInventoryCloudConfig(conn *websocket.Conn,
Install: registration.Spec.Config.Elemental.Install,
},
CloudConfig: registration.Spec.Config.CloudConfig,
})
}

// If client does not support MsgConfig we send back the config as a
// byte-stream without a message-type to keep backwards-compatibility.
if protoVersion < register.MsgConfig {
logrus.Debug("Client does not support MsgConfig, sending back raw config.")

writer, err := conn.NextWriter(websocket.BinaryMessage)
if err != nil {
return err
}
defer writer.Close()

return yaml.NewEncoder(writer).Encode(config)
}

logrus.Debug("Client supports MsgConfig, sending back config.")

// If the client supports the MsgConfig-message we use that.
data, err := yaml.Marshal(config)
if err != nil {
logrus.Errorf("error marshalling config: %v", err)
return err
}

return register.WriteMessage(conn, register.MsgConfig, data)
}

func buildStringFromSmbiosData(data map[string]interface{}, name string) string {
Expand Down Expand Up @@ -368,18 +387,7 @@ func (i *InventoryServer) serveLoop(conn *websocket.Conn, inventory *elementalv1
return err
}
case register.MsgGet:
inventory, err = i.commitMachineInventory(inventory)
if err != nil {
return err
}
if err := i.writeMachineInventoryCloudConfig(conn, inventory, registration); err != nil {
return fmt.Errorf("failed sending elemental cloud config: %w", err)
}
logrus.Debug("Elemental cloud config sent")
if protoVersion == register.MsgUndefined {
logrus.Warn("Detected old elemental-register client: cloud-config data may not be applied correctly")
}
return nil
return i.handleGet(conn, protoVersion, inventory, registration)
case register.MsgSystemData:
err = updateInventoryFromSystemData(data, inventory)
if err != nil {
Expand All @@ -395,6 +403,51 @@ func (i *InventoryServer) serveLoop(conn *websocket.Conn, inventory *elementalv1
}
}

func (i *InventoryServer) handleGet(conn *websocket.Conn, protoVersion register.MessageType, inventory *elementalv1.MachineInventory, registration *elementalv1.MachineRegistration) error {
var err error

inventory, err = i.commitMachineInventory(inventory)
if err != nil {
if writeErr := writeError(conn, protoVersion, register.NewErrorMessage(err)); writeErr != nil {
logrus.Errorf("Error reporting back error to client: %v", writeErr)
}

return err
}

if err := i.writeMachineInventoryCloudConfig(conn, protoVersion, inventory, registration); err != nil {
if writeErr := writeError(conn, protoVersion, register.NewErrorMessage(err)); writeErr != nil {
logrus.Errorf("Error reporting back error to client: %v", writeErr)
}

return fmt.Errorf("failed sending elemental cloud config: %w", err)
}

logrus.Debug("Elemental cloud config sent")

if protoVersion == register.MsgUndefined {
logrus.Warn("Detected old elemental-register client: cloud-config data may not be applied correctly")
}

return nil
}

// writeError reports back an error to the client if the negotiated protocol
// version supports it.
func writeError(conn *websocket.Conn, protoVersion register.MessageType, errorMessage register.ErrorMessage) error {
if protoVersion < register.MsgError {
logrus.Debugf("client does not support reporting errors, skipping")
return nil
}

data, err := yaml.Marshal(errorMessage)
if err != nil {
return fmt.Errorf("error marshalling error-message: %w", err)
}

return register.WriteMessage(conn, register.MsgError, data)
}

func decodeProtocolVersion(data []byte) (register.MessageType, error) {
protoVersion := register.MsgUndefined

Expand Down

0 comments on commit 2003655

Please sign in to comment.