Skip to main content

apollo_redaction/strategy/
ip.rs

1use crate::RedactionStrategy;
2use std::fmt;
3
4/// Redact IPv4 and IPv6 addresses.
5///
6/// ## IPv4
7/// The first two octets are shown.
8/// ```rust
9/// use apollo_redaction::Redacted;
10/// use apollo_redaction::strategy::IpRedactor;
11///
12/// let ip = Redacted::<_, IpRedactor>::new("192.168.1.100");
13/// assert_eq!(
14///     format!("private ip is {ip}"),
15///     "private ip is 192.168.*.*",
16/// );
17/// ```
18///
19/// ## IPv6
20/// The first four groups are shown.
21/// ```rust
22/// use apollo_redaction::Redacted;
23/// use apollo_redaction::strategy::IpRedactor;
24///
25/// let ip = Redacted::<_, IpRedactor>::new("4fd3:694a:d3f4:c465:9b17:18af:e6ae:a243");
26/// assert_eq!(
27///     format!("private ip is {ip}"),
28///     "private ip is 4fd3:694a:d3f4:c465:****:****:****:****",
29/// );
30/// ```
31#[derive(Default, Clone)]
32pub struct IpRedactor;
33
34impl<T: AsRef<str>> RedactionStrategy<T> for IpRedactor {
35    fn display(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        redact_ip(value.as_ref(), f)
37    }
38}
39
40fn redact_ip(ip: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41    // For IPs: show first two octets/groups, redact the rest
42    // Handle both IPv4 (dot-separated) and IPv6 (colon-separated)
43    if ip.contains(':') {
44        // IPv6 or SocketAddrV6 - show first 4 groups
45        let parts: Vec<&str> = ip.split(':').collect();
46        if parts.len() >= 4 {
47            write!(
48                f,
49                "{}:{}:{}:{}:****:****:****:****",
50                parts[0], parts[1], parts[2], parts[3]
51            )
52        } else {
53            write!(f, "****:****:****:****:****:****:****:****")
54        }
55    } else {
56        // IPv4 or SocketAddrV4 - show first two octets
57        let parts: Vec<&str> = ip.split('.').collect();
58        if parts.len() == 4 {
59            write!(f, "{}.{}.*.*", parts[0], parts[1])
60        } else {
61            // Fallback for malformed input
62            write!(f, "[REDACTED: invalid IP]")
63        }
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn display_ipv4() {
73        let my_secret = "192.168.1.100";
74        let redacted = crate::Redacted::<_, IpRedactor>::new(my_secret);
75
76        assert_eq!(
77            format!("private ip is {redacted}"),
78            "private ip is 192.168.*.*",
79        );
80    }
81
82    #[test]
83    fn display_ipv6() {
84        let my_secret = "4fd3:694a:d3f4:c465:9b17:18af:e6ae:a243";
85        let redacted = crate::Redacted::<_, IpRedactor>::new(my_secret);
86
87        assert_eq!(
88            format!("private ip is {redacted}"),
89            "private ip is 4fd3:694a:d3f4:c465:****:****:****:****",
90        );
91    }
92
93    #[test]
94    fn display_invalid() {
95        let my_secret = "this is not an ip address";
96        let redacted = crate::Redacted::<_, IpRedactor>::new(my_secret);
97
98        assert_eq!(
99            format!("private ip is {redacted}"),
100            "private ip is [REDACTED: invalid IP]",
101            "must not leak secret if it's malformed"
102        );
103    }
104}