Skip to main content

rws_gateway_api/gateway_api/ext_auth/
proto.rs

1//! The few protobuf messages of Envoy's `ext_authz` API rws needs
2//! (`envoy.service.auth.v3`, `envoy.config.core.v3`, `google.rpc.Status`),
3//! encoded and decoded by hand: a `CheckRequest` with the HTTP request's
4//! attributes, and the parts of a `CheckResponse` that decide.
5//!
6//! Field numbers from envoy/service/auth/v3/{external_auth,attribute_context}.proto
7//! and envoy/config/core/v3/{address,base}.proto.
8
9/// What the `CheckRequest` says about the client's request.
10pub struct HttpAttributes<'a> {
11    pub method: &'a str,
12    /// The request target as sent: path and query.
13    pub path: &'a str,
14    pub host: &'a str,
15    pub scheme: &'a str,
16    /// Lower-case names.
17    pub headers: &'a [(String, String)],
18    pub body: &'a [u8],
19    pub source_ip: &'a str,
20}
21
22fn varint(out: &mut Vec<u8>, mut value: u64) {
23    while value >= 0x80 {
24        out.push((value as u8) | 0x80);
25        value >>= 7;
26    }
27    out.push(value as u8);
28}
29
30fn bytes_field(out: &mut Vec<u8>, field: u32, value: &[u8]) {
31    varint(out, ((field as u64) << 3) | 2);
32    varint(out, value.len() as u64);
33    out.extend_from_slice(value);
34}
35
36fn string_field(out: &mut Vec<u8>, field: u32, value: &str) {
37    if !value.is_empty() {
38        bytes_field(out, field, value.as_bytes());
39    }
40}
41
42fn varint_field(out: &mut Vec<u8>, field: u32, value: u64) {
43    varint(out, (field as u64) << 3);
44    varint(out, value);
45}
46
47/// `CheckRequest { attributes: AttributeContext { source, request: { http } } }`.
48pub fn check_request(a: &HttpAttributes) -> Vec<u8> {
49    // HttpRequest
50    let mut http = Vec::new();
51    string_field(&mut http, 2, a.method);
52    for (name, value) in a.headers {
53        let mut entry = Vec::new();
54        string_field(&mut entry, 1, name);
55        string_field(&mut entry, 2, value);
56        bytes_field(&mut http, 3, &entry);
57    }
58    string_field(&mut http, 4, a.path);
59    string_field(&mut http, 5, a.host);
60    string_field(&mut http, 6, a.scheme);
61    if let Some((_, query)) = a.path.split_once('?') {
62        string_field(&mut http, 7, query);
63    }
64    varint_field(&mut http, 9, a.body.len() as u64);
65    string_field(&mut http, 10, "HTTP/1.1");
66    if !a.body.is_empty() {
67        bytes_field(&mut http, 12, a.body);
68    }
69    // AttributeContext.Request { http = 2 }
70    let mut request = Vec::new();
71    bytes_field(&mut request, 2, &http);
72    // AttributeContext.Peer { address = 1: Address { socket_address = 1: { address = 2 } } }
73    let mut socket_address = Vec::new();
74    string_field(&mut socket_address, 2, a.source_ip);
75    let mut address = Vec::new();
76    bytes_field(&mut address, 1, &socket_address);
77    let mut source = Vec::new();
78    bytes_field(&mut source, 1, &address);
79    // AttributeContext { source = 1, request = 4 }
80    let mut context = Vec::new();
81    bytes_field(&mut context, 1, &source);
82    bytes_field(&mut context, 4, &request);
83    let mut out = Vec::new();
84    bytes_field(&mut out, 1, &context);
85    out
86}
87
88/// One field: its number and a varint or length-delimited value.
89enum Value<'a> {
90    Varint(u64),
91    Bytes(&'a [u8]),
92}
93
94fn read_varint(input: &[u8], at: &mut usize) -> Result<u64, String> {
95    let mut value = 0u64;
96    for shift in (0..64).step_by(7) {
97        let byte = *input.get(*at).ok_or("truncated varint")?;
98        *at += 1;
99        value |= u64::from(byte & 0x7f) << shift;
100        if byte < 0x80 {
101            return Ok(value);
102        }
103    }
104    Err("varint too long".to_string())
105}
106
107/// The fields of a message (fixed-width fields are skipped).
108fn fields(input: &[u8]) -> Result<Vec<(u64, Value<'_>)>, String> {
109    let mut at = 0;
110    let mut out = Vec::new();
111    while at < input.len() {
112        let key = read_varint(input, &mut at)?;
113        let (field, wire) = (key >> 3, key & 7);
114        match wire {
115            0 => out.push((field, Value::Varint(read_varint(input, &mut at)?))),
116            2 => {
117                let len = read_varint(input, &mut at)? as usize;
118                let end = at.checked_add(len).filter(|e| *e <= input.len()).ok_or("truncated field")?;
119                out.push((field, Value::Bytes(&input[at..end])));
120                at = end;
121            }
122            1 => at = at.checked_add(8).filter(|e| *e <= input.len()).ok_or("truncated field")?,
123            5 => at = at.checked_add(4).filter(|e| *e <= input.len()).ok_or("truncated field")?,
124            other => return Err(format!("unsupported wire type {other}")),
125        }
126    }
127    Ok(out)
128}
129
130fn text(bytes: &[u8]) -> String {
131    String::from_utf8_lossy(bytes).into_owned()
132}
133
134/// The deciding parts of a `CheckResponse`.
135#[derive(Debug, Default, PartialEq)]
136pub struct Verdict {
137    /// `status.code`: `0` (OK) allows.
138    pub code: i32,
139    /// `ok_response.headers`, or `denied_response.headers`.
140    pub headers: Vec<(String, String)>,
141    /// `ok_response.headers_to_remove`.
142    pub headers_to_remove: Vec<String>,
143    /// `denied_response.status.code` (0 when unset).
144    pub denied_status: i32,
145    /// `denied_response.body`.
146    pub denied_body: String,
147}
148
149/// `HeaderValueOption { header = 1: HeaderValue { key = 1, value = 2, raw_value = 3 } }`.
150fn header_option(bytes: &[u8]) -> Result<Option<(String, String)>, String> {
151    for (field, value) in fields(bytes)? {
152        if let (1, Value::Bytes(header)) = (field, value) {
153            let (mut key, mut text_value, mut raw) = (String::new(), String::new(), None);
154            for (f, v) in fields(header)? {
155                match (f, v) {
156                    (1, Value::Bytes(b)) => key = text(b),
157                    (2, Value::Bytes(b)) => text_value = text(b),
158                    (3, Value::Bytes(b)) => raw = Some(text(b)),
159                    _ => {}
160                }
161            }
162            return Ok(Some((key, raw.unwrap_or(text_value))));
163        }
164    }
165    Ok(None)
166}
167
168/// Read a `CheckResponse`.
169pub fn check_response(message: &[u8]) -> Result<Verdict, String> {
170    let mut verdict = Verdict::default();
171    for (field, value) in fields(message)? {
172        match (field, value) {
173            // google.rpc.Status { code = 1 }
174            (1, Value::Bytes(status)) => {
175                for (f, v) in fields(status)? {
176                    if let (1, Value::Varint(code)) = (f, v) {
177                        verdict.code = code as i32;
178                    }
179                }
180            }
181            // DeniedHttpResponse { status = 1: HttpStatus { code = 1 }, headers = 2, body = 3 }
182            (2, Value::Bytes(denied)) => {
183                for (f, v) in fields(denied)? {
184                    match (f, v) {
185                        (1, Value::Bytes(status)) => {
186                            for (sf, sv) in fields(status)? {
187                                if let (1, Value::Varint(code)) = (sf, sv) {
188                                    verdict.denied_status = code as i32;
189                                }
190                            }
191                        }
192                        (2, Value::Bytes(option)) => verdict.headers.extend(header_option(option)?),
193                        (3, Value::Bytes(body)) => verdict.denied_body = text(body),
194                        _ => {}
195                    }
196                }
197            }
198            // OkHttpResponse { headers = 2, headers_to_remove = 5 }
199            (3, Value::Bytes(ok)) => {
200                for (f, v) in fields(ok)? {
201                    match (f, v) {
202                        (2, Value::Bytes(option)) => verdict.headers.extend(header_option(option)?),
203                        (5, Value::Bytes(name)) => verdict.headers_to_remove.push(text(name)),
204                        _ => {}
205                    }
206                }
207            }
208            _ => {}
209        }
210    }
211    Ok(verdict)
212}
213
214/// Test and fuzz helpers: a `CheckResponse` encoded.
215#[doc(hidden)]
216pub fn encode_check_response(verdict: &Verdict) -> Vec<u8> {
217    let header = |name: &str, value: &str| {
218        let mut h = Vec::new();
219        string_field(&mut h, 1, name);
220        string_field(&mut h, 2, value);
221        let mut option = Vec::new();
222        bytes_field(&mut option, 1, &h);
223        option
224    };
225    let mut status = Vec::new();
226    if verdict.code != 0 {
227        varint_field(&mut status, 1, verdict.code as u64);
228    }
229    let mut out = Vec::new();
230    bytes_field(&mut out, 1, &status);
231    if verdict.code == 0 {
232        let mut ok = Vec::new();
233        for (name, value) in &verdict.headers {
234            bytes_field(&mut ok, 2, &header(name, value));
235        }
236        for name in &verdict.headers_to_remove {
237            string_field(&mut ok, 5, name);
238        }
239        bytes_field(&mut out, 3, &ok);
240    } else {
241        let mut denied = Vec::new();
242        let mut http_status = Vec::new();
243        if verdict.denied_status != 0 {
244            varint_field(&mut http_status, 1, verdict.denied_status as u64);
245        }
246        bytes_field(&mut denied, 1, &http_status);
247        for (name, value) in &verdict.headers {
248            bytes_field(&mut denied, 2, &header(name, value));
249        }
250        string_field(&mut denied, 3, &verdict.denied_body);
251        bytes_field(&mut out, 2, &denied);
252    }
253    out
254}
255
256/// Test helper: the attributes a `CheckRequest` carries, read back.
257#[doc(hidden)]
258pub fn decode_check_request(message: &[u8]) -> Result<(String, String, String, Vec<(String, String)>), String> {
259    let field = |bytes: &[u8], wanted: u64| -> Result<Vec<u8>, String> {
260        fields(bytes)?.into_iter().find_map(|(f, v)| match v {
261            Value::Bytes(b) if f == wanted => Some(b.to_vec()),
262            _ => None,
263        }).ok_or_else(|| format!("no field {wanted}"))
264    };
265    let context = field(message, 1)?;
266    let request = field(&context, 4)?;
267    let http = field(&request, 2)?;
268    let (mut method, mut path, mut host, mut headers) = (String::new(), String::new(), String::new(), Vec::new());
269    for (f, v) in fields(&http)? {
270        match (f, v) {
271            (2, Value::Bytes(b)) => method = text(b),
272            (3, Value::Bytes(entry)) => {
273                let (mut k, mut val) = (String::new(), String::new());
274                for (ef, ev) in fields(entry)? {
275                    match (ef, ev) {
276                        (1, Value::Bytes(b)) => k = text(b),
277                        (2, Value::Bytes(b)) => val = text(b),
278                        _ => {}
279                    }
280                }
281                headers.push((k, val));
282            }
283            (4, Value::Bytes(b)) => path = text(b),
284            (5, Value::Bytes(b)) => host = text(b),
285            _ => {}
286        }
287    }
288    Ok((method, path, host, headers))
289}