313 lines
10 KiB
Go
313 lines
10 KiB
Go
package controllers
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"maps"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"github.com/rancher/scc-operator/internal/consts"
|
|
coreUtil "github.com/rancher/scc-operator/internal/initializer"
|
|
v1 "github.com/rancher/scc-operator/pkg/apis/scc.cattle.io/v1"
|
|
"github.com/rancher/scc-operator/pkg/controllers/shared"
|
|
"github.com/rancher/scc-operator/pkg/util"
|
|
"github.com/rancher/scc-operator/pkg/util/log"
|
|
"github.com/rancher/scc-operator/pkg/util/salt"
|
|
)
|
|
|
|
type HashType int
|
|
|
|
const (
|
|
NameHash HashType = iota
|
|
ContentHash
|
|
)
|
|
|
|
func (ht *HashType) String() string {
|
|
if *ht == NameHash {
|
|
return "name"
|
|
}
|
|
|
|
return "content"
|
|
}
|
|
|
|
type hashCleanupRequest struct {
|
|
hash string
|
|
hashType any
|
|
}
|
|
|
|
const (
|
|
dataKeyRegistrationType = "registrationType"
|
|
)
|
|
|
|
func (h *handler) isSCCEntrypointSecret(secretObj *corev1.Secret) bool {
|
|
if secretObj.Name != consts.ResourceSCCEntrypointSecretName || secretObj.Namespace != h.options.SystemNamespace() {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// prepareSecretSalt applies an instance salt onto an entrypoint secret used to create randomness in hashes
|
|
func (h *handler) prepareSecretSalt(secret *corev1.Secret) (*corev1.Secret, error) {
|
|
h.log.Debugf("Preparing salt for secret %s/%s", secret.Namespace, secret.Name)
|
|
preparedSecret := secret.DeepCopy()
|
|
generatedSalt := salt.NewSaltGen(nil, nil).GenerateSalt()
|
|
|
|
existingLabels := make(map[string]string)
|
|
if objLabels := secret.GetLabels(); objLabels != nil {
|
|
existingLabels = objLabels
|
|
}
|
|
existingLabels[consts.LabelObjectSalt] = generatedSalt
|
|
preparedSecret.SetLabels(existingLabels)
|
|
|
|
h.log.Debugf("Prepared salt for secret %s/%s", secret.Namespace, secret.Name)
|
|
_, updateErr := h.secretRepo.RetryingPatchUpdate(secret, preparedSecret)
|
|
if updateErr != nil {
|
|
h.log.Error("error applying metadata updates to default SCC registration secret; cannot initialize secret salt value")
|
|
return nil, updateErr
|
|
}
|
|
h.log.Debugf("Added salt to secret %s/%s", secret.Namespace, secret.Name)
|
|
|
|
return secret, nil
|
|
}
|
|
|
|
func getCurrentRegURL(secret *corev1.Secret) (regURL []byte) {
|
|
regURLBytes, ok := secret.Data[consts.RegistrationURL]
|
|
if ok {
|
|
return regURLBytes
|
|
}
|
|
if util.HasGlobalPrimeRegistrationURL() {
|
|
globalRegistrationURL := util.GetGlobalPrimeRegistrationURL()
|
|
return []byte(globalRegistrationURL)
|
|
}
|
|
if coreUtil.DevMode.Get() {
|
|
return []byte(consts.StagingSccURL)
|
|
}
|
|
return []byte{}
|
|
}
|
|
|
|
// extractRegistrationParamsFromSecret will extract secret data and prepare it into a RegistrationParams
|
|
func extractRegistrationParamsFromSecret(secret *corev1.Secret, managedByName string) (RegistrationParams, error) {
|
|
extractParamsLog := log.NewComponentLogger("params-extractor")
|
|
incomingSalt := []byte(secret.GetLabels()[consts.LabelObjectSalt])
|
|
extractParamsLog.Debugf("extracting registration params from secret %s/%s - with salt %s", secret.Namespace, secret.Name, incomingSalt)
|
|
|
|
regMode := v1.RegistrationModeOnline
|
|
regType, ok := secret.Data[dataKeyRegistrationType]
|
|
if !ok || len(regType) == 0 {
|
|
extractParamsLog.Warnf("secret does not have the `%s` field, defaulting to %s", dataKeyRegistrationType, regMode)
|
|
} else {
|
|
regMode = v1.RegistrationMode(regType)
|
|
if !regMode.Valid() {
|
|
return RegistrationParams{}, fmt.Errorf("invalid registration mode %s", string(regMode))
|
|
}
|
|
}
|
|
extractParamsLog.Debugf("incoming %s/%s secret params mode: %s", secret.Namespace, secret.Name, string(regMode))
|
|
|
|
regCode, ok := secret.Data[consts.SecretKeyRegistrationCode]
|
|
if !ok || len(regCode) == 0 {
|
|
if regMode == v1.RegistrationModeOnline {
|
|
return RegistrationParams{}, fmt.Errorf("secret does not have data %s; this is required in online mode", consts.SecretKeyRegistrationCode)
|
|
}
|
|
}
|
|
|
|
offlineRegCertData, certOk := secret.Data[consts.SecretKeyOfflineRegCert]
|
|
hasOfflineCert := certOk && len(offlineRegCertData) > 0
|
|
|
|
// TODO: when RMT needs to be supported eventually we need to accept Reg URL and Reg Server Cert.
|
|
var regURLBytes []byte
|
|
regURLString := ""
|
|
if regMode == v1.RegistrationModeOnline {
|
|
regURLBytes = getCurrentRegURL(secret)
|
|
regURLString = string(regURLBytes)
|
|
}
|
|
|
|
hasher := md5.New()
|
|
nameData := append(incomingSalt, regType...)
|
|
nameData = append(nameData, regCode...)
|
|
nameData = append(nameData, regURLBytes...)
|
|
data := append(nameData, offlineRegCertData...)
|
|
|
|
// Generate a hash for the name data
|
|
if _, err := hasher.Write(nameData); err != nil {
|
|
return RegistrationParams{}, fmt.Errorf("failed to hash name data: %v", err)
|
|
}
|
|
nameID := hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
// Generate hash for the content data
|
|
if _, err := hasher.Write(data); err != nil {
|
|
return RegistrationParams{}, fmt.Errorf("failed to hash data: %v", err)
|
|
}
|
|
contentsID := hex.EncodeToString(hasher.Sum(nil))
|
|
extractParamsLog.Debugf("incoming %s/%s secret hashes; name hash: %s, content hash: %s", secret.Namespace, secret.Name, nameID, contentsID)
|
|
|
|
return RegistrationParams{
|
|
managedByName: managedByName,
|
|
regType: regMode,
|
|
nameID: nameID,
|
|
contentHash: contentsID,
|
|
regCode: regCode,
|
|
regCodeSecretRef: &corev1.SecretReference{
|
|
Name: consts.RegistrationCodeSecretName(nameID),
|
|
Namespace: secret.Namespace,
|
|
},
|
|
hasOfflineCertData: hasOfflineCert,
|
|
offlineCertData: &offlineRegCertData,
|
|
offlineCertSecretRef: &corev1.SecretReference{
|
|
Name: consts.OfflineCertificateSecretName(nameID),
|
|
Namespace: secret.Namespace,
|
|
},
|
|
regURL: regURLString,
|
|
}, nil
|
|
}
|
|
|
|
type RegistrationParams struct {
|
|
managedByName string
|
|
regType v1.RegistrationMode
|
|
nameID string
|
|
contentHash string
|
|
regCode []byte
|
|
regCodeSecretRef *corev1.SecretReference
|
|
regURL string
|
|
hasOfflineCertData bool
|
|
offlineCertData *[]byte
|
|
offlineCertSecretRef *corev1.SecretReference
|
|
}
|
|
|
|
// Labels produces the labels to apply to related resources
|
|
func (r RegistrationParams) Labels() map[string]string {
|
|
return map[string]string{
|
|
consts.LabelNameSuffix: r.nameID,
|
|
consts.LabelSccHash: r.contentHash,
|
|
consts.LabelSccManagedBy: r.managedByName + "/" + consts.ManagedByValueSecretBroker,
|
|
consts.LabelK8sManagedBy: r.managedByName,
|
|
}
|
|
}
|
|
|
|
// registrationFromSecretEntrypoint will produce a valid Registration object desired state based on Entrypoint secret
|
|
func (h *handler) registrationFromSecretEntrypoint(
|
|
params RegistrationParams,
|
|
) (*v1.Registration, error) {
|
|
if !params.regType.Valid() {
|
|
return nil, fmt.Errorf(
|
|
"invalid registration type %s, must be one of %s or %s",
|
|
params.regType,
|
|
v1.RegistrationModeOnline,
|
|
v1.RegistrationModeOffline,
|
|
)
|
|
}
|
|
|
|
hashedName := fmt.Sprintf("scc-registration-%s", params.nameID)
|
|
var reg *v1.Registration
|
|
var err error
|
|
|
|
reg, err = h.registrationCache.Get(hashedName)
|
|
if err != nil && apierrors.IsNotFound(err) {
|
|
reg = &v1.Registration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: hashedName,
|
|
},
|
|
}
|
|
}
|
|
if reg.Labels == nil {
|
|
reg.Labels = map[string]string{}
|
|
}
|
|
maps.Copy(reg.Labels, params.Labels())
|
|
|
|
reg.Spec = paramsToRegSpec(params)
|
|
if !shared.RegistrationHasManagedFinalizer(reg) {
|
|
reg = shared.RegistrationAddManagedFinalizer(reg)
|
|
}
|
|
|
|
return reg, nil
|
|
}
|
|
|
|
// paramsToRegSpec transforms our configured params into a valid Registration Spec
|
|
func paramsToRegSpec(params RegistrationParams) v1.RegistrationSpec {
|
|
regSpec := v1.RegistrationSpec{
|
|
Mode: params.regType,
|
|
}
|
|
|
|
if params.regType == v1.RegistrationModeOnline {
|
|
regSpec.RegistrationRequest = &v1.RegistrationRequest{
|
|
RegistrationCodeSecretRef: params.regCodeSecretRef,
|
|
}
|
|
} else if params.regType == v1.RegistrationModeOffline && params.hasOfflineCertData {
|
|
regSpec.OfflineRegistrationCertificateSecretRef = params.offlineCertSecretRef
|
|
}
|
|
|
|
// check if params has regURL and use, otherwise check if devmode and when true use staging Scc url
|
|
if params.regURL != "" {
|
|
regSpec.RegistrationRequest.RegistrationAPIUrl = ¶ms.regURL
|
|
}
|
|
|
|
return regSpec
|
|
}
|
|
|
|
// regCodeFromSecretEntrypoint fetches the registration code provided by an entrypoint secret
|
|
func (h *handler) regCodeFromSecretEntrypoint(params RegistrationParams) (*corev1.Secret, error) {
|
|
secretName := params.regCodeSecretRef.Name
|
|
|
|
regcodeSecret, err := h.secretRepo.Cache.Get(h.options.SystemNamespace(), secretName)
|
|
if err != nil && apierrors.IsNotFound(err) {
|
|
regcodeSecret = &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: h.options.SystemNamespace(),
|
|
Name: secretName,
|
|
},
|
|
Data: map[string][]byte{
|
|
consts.SecretKeyRegistrationCode: params.regCode,
|
|
},
|
|
}
|
|
}
|
|
|
|
if regcodeSecret.Labels == nil {
|
|
regcodeSecret.Labels = map[string]string{}
|
|
}
|
|
defaultLabels := params.Labels()
|
|
defaultLabels[consts.LabelSccSecretRole] = string(consts.RegistrationCode)
|
|
maps.Copy(regcodeSecret.Labels, defaultLabels)
|
|
|
|
if !shared.SecretHasRegCodeFinalizer(regcodeSecret) {
|
|
regcodeSecret = shared.SecretAddRegCodeFinalizer(regcodeSecret)
|
|
}
|
|
|
|
return regcodeSecret, nil
|
|
}
|
|
|
|
// offlineCertFromSecretEntrypoint helps to extract and prepare the Offline Cert secret for creation based on entrypoint secret
|
|
func (h *handler) offlineCertFromSecretEntrypoint(params RegistrationParams) (*corev1.Secret, error) {
|
|
secretName := consts.OfflineCertificateSecretName(params.nameID)
|
|
|
|
offlineCertSecret, err := h.secretRepo.Cache.Get(h.options.SystemNamespace(), secretName)
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
offlineCertSecret = &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: h.options.SystemNamespace(),
|
|
Name: secretName,
|
|
},
|
|
Data: map[string][]byte{
|
|
consts.SecretKeyOfflineRegCert: *params.offlineCertData,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
if offlineCertSecret.Labels == nil {
|
|
offlineCertSecret.Labels = map[string]string{}
|
|
}
|
|
defaultLabels := params.Labels()
|
|
defaultLabels[consts.LabelSccSecretRole] = string(consts.OfflineCertificate)
|
|
maps.Copy(offlineCertSecret.Labels, defaultLabels)
|
|
|
|
if !shared.SecretHasOfflineFinalizer(offlineCertSecret) {
|
|
offlineCertSecret = shared.SecretAddOfflineFinalizer(offlineCertSecret)
|
|
}
|
|
|
|
return offlineCertSecret, nil
|
|
}
|