-
Notifications
You must be signed in to change notification settings - Fork 951
Introduce log maskers for AnnotatedService
#6232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6232 +/- ##
============================================
+ Coverage 74.46% 74.71% +0.25%
- Complexity 22234 22537 +303
============================================
Files 1963 1998 +35
Lines 82437 83227 +790
Branches 10764 10801 +37
============================================
+ Hits 61385 62186 +801
+ Misses 15918 15896 -22
- Partials 5134 5145 +11 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Ready for review |
Bump) Ready for review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, looks good. Left some questions.
@@ -45,6 +53,8 @@ public void serialize(AnnotatedResponse value, JsonGenerator gen, SerializerProv | |||
return; | |||
} | |||
|
|||
final FieldMasker fieldMasker = fieldMaskerCache.fieldMasker(value.beanFieldInfo()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) Are there any plans to add an extension point for maybeUnwrapFuture()
? If not, should users implement their own JsonSerializer
to handle the case?
Related: #6231 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I've added a AnnotatedRequest#parameter(int)
, AnnotatedResponse#value()
method which unwraps automatically. These methods will be called for both Annotated[Request|Response]JsonSerializer
and toString
methods.
I've also added an internal utility class AnnotatedServiceLogUtil
which serializes Annotated[Request|Response]JsonSerializer
given that Annotated[Request|Response]
is not public.
core/src/main/java/com/linecorp/armeria/common/logging/ContentSanitizerBuilder.java
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/logging/FieldMaskers.java
Show resolved
Hide resolved
|
||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface Masker {} | ||
@Masker |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion) Should we provide built-in annotations to let users take to use log masker without customizing ContentSanitizer
in the f/u PR?
public class Foo {
@Masking(Masker.NULL)
private String field1;
@Masking(Masker.ASTERISK)
private String field2;
@Masking(value = Masker.PARTIAL_LEFT, keep = 4)
private String field3;
}
// LoggingService.newDecorator() applies the maskers by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a good idea.
It may be more convenient if the new interface is used as the default contentSanitizer
since users won't have to worry about setting contentSanitizer
at all and only has to add annotations.
core/src/main/java/com/linecorp/armeria/internal/server/annotation/AnnotatedBeanFieldInfo.java
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/logging/BeanFieldInfo.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @jrhee17! Looks good to me. 🙇♂️🙇♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks good 👍
Left some style comments and questions. 😉
core/src/main/java/com/linecorp/armeria/common/logging/FieldMaskerSelector.java
Show resolved
Hide resolved
* Delegates {@link FieldMasker} selection to a different {@link BeanFieldMaskerSelector} | ||
* if the current {@link BeanFieldMaskerSelector} returns {@link FieldMasker#fallthrough()}. | ||
*/ | ||
default BeanFieldMaskerSelector orElse(BeanFieldMaskerSelector other) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be in FieldMaskerSelector
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was intentional since it doesn't make sense to do a BeanFieldMaskerSelector .orElse(ThriftFieldMaskerSelector)
.
Let me know if you still feel like this should be in FieldMaskerSelector
though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BeanFieldMaskerSelector.orElse(ThriftFieldMaskerSelector)
Isn't it impossible because FieldMaskerSelector
has type T? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot about this, but the type of FieldMaskerSelector
is checked when determining how to use them from the provider.
armeria/core/src/main/java/com/linecorp/armeria/common/logging/ContentSanitizerBuilder.java
Line 136 in 5c29fdd
selectors.stream().filter(selector -> provider.supportedType().isInstance(selector)) |
I prefer that BeanFieldMaskerSelector
is a functional interface. Even if a marker is added, I think the functional style may not work in some cases.
e.g.
public interface BeanFieldMaskerSelector extends ... {
@Override
default Class<?> supportedType() {
return BeanFieldInfo.class;
}
}
ContentSanitizer.builder()
.fieldMaskerSelector((BeanFieldMaskerSelector) info -> {
acc.add(info);
return FieldMasker.fallthrough();
})
.fieldMaskerSelector(((BeanFieldMaskerSelector) info -> {
I think I prefer the original version unless you have another idea
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, let's use the original version, then.
core/src/main/java/com/linecorp/armeria/common/logging/ContentSanitizerBuilder.java
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/logging/ContentSanitizerBuilder.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/logging/FieldMaskerBuilder.java
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/logging/FieldMaskers.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/server/annotation/AnnotatedBeanFieldInfo.java
Outdated
Show resolved
Hide resolved
final class BeanFieldMaskerCache { | ||
|
||
private final List<BeanFieldMaskerSelector> selectors; | ||
private final Cache<BeanFieldInfo, FieldMasker> fieldMaskerCache = Caffeine.newBuilder().build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Is there any reason that you use the Caffeine instead of ConcurrentMap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be fine to just use a simple map. Updated
core/src/main/java/com/linecorp/armeria/common/logging/FieldMasker.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not exactly sure how the decryptorFunction
in FieldMaskerBuilder
will be used yet, but that's totally fine because:
- You mentioned that implementing the unmasking logic in Thrift and gRPC isn’t difficult.
- The unmasking logic is completely decoupled, so it doesn't add complexity to the current implementation.
Great work — looking forward to your next PR. Thanks!
Motivation:
This PR includes changes for #6231 .
While logging request/response logs provides developers more tools for debugging, logging sensitive data runs the risk of exposing certain information outside of the intended scope.
For this reason, there have been multiple attempts to add sanitization at #3795 and #4267.
This PR attempts to provide users an easier way to mask logging data by recursively introspecting fields for cross-cutting concerns. In detail, users may determine whether to mask a POJO field or not depending on annotations.
The following illustrates a sample usage where all fields with a
@ShouldMask
annotation are nullified:Note that the default serializers for
AnnotatedRequest
andAnnotatedResponse
have been removed.I believe this wouldn't be an issue since
ContentPreviewingService
for annotated services anywaysJsonLogFormatter
naively appliesObjectMapper
- even now Thrift, gRPC content probably wasn't correctly serialized anywaysIt's probably worth tackling this issue when renovating
JsonLogFormatter
rather than in this PRModifications:
AnnotatedRequest
andAnnotatedResponse
have been addedBeanFieldMaskerSelector
is introduced so users can determine how to mask a field depending on the annotation informationBeanFieldInfo
is introduced which contains annotation information of a bean field.JacksonBeanFieldInfo
is created while recursively iterating fields viaJackson
.AnnotatedBeanFieldInfo
is created fromAnnotatedService
for parameters or return values.FieldMasker
is introduced which actually performs the masking of a field value.FieldMasker
directly,FieldMaskerBuilder
is introduced so users can easily create aFieldMasker
in a type-safe manner.FieldMaskerSelectorProvider
is introduced to allowFieldMaskerSelector
customization for different protocols (thrift, gRPC)BeanFieldMaskerSelectorProvider
is implemented which customizes anObjectMapper
to correctly handle log masking forAnnotatedService
MaskingBeanDeserializerModifier
andMaskingBeanSerializerModifier
are added, which applies the appropriateFieldMasker
during serde.Result: