Skip to content
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

[APPSEC-8115] create AppSec::Monitor to subscribe to internal app sec events #2617

Merged
merged 13 commits into from
Feb 23, 2023

Conversation

GustavoCaso
Copy link
Member

@GustavoCaso GustavoCaso commented Feb 14, 2023

What does this PR do?

This PR introduces a new reactive component called Rack::Reactive::SetUser. The reactive component observes the usr-id key. It uses that key to communicate with the waf.

Update after talking with @lloeki

We decided to move the reactive SetUser component outside of the contrib/rack namespace. This monitor is agnostic of Rack or any integration within app sec. For those types of events, we would like an internal monitor that would always be enabled when appsec is required.


This PR doesn't publish events yet to the identity.set_user event. That will be done in a future PR.

The PR is divided into separate commits to make easier the review of each of them separately:

Motivation
In the future, we will publish an event when Datadog::Kit::Identity.set_user function is being called from the customer code. Depending on the configured user_id deny list we would be able to block the request.

@github-actions github-actions bot added appsec Application Security monitoring product integrations Involves tracing integrations labels Feb 14, 2023
@GustavoCaso GustavoCaso force-pushed the appsec-pass-user-id-to-waf branch 2 times, most recently from 31a5be1 to 3c068ca Compare February 14, 2023 17:17
@GustavoCaso GustavoCaso changed the title Initial approach to halt request for configured user_id denylist [APPSEC-8115] create Rack::Reactive::SetUser to subscribe when the application code tags an user Feb 14, 2023
@GustavoCaso GustavoCaso force-pushed the appsec-pass-user-id-to-waf branch from 3c068ca to bbe6a34 Compare February 14, 2023 18:00
@GustavoCaso GustavoCaso marked this pull request as ready for review February 15, 2023 09:37
@GustavoCaso GustavoCaso requested review from a team and lloeki February 15, 2023 09:37
@GustavoCaso GustavoCaso force-pushed the appsec-pass-user-id-to-waf branch 2 times, most recently from e792d93 to 3fa663c Compare February 16, 2023 10:51
@codecov-commenter
Copy link

Codecov Report

Merging #2617 (3fa663c) into master (8f60e0f) will decrease coverage by 0.02%.
The diff coverage is 92.74%.

@@            Coverage Diff             @@
##           master    #2617      +/-   ##
==========================================
- Coverage   98.01%   98.00%   -0.02%     
==========================================
  Files        1148     1150       +2     
  Lines       62426    62620     +194     
  Branches     2808     2822      +14     
==========================================
+ Hits        61189    61369     +180     
- Misses       1237     1251      +14     
Impacted Files Coverage Δ
lib/datadog/appsec/contrib/rack/gateway/watcher.rb 81.08% <77.17%> (-14.16%) ⬇️
.../datadog/appsec/contrib/sinatra/gateway/watcher.rb 97.01% <95.91%> (+0.24%) ⬆️
...ib/datadog/appsec/contrib/rails/gateway/watcher.rb 97.61% <96.00%> (+0.18%) ⬆️
...b/datadog/appsec/contrib/rack/reactive/set_user.rb 100.00% <100.00%> (ø)
.../datadog/appsec/contrib/rack/request_middleware.rb 95.89% <100.00%> (+0.17%) ⬆️
lib/datadog/appsec/processor.rb 92.98% <100.00%> (+0.67%) ⬆️
...adog/appsec/contrib/rack/reactive/set_user_spec.rb 100.00% <100.00%> (ø)
spec/datadog/appsec/processor_spec.rb 99.29% <100.00%> (+0.06%) ⬆️
...atadog/tracing/contrib/grpc/support/grpc_helper.rb 98.24% <0.00%> (-1.76%) ⬇️
lib/datadog/core/diagnostics/environment_logger.rb 97.69% <0.00%> (-1.57%) ⬇️
... and 3 more

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

Copy link
Member

@lloeki lloeki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation is okay, but the location of it doesn't fit as it's independent of Rack. I'm still not sure where exactly to put it though. Maybe we can talk about it and see if we can design something up?

lib/datadog/appsec/contrib/rack/reactive/set_user.rb Outdated Show resolved Hide resolved
lib/datadog/appsec/contrib/rack/request_middleware.rb Outdated Show resolved Hide resolved
lib/datadog/appsec/processor.rb Outdated Show resolved Hide resolved
lib/datadog/appsec/processor.rb Outdated Show resolved Hide resolved
lib/datadog/appsec/processor.rb Outdated Show resolved Hide resolved
lib/datadog/appsec/processor.rb Outdated Show resolved Hide resolved
lib/datadog/appsec/contrib/rack/gateway/watcher.rb Outdated Show resolved Hide resolved
lib/datadog/appsec/contrib/rack/gateway/watcher.rb Outdated Show resolved Hide resolved
@GustavoCaso GustavoCaso changed the title [APPSEC-8115] create Rack::Reactive::SetUser to subscribe when the application code tags an user [APPSEC-8115] create AppSec::Monitor to subscribe to internal app sec events Feb 17, 2023
@GustavoCaso GustavoCaso force-pushed the appsec-pass-user-id-to-waf branch from c6541e1 to 341ea0f Compare February 17, 2023 16:02
Copy link
Member

@lloeki lloeki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Naming we'll sort out as this is internal.


# Internal appsec events monitor
require_relative 'appsec/monitor'
Datadog::AppSec::Monitor::Gateway::Watcher.watch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably this should be plugged into the AppSec component soon, notably to condition this behind AppSec being enabled.

@GustavoCaso GustavoCaso force-pushed the appsec-pass-user-id-to-waf branch 2 times, most recently from ff3bdba to c8fc1ee Compare February 22, 2023 15:12
Copy link
Member

@lloeki lloeki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good enough for me, only a few little things to act upon.

As mentioned in the comments I'm not too fond of some parts but can't figure a better way right now so let's move forward.

Comment on lines +10 to +11
ignore 'lib/datadog/appsec/monitor'
ignore 'lib/datadog/appsec/component.rb'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were there any challenges in typing these that resulted in them being ignored?

@@ -19,12 +20,20 @@ def build_appsec_component(settings)

def initialize(processor: Processor.new)
@processor = processor

start_monitor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when the component gets rebuilt? Are we at risk of having multiple watchers?

This might be good enough for now but to me it highlights that this may not be the correct place as this is instrumentation business and this way we're basically auto-instrumenting.

I'm having some idea, I need to chart it out.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that.

Currently, the gateway, makes sure the same block is not registered twice.

@middlewares[name] << Middleware.new(key, &block) unless middlewares[name].any? { |m| m.key == key }

Comment on lines +90 to +95
context = new_context
Processor.send(:active_context=, context)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if active_context is not nil?

Should we allow a stack (i.e []#push on activation and []#pop on deactivation) or should we blow up and raise an exception?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for simplicity know in which there is only a single processor, we could raise an exception

@@ -7,6 +7,8 @@ target :appsec do
# check 'lib/datadog/kit'

ignore 'lib/datadog/appsec/contrib'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this is ignored because of the dependency on actual Sinatra/Rails/Rack constants and methods, which are not typed. We can relax that ignore by minimally typing just what we need in vendor/rbs.

@GustavoCaso GustavoCaso force-pushed the appsec-pass-user-id-to-waf branch from c8fc1ee to e625807 Compare February 23, 2023 09:53
@GustavoCaso GustavoCaso force-pushed the appsec-pass-user-id-to-waf branch 8 times, most recently from 7008bd2 to f566616 Compare February 23, 2023 11:24
@github-actions github-actions bot added the core Involves Datadog core libraries label Feb 23, 2023
@GustavoCaso GustavoCaso force-pushed the appsec-pass-user-id-to-waf branch from f566616 to 4abbd89 Compare February 23, 2023 11:38
@github-actions github-actions bot removed the core Involves Datadog core libraries label Feb 23, 2023
@GustavoCaso GustavoCaso merged commit 923f2a9 into master Feb 23, 2023
@GustavoCaso GustavoCaso deleted the appsec-pass-user-id-to-waf branch February 23, 2023 12:05
@github-actions github-actions bot added this to the 1.10.0 milestone Feb 23, 2023
Comment on lines -141 to -144
step_sorbet_type_checker: &step_sorbet_type_checker
run:
name: Run sorbet type checker
command: bundle exec rake typecheck
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Adding this for future reference) See #2641 for details on why we're removing Sorbet.

ivoanjo added a commit that referenced this pull request Feb 24, 2023
**What does this PR do?**:

This PR is spiritually a revert of #1607, when we added the Sorbet
typechecker to dd-trace-rb.

It includes two commits: One where we remove all configuration
and scaffolding surrounding Sorbet, and one where we remove all of the
`# typed: ...` magic comments and `include Kernel` definitions added
to make Sorbet happy.

**Motivation**:

As documented in #2641, the team has decided that the value vs pain
equation for Sorbet has shifted in the past months, and thus that
it was time to remove Sorbet.

**Additional Notes**:

Sorbet type checking in CI was actually removed earlier this week in
 #2617.

**How to test the change?**:

CI should still be green.
@GustavoCaso GustavoCaso mentioned this pull request Feb 24, 2023
@@ -63,6 +87,23 @@ def new_context
Context.new(self)
end

def activate_context
existing_active_context = Processor.active_context
raise AlreadyActiveContextError if existing_active_context
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error is never rescued and breaks the app when it raises, is that intended behavior?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @idanci.

Thanks for reporting it.

Could you elaborate on which scenario this is happening in?

Copy link

@idanci idanci Mar 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't figure out the root cause. I thought it was because we were using custom instrumentation before the appsec middleware kicked in, but even after removing all the ddtrace/ddstatsd interaction the error is still there.
I don't think the backtrace will be helpful, but here's an example:

"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/processor.rb:90:in `activate_context'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/request_middleware.rb:35:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/rack/middlewares.rb:70:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:2007:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/urlmap.rb:74:in `block in call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/urlmap.rb:58:in `each'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/urlmap.rb:58:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-parser-0.7.0/lib/rack/parser.rb:24:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-utf8_sanitizer-1.8.0/lib/rack/utf8_sanitizer.rb:28:in `call'",
"/home/wetransfer/frontend/lib/middleware/refuse_non_json_or_multipart_requests.rb:7:in `call'",
"/home/wetransfer/frontend/lib/middleware/json_rescue.rb:38:in `call_with_exception_handling'",
"/home/wetransfer/frontend/lib/middleware/json_rescue.rb:17:in `call'",
"/home/wetransfer/frontend/lib/middleware/x_frame_options_deny.rb:5:in `call'",
"/home/wetransfer/frontend/lib/api/v4.rb:41:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/routing/mapper.rb:20:in `block in <class:Constraints>'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/routing/mapper.rb:49:in `serve'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/journey/router.rb:50:in `block in serve'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/journey/router.rb:32:in `each'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/journey/router.rb:32:in `serve'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/routing/route_set.rb:842:in `call'",
"/home/wetransfer/frontend/config/initializers/redirect_invalid_subdomains.rb:22:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/sanitize_user_agent_header-0.1.0/lib/sanitize_user_agent_header.rb:21:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/deflater.rb:44:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activerecord-6.1.7.1/lib/active_record/middleware/database_selector.rb:53:in `block in call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activerecord-6.1.7.1/lib/active_record/middleware/database_selector/resolver.rb:71:in `block (2 levels) in write_to_primary'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activesupport-6.1.7.1/lib/active_support/notifications/instrumenter.rb:24:in `instrument'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activerecord-6.1.7.1/lib/active_record/middleware/database_selector/resolver.rb:70:in `block in write_to_primary'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activerecord-6.1.7.1/lib/active_record/connection_handling.rb:388:in `with_role_and_shard'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activerecord-6.1.7.1/lib/active_record/connection_handling.rb:175:in `connected_to'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activerecord-6.1.7.1/lib/active_record/middleware/database_selector/resolver.rb:69:in `write_to_primary'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activerecord-6.1.7.1/lib/active_record/middleware/database_selector/resolver.rb:44:in `write'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activerecord-6.1.7.1/lib/active_record/middleware/database_selector.rb:65:in `select_database'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activerecord-6.1.7.1/lib/active_record/middleware/database_selector.rb:52:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/tempfile_reaper.rb:15:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/etag.rb:27:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/conditional_get.rb:27:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/head.rb:12:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/http/permissions_policy.rb:22:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/http/content_security_policy.rb:19:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/strict_request_uri-1.0.3/lib/strict_request_uri.rb:36:in `call'",
"/home/wetransfer/frontend/config/initializers/set_amplitude_user_properties.rb:7:in `call'",
"/home/wetransfer/frontend/config/initializers/set_amplitude_device_id.rb:7:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/session/abstract/id.rb:266:in `context'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/session/abstract/id.rb:260:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/cookies.rb:697:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activesupport-6.1.7.1/lib/active_support/callbacks.rb:98:in `run_callbacks'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/callbacks.rb:26:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/actionable_exceptions.rb:18:in `call'",
"/home/wetransfer/frontend/config/initializers/client_ip_injection.rb:46:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/rails/middlewares.rb:17:in `call'",
"/home/wetransfer/frontend/config/initializers/suppress_unacceptable_format.rb:8:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/debug_exceptions.rb:29:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/lograge-0.12.0/lib/lograge/rails_ext/rack/logger.rb:18:in `call_app'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/railties-6.1.7.1/lib/rails/rack/logger.rb:26:in `block in call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activesupport-6.1.7.1/lib/active_support/tagged_logging.rb:99:in `block in tagged'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activesupport-6.1.7.1/lib/active_support/tagged_logging.rb:37:in `tagged'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/activesupport-6.1.7.1/lib/active_support/tagged_logging.rb:99:in `tagged'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/railties-6.1.7.1/lib/rails/rack/logger.rb:26:in `call'",
"/home/wetransfer/frontend/config/initializers/quiet_assets.rb:6:in `call_with_quiet_assets'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/request_store-1.5.1/lib/request_store/middleware.rb:19:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/request_id.rb:26:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/method_override.rb:24:in `call'",
"/home/wetransfer/frontend/config/initializers/host_ident.rb:18:in `call'",
"/home/wetransfer/frontend/config/initializers/i18n.rb:20:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/runtime.rb:22:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/executor.rb:14:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/static.rb:24:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/sendfile.rb:110:in `call'",
"/home/wetransfer/frontend/config/initializers/accept_header_fix.rb:7:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/actionpack-6.1.7.1/lib/action_dispatch/middleware/host_authorization.rb:148:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-cors-2.0.0/lib/rack/cors.rb:102:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/request_middleware.rb:44:in `block (2 levels) in call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/instrumentation/gateway.rb:37:in `block in push'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/gateway/watcher.rb:56:in `block in watch_request'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/instrumentation/gateway.rb:19:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/instrumentation/gateway.rb:43:in `block (2 levels) in push'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/instrumentation/gateway.rb:47:in `push'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/request_middleware.rb:43:in `block in call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/request_middleware.rb:42:in `catch'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/request_middleware.rb:42:in `call'",
"/home/wetransfer/frontend/config/initializers/referer_tracking_middleware.rb:5:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/rack/middlewares.rb:98:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/railties-6.1.7.1/lib/rails/engine.rb:539:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/urlmap.rb:74:in `block in call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/urlmap.rb:58:in `each'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/rack-2.2.6.4/lib/rack/urlmap.rb:58:in `call'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/unicorn-6.1.0/lib/unicorn/http_server.rb:634:in `process_client'",
"config/unicorn.rb:33:in `block (2 levels) in reload'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/unicorn-6.1.0/lib/unicorn/http_server.rb:739:in `worker_loop'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/unicorn-6.1.0/lib/unicorn/http_server.rb:547:in `spawn_missing_workers'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/unicorn-6.1.0/lib/unicorn/http_server.rb:143:in `start'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/gems/unicorn-6.1.0/bin/unicorn:128:in `<top (required)>'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/bin/unicorn:25:in `load'",
"/home/wetransfer/frontend/vendor/bundle/ruby/3.2.0/bin/unicorn:25:in `<top (required)>'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/cli/exec.rb:58:in `load'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/cli/exec.rb:58:in `kernel_load'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/cli/exec.rb:23:in `run'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/cli.rb:479:in `exec'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/cli.rb:31:in `dispatch'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/cli.rb:25:in `start'",
"/usr/local/bundle/gems/bundler-2.2.33/exe/bundle:49:in `block in <top (required)>'",
"/usr/local/bundle/gems/bundler-2.2.33/lib/bundler/friendly_errors.rb:103:in `with_friendly_errors'",
"/usr/local/bundle/gems/bundler-2.2.33/exe/bundle:37:in `<top (required)>'",
"/usr/local/bundle/bin/bundle:25:in `load'",
"/usr/local/bundle/bin/bundle:25:in `<main>'"

Please let me know if there is a way to evade appsec errors, it breaks our app. If there is no mitigation, let me know if we need to open a ticket with the dd support instead.

Thanks 🙌

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@idanci, thanks for sharing the backtrace.

Could you share more information about your system?
From the backtrace, I see you are using dd-trace-rb 1.10.1
Is it a Sinatra app?

Could you share your Datadog.configure block?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rails 6.1.7.1
ruby 3.2
ddtrace 1.10.1
dogstatsd-ruby 5.4.0

We use rails and mount several sinatra apps within rails. Those sinatra apps also have home custom middlewares on top of default stack.

require 'ddtrace/auto_instrument' unless Rails.env.test?

Datadog.configure do |c|
  options = { service_name: 'frontend' }

  c.service = options[:service_name]
  c.tracing.enabled = true

  if ENV['DD_PROFILING_ENABLED'].present?
    c.profiling.enabled = true
    c.profiling.advanced.force_enable_new_profiler = true
  end

  c.tracing.instrument :sinatra, options.merge(resource_script_names: true)
  c.tracing.instrument :active_support, cache_service: options[:service_name]
  c.tracing.instrument :active_record, options
  c.tracing.instrument :rails, options unless Rails.env.test?
  c.tracing.instrument :faraday, options
  c.tracing.instrument :rest_client, options
  c.tracing.instrument :mysql2, options
  c.tracing.instrument :redis, options
  c.tracing.instrument :active_job, options
  c.tracing.instrument :rake, options.merge(tasks: %w[list of our tasks])

  if Rails.env.production? # we disabled this for now, it breaks the app
    require 'datadog/appsec'

    c.appsec.enabled = true
    c.appsec.instrument :rack
    c.appsec.instrument :rails
    c.appsec.instrument :sinatra
  end
end

class DatadogCustomTraceProcessor
  def call(trace)
    trace.spans.each do |span|
      next unless span.status == Datadog::Tracing::Metadata::Ext::Errors::STATUS

      error_stack = span.get_tag('error.stack').dup
      next if error_stack.blank?

      span.set_tag('error.original_stack', error_stack)
      span.set_tag('error.stack', backtrace_for(error_stack))
    end

    trace
  end

  private

  def backtrace_for(error_stack)
    cleaned_stack = backtrace_cleaner.clean(error_stack.split("\n\tfrom "))
    cleaned_stack.map(&:strip).join("\n")
  end

  def backtrace_cleaner
    @backtrace_cleaner ||= ActiveSupport::BacktraceCleaner.new.tap do |bc|
      bc.add_filter   { |line| line.gsub(Rails.root.to_s, 'frontend') } # strip the Rails.root prefix
      bc.add_silencer { |line| /\/gems\/|vendor\/bundle|usr\/local/.match?(line) } # skip irrelevant lines
    end
  end
end

Datadog::Tracing.before_flush(DatadogCustomTraceProcessor.new)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While disabling sinatra fixes the error, enabling appsec even without sinatra on ddtrace 1.10.1 resulted sky rocketing latency 😬

Were there any reported regressions after 1.10? Can't find anything in issues

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @idanci so sorry for the delay

Regarding:

What will be the best way to proceed? Should we revert to an older ddtrace version or keep the current one with sinatra appsec disabled and wait for the ddtrace update?

Version 1.10.1 has bug fixes on the Client IP resolution logic #2665

So reverting to an earlier version will fix the issue with raise AlreadyActiveContextError, meaning that the ClientIP reporting could be buggy.

Staying in 1.10.1 and removing c.appsec.instrument :sinatra would mean that app would be able to load, but the custom appsec events for the nested sinatra apps won't be checked. The events are checking the request body, and the URL path of the sinatra requests.

So is up to you which setup works best in your case.

Again I'm so sorry that we have affected the normal workflow for your application setup and appsec. We are going to make sure to have a fix on the next release that would allow you to use your current setup with nested apps.

As for:

While disabling sinatra fixes the error, enabling appsec even without sinatra on ddtrace 1.10.1 resulted sky rocketing latency 😬

Do you mind opening a GH issue so someone from the team can have a look and figure out what is going on?

Thank you

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We disabled appsec completely due to performance issues, will try to debug it when there's time.
I can open a GH issue, can you please advise what info to put there?
There are no datadog traces for the requests that failed because they were cut off on the client side due to timeouts. We can't spot the issue with ddtrace because ddtrace itself has an issue 😬 . The problem as well might be in our ddtrace config that is shared above, but nothing seems suspicious there.

Thanks for looking into it 🙌

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@idanci

I can open a GH issue, can you please advise what info to put there?

Mentioning your current Datadog configuration, and mentioning what you experience when you upgraded from 1.10.0 to 1.10.1 would be enough.

We would look into our reliability environment to figure out what change could have caused that degradation between releases.

Thank you so much for your patience 🙌

Copy link
Member

@lloeki lloeki Mar 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a GH issue to track this: #2722

Let's move any future discussion over there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
appsec Application Security monitoring product integrations Involves tracing integrations
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants