github.com/microsoft/moc@v0.17.1/pkg/redact/redact.go (about)

     1  // Copyright (c) Microsoft Corporation. All rights reserved.
     2  // Licensed under the Apache v2.0 license.
     3  
     4  package redact
     5  
     6  import (
     7  	"encoding/json"
     8  	"reflect"
     9  	"strings"
    10  
    11  	"github.com/golang/protobuf/descriptor"
    12  	"github.com/golang/protobuf/proto"
    13  	descriptorpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
    14  	"github.com/microsoft/moc/rpc/common"
    15  )
    16  
    17  const (
    18  	RedactedString = "** Redacted **"
    19  )
    20  
    21  // RedactedMessage - returns a copy of a proto message struct with data from fields marked as sensitive redacted
    22  func RedactedMessage(msg interface{}) interface{} {
    23  	rMsg := proto.Clone((msg).(proto.Message))
    24  	Redact(rMsg, reflect.ValueOf(rMsg))
    25  	return rMsg
    26  }
    27  
    28  // Redact - removes data from fields marked as sensitive given a proto message struct
    29  func Redact(msg interface{}, val reflect.Value) {
    30  	// TODO: This needs to be optimized!
    31  	// Should cache messages that contain no sensitive data to ignore, and should cache the map of tag number to field name
    32  
    33  	if !val.IsValid() {
    34  		return
    35  	}
    36  
    37  	switch val.Kind() {
    38  	case reflect.Slice:
    39  		for i := 0; i < val.Len(); i += 1 {
    40  			Redact(val.Index(i).Interface(), val.Index(i))
    41  		}
    42  	case reflect.Map:
    43  		// TODO: Implement map logic. Currently only certificates in identity use it, and are redacted
    44  	case reflect.Ptr:
    45  		Redact(msg, val.Elem())
    46  	case reflect.Struct:
    47  		redactMessage(msg, val)
    48  	}
    49  }
    50  
    51  func redactMessage(msg interface{}, val reflect.Value) {
    52  	properties := proto.GetProperties(reflect.TypeOf(msg).Elem())
    53  	_, md := descriptor.ForMessage((msg).(descriptor.Message))
    54  
    55  	for _, field := range md.GetField() {
    56  		var fieldVal reflect.Value
    57  		if field.Options != nil || field.GetType() == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE {
    58  			for _, p := range properties.Prop {
    59  				if int32(p.Tag) == field.GetNumber() {
    60  					fieldVal = val.FieldByName(p.Name)
    61  					break
    62  				}
    63  			}
    64  			if !fieldVal.IsValid() {
    65  				for _, oot := range properties.OneofTypes {
    66  					if int32(oot.Prop.Tag) == field.GetNumber() {
    67  						fieldVal = val.Field(oot.Field).Elem().FieldByName(oot.Prop.Name)
    68  						break
    69  					}
    70  				}
    71  			}
    72  			if !fieldVal.IsValid() {
    73  				return
    74  			}
    75  		}
    76  		if field.Options != nil {
    77  			ex, err := proto.GetExtension(field.Options, common.E_Sensitivejson)
    78  			if err != proto.ErrMissingExtension && err == nil && *ex.(*bool) {
    79  				if fieldVal.Kind() == reflect.String {
    80  					redactJsonSensitiveField(fieldVal)
    81  				} else {
    82  					t := fieldVal.Type()
    83  					fieldVal.Set(reflect.Zero(t))
    84  				}
    85  				continue
    86  			}
    87  
    88  			ex, err = proto.GetExtension(field.Options, common.E_Sensitive)
    89  			if err == proto.ErrMissingExtension {
    90  				continue
    91  			}
    92  
    93  			if err == nil && *ex.(*bool) {
    94  				if fieldVal.Kind() == reflect.String {
    95  					fieldVal.SetString(RedactedString)
    96  				} else {
    97  					t := fieldVal.Type()
    98  					fieldVal.Set(reflect.Zero(t))
    99  				}
   100  				continue
   101  			}
   102  		}
   103  		if field.GetType() == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE {
   104  			Redact(fieldVal.Interface(), reflect.ValueOf(fieldVal.Interface()))
   105  		}
   106  	}
   107  }
   108  
   109  func redactJsonSensitiveField(val reflect.Value) {
   110  	var jsonData map[string]interface{}
   111  	validJsonString := strings.ReplaceAll(val.String(), `\`, `"`)
   112  	if err := json.Unmarshal([]byte(validJsonString), &jsonData); err != nil {
   113  		return
   114  	}
   115  	for key := range jsonData {
   116  		// This can be extended to an array of sensitive keys if needed
   117  		if key == "private-key" {
   118  			jsonData[key] = RedactedString
   119  		}
   120  	}
   121  	redactedJson, err := json.Marshal(jsonData)
   122  	if err == nil {
   123  		val.SetString(string(redactedJson))
   124  	}
   125  }