* clean up older secret versions * start gargbage collection worker as background service * make gen-go * fix typo * make update-workspace * undo go mod changes * undo go work sum changes * Update pkg/registry/apis/secret/garbagecollectionworker/worker.go Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> * Update pkg/registry/apis/secret/garbagecollectionworker/worker.go Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> * default gc_worker_batch_size to 1 minute * fix typo * fix typo * add test to ensure cleaning up secure values is idempotent * make gen-go * make update-workspace * undo go.mod and .sum changes * undo enterprise imports --------- Co-authored-by: Matheus Macabu <macabu.matheus@gmail.com> Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>
262 lines
7.6 KiB
Go
262 lines
7.6 KiB
Go
package metadata
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
|
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
)
|
|
|
|
type secureValueDB struct {
|
|
// Kubernetes Metadata
|
|
GUID string
|
|
Name string
|
|
Namespace string
|
|
Annotations string // map[string]string
|
|
Labels string // map[string]string
|
|
Created int64
|
|
CreatedBy string
|
|
Updated int64
|
|
UpdatedBy string
|
|
OwnerReferenceAPIGroup sql.NullString
|
|
OwnerReferenceAPIVersion sql.NullString
|
|
OwnerReferenceKind sql.NullString
|
|
OwnerReferenceName sql.NullString
|
|
|
|
// Kubernetes Status
|
|
Active bool
|
|
Version int64
|
|
|
|
// Spec
|
|
Description string
|
|
Keeper sql.NullString
|
|
Decrypters sql.NullString
|
|
Ref sql.NullString
|
|
ExternalID string
|
|
}
|
|
|
|
// toKubernetes maps a DB row into a Kubernetes resource (metadata + spec).
|
|
func (sv *secureValueDB) toKubernetes() (*secretv1beta1.SecureValue, error) {
|
|
annotations := make(map[string]string, 0)
|
|
if sv.Annotations != "" {
|
|
if err := json.Unmarshal([]byte(sv.Annotations), &annotations); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal annotations: %w", err)
|
|
}
|
|
}
|
|
|
|
labels := make(map[string]string, 0)
|
|
if sv.Labels != "" {
|
|
if err := json.Unmarshal([]byte(sv.Labels), &labels); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal labels: %w", err)
|
|
}
|
|
}
|
|
|
|
decrypters := make([]string, 0)
|
|
|
|
if sv.Decrypters.Valid && sv.Decrypters.String != "" {
|
|
if err := json.Unmarshal([]byte(sv.Decrypters.String), &decrypters); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal decrypters: %w", err)
|
|
}
|
|
}
|
|
|
|
resource := &secretv1beta1.SecureValue{
|
|
Spec: secretv1beta1.SecureValueSpec{
|
|
Description: sv.Description,
|
|
Decrypters: decrypters,
|
|
},
|
|
Status: secretv1beta1.SecureValueStatus{
|
|
ExternalID: sv.ExternalID,
|
|
Version: sv.Version,
|
|
},
|
|
}
|
|
|
|
if sv.Keeper.Valid {
|
|
resource.Spec.Keeper = &sv.Keeper.String
|
|
}
|
|
if sv.Ref.Valid {
|
|
resource.Spec.Ref = &sv.Ref.String
|
|
}
|
|
|
|
// Set all meta fields here for consistency.
|
|
meta, err := utils.MetaAccessor(resource)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get meta accessor: %w", err)
|
|
}
|
|
|
|
updated := time.Unix(sv.Updated, 0).UTC()
|
|
|
|
meta.SetUID(types.UID(sv.GUID))
|
|
meta.SetName(sv.Name)
|
|
meta.SetNamespace(sv.Namespace)
|
|
meta.SetAnnotations(annotations)
|
|
meta.SetLabels(labels)
|
|
meta.SetCreatedBy(sv.CreatedBy)
|
|
meta.SetCreationTimestamp(metav1.NewTime(time.Unix(sv.Created, 0).UTC()))
|
|
meta.SetUpdatedBy(sv.UpdatedBy)
|
|
meta.SetUpdatedTimestamp(&updated)
|
|
meta.SetResourceVersionInt64(sv.Updated)
|
|
|
|
hasOwnerReference := sv.OwnerReferenceAPIGroup.Valid && sv.OwnerReferenceAPIGroup.String != "" &&
|
|
sv.OwnerReferenceAPIVersion.Valid && sv.OwnerReferenceAPIVersion.String != "" &&
|
|
sv.OwnerReferenceKind.Valid && sv.OwnerReferenceKind.String != "" &&
|
|
sv.OwnerReferenceName.Valid && sv.OwnerReferenceName.String != ""
|
|
if hasOwnerReference {
|
|
meta.SetOwnerReferences([]metav1.OwnerReference{
|
|
{
|
|
APIVersion: schema.GroupVersion{Group: sv.OwnerReferenceAPIGroup.String, Version: sv.OwnerReferenceAPIVersion.String}.String(),
|
|
Kind: sv.OwnerReferenceKind.String,
|
|
Name: sv.OwnerReferenceName.String,
|
|
},
|
|
})
|
|
}
|
|
|
|
return resource, nil
|
|
}
|
|
|
|
// toCreateRow maps a Kubernetes resource into a DB row for new resources being created/inserted.
|
|
func toCreateRow(now time.Time, sv *secretv1beta1.SecureValue, actorUID string) (*secureValueDB, error) {
|
|
row, err := toRow(sv, "")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert SecureValue to secureValueDB: %w", err)
|
|
}
|
|
|
|
timestamp := now.UTC().Unix()
|
|
|
|
row.GUID = uuid.New().String()
|
|
row.Created = timestamp
|
|
row.CreatedBy = actorUID
|
|
row.Updated = timestamp
|
|
row.UpdatedBy = actorUID
|
|
|
|
return row, nil
|
|
}
|
|
|
|
// toRow maps a Kubernetes resource into a DB row.
|
|
func toRow(sv *secretv1beta1.SecureValue, externalID string) (*secureValueDB, error) {
|
|
var annotations string
|
|
if len(sv.Annotations) > 0 {
|
|
cleanedAnnotations := xkube.CleanAnnotations(sv.Annotations)
|
|
if len(cleanedAnnotations) > 0 {
|
|
sv.Annotations = make(map[string]string) // Safety: reset to prohibit use of sv.Annotations further.
|
|
|
|
encodedAnnotations, err := json.Marshal(cleanedAnnotations)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to encode annotations: %w", err)
|
|
}
|
|
|
|
annotations = string(encodedAnnotations)
|
|
}
|
|
}
|
|
|
|
var labels string
|
|
if len(sv.Labels) > 0 {
|
|
encodedLabels, err := json.Marshal(sv.Labels)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to encode labels: %w", err)
|
|
}
|
|
|
|
labels = string(encodedLabels)
|
|
}
|
|
|
|
var decrypters *string
|
|
if len(sv.Spec.Decrypters) > 0 {
|
|
encodedDecrypters, err := json.Marshal(sv.Spec.Decrypters)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to encode decrypters: %w", err)
|
|
}
|
|
|
|
rawDecrypters := string(encodedDecrypters)
|
|
decrypters = &rawDecrypters
|
|
}
|
|
|
|
meta, err := utils.MetaAccessor(sv)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get meta accessor: %w", err)
|
|
}
|
|
|
|
if meta.GetFolder() != "" {
|
|
return nil, fmt.Errorf("folders are not supported")
|
|
}
|
|
|
|
updatedTimestamp, err := meta.GetResourceVersionInt64()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get resource version: %w", err)
|
|
}
|
|
|
|
var (
|
|
ownerReferenceAPIGroup sql.NullString
|
|
ownerReferenceAPIVersion sql.NullString
|
|
ownerReferenceKind sql.NullString
|
|
ownerReferenceName sql.NullString
|
|
)
|
|
|
|
ownerReferences := meta.GetOwnerReferences()
|
|
if len(ownerReferences) > 1 {
|
|
return nil, fmt.Errorf("only one owner reference is supported, found %d", len(ownerReferences))
|
|
}
|
|
if len(ownerReferences) == 1 {
|
|
ownerReference := ownerReferences[0]
|
|
|
|
gv, err := schema.ParseGroupVersion(ownerReference.APIVersion)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse owner reference API version %s: %w", ownerReference.APIVersion, err)
|
|
}
|
|
if gv.Group == "" {
|
|
return nil, fmt.Errorf("malformed api version %s requires <group>/<version> format", ownerReference.APIVersion)
|
|
}
|
|
|
|
ownerReferenceAPIGroup = toNullString(&gv.Group)
|
|
ownerReferenceAPIVersion = toNullString(&gv.Version)
|
|
ownerReferenceKind = toNullString(&ownerReference.Kind)
|
|
ownerReferenceName = toNullString(&ownerReference.Name)
|
|
}
|
|
|
|
return &secureValueDB{
|
|
GUID: string(sv.UID),
|
|
Name: sv.Name,
|
|
Namespace: sv.Namespace,
|
|
Annotations: annotations,
|
|
Labels: labels,
|
|
Created: meta.GetCreationTimestamp().UnixMilli(),
|
|
CreatedBy: meta.GetCreatedBy(),
|
|
Updated: updatedTimestamp,
|
|
UpdatedBy: meta.GetUpdatedBy(),
|
|
OwnerReferenceAPIGroup: ownerReferenceAPIGroup,
|
|
OwnerReferenceAPIVersion: ownerReferenceAPIVersion,
|
|
OwnerReferenceKind: ownerReferenceKind,
|
|
OwnerReferenceName: ownerReferenceName,
|
|
|
|
Version: sv.Status.Version,
|
|
|
|
Description: sv.Spec.Description,
|
|
Keeper: toNullString(sv.Spec.Keeper),
|
|
Decrypters: toNullString(decrypters),
|
|
Ref: toNullString(sv.Spec.Ref),
|
|
ExternalID: externalID,
|
|
}, nil
|
|
}
|
|
|
|
// toNullString returns a sql.NullString struct given a *string
|
|
// assumes that "" (empty string) is a valid string
|
|
func toNullString(s *string) sql.NullString {
|
|
if s == nil {
|
|
return sql.NullString{
|
|
String: "",
|
|
Valid: false,
|
|
}
|
|
}
|
|
|
|
return sql.NullString{
|
|
String: *s,
|
|
Valid: true,
|
|
}
|
|
}
|