github.com/Axway/agent-sdk@v1.1.101/pkg/traceability/redaction/redaction.go (about)

     1  package redaction
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/Axway/agent-sdk/pkg/util/log"
    10  )
    11  
    12  const (
    13  	defaultSanitizeValue = "{*}"
    14  	http                 = "http"
    15  	https                = "https"
    16  )
    17  
    18  // Redactions - the public methods available for redaction config
    19  type Redactions interface {
    20  	URIRedaction(uri string) (string, error)
    21  	PathRedaction(path string) string
    22  	QueryArgsRedaction(queryArgs map[string][]string) (map[string][]string, error)
    23  	QueryArgsRedactionString(queryArgs string) (string, error)
    24  	RequestHeadersRedaction(requestHeaders map[string]string) (map[string]string, error)
    25  	ResponseHeadersRedaction(responseHeaders map[string]string) (map[string]string, error)
    26  	JMSPropertiesRedaction(jmsProperties map[string]string) (map[string]string, error)
    27  }
    28  
    29  // Config - the configuration of all redactions
    30  type Config struct {
    31  	Path              Path   `config:"path" yaml:"path"`
    32  	Args              Filter `config:"queryArgument" yaml:"queryArgument"`
    33  	RequestHeaders    Filter `config:"requestHeader" yaml:"requestHeader"`
    34  	ResponseHeaders   Filter `config:"responseHeader" yaml:"responseHeader"`
    35  	MaskingCharacters string `config:"maskingCharacters" yaml:"maskingCharacters"`
    36  	JMSProperties     Filter `config:"jmsProperties" yaml:"jmsProperties"`
    37  }
    38  
    39  // path - the keyMatches to show, all else are redacted
    40  type Path struct {
    41  	Allowed []Show `config:"show" yaml:"show"`
    42  }
    43  
    44  // filter - the configuration of a filter for each redaction config
    45  type Filter struct {
    46  	Allowed  []Show     `config:"show" yaml:"show"`
    47  	Sanitize []Sanitize `config:"sanitize" yaml:"sanitize"`
    48  }
    49  
    50  // show - the keyMatches to show, all else are redacted
    51  type Show struct {
    52  	KeyMatch string `config:"keyMatch" yaml:"keyMatch"`
    53  }
    54  
    55  // sanitize - the keys and values to sanitize
    56  type Sanitize struct {
    57  	KeyMatch   string `config:"keyMatch" yaml:"keyMatch"`
    58  	ValueMatch string `config:"valueMatch" yaml:"valueMatch"`
    59  }
    60  
    61  // redactionRegex - the compiled regex of the configuration fields
    62  type redactionRegex struct {
    63  	Redactions
    64  	pathFilters           []showRegex
    65  	argsFilters           filterRegex
    66  	requestHeaderFilters  filterRegex
    67  	responseHeaderFilters filterRegex
    68  	jmsPropertiesFilters  filterRegex
    69  	sanitizeValue         string
    70  }
    71  
    72  type filterRegex struct {
    73  	show     []showRegex
    74  	sanitize []sanitizeRegex
    75  }
    76  
    77  type showRegex struct {
    78  	keyMatch *regexp.Regexp
    79  }
    80  
    81  type sanitizeRegex struct {
    82  	keyMatch   *regexp.Regexp
    83  	valueMatch *regexp.Regexp
    84  }
    85  
    86  // DefaultConfig - returns a default reaction config where all things are redacted
    87  func DefaultConfig() Config {
    88  	return Config{
    89  		Path: Path{
    90  			Allowed: []Show{},
    91  		},
    92  		Args: Filter{
    93  			Allowed:  []Show{},
    94  			Sanitize: []Sanitize{},
    95  		},
    96  		RequestHeaders: Filter{
    97  			Allowed:  []Show{},
    98  			Sanitize: []Sanitize{},
    99  		},
   100  		ResponseHeaders: Filter{
   101  			Allowed:  []Show{},
   102  			Sanitize: []Sanitize{},
   103  		},
   104  		MaskingCharacters: "{*}",
   105  		JMSProperties: Filter{
   106  			Allowed:  []Show{},
   107  			Sanitize: []Sanitize{},
   108  		},
   109  	}
   110  }
   111  
   112  // SetupRedactions - set up redactionRegex based on the redactionConfig
   113  func (cfg *Config) SetupRedactions() (Redactions, error) {
   114  	var redactionSetup redactionRegex
   115  	var err error
   116  
   117  	// Setup the path filters
   118  	redactionSetup.pathFilters, err = setupShowRegex(cfg.Path.Allowed)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	// Setup the arg filters
   124  	redactionSetup.argsFilters.show, err = setupShowRegex(cfg.Args.Allowed)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	redactionSetup.argsFilters.sanitize, err = setupSanitizeRegex(cfg.Args.Sanitize)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	// Setup the request header filters
   134  	redactionSetup.requestHeaderFilters.show, err = setupShowRegex(cfg.RequestHeaders.Allowed)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	redactionSetup.requestHeaderFilters.sanitize, err = setupSanitizeRegex(cfg.RequestHeaders.Sanitize)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	// Setup the response header filters
   144  	redactionSetup.responseHeaderFilters.show, err = setupShowRegex(cfg.ResponseHeaders.Allowed)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	redactionSetup.responseHeaderFilters.sanitize, err = setupSanitizeRegex(cfg.ResponseHeaders.Sanitize)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	// Setup the jms properties filters
   154  	redactionSetup.jmsPropertiesFilters.show, err = setupShowRegex(cfg.JMSProperties.Allowed)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	redactionSetup.jmsPropertiesFilters.sanitize, err = setupSanitizeRegex(cfg.JMSProperties.Sanitize)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	isValidMask, err := validateMaskingChars(cfg.MaskingCharacters)
   164  	if err != nil {
   165  		err = ErrInvalidRegex.FormatError("validate masking characters", cfg.MaskingCharacters, err)
   166  		log.Error(err)
   167  		return nil, err
   168  	}
   169  
   170  	if isValidMask {
   171  		redactionSetup.sanitizeValue = cfg.MaskingCharacters
   172  	} else {
   173  		log.Error("error validating masking characters: ", string(cfg.MaskingCharacters), ", using default mask: ", defaultSanitizeValue)
   174  		redactionSetup.sanitizeValue = defaultSanitizeValue
   175  	}
   176  
   177  	return &redactionSetup, err
   178  }
   179  
   180  // validateMaskingChars - validates the supplied masking character string against the accepted characters
   181  func validateMaskingChars(mask string) (bool, error) {
   182  	// available characters are alphanumeric, between 1-5 characters, and can contain '-' (hyphen), '*' (star), '#' (sharp), '^' (caret), '~' (tilde), '.' (dot), '{' (open curly bracket), '}' (closing curly bracket)
   183  	regEx := "^([a-zA-Z0-9-*#^~.{}]){1,5}$"
   184  	isMatch, err := regexp.MatchString(regEx, mask)
   185  
   186  	return isMatch, err
   187  }
   188  
   189  // URIRedaction - takes a uri and returns the redacted version of that URI
   190  func (r *redactionRegex) URIRedaction(fullURI string) (string, error) {
   191  	// skip redaction if nothing sent in
   192  	if fullURI == "" {
   193  		return "", nil
   194  	}
   195  
   196  	// just in case uri is really a full url, we want to only want the URI portion
   197  	parsedURI, err := url.ParseRequestURI(fullURI)
   198  	if err != nil {
   199  		return "", err
   200  	}
   201  	parsedURL, err := url.ParseRequestURI(parsedURI.RequestURI())
   202  	if err != nil {
   203  		return "", err
   204  	}
   205  	switch parsedURL.Scheme {
   206  	case http, https, "":
   207  		parsedURL.Path = r.PathRedaction(parsedURL.Path)
   208  
   209  		parsedURL.RawQuery, err = r.QueryArgsRedactionString(parsedURL.RawQuery)
   210  		if err != nil {
   211  			return "", err
   212  		}
   213  	}
   214  
   215  	return url.QueryUnescape(parsedURL.String())
   216  }
   217  
   218  // PathRedaction - returns a string that has only allowed path elements
   219  func (r *redactionRegex) PathRedaction(path string) string {
   220  	pathSegments := strings.Split(path, "/")
   221  
   222  	for i, segment := range pathSegments {
   223  		if segment == "" {
   224  			continue // skip blank segments
   225  		}
   226  		// If the value is not matched, sanitize it
   227  		if !isValidValueToShow(segment, r.pathFilters) {
   228  			pathSegments[i] = r.sanitizeValue
   229  		}
   230  	}
   231  
   232  	return strings.Join(pathSegments, "/")
   233  }
   234  
   235  // QueryArgsRedaction - accepts a map[string][]string for arguments and returns the same map[string][]string with redacted
   236  func (r *redactionRegex) QueryArgsRedaction(args map[string][]string) (map[string][]string, error) {
   237  	queryArgs := url.Values{}
   238  
   239  	for argName, argValue := range args {
   240  		// First check for removals
   241  		removed := false
   242  		// If the name is not matched, remove it
   243  		if !isValidValueToShow(argName, r.argsFilters.show) {
   244  			removed = true
   245  		}
   246  
   247  		// Don't check for sanitization if arg was removed entirely
   248  		if removed {
   249  			continue
   250  		}
   251  
   252  		// Now check for sanitization
   253  		runSanitize, sanitizeRegex := shouldSanitize(argName, r.argsFilters.sanitize)
   254  		for _, value := range argValue {
   255  			if runSanitize {
   256  				queryArgs.Add(argName, sanitizeRegex.ReplaceAllLiteralString(value, r.sanitizeValue))
   257  			} else {
   258  				queryArgs.Add(argName, value)
   259  			}
   260  		}
   261  	}
   262  
   263  	return queryArgs, nil
   264  }
   265  
   266  // QueryArgsRedactionString - accepts a string for arguments and returns the same string with redacted
   267  func (r *redactionRegex) QueryArgsRedactionString(args string) (string, error) {
   268  	if args == "" {
   269  		return "", nil // skip if there are no query args
   270  	}
   271  
   272  	queryArgs, _ := url.ParseQuery(args)
   273  
   274  	redactedArgs, err := r.QueryArgsRedaction(queryArgs)
   275  	if err != nil {
   276  		return "", err
   277  	}
   278  
   279  	queryArgString := ""
   280  	for key, val := range redactedArgs {
   281  		if queryArgString != "" {
   282  			queryArgString += "&"
   283  		}
   284  		queryArgString += fmt.Sprintf("%s=%s", key, strings.Join(val, ","))
   285  	}
   286  
   287  	return queryArgString, nil
   288  }
   289  
   290  // RequestHeadersRedaction - accepts a map of response headers and returns the redacted and sanitize map
   291  func (r *redactionRegex) RequestHeadersRedaction(headers map[string]string) (map[string]string, error) {
   292  	return r.headersRedaction(headers, r.requestHeaderFilters)
   293  }
   294  
   295  // ResponseHeadersRedaction - accepts a map of response headers and returns the redacted and sanitize map
   296  func (r *redactionRegex) ResponseHeadersRedaction(headers map[string]string) (map[string]string, error) {
   297  	return r.headersRedaction(headers, r.responseHeaderFilters)
   298  }
   299  
   300  // JMSPropertiesRedaction - accepts a map of JMS properties and returns the redacted and sanitize map
   301  func (r *redactionRegex) JMSPropertiesRedaction(properties map[string]string) (map[string]string, error) {
   302  	return r.headersRedaction(properties, r.jmsPropertiesFilters)
   303  }
   304  
   305  // headersRedaction - accepts a string of headers and the filters to apply then returns the redacted and sanitize map
   306  func (r *redactionRegex) headersRedaction(properties map[string]string, filters filterRegex) (map[string]string, error) {
   307  	newProperties := make(map[string]string)
   308  
   309  	for propName, propValue := range properties {
   310  		// If the name is not matched, remove it
   311  		if !isValidValueToShow(propName, filters.show) {
   312  			continue
   313  		}
   314  
   315  		newProperties[propName] = propValue
   316  		// Now check for sanitization
   317  		if runSanitize, sanitizeRegex := shouldSanitize(propName, filters.sanitize); runSanitize {
   318  			newProperties[propName] = sanitizeRegex.ReplaceAllLiteralString(propValue, r.sanitizeValue)
   319  		}
   320  	}
   321  
   322  	return newProperties, nil
   323  }