Skip to content

Commit

Permalink
Create the RedactedAdapter Rust ThriftAdapter
Browse files Browse the repository at this point in the history
Summary:
Context: https://fb.workplace.com/groups/learningrust/posts/3785786678358856/

TLDR:
I'm looking to prevent some of the fields that I pass around via Thrift RPC from being logged accidentally to Scuba/stdout logs.

This new adapter provides that functionality by wrapping the field and providing a `<REDACTED>` output for `Debug` and `Valuable`.

I've put more of context of the design (e.g. lack of `Deref`/`Display`/`Into`) into the module's documentation to ensure future maintainers don't miss out on that context.

Reviewed By: edward-shen

Differential Revision: D62039980

fbshipit-source-id: f50f694c049f00fd076533fc446500f55c627f2f
  • Loading branch information
Chris Konstad authored and facebook-github-bot committed Aug 30, 2024
1 parent c6ce28c commit 7b4ba6d
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 0 deletions.
1 change: 1 addition & 0 deletions shed/fbthrift_ext/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ rust_library(
"fbsource//third-party/rust:paste",
"fbsource//third-party/rust:thiserror",
"fbsource//third-party/rust:uuid",
"fbsource//third-party/rust:valuable",
"//thrift/lib/rust:fbthrift",
],
)
1 change: 1 addition & 0 deletions shed/fbthrift_ext/adapters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ ordered-float = { version = "3.7", features = ["serde"] }
paste = "1.0.14"
thiserror = "1.0.49"
uuid = { version = "1.2", features = ["serde", "v4", "v5", "v6", "v7", "v8"] }
valuable = { version = "0.1", features = ["derive"] }
5 changes: 5 additions & 0 deletions shed/fbthrift_ext/adapters/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ pub mod ipv6;
pub mod nonnegative;
pub mod ordered_float;
pub mod path;
pub mod redacted;
pub mod socket_addr;
pub mod unsigned_int;
pub mod uuid;
Expand All @@ -107,6 +108,10 @@ pub use crate::ordered_float::OrderedFloatAdapter;
#[doc(inline)]
pub use crate::path::Utf8PathAdapter;
#[doc(inline)]
pub use crate::redacted::Redacted;
#[doc(inline)]
pub use crate::redacted::RedactedAdapter;
#[doc(inline)]
pub use crate::socket_addr::SocketAddrAdapter;
#[doc(inline)]
pub use crate::unsigned_int::UnsignedIntAdapter;
Expand Down
157 changes: 157 additions & 0 deletions shed/fbthrift_ext/adapters/redacted.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under both the MIT license found in the
* LICENSE-MIT file in the root directory of this source tree and the Apache
* License, Version 2.0 found in the LICENSE-APACHE file in the root directory
* of this source tree.
*/

//! Adapters that prevent logging of sensitive data.
use std::marker::PhantomData;

use fbthrift::adapter::ThriftAdapter;

const REDACTED: &str = "<REDACTED>";

/// Prevents logging of sensitive data used in RPC requests.
///
/// The wrapped types are not affected on the wire, but the generated fields
/// will use [`Redacted`] to prevent logging of sensitive data.
///
/// For more information, see implementation documentation.
pub struct RedactedAdapter<T> {
inner: PhantomData<T>,
}

/// Helper trait to make writing the adapter easier.
pub trait Redactable: Clone + PartialEq + Send + Sync {}
impl<T: Clone + PartialEq + Send + Sync> Redactable for T {}

/// A wrapper type that prevents logging of sensitive data via commonly used traits.
///
/// This type is intended to be used as a field in a Thrift struct. It will ensure that
/// the field is not logged when the struct is printed via [`Debug`] or [`Valuable`].
///
/// This does not affect serialization or deserialization of the field, and *no* extra processing is
/// done (such as zeroing the value on destruction). The field has no extra security applied, it
/// only prevents accidental logging.
///
/// The inner value is accessible via the [`unredact*`] methods.
///
/// To prevent accidental misuse, this type does not (and should not) implement the following traits:
/// - [`Deref`]
/// - [`Display`] (enables an ambiguous `to_string()` impl)
/// - [`Into`]
///
/// [`Debug`]: std::fmt::Debug
/// [`Display`]: std::fmt::Display
/// [`Valuable`]: valuable::Valuable
#[derive(Clone, PartialEq)]
pub struct Redacted<T: Redactable> {
inner: T,
}

/// Purposefully doesn't implement [`Deref`] as that would make it easy to accidentally misuse the
/// values.
impl<T: Redactable> Redacted<T> {
/// Consumes the `Redacted` and returns the inner value.
pub fn unredact(self) -> T {
self.inner
}

/// Borrows the `Redacted` and returns a reference to the inner value.
pub fn unredact_ref(&self) -> &T {
&self.inner
}

/// Mutably borrows the `Redacted` and returns a mutable reference to the inner value.
pub fn unredact_mut(&mut self) -> &mut T {
&mut self.inner
}
}

impl<T: Redactable> From<T> for Redacted<T> {
fn from(inner: T) -> Self {
Self { inner }
}
}

impl<T: Redactable> std::fmt::Debug for Redacted<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(REDACTED)
}
}

impl<T: Redactable> valuable::Valuable for Redacted<T> {
fn as_value(&self) -> valuable::Value {
REDACTED.as_value()
}

fn visit(&self, visitor: &mut dyn valuable::Visit) {
REDACTED.visit(visitor)
}
}

/// Implementation for redacting a thrift string.
///
/// This adapter can perform round-trip serialization and deserialization
/// without transforming data for all non-empty inputs.
///
/// # Examples
///
/// ```thrift
/// include "thrift/annotation/rust.thrift";
///
/// @rust.Adapter{name = "::fbthrift_adapters::Redacted<>"}
/// typedef string RedactedString;
///
/// struct CreateWorkflowRequest {
/// 1: RedactedString target;
/// }
/// ```
impl<T: Redactable> ThriftAdapter for RedactedAdapter<T> {
type StandardType = T;
type AdaptedType = Redacted<T>;

type Error = std::convert::Infallible;

fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
value.unredact_ref().clone()
}

fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
Ok(Redacted { inner: value })
}
}

#[cfg(test)]
mod string_impl {
use valuable::Valuable;

use super::*;

#[test]
fn round_trip() {
let raw = "korra".to_string();
let adapted = RedactedAdapter::from_thrift(raw.clone()).unwrap();
assert_eq!(RedactedAdapter::to_thrift(&adapted), raw);
}

#[test]
fn debug_redacted() {
let raw = "sokka".to_string();
let adapted = RedactedAdapter::from_thrift(raw.clone()).unwrap();
assert_ne!(raw, format!("{adapted:?}"));
assert_eq!(REDACTED, format!("{adapted:?}"));
}

#[test]
fn valuable_redacted() {
let raw = "secret tunnel".to_string();
let adapted = RedactedAdapter::from_thrift(raw.clone()).unwrap();
assert_ne!(raw, adapted.as_value().as_str().unwrap());
assert_eq!(REDACTED, adapted.as_value().as_str().unwrap());
}
}

0 comments on commit 7b4ba6d

Please sign in to comment.