elemental-operator/pkg/server/server.go

201 lines
6.5 KiB
Go

/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/pkg/errors"
v1 "github.com/rancher-sandbox/rancheros-operator/pkg/apis/rancheros.cattle.io/v1"
"github.com/rancher-sandbox/rancheros-operator/pkg/clients"
ranchercontrollers "github.com/rancher-sandbox/rancheros-operator/pkg/generated/controllers/management.cattle.io/v3"
roscontrollers "github.com/rancher-sandbox/rancheros-operator/pkg/generated/controllers/rancheros.cattle.io/v1"
"github.com/rancher-sandbox/rancheros-operator/pkg/tpm"
v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
var (
tokenType = "rancheros.cattle.io/token"
tokenKey = "token"
tokenIndex = "tokenIndex"
machineBySecretNameIndex = "machineBySecretNameIndex"
registrationTokenIndex = "registrationTokenIndex"
tpmHashIndex = "tpmHashIndex"
)
type authenticator interface {
Authenticate(resp http.ResponseWriter, req *http.Request, registerNamespace string) (*v1.MachineInventory, bool, io.WriteCloser, error)
}
type InventoryServer struct {
settingCache ranchercontrollers.SettingCache
secretCache corecontrollers.SecretCache
machineCache roscontrollers.MachineInventoryCache
machineClient roscontrollers.MachineInventoryClient
machineRegistrationCache roscontrollers.MachineRegistrationCache
clusterRegistrationToken ranchercontrollers.ClusterRegistrationTokenCache
authenticators []authenticator
}
func New(clients *clients.Clients) *InventoryServer {
server := &InventoryServer{
authenticators: []authenticator{
tpm.New(clients),
newSharedSecretAuth(clients),
},
secretCache: clients.Core.Secret().Cache(),
machineCache: clients.OS.MachineInventory().Cache(),
machineClient: clients.OS.MachineInventory(),
machineRegistrationCache: clients.OS.MachineRegistration().Cache(),
settingCache: clients.Rancher.Setting().Cache(),
clusterRegistrationToken: clients.Rancher.ClusterRegistrationToken().Cache(),
}
server.secretCache.AddIndexer(tokenHash, func(obj *corev1.Secret) ([]string, error) {
if string(obj.Type) != tokenType {
return nil, nil
}
if token := obj.Data[tokenKey]; len(token) > 0 {
hash := sha256.Sum256(token)
return []string{base64.StdEncoding.EncodeToString(hash[:])}, nil
}
return nil, nil
})
server.machineCache.AddIndexer(tokenHash, func(obj *v1.MachineInventory) ([]string, error) {
if obj.Spec.TPMHash == "" {
return nil, nil
}
hash := sha256.Sum256([]byte(obj.Spec.TPMHash))
return []string{base64.StdEncoding.EncodeToString(hash[:])}, nil
})
server.machineRegistrationCache.AddIndexer(registrationTokenIndex, func(obj *v1.MachineRegistration) ([]string, error) {
if obj.Status.RegistrationToken == "" {
return nil, nil
}
return []string{
obj.Status.RegistrationToken,
}, nil
})
server.machineCache.AddIndexer(tpmHashIndex, func(obj *v1.MachineInventory) ([]string, error) {
if obj.Spec.TPMHash == "" {
return nil, nil
}
return []string{obj.Spec.TPMHash}, nil
})
return server
}
func (i *InventoryServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
if strings.Contains(req.URL.Path, "/registration/") {
i.register(resp, req)
} else if strings.HasSuffix(req.URL.Path, "/cacerts") {
i.cacerts(resp, req)
} else {
i.handle(resp, req)
}
}
func (i *InventoryServer) authMachine(resp http.ResponseWriter, req *http.Request, registerNamespace string) (*v1.MachineInventory, io.WriteCloser, error) {
for _, auth := range i.authenticators {
machine, cont, writer, err := auth.Authenticate(resp, req, registerNamespace)
if err != nil {
return nil, nil, err
}
if machine != nil || !cont {
return machine, writer, nil
}
}
return nil, nil, nil
}
func writeErr(writer io.Writer, resp http.ResponseWriter, err error) {
message := "Unauthorized"
if err != nil {
message = err.Error()
}
if writer == nil {
http.Error(resp, message, http.StatusUnauthorized)
} else {
_, _ = writer.Write([]byte(message))
}
}
func (i *InventoryServer) handle(resp http.ResponseWriter, req *http.Request) {
machine, writer, err := i.authMachine(resp, req, "")
if machine == nil || err != nil {
writeErr(writer, resp, err)
return
}
defer writer.Close()
if machine.Spec.ClusterName == "" {
writeErr(writer, resp, errors.New("cluster not assigned"))
return
}
crt, err := i.clusterRegistrationToken.Get(machine.Status.ClusterRegistrationTokenNamespace,
machine.Status.ClusterRegistrationTokenName)
if apierrors.IsNotFound(err) || crt.Status.Token == "" {
writeErr(writer, resp, errors.New("cluster token not assigned"))
}
if err := writeResponse(writer, machine, crt); err != nil {
writeErr(writer, resp, err)
}
}
type config struct {
Role string `json:"role,omitempty"`
NodeName string `json:"nodeName,omitempty"`
Address string `json:"address,omitempty"`
InternalAddress string `json:"internalAddress,omitempty"`
Taints []string `json:"taints,omitempty"`
Labels []string `json:"labels,omitempty"`
Token string `json:"token,omitempty"`
}
func writeResponse(writer io.Writer, inventory *v1.MachineInventory, crt *v3.ClusterRegistrationToken) error {
config := config{
Role: inventory.Spec.Config.Role,
NodeName: inventory.Spec.Config.NodeName,
Address: inventory.Spec.Config.Address,
InternalAddress: inventory.Spec.Config.InternalAddress,
Taints: nil,
Labels: nil,
Token: crt.Status.Token,
}
for k, v := range inventory.Spec.Config.Labels {
config.Labels = append(config.Labels, fmt.Sprintf("%s=%s", k, v))
}
for _, taint := range inventory.Spec.Config.Taints {
config.Labels = append(config.Labels, taint.ToString())
}
return json.NewEncoder(writer).Encode(config)
}