redact secrets

This commit is contained in:
Yuri Tseretyan
2025-04-30 11:27:59 -04:00
parent 5dcd241577
commit 927654a65d
5 changed files with 141 additions and 30 deletions
@@ -5,8 +5,14 @@ BaseIntegration: {
disable_resolve_message?: bool
}
#SecretString: string
#RedactedSecret: {
specified: bool
}
// A string that contain sensitive information.
#Secret: string
#Secret: #SecretString | #RedactedSecret
AlertmanagerIntegration: BaseIntegration & {
url: string
@@ -18,7 +18,20 @@ func NewAlertmanagerIntegration() *AlertmanagerIntegration {
// A string that contain sensitive information.
// +k8s:openapi-gen=true
type Secret string
type Secret interface{}
// +k8s:openapi-gen=true
type SecretString string
// +k8s:openapi-gen=true
type RedactedSecret struct {
Specified bool `json:"specified"`
}
// NewRedactedSecret creates a new RedactedSecret object.
func NewRedactedSecret() *RedactedSecret {
return &RedactedSecret{}
}
// +k8s:openapi-gen=true
type DingdingIntegration struct {
@@ -45,7 +45,7 @@ func convertToK8sResources(
metadata = &m
}
}
k8sResource, err := convertToK8sResource(orgID, receiver, access, metadata, namespacer)
k8sResource, err := convertToK8sResource(orgID, receiver, access, metadata, namespacer, false)
if err != nil {
return nil, err
}
@@ -63,8 +63,9 @@ func convertToK8sResource(
access *ngmodels.ReceiverPermissionSet,
metadata *ngmodels.ReceiverMetadata,
namespacer request.NamespaceMapper,
keepSecrets bool,
) (*model.Receiver, error) {
spec, err := specFromDomainReceiver(receiver)
spec, err := specFromDomainReceiver(receiver, keepSecrets)
if err != nil {
return nil, err
}
@@ -111,9 +112,11 @@ var permissionMapper = map[ngmodels.ReceiverPermission]string{
// ContactPointFromContactPointExport parses the database model of the contact point (group of integrations) where settings are represented in JSON,
// to strongly typed ContactPoint.
func specFromDomainReceiver(domain *ngmodels.Receiver) (model.Spec, error) {
func specFromDomainReceiver(domain *ngmodels.Receiver, secrets bool) (model.Spec, error) {
j := jsoniter.ConfigCompatibleWithStandardLibrary
j.RegisterExtension(&contactPointsExtension{})
j.RegisterExtension(&contactPointsExtension{
KeepSecret: secrets,
})
result := model.Spec{
Title: domain.Name,
@@ -492,9 +495,28 @@ func parseIntegration(json jsoniter.API, result *model.Spec, integration *ngmode
// contactPointsExtension extends jsoniter with special codecs for some integrations' fields that are encoded differently in the legacy configuration.
type contactPointsExtension struct {
jsoniter.DummyExtension
KeepSecret bool
}
func (c contactPointsExtension) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) {
// CreateEncoder creates a custom encoder for MyInterface
func (c *contactPointsExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {
if typ == reflect2.TypeOfPtr((*model.Secret)(nil)).Elem() {
return &SecretEncoder{}
}
return nil
}
// CreateDecoder creates a custom decoder for MyInterface
func (c *contactPointsExtension) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder {
if typ == reflect2.TypeOfPtr((*model.Secret)(nil)).Elem() {
return &SecretDecoder{
KeepSecret: c.KeepSecret,
}
}
return nil
}
func (c *contactPointsExtension) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) {
if structDescriptor.Type == reflect2.TypeOf(model.EmailIntegration{}) {
bind := structDescriptor.GetField("Addresses")
codec := &emailAddressCodec{}
@@ -609,3 +631,67 @@ func (d *numberAsStringCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator
}
*((*(*int64))(ptr)) = &value
}
type SecretEncoder struct{}
func (encoder *SecretEncoder) IsEmpty(ptr unsafe.Pointer) bool {
return *(*model.Secret)(ptr) == nil
}
func (encoder *SecretEncoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
val := *(*model.Secret)(ptr)
if val == nil {
stream.WriteNil()
return
}
switch v := val.(type) {
case string:
// If it's a raw string, write it as-is
stream.WriteString(v)
case model.SecretString:
// If it's a SecretString, write it as a string
stream.WriteString(string(v))
case *model.RedactedSecret:
// If it's a RedactedSecret (has "specified" field), write null
stream.WriteNil()
default:
stream.Error = fmt.Errorf("unsupported Secret type: %T", val)
}
}
type SecretDecoder struct {
KeepSecret bool
}
func (decoder *SecretDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
// Peek at the next token to determine the JSON type
switch iter.WhatIsNext() {
case jsoniter.StringValue:
// If it's a string, decode as SecretString
str := iter.ReadString()
if decoder.KeepSecret {
*(*model.Secret)(ptr) = model.SecretString(str)
return
}
*(*model.Secret)(ptr) = &model.RedactedSecret{
Specified: len(str) > 0,
}
// case jsoniter.NilValue:
// // If it's a string, decode as SecretString
// _ = iter.ReadNil()
// if !decoder.KeepSecret {
// // If it's an object, decode as RedactedSecret
// redacted := &model.RedactedSecret{
// Specified: len(str) > 0,
// }
// iter.ReadVal(redacted)
// if iter.Error != nil {
// return
// }
// *(*model.Secret)(ptr) = redacted
// }
default:
iter.Error = fmt.Errorf("invalid JSON type for Secret; expected string or object")
}
}
@@ -37,7 +37,7 @@ func TestConvertToK8sResource(t *testing.T) {
Version: "1234",
}
result, err := convertToK8sResource(1, recCfg, &models.ReceiverPermissionSet{}, &models.ReceiverMetadata{}, request.GetNamespaceMapper(nil))
result, err := convertToK8sResource(1, recCfg, &models.ReceiverPermissionSet{}, &models.ReceiverMetadata{}, request.GetNamespaceMapper(nil), true)
require.NoError(t, err)
back, err := convertToDomainModel(result)
@@ -109,6 +109,10 @@ func (s *legacyStorage) List(ctx context.Context, opts *internalversion.ListOpti
}
func (s *legacyStorage) Get(ctx context.Context, uid string, _ *metav1.GetOptions) (runtime.Object, error) {
return s.get(ctx, uid, false, true)
}
func (s *legacyStorage) get(ctx context.Context, uid string, keepSecrets bool, fillMetadata bool) (runtime.Object, error) {
info, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
@@ -121,7 +125,7 @@ func (s *legacyStorage) Get(ctx context.Context, uid string, _ *metav1.GetOption
q := ngmodels.GetReceiverQuery{
OrgID: info.OrgID,
Name: name,
Decrypt: false,
Decrypt: true,
}
user, err := identity.GetRequester(ctx)
@@ -135,26 +139,28 @@ func (s *legacyStorage) Get(ctx context.Context, uid string, _ *metav1.GetOption
}
var access *ngmodels.ReceiverPermissionSet
accesses, err := s.metadata.AccessControlMetadata(ctx, user, r)
if err == nil {
if a, ok := accesses[r.GetUID()]; ok {
access = &a
}
} else {
return nil, fmt.Errorf("failed to get access control metadata: %w", err)
}
var inUse *ngmodels.ReceiverMetadata
inUses, err := s.metadata.InUseMetadata(ctx, info.OrgID, r)
if err == nil {
if a, ok := inUses[r.GetUID()]; ok {
inUse = &a
}
} else {
return nil, fmt.Errorf("failed to get access control metadata: %w", err)
}
if fillMetadata {
return convertToK8sResource(info.OrgID, r, access, inUse, s.namespacer)
accesses, err := s.metadata.AccessControlMetadata(ctx, user, r)
if err == nil {
if a, ok := accesses[r.GetUID()]; ok {
access = &a
}
} else {
return nil, fmt.Errorf("failed to get access control metadata: %w", err)
}
inUses, err := s.metadata.InUseMetadata(ctx, info.OrgID, r)
if err == nil {
if a, ok := inUses[r.GetUID()]; ok {
inUse = &a
}
} else {
return nil, fmt.Errorf("failed to get access control metadata: %w", err)
}
}
return convertToK8sResource(info.OrgID, r, access, inUse, s.namespacer, keepSecrets)
}
func (s *legacyStorage) Create(ctx context.Context,
@@ -192,7 +198,7 @@ func (s *legacyStorage) Create(ctx context.Context,
if err != nil {
return nil, err
}
return convertToK8sResource(info.OrgID, out, nil, nil, s.namespacer)
return convertToK8sResource(info.OrgID, out, nil, nil, s.namespacer, false)
}
func (s *legacyStorage) Update(ctx context.Context,
@@ -213,7 +219,7 @@ func (s *legacyStorage) Update(ctx context.Context,
return nil, false, err
}
old, err := s.Get(ctx, uid, nil)
old, err := s.get(ctx, uid, true, false)
if err != nil {
return old, false, err
}
@@ -240,7 +246,7 @@ func (s *legacyStorage) Update(ctx context.Context,
return nil, false, err
}
r, err := convertToK8sResource(info.OrgID, updated, nil, nil, s.namespacer)
r, err := convertToK8sResource(info.OrgID, updated, nil, nil, s.namespacer, false)
return r, false, err
}