redact secrets
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user