1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 15:37:19 +00:00
kyverno/pkg/engine/jmespath/functions.go
Ayush Chandekar e64a5ff647
Add SHA1 and MD5 hash functions to JMESPath (#11564)
Signed-off-by: ayu-ch <ayu.chandekar@gmail.com>
Co-authored-by: shuting <shuting@nirmata.com>
2024-11-12 08:53:44 +00:00

1345 lines
37 KiB
Go

package jmespath
import (
"bytes"
"crypto/md5" // #nosec G501
"crypto/rand"
"crypto/rsa"
"crypto/sha1" // #nosec G505
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"math"
"net"
"net/url"
"path/filepath"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
trunc "github.com/aquilax/truncate"
"github.com/blang/semver/v4"
gojmespath "github.com/kyverno/go-jmespath"
"github.com/kyverno/kyverno/ext/wildcard"
"github.com/kyverno/kyverno/pkg/config"
imageutils "github.com/kyverno/kyverno/pkg/utils/image"
regen "github.com/zach-klippenstein/goregen"
"golang.org/x/crypto/cryptobyte"
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
"sigs.k8s.io/yaml"
)
type PublicKey struct {
N string
E int
}
// function names
var (
compare = "compare"
equalFold = "equal_fold"
replace = "replace"
replaceAll = "replace_all"
toUpper = "to_upper"
toLower = "to_lower"
trim = "trim"
trimPrefix = "trim_prefix"
split = "split"
regexReplaceAll = "regex_replace_all"
regexReplaceAllLiteral = "regex_replace_all_literal"
regexMatch = "regex_match"
patternMatch = "pattern_match"
labelMatch = "label_match"
toBoolean = "to_boolean"
add = "add"
sum = "sum"
subtract = "subtract"
multiply = "multiply"
divide = "divide"
modulo = "modulo"
round = "round"
base64Decode = "base64_decode"
base64Encode = "base64_encode"
pathCanonicalize = "path_canonicalize"
truncate = "truncate"
semverCompare = "semver_compare"
parseJson = "parse_json"
parseYAML = "parse_yaml"
lookup = "lookup"
items = "items"
objectFromLists = "object_from_lists"
random = "random"
x509_decode = "x509_decode"
imageNormalize = "image_normalize"
isExternalURL = "is_external_url"
SHA256 = "sha256"
SHA1 = "sha1"
MD5 = "md5"
)
func GetFunctions(configuration config.Configuration) []FunctionEntry {
return []FunctionEntry{{
FunctionEntry: gojmespath.FunctionEntry{
Name: compare,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpfCompare,
},
ReturnType: []jpType{jpNumber},
Note: "compares two strings lexicographically",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: equalFold,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpfEqualFold,
},
ReturnType: []jpType{jpBool},
Note: "allows comparing two strings for equivalency where the only differences are letter cases",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: replace,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
{Types: []jpType{jpNumber}},
},
Handler: jpfReplace,
},
ReturnType: []jpType{jpString},
Note: "replaces a specified number of instances of the source string with the replacement string in a parent ",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: replaceAll,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpfReplaceAll,
},
ReturnType: []jpType{jpString},
Note: "replace all instances of one string with another in an overall parent string",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: toUpper,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpfToUpper,
},
ReturnType: []jpType{jpString},
Note: "takes in a string and outputs the same string with all upper-case letters",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: toLower,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpfToLower,
},
ReturnType: []jpType{jpString},
Note: "takes in a string and outputs the same string with all lower-case letters",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: trim,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpfTrim,
},
ReturnType: []jpType{jpString},
Note: "trims both ends of the source string by characters appearing in the second string",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: trimPrefix,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpfTrimPrefix,
},
ReturnType: []jpType{jpString},
Note: "trims the second string prefix from the first string if the first string starts with the prefix",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: split,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpfSplit,
},
ReturnType: []jpType{jpArrayString},
Note: "splits the first string when the second string is found and converts it into an array ",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: regexReplaceAll,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString, jpNumber}},
{Types: []jpType{jpString, jpNumber}},
},
Handler: jpRegexReplaceAll,
},
ReturnType: []jpType{jpString},
Note: "converts all parameters to string",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: regexReplaceAllLiteral,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString, jpNumber}},
{Types: []jpType{jpString, jpNumber}},
},
Handler: jpRegexReplaceAllLiteral,
},
ReturnType: []jpType{jpString},
Note: "converts all parameters to string",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: regexMatch,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString, jpNumber}},
},
Handler: jpRegexMatch,
},
ReturnType: []jpType{jpBool},
Note: "first string is the regular exression which is compared with second input which can be a number or string",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: patternMatch,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString, jpNumber}},
},
Handler: jpPatternMatch,
},
ReturnType: []jpType{jpBool},
Note: "'*' matches zero or more alphanumeric characters, '?' matches a single alphanumeric character",
}, {
// Validates if label (param1) would match pod/host/etc labels (param2)
FunctionEntry: gojmespath.FunctionEntry{
Name: labelMatch,
Arguments: []argSpec{
{Types: []jpType{jpObject}},
{Types: []jpType{jpObject}},
},
Handler: jpLabelMatch,
},
ReturnType: []jpType{jpBool},
Note: "object arguments must be enclosed in backticks; ex. `{{request.object.spec.template.metadata.labels}}`",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: toBoolean,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpToBoolean,
},
ReturnType: []jpType{jpBool},
Note: "It returns true or false for any string, such as 'True', 'TruE', 'False', 'FAlse', 'faLSE', etc.",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: add,
Arguments: []argSpec{
{Types: []jpType{jpAny}},
{Types: []jpType{jpAny}},
},
Handler: jpAdd,
},
ReturnType: []jpType{jpAny},
Note: "does arithmetic addition of two specified values of numbers, quantities, and durations",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: sum,
Arguments: []argSpec{
{Types: []jpType{jpArray}},
},
Handler: jpSum,
},
ReturnType: []jpType{jpAny},
Note: "does arithmetic addition of specified array of values of numbers, quantities, and durations",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: subtract,
Arguments: []argSpec{
{Types: []jpType{jpAny}},
{Types: []jpType{jpAny}},
},
Handler: jpSubtract,
},
ReturnType: []jpType{jpAny},
Note: "does arithmetic subtraction of two specified values of numbers, quantities, and durations",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: multiply,
Arguments: []argSpec{
{Types: []jpType{jpAny}},
{Types: []jpType{jpAny}},
},
Handler: jpMultiply,
},
ReturnType: []jpType{jpAny},
Note: "does arithmetic multiplication of two specified values of numbers, quantities, and durations",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: divide,
Arguments: []argSpec{
{Types: []jpType{jpAny}},
{Types: []jpType{jpAny}},
},
Handler: jpDivide,
},
ReturnType: []jpType{jpAny},
Note: "divisor must be non zero",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: modulo,
Arguments: []argSpec{
{Types: []jpType{jpAny}},
{Types: []jpType{jpAny}},
},
Handler: jpModulo,
},
ReturnType: []jpType{jpAny},
Note: "divisor must be non-zero, arguments must be integers",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: round,
Arguments: []argSpec{
{Types: []jpType{jpNumber}},
{Types: []jpType{jpNumber}},
},
Handler: jpRound,
},
ReturnType: []jpType{jpNumber},
Note: "does roundoff to upto the given decimal places",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: base64Decode,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpBase64Decode,
},
ReturnType: []jpType{jpString},
Note: "decodes a base 64 string",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: base64Encode,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpBase64Encode,
},
ReturnType: []jpType{jpString},
Note: "encodes a regular, plaintext and unencoded string to base64",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeSince,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpTimeSince,
},
ReturnType: []jpType{jpString},
Note: "calculate the difference between a start and end period of time where the end may either be a static definition or the then-current time",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeNow,
Handler: jpTimeNow,
},
ReturnType: []jpType{jpString},
Note: "returns current time in RFC 3339 format",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeNowUtc,
Handler: jpTimeNowUtc,
},
ReturnType: []jpType{jpString},
Note: "returns current UTC time in RFC 3339 format",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: pathCanonicalize,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpPathCanonicalize,
},
ReturnType: []jpType{jpString},
Note: "normalizes or canonicalizes a given path by removing excess slashes",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: truncate,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpNumber}},
},
Handler: jpTruncate,
},
ReturnType: []jpType{jpString},
Note: "length argument must be enclosed in backticks; ex. \"{{request.object.metadata.name | truncate(@, `9`)}}\"",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: semverCompare,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpSemverCompare,
},
ReturnType: []jpType{jpBool},
Note: "compares two strings which comply with the semantic versioning schema and outputs a boolean response as to the position of the second relative to the first",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: parseJson,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpParseJson,
},
ReturnType: []jpType{jpAny},
Note: "decodes a valid JSON encoded string to the appropriate type. Opposite of `to_string` function",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: parseYAML,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpParseYAML,
},
ReturnType: []jpType{jpAny},
Note: "decodes a valid YAML encoded string to the appropriate type provided it can be represented as JSON",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: lookup,
Arguments: []argSpec{
{Types: []jpType{jpObject, jpArray}},
{Types: []jpType{jpString, jpNumber}},
},
Handler: jpLookup,
},
ReturnType: []jpType{jpAny},
Note: "returns the value corresponding to the given key/index in the given object/array",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: items,
Arguments: []argSpec{
{Types: []jpType{jpObject, jpArray}},
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpItems,
},
ReturnType: []jpType{jpArray},
Note: "converts a map or array to an array of objects where each key:value is an item in the array",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: objectFromLists,
Arguments: []argSpec{
{Types: []jpType{jpArray}},
{Types: []jpType{jpArray}},
},
Handler: jpObjectFromLists,
},
ReturnType: []jpType{jpObject},
Note: "converts a pair of lists containing keys and values to an object",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: random,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpRandom,
},
ReturnType: []jpType{jpString},
Note: "Generates a random sequence of characters",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: x509_decode,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpX509Decode,
},
ReturnType: []jpType{jpObject},
Note: "decodes an x.509 certificate to an object. you may also use this in conjunction with `base64_decode` jmespath function to decode a base64-encoded certificate",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeToCron,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpTimeToCron,
},
ReturnType: []jpType{jpString},
Note: "converts a time (RFC 3339) to a cron expression (string).",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeAdd,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpTimeAdd,
},
ReturnType: []jpType{jpString},
Note: "adds duration (second string) to a time value (first string)",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeParse,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpTimeParse,
},
ReturnType: []jpType{jpString},
Note: "changes a time value of a given layout to RFC 3339",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeUtc,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpTimeUtc,
},
ReturnType: []jpType{jpString},
Note: "calcutes time in UTC from a given time in RFC 3339 format",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeDiff,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpTimeDiff,
},
ReturnType: []jpType{jpString},
Note: "calculate the difference between a start and end date in RFC3339 format",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeBefore,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpTimeBefore,
},
ReturnType: []jpType{jpBool},
Note: "checks if a time is before another time, both in RFC3339 format",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeAfter,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpTimeAfter,
},
ReturnType: []jpType{jpBool},
Note: "checks if a time is after another time, both in RFC3339 format",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeBetween,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpTimeBetween,
},
ReturnType: []jpType{jpBool},
Note: "checks if a time is between a start and end time, all in RFC3339 format",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: timeTruncate,
Arguments: []argSpec{
{Types: []jpType{jpString}},
{Types: []jpType{jpString}},
},
Handler: jpTimeTruncate,
},
ReturnType: []jpType{jpString},
Note: "returns the result of rounding time down to a multiple of duration",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: imageNormalize,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpImageNormalize(configuration),
},
ReturnType: []jpType{jpString},
Note: "normalizes an image reference",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: isExternalURL,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpIsExternalURL,
},
ReturnType: []jpType{jpBool},
Note: "determine if a URL points to an external network address",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: SHA256,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpSha256,
},
ReturnType: []jpType{jpString},
Note: "generate unique resources name if length exceeds the limit",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: SHA1,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpSha1,
},
ReturnType: []jpType{jpString},
Note: "generate a SHA-1 hash",
}, {
FunctionEntry: gojmespath.FunctionEntry{
Name: MD5,
Arguments: []argSpec{
{Types: []jpType{jpString}},
},
Handler: jpMd5,
},
ReturnType: []jpType{jpString},
Note: "generates an MD5 hash",
}}
}
func jpfCompare(arguments []interface{}) (interface{}, error) {
if a, err := validateArg(compare, arguments, 0, reflect.String); err != nil {
return nil, err
} else if b, err := validateArg(compare, arguments, 1, reflect.String); err != nil {
return nil, err
} else {
return strings.Compare(a.String(), b.String()), nil
}
}
func jpfEqualFold(arguments []interface{}) (interface{}, error) {
if a, err := validateArg(equalFold, arguments, 0, reflect.String); err != nil {
return nil, err
} else if b, err := validateArg(equalFold, arguments, 1, reflect.String); err != nil {
return nil, err
} else {
return strings.EqualFold(a.String(), b.String()), nil
}
}
func jpfReplace(arguments []interface{}) (interface{}, error) {
if str, err := validateArg(replace, arguments, 0, reflect.String); err != nil {
return nil, err
} else if old, err := validateArg(replace, arguments, 1, reflect.String); err != nil {
return nil, err
} else if new, err := validateArg(replace, arguments, 2, reflect.String); err != nil {
return nil, err
} else if n, err := validateArg(replace, arguments, 3, reflect.Float64); err != nil {
return nil, err
} else {
return strings.Replace(str.String(), old.String(), new.String(), int(n.Float())), nil
}
}
func jpfReplaceAll(arguments []interface{}) (interface{}, error) {
if str, err := validateArg(replaceAll, arguments, 0, reflect.String); err != nil {
return nil, err
} else if old, err := validateArg(replaceAll, arguments, 1, reflect.String); err != nil {
return nil, err
} else if new, err := validateArg(replaceAll, arguments, 2, reflect.String); err != nil {
return nil, err
} else {
return strings.ReplaceAll(str.String(), old.String(), new.String()), nil
}
}
func jpfToUpper(arguments []interface{}) (interface{}, error) {
if str, err := validateArg(toUpper, arguments, 0, reflect.String); err != nil {
return nil, err
} else {
return strings.ToUpper(str.String()), nil
}
}
func jpfToLower(arguments []interface{}) (interface{}, error) {
if str, err := validateArg(toLower, arguments, 0, reflect.String); err != nil {
return nil, err
} else {
return strings.ToLower(str.String()), nil
}
}
func jpfTrim(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(trim, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
cutset, err := validateArg(trim, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
return strings.Trim(str.String(), cutset.String()), nil
}
func jpfTrimPrefix(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(trimPrefix, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
prefix, err := validateArg(trimPrefix, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
return strings.TrimPrefix(str.String(), prefix.String()), nil
}
func jpfSplit(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(split, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
sep, err := validateArg(split, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
split := strings.Split(str.String(), sep.String())
arr := make([]interface{}, len(split))
for i, v := range split {
arr[i] = v
}
return arr, nil
}
func jpRegexReplaceAll(arguments []interface{}) (interface{}, error) {
var err error
regex, err := validateArg(regexReplaceAll, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
src, err := ifaceToString(arguments[1])
if err != nil {
return nil, formatError(invalidArgumentTypeError, regexReplaceAll, 2, "String or Real")
}
repl, err := ifaceToString(arguments[2])
if err != nil {
return nil, formatError(invalidArgumentTypeError, regexReplaceAll, 3, "String or Real")
}
reg, err := regexp.Compile(regex.String())
if err != nil {
return nil, formatError(genericError, regexReplaceAll, err.Error())
}
return string(reg.ReplaceAll([]byte(src), []byte(repl))), nil
}
func jpRegexReplaceAllLiteral(arguments []interface{}) (interface{}, error) {
var err error
regex, err := validateArg(regexReplaceAllLiteral, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
src, err := ifaceToString(arguments[1])
if err != nil {
return nil, formatError(invalidArgumentTypeError, regexReplaceAllLiteral, 2, "String or Real")
}
repl, err := ifaceToString(arguments[2])
if err != nil {
return nil, formatError(invalidArgumentTypeError, regexReplaceAllLiteral, 3, "String or Real")
}
reg, err := regexp.Compile(regex.String())
if err != nil {
return nil, formatError(genericError, regexReplaceAllLiteral, err.Error())
}
return string(reg.ReplaceAllLiteral([]byte(src), []byte(repl))), nil
}
func jpRegexMatch(arguments []interface{}) (interface{}, error) {
var err error
regex, err := validateArg(regexMatch, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
src, err := ifaceToString(arguments[1])
if err != nil {
return nil, formatError(invalidArgumentTypeError, regexMatch, 2, "String or Real")
}
return regexp.Match(regex.String(), []byte(src))
}
func jpPatternMatch(arguments []interface{}) (interface{}, error) {
pattern, err := validateArg(regexMatch, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
src, err := ifaceToString(arguments[1])
if err != nil {
return nil, formatError(invalidArgumentTypeError, regexMatch, 2, "String or Real")
}
return wildcard.Match(pattern.String(), src), nil
}
func jpLabelMatch(arguments []interface{}) (interface{}, error) {
labelMap, ok := arguments[0].(map[string]interface{})
if !ok {
return nil, formatError(invalidArgumentTypeError, labelMatch, 0, "Object")
}
matchMap, ok := arguments[1].(map[string]interface{})
if !ok {
return nil, formatError(invalidArgumentTypeError, labelMatch, 1, "Object")
}
for key, value := range labelMap {
if val, ok := matchMap[key]; !ok || val != value {
return false, nil
}
}
return true, nil
}
func jpToBoolean(arguments []interface{}) (interface{}, error) {
if input, err := validateArg(toBoolean, arguments, 0, reflect.String); err != nil {
return nil, err
} else {
switch strings.ToLower(input.String()) {
case "true":
return true, nil
case "false":
return false, nil
default:
return nil, formatError(genericError, toBoolean, fmt.Sprintf("lowercase argument must be 'true' or 'false' (provided: '%s')", input.String()))
}
}
}
func _jpAdd(arguments []interface{}, operator string) (interface{}, error) {
op1, op2, err := parseArithemticOperands(arguments, operator)
if err != nil {
return nil, err
}
return op1.Add(op2, operator)
}
func jpAdd(arguments []interface{}) (interface{}, error) {
return _jpAdd(arguments, add)
}
func jpSum(arguments []interface{}) (interface{}, error) {
items, ok := arguments[0].([]interface{})
if !ok {
return nil, formatError(typeMismatchError, sum)
}
if len(items) == 0 {
return nil, formatError(genericError, sum, "at least one element in the array is required")
}
var err error
result := items[0]
for _, item := range items[1:] {
result, err = _jpAdd([]interface{}{result, item}, sum)
if err != nil {
return nil, err
}
}
return result, nil
}
func jpSubtract(arguments []interface{}) (interface{}, error) {
op1, op2, err := parseArithemticOperands(arguments, subtract)
if err != nil {
return nil, err
}
return op1.Subtract(op2)
}
func jpMultiply(arguments []interface{}) (interface{}, error) {
op1, op2, err := parseArithemticOperands(arguments, multiply)
if err != nil {
return nil, err
}
return op1.Multiply(op2)
}
func jpDivide(arguments []interface{}) (interface{}, error) {
op1, op2, err := parseArithemticOperands(arguments, divide)
if err != nil {
return nil, err
}
return op1.Divide(op2)
}
func jpModulo(arguments []interface{}) (interface{}, error) {
op1, op2, err := parseArithemticOperands(arguments, modulo)
if err != nil {
return nil, err
}
return op1.Modulo(op2)
}
func jpRound(arguments []interface{}) (interface{}, error) {
op, err := validateArg(round, arguments, 0, reflect.Float64)
if err != nil {
return nil, err
}
length, err := validateArg(round, arguments, 1, reflect.Float64)
if err != nil {
return nil, err
}
intLength, err := intNumber(length.Float())
if err != nil {
return nil, formatError(nonIntRoundError, round)
}
if intLength < 0 {
return nil, formatError(argOutOfBoundsError, round)
}
shift := math.Pow(10, float64(intLength))
rounded := math.Round(op.Float()*shift) / shift
return rounded, nil
}
func jpBase64Decode(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg("", arguments, 0, reflect.String)
if err != nil {
return nil, err
}
decodedStr, err := base64.StdEncoding.DecodeString(str.String())
if err != nil {
return nil, err
}
return string(decodedStr), nil
}
func jpBase64Encode(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg("", arguments, 0, reflect.String)
if err != nil {
return nil, err
}
return base64.StdEncoding.EncodeToString([]byte(str.String())), nil
}
func jpPathCanonicalize(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(pathCanonicalize, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
return filepath.Join(str.String()), nil
}
func jpTruncate(arguments []interface{}) (interface{}, error) {
var err error
var normalizedLength float64
str, err := validateArg(truncate, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
length, err := validateArg(truncate, arguments, 1, reflect.Float64)
if err != nil {
return nil, err
}
if length.Float() < 0 {
normalizedLength = float64(0)
} else {
normalizedLength = length.Float()
}
return trunc.Truncator(str.String(), int(normalizedLength), trunc.CutStrategy{}), nil
}
func jpSemverCompare(arguments []interface{}) (interface{}, error) {
var err error
v, err := validateArg(semverCompare, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
r, err := validateArg(semverCompare, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
version, _ := semver.Parse(v.String())
expectedRange, err := semver.ParseRange(r.String())
if err != nil {
return nil, err
}
if expectedRange(version) {
return true, nil
}
return false, nil
}
func jpParseJson(arguments []interface{}) (interface{}, error) {
input, err := validateArg(parseJson, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
var output interface{}
err = json.Unmarshal([]byte(input.String()), &output)
return output, err
}
func jpParseYAML(arguments []interface{}) (interface{}, error) {
input, err := validateArg(parseYAML, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
jsonData, err := yaml.YAMLToJSON([]byte(input.String()))
if err != nil {
return nil, err
}
var output interface{}
err = json.Unmarshal(jsonData, &output)
return output, err
}
func jpLookup(arguments []interface{}) (interface{}, error) {
switch input := arguments[0].(type) {
case map[string]interface{}:
key, ok := arguments[1].(string)
if !ok {
return nil, formatError(invalidArgumentTypeError, lookup, 2, "String")
}
return input[key], nil
case []interface{}:
key, ok := arguments[1].(float64)
if !ok {
return nil, formatError(invalidArgumentTypeError, lookup, 2, "Number")
}
keyInt, err := intNumber(key)
if err != nil {
return nil, fmt.Errorf(
"JMESPath function '%s': argument #2: %s",
lookup, err.Error(),
)
}
if keyInt < 0 || keyInt > len(input)-1 {
return nil, nil
}
return input[keyInt], nil
default:
return nil, formatError(invalidArgumentTypeError, lookup, 1, "Object or Array")
}
}
func jpItems(arguments []interface{}) (interface{}, error) {
keyName, ok := arguments[1].(string)
if !ok {
return nil, formatError(invalidArgumentTypeError, items, 2, "String")
}
valName, ok := arguments[2].(string)
if !ok {
return nil, formatError(invalidArgumentTypeError, items, 3, "String")
}
switch input := arguments[0].(type) {
case map[string]interface{}:
keys := make([]string, 0, len(input))
// Sort the keys so that the output is deterministic
for key := range input {
keys = append(keys, key)
}
sort.Strings(keys)
arrayOfObj := make([]map[string]interface{}, 0, len(input))
for _, key := range keys {
arrayOfObj = append(arrayOfObj, map[string]interface{}{
keyName: key,
valName: input[key],
})
}
return arrayOfObj, nil
case []interface{}:
arrayOfObj := make([]map[string]interface{}, 0, len(input))
for index, value := range input {
arrayOfObj = append(arrayOfObj, map[string]interface{}{
keyName: float64(index),
valName: value,
})
}
return arrayOfObj, nil
default:
return nil, formatError(invalidArgumentTypeError, items, 1, "Object or Array")
}
}
func jpObjectFromLists(arguments []interface{}) (interface{}, error) {
keys, ok := arguments[0].([]interface{})
if !ok {
return nil, formatError(invalidArgumentTypeError, objectFromLists, 1, "Array")
}
values, ok := arguments[1].([]interface{})
if !ok {
return nil, formatError(invalidArgumentTypeError, objectFromLists, 2, "Array")
}
output := map[string]interface{}{}
for i, ikey := range keys {
key, err := ifaceToString(ikey)
if err != nil {
return nil, formatError(invalidArgumentTypeError, objectFromLists, 1, "StringArray")
}
if i < len(values) {
output[key] = values[i]
} else {
output[key] = nil
}
}
return output, nil
}
// InterfaceToString casts an interface to a string type
func ifaceToString(iface interface{}) (string, error) {
switch i := iface.(type) {
case int:
return strconv.Itoa(i), nil
case float64:
return strconv.FormatFloat(i, 'f', -1, 32), nil
case float32:
return strconv.FormatFloat(float64(i), 'f', -1, 32), nil
case string:
return i, nil
case bool:
return strconv.FormatBool(i), nil
default:
return "", errors.New("error, undefined type cast")
}
}
func jpRandom(arguments []interface{}) (interface{}, error) {
pattern := arguments[0].(string)
if pattern == "" {
return "", errors.New("no pattern provided")
}
b := make([]byte, 8)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
ans, err := regen.Generate(pattern)
if err != nil {
return nil, err
}
return ans, nil
}
func encode[T any](in T) (interface{}, error) {
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
if err := enc.Encode(in); err != nil {
return nil, err
}
res := map[string]interface{}{}
if err := json.Unmarshal(buf.Bytes(), &res); err != nil {
return nil, err
}
return res, nil
}
func jpX509Decode(arguments []interface{}) (interface{}, error) {
parseSubjectPublicKeyInfo := func(data []byte) (*rsa.PublicKey, error) {
spki := cryptobyte.String(data)
if !spki.ReadASN1(&spki, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("writing asn.1 element to 'spki' failed")
}
var pkAISeq cryptobyte.String
if !spki.ReadASN1(&pkAISeq, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("writing asn.1 element to 'pkAISeq' failed")
}
var spk asn1.BitString
if !spki.ReadASN1BitString(&spk) {
return nil, errors.New("writing asn.1 bit string to 'spk' failed")
}
if kk, err := x509.ParsePKCS1PublicKey(spk.Bytes); err != nil {
return nil, err
} else {
return kk, nil
}
}
if input, err := validateArg(x509_decode, arguments, 0, reflect.String); err != nil {
return nil, err
} else if block, _ := pem.Decode([]byte(input.String())); block == nil {
return nil, errors.New("failed to decode PEM block")
} else {
switch block.Type {
case "CERTIFICATE":
var cert *x509.Certificate
if cert, err = x509.ParseCertificate(block.Bytes); err != nil {
return nil, err
} else if cert.PublicKeyAlgorithm != x509.RSA {
return nil, errors.New("certificate should use rsa algorithm")
} else if pk, err := parseSubjectPublicKeyInfo(cert.RawSubjectPublicKeyInfo); err != nil {
return nil, errors.New("failed to parse subject public key info")
} else {
cert.PublicKey = PublicKey{
N: pk.N.String(),
E: pk.E,
}
return encode(cert)
}
case "CERTIFICATE REQUEST":
var csr *x509.CertificateRequest
if csr, err = x509.ParseCertificateRequest(block.Bytes); err != nil {
return nil, err
} else if csr.PublicKeyAlgorithm != x509.RSA {
return nil, errors.New("certificate should use rsa algorithm")
} else if pk, err := parseSubjectPublicKeyInfo(csr.RawSubjectPublicKeyInfo); err != nil {
return nil, errors.New("failed to parse subject public key info")
} else {
csr.PublicKey = PublicKey{
N: pk.N.String(),
E: pk.E,
}
return encode(csr)
}
default:
return nil, errors.New("PEM block neither contains a CERTIFICATE or CERTIFICATE REQUEST")
}
}
}
func jpImageNormalize(configuration config.Configuration) gojmespath.JpFunction {
return func(arguments []interface{}) (interface{}, error) {
if image, err := validateArg(imageNormalize, arguments, 0, reflect.String); err != nil {
return nil, err
} else if infos, err := imageutils.GetImageInfo(image.String(), configuration); err != nil {
return nil, formatError(genericError, imageNormalize, err)
} else {
return infos.String(), nil
}
}
}
func jpIsExternalURL(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(pathCanonicalize, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
parsedURL, err := url.Parse(str.String())
if err != nil {
return false, err
}
ip := net.ParseIP(parsedURL.Hostname())
if ip != nil {
return !(ip.IsLoopback() || ip.IsPrivate()), nil
}
// If it can't be parsed as an IP, then resolve the domain name
ips, err := net.LookupIP(parsedURL.Hostname())
if err != nil {
return nil, err
}
for _, ip := range ips {
if ip.IsLoopback() || ip.IsPrivate() {
return false, nil
}
}
return true, nil
}
func jpSha256(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg("", arguments, 0, reflect.String)
if err != nil {
return nil, err
}
hasher := sha256.New()
_, err = hasher.Write([]byte(str.String()))
if err != nil {
return "", err
}
hashedBytes := hasher.Sum(nil)
return hex.EncodeToString(hashedBytes), nil
}
func jpSha1(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg("", arguments, 0, reflect.String)
if err != nil {
return nil, err
}
hasher := sha1.New() // #nosec G401
_, err = hasher.Write([]byte(str.String()))
if err != nil {
return "", err
}
hashedBytes := hasher.Sum(nil)
return hex.EncodeToString(hashedBytes), nil
}
func jpMd5(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg("", arguments, 0, reflect.String)
if err != nil {
return nil, err
}
hasher := md5.New() // #nosec G401
_, err = hasher.Write([]byte(str.String()))
if err != nil {
return "", err
}
hashedBytes := hasher.Sum(nil)
return hex.EncodeToString(hashedBytes), nil
}