forked from mirrors/kingfisher
updated confluent rule with a checksum. Added zuplo rule with a checksum
This commit is contained in:
parent
0c07f3729f
commit
1ee9e804b0
4 changed files with 306 additions and 1 deletions
|
|
@ -52,6 +52,43 @@ rules:
|
|||
- 200
|
||||
type: StatusMatch
|
||||
url: https://api.confluent.cloud/iam/v2/api-keys/{{ CLIENTID }}
|
||||
depends_on_rule:
|
||||
- rule_id: "kingfisher.confluent.1"
|
||||
variable: CLIENTID
|
||||
- name: Confluent API Secret - Updated Format
|
||||
id: kingfisher.confluent.3
|
||||
pattern: |
|
||||
(?xi)
|
||||
\b
|
||||
(
|
||||
cflt(?P<body>[A-Za-z0-9\+/]{54})(?P<checksum>[A-Za-z0-9\+/]{6})
|
||||
)
|
||||
pattern_requirements:
|
||||
checksum:
|
||||
actual:
|
||||
template: "{{ MATCH | suffix: 6 }}"
|
||||
requires_capture: checksum
|
||||
expected: "{{ BODY | crc32_le_b64: 6 }}"
|
||||
skip_if_missing: true
|
||||
min_entropy: 3.3
|
||||
confidence: medium
|
||||
examples:
|
||||
- confluent secret=cfltqPLd2lLPAtWtHGNhN32WlZxoEj30pcg8mzaPlPJ937JlMa7n9YCRLooqgifw
|
||||
references:
|
||||
- https://docs.confluent.io/cloud/current/api.html#tag/API-Keys-(iamv2)/operation/getIamV2ApiKey
|
||||
validation:
|
||||
type: Http
|
||||
content:
|
||||
request:
|
||||
headers:
|
||||
Authorization: 'Basic {{ CLIENTID | append: ":" | append: TOKEN | b64enc }}'
|
||||
method: GET
|
||||
response_matcher:
|
||||
- report_response: true
|
||||
- status:
|
||||
- 200
|
||||
type: StatusMatch
|
||||
url: https://api.confluent.cloud/iam/v2/api-keys/{{ CLIENTID }}
|
||||
depends_on_rule:
|
||||
- rule_id: "kingfisher.confluent.1"
|
||||
variable: CLIENTID
|
||||
22
data/rules/zuplo.yml
Normal file
22
data/rules/zuplo.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
rules:
|
||||
- name: Zuplo API Key
|
||||
id: kingfisher.zuplo.1
|
||||
pattern: |
|
||||
(?xi)
|
||||
\b
|
||||
(
|
||||
zpka_(?P<body>[a-z0-9]{32})_(?P<checksum>[0-9a-f]{8})
|
||||
)
|
||||
pattern_requirements:
|
||||
checksum:
|
||||
actual:
|
||||
template: "{{ CHECKSUM | downcase }}"
|
||||
requires_capture: checksum
|
||||
expected: "{{ BODY | crc32_hex }}"
|
||||
min_entropy: 3.3
|
||||
confidence: medium
|
||||
examples:
|
||||
- zpka_3e6c4f7d39954ca29353b7ab88589b64_de26cd55
|
||||
- zpka_b3f94d8d3d4d4a6ea5c5b20d0a5bb407_18eb262b
|
||||
references:
|
||||
- https://zuplo.com/blog/api-key-authentication
|
||||
|
|
@ -119,11 +119,15 @@ Below is the complete list of Liquid filters available in Kingfisher, along with
|
|||
| `b64url_enc` | – | URL-safe Base64 (no padding). Useful for JWT headers & payloads. | `{{ TOKEN \| b64url_enc }}` |
|
||||
| `b64dec` | – | Decodes a Base64 string. | `{{ "aGVsbG8=" \| b64dec }}` |
|
||||
| `sha256` | – | Computes the SHA-256 hex digest of the input. | `{{ TOKEN \| sha256 }}` |
|
||||
| `crc32` | – | Computes the CRC32 checksum of the input and returns a decimal value. | `{{ TOKEN \| crc32 }}` |
|
||||
| `crc32` | – | Computes the CRC32 checksum of the input and returns a decimal value. | `{{ TOKEN \| crc32 }}` |
|
||||
| `crc32_dec` | `digits` (integer, optional) | Computes the CRC32 checksum and returns the last `digits` decimal characters (zero-padded). Defaults to the full value when omitted. | `{{ TOKEN \| crc32_dec: 6 }}` |
|
||||
| `crc32_hex` | `digits` (integer, optional) | Computes the CRC32 checksum and returns the last `digits` hexadecimal characters (zero-padded). Defaults to the full value when omitted. | `{{ TOKEN \| crc32_hex: 8 }}` |
|
||||
| `crc32_le_b64` | `len` (integer, optional) | Computes the CRC32 checksum, encodes the little-endian bytes using Base64, and optionally truncates to the first `len` characters. | `{{ TOKEN \| crc32_le_b64: 6 }}` |
|
||||
| `hmac_sha1` | `key` (string) | Computes HMAC-SHA1 over the input, returns Base64-encoded result. | `{{ TOKEN \| hmac_sha1: "secret-key" }}` |
|
||||
| `hmac_sha256` | `key` (string) | Computes HMAC-SHA256 over the input, returns Base64-encoded result. | `{{ TOKEN \| hmac_sha256: "secret-key" }}` |
|
||||
| `hmac_sha384` | `key` (string) | Computes HMAC-SHA384 over the input, returns Base64-encoded result. | `{{ TOKEN \| hmac_sha384: "secret-key" }}` |
|
||||
| `random_string` | `len` (integer, optional) | Generates a cryptographically-secure random alphanumeric string of the specified length (default: 32). | `{{ "" \| random_string: 16 }}` |
|
||||
| `prefix` | `len` (integer, optional) | Returns the first `len` characters from the string (default: full). | `{{ TOKEN \| prefix: 6 }}` |
|
||||
| `suffix` | `len` (integer, optional) | Returns the last `len` characters from the string (default: full). | `{{ TOKEN \| suffix: 6 }}` |
|
||||
| `base62` | `width` (integer, optional) | Encodes the input number as Base62, left-padding with zeros as needed. | `{{ TOKEN \| crc32 \| base62: 6 }}` |
|
||||
| `url_encode` | – | Percent-encodes the input according to RFC 3986. | `{{ TOKEN \| url_encode }}` |
|
||||
|
|
|
|||
|
|
@ -309,6 +309,49 @@ impl Filter for Suffix {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FilterParameters)]
|
||||
struct PrefixArgs {
|
||||
#[parameter(description = "Number of leading characters to keep", arg_type = "integer")]
|
||||
len: Option<Expression>,
|
||||
}
|
||||
|
||||
#[derive(Clone, ParseFilter, FilterReflection, Default)]
|
||||
#[filter(
|
||||
name = "prefix",
|
||||
description = "Return the prefix (first N characters) of the provided string.",
|
||||
parameters(PrefixArgs),
|
||||
parsed(Prefix)
|
||||
)]
|
||||
pub struct PrefixFilter;
|
||||
|
||||
#[derive(Debug, FromFilterParameters, Display_filter)]
|
||||
#[name = "prefix"]
|
||||
struct Prefix {
|
||||
#[parameters]
|
||||
args: PrefixArgs,
|
||||
}
|
||||
|
||||
impl Filter for Prefix {
|
||||
fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
|
||||
let args = self.args.evaluate(runtime)?;
|
||||
let text = input.to_kstr();
|
||||
let requested = args
|
||||
.len
|
||||
.and_then(|value| {
|
||||
let scalar = Value::scalar(value);
|
||||
value_to_usize(&scalar)
|
||||
})
|
||||
.unwrap_or_else(|| text.len());
|
||||
if requested == 0 {
|
||||
return Ok(Value::scalar(String::new()));
|
||||
}
|
||||
|
||||
let mut chars: Vec<char> = text.chars().collect();
|
||||
chars.truncate(requested.min(chars.len()));
|
||||
Ok(Value::scalar(chars.into_iter().collect::<String>()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, FilterReflection, ParseFilter)]
|
||||
#[filter(
|
||||
name = "b64enc",
|
||||
|
|
@ -387,6 +430,175 @@ static_filter!(
|
|||
}
|
||||
);
|
||||
|
||||
#[derive(Debug, FilterParameters)]
|
||||
struct Crc32DecArgs {
|
||||
#[parameter(
|
||||
description = "Number of trailing decimal digits to return (zero padded)",
|
||||
arg_type = "integer"
|
||||
)]
|
||||
digits: Option<Expression>,
|
||||
}
|
||||
|
||||
#[derive(Clone, ParseFilter, FilterReflection, Default)]
|
||||
#[filter(
|
||||
name = "crc32_dec",
|
||||
description = "Compute the CRC32 and optionally return the last N decimal digits.",
|
||||
parameters(Crc32DecArgs),
|
||||
parsed(Crc32Dec)
|
||||
)]
|
||||
pub struct Crc32DecFilter;
|
||||
|
||||
#[derive(Debug, FromFilterParameters, Display_filter)]
|
||||
#[name = "crc32_dec"]
|
||||
struct Crc32Dec {
|
||||
#[parameters]
|
||||
args: Crc32DecArgs,
|
||||
}
|
||||
|
||||
impl Filter for Crc32Dec {
|
||||
fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
|
||||
let args = self.args.evaluate(runtime)?;
|
||||
let mut hasher = Hasher::new();
|
||||
hasher.update(input.to_kstr().as_bytes());
|
||||
let checksum = u128::from(hasher.finalize());
|
||||
|
||||
let digits = args
|
||||
.digits
|
||||
.and_then(|value| {
|
||||
let scalar = Value::scalar(value);
|
||||
value_to_usize(&scalar)
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
if digits == 0 {
|
||||
return Ok(Value::scalar(checksum.to_string()));
|
||||
}
|
||||
|
||||
let clamped_digits = digits.min(38); // 10^38 fits within u128
|
||||
let modulus = 10u128.pow(clamped_digits as u32);
|
||||
let truncated = checksum % modulus;
|
||||
let mut value = truncated.to_string();
|
||||
if clamped_digits > value.len() {
|
||||
let mut padded = String::with_capacity(clamped_digits);
|
||||
for _ in 0..(clamped_digits - value.len()) {
|
||||
padded.push('0');
|
||||
}
|
||||
padded.push_str(&value);
|
||||
value = padded;
|
||||
}
|
||||
|
||||
Ok(Value::scalar(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FilterParameters)]
|
||||
struct Crc32HexArgs {
|
||||
#[parameter(
|
||||
description = "Number of trailing hexadecimal digits to return (zero padded)",
|
||||
arg_type = "integer"
|
||||
)]
|
||||
digits: Option<Expression>,
|
||||
}
|
||||
|
||||
#[derive(Clone, ParseFilter, FilterReflection, Default)]
|
||||
#[filter(
|
||||
name = "crc32_hex",
|
||||
description = "Compute the CRC32 and optionally return the last N hexadecimal digits.",
|
||||
parameters(Crc32HexArgs),
|
||||
parsed(Crc32Hex)
|
||||
)]
|
||||
pub struct Crc32HexFilter;
|
||||
|
||||
#[derive(Debug, FromFilterParameters, Display_filter)]
|
||||
#[name = "crc32_hex"]
|
||||
struct Crc32Hex {
|
||||
#[parameters]
|
||||
args: Crc32HexArgs,
|
||||
}
|
||||
|
||||
impl Filter for Crc32Hex {
|
||||
fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
|
||||
let args = self.args.evaluate(runtime)?;
|
||||
let mut hasher = Hasher::new();
|
||||
hasher.update(input.to_kstr().as_bytes());
|
||||
let checksum = hasher.finalize();
|
||||
let mut hex = format!("{checksum:08x}");
|
||||
|
||||
let digits = args
|
||||
.digits
|
||||
.and_then(|value| {
|
||||
let scalar = Value::scalar(value);
|
||||
value_to_usize(&scalar)
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
if digits == 0 {
|
||||
return Ok(Value::scalar(hex));
|
||||
}
|
||||
|
||||
let clamped = digits.min(32);
|
||||
if clamped > hex.len() {
|
||||
let mut padded = String::with_capacity(clamped);
|
||||
for _ in 0..(clamped - hex.len()) {
|
||||
padded.push('0');
|
||||
}
|
||||
padded.push_str(&hex);
|
||||
hex = padded;
|
||||
} else {
|
||||
let start = hex.len() - clamped;
|
||||
hex = hex[start..].to_string();
|
||||
}
|
||||
|
||||
Ok(Value::scalar(hex))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FilterParameters)]
|
||||
struct Crc32LeB64Args {
|
||||
#[parameter(
|
||||
description = "Number of leading characters from the Base64 string to keep",
|
||||
arg_type = "integer"
|
||||
)]
|
||||
len: Option<Expression>,
|
||||
}
|
||||
|
||||
#[derive(Clone, ParseFilter, FilterReflection, Default)]
|
||||
#[filter(
|
||||
name = "crc32_le_b64",
|
||||
description = "Compute the CRC32, encode little-endian bytes as Base64, optionally truncating.",
|
||||
parameters(Crc32LeB64Args),
|
||||
parsed(Crc32LeB64)
|
||||
)]
|
||||
pub struct Crc32LeB64Filter;
|
||||
|
||||
#[derive(Debug, FromFilterParameters, Display_filter)]
|
||||
#[name = "crc32_le_b64"]
|
||||
struct Crc32LeB64 {
|
||||
#[parameters]
|
||||
args: Crc32LeB64Args,
|
||||
}
|
||||
|
||||
impl Filter for Crc32LeB64 {
|
||||
fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
|
||||
let args = self.args.evaluate(runtime)?;
|
||||
let mut hasher = Hasher::new();
|
||||
hasher.update(input.to_kstr().as_bytes());
|
||||
let checksum = hasher.finalize();
|
||||
let encoded = general_purpose::STANDARD.encode(checksum.to_le_bytes());
|
||||
|
||||
let output = if let Some(len) = args.len.and_then(|value| {
|
||||
let scalar = Value::scalar(value);
|
||||
value_to_usize(&scalar)
|
||||
}) {
|
||||
encoded.chars().take(len).collect::<String>()
|
||||
} else {
|
||||
encoded
|
||||
};
|
||||
|
||||
Ok(Value::scalar(output))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FilterParameters)]
|
||||
struct Base62Args {
|
||||
#[parameter(
|
||||
|
|
@ -590,7 +802,11 @@ pub fn register_all(builder: liquid::ParserBuilder) -> liquid::ParserBuilder {
|
|||
.filter(B64DecFilter::default())
|
||||
.filter(RandomStringFilter::default())
|
||||
.filter(SuffixFilter::default())
|
||||
.filter(PrefixFilter::default())
|
||||
.filter(Crc32Filter::default())
|
||||
.filter(Crc32DecFilter::default())
|
||||
.filter(Crc32HexFilter::default())
|
||||
.filter(Crc32LeB64Filter::default())
|
||||
.filter(Base62Filter::default())
|
||||
.filter(HmacSha256::default())
|
||||
.filter(HmacSha1::default())
|
||||
|
|
@ -645,6 +861,13 @@ mod tests {
|
|||
assert_eq!(render(r#"{{ "value" | suffix: 0 }}"#), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_filter() {
|
||||
assert_eq!(render(r#"{{ "abcdef" | prefix: 3 }}"#), "abc");
|
||||
assert_eq!(render(r#"{{ "short" | prefix: 10 }}"#), "short");
|
||||
assert_eq!(render(r#"{{ "value" | prefix: 0 }}"#), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crc32_and_base62_filters() {
|
||||
assert_eq!(render(r#"{{ "hello" | crc32 }}"#), "907060870");
|
||||
|
|
@ -652,6 +875,25 @@ mod tests {
|
|||
assert_eq!(render(r#"{{ "hello" | crc32 | base62: 6 }}"#), "0zNvy2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crc32_dec_filter() {
|
||||
assert_eq!(render(r#"{{ "hello" | crc32_dec }}"#), "907060870");
|
||||
assert_eq!(render(r#"{{ "hello" | crc32_dec: 6 }}"#), "060870");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crc32_hex_filter() {
|
||||
assert_eq!(render(r#"{{ "hello" | crc32_hex }}"#), "3610a686");
|
||||
assert_eq!(render(r#"{{ "hello" | crc32_hex: 4 }}"#), "a686");
|
||||
assert_eq!(render(r#"{{ "hello" | crc32_hex: 10 }}"#), "003610a686");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crc32_le_b64_filter() {
|
||||
assert_eq!(render(r#"{{ "hello" | crc32_le_b64 }}"#), "hqYQNg==");
|
||||
assert_eq!(render(r#"{{ "hello" | crc32_le_b64: 6 }}"#), "hqYQNg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hmac_sha1_filter() {
|
||||
let key = b"key1";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue