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 }