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

feat(logger): introduce audit logging #217

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

vkrizan
Copy link

@vkrizan vkrizan commented Feb 2, 2021

Introduction of a middleware and utility to add entries to an audit log. By default it logs them to STDOUT, but eventually it can be tied into CloudWatch. Log entries are formatted into JSON, split by a new-line. Usage is documented in README.

TODO:

  • Capture Rails requests
  • Add tests
  • Find a way to have a shortcut to the logger
  • Support within a Sidekiq
  • Support within a Racecar (only general logging)

Reference: RHICOMPL-1217

@vkrizan
Copy link
Author

vkrizan commented Feb 2, 2021

@akofink, here is the audit logger to get an idea. It is still experimental, and if it would not be fitted in this common package we can bundle it in our Compliance app. Feel free to check it out and comment. Thanks!

@dippy-bot
Copy link

Checked commits vkrizan/insights-api-common-rails@04d8aa0~...58e6586 with ruby 2.5.7, rubocop 0.82.0, haml-lint 0.35.0, and yamllint
4 files checked, 0 offenses detected
Everything looks fine. ⭐

Copy link

@akofink akofink left a comment

Choose a reason for hiding this comment

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

Nice work, @vkrizan! Just a few minor comments/questions, but it seems reasonable to me. I'd like to revisit it to actually test it with compliance once it's ready.

end
```

### Insights::Api::Common::Filter
Copy link

Choose a reason for hiding this comment

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

Seems like this should still be an H4. Same with Insights::Api::Common::AuditLog above. See #### Sorting Results below.

Copy link
Author

Choose a reason for hiding this comment

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

This one I was not sure about. I'd let owners to look into it. The "Usage" is H2, so the next one should be H3. I'm not sure whether the "Sorting Results" are part of the Filter or not.

Copy link

Choose a reason for hiding this comment

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

Nothing says you have to use headers in order H1 -> H2 -> H3 etc. H1 -> H3 -> H5 works just as well, probably chosen so the markdown renders more nicely in the GitHub UI without overly large headers.

Copy link
Author

Choose a reason for hiding this comment

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

There are document composition guidelines, hence the structure (and order).

return unless block_given?

yield
Thread.current[:audit_account_number] = original
Copy link

Choose a reason for hiding this comment

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

I guess, another option would be to set this to nil. I'm fine with the current implementation, so long as logs don't show the incorrect account_number from a thread servicing multiple requests.

Copy link
Author

Choose a reason for hiding this comment

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

We use hooks for the auth, which means that the account_number would be only setting. Ensuring that the account num is reset is one thing I'm working on. It would not be a 100%, but at least it should cover controllers.

Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest setting the original account number in ensure block in case of exception

Comment on lines +54 to +56
def account_number
Thread.current[:audit_account_number]
end
Copy link

Choose a reason for hiding this comment

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

This could just be an attr_accessor on the Formatter and set by the AuditLog in with_account.

Copy link
Author

Choose a reason for hiding this comment

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

There is only a one instance of Formatter. Setting it on that instance would not work correctly on multi-threaded env.

Copy link

Choose a reason for hiding this comment

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

There will be one instance of Formatter per thread as written afaik. Thread.current represents only the local thread, so it also won't share information across threads in a multi-threaded env.

Copy link
Author

Choose a reason for hiding this comment

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

Thread.current represents only the local thread, so it also won't share information across threads in a multi-threaded env.

^ That's the sole purpose of this.

I'd like to keep it defensive as is. I don't think that there is a separate instance for each thread as it is instantiated once with a logger. I don't want to rely on that assumption either.

Comment on lines +16 to +19
@app.call(env).tap do |status, _headers, _body|
@status = status
response_finished
end
Copy link

Choose a reason for hiding this comment

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

Nice! 💯

Comment on lines +35 to +45
def generate_message
status_label = Rack::Utils::HTTP_STATUS_CODES[status]
msg = "#{request.method} #{request.original_fullpath} -> #{status} #{status_label}"
if evidence[:unpermitted_parameters]
msg += "; unpermitted params #{fmt_params(evidence[:unpermitted_parameters])}"
end
if evidence[:halted_callback].present?
msg += "; filter chain halted by :#{evidence[:halted_callback]}"
end
msg
end
Copy link

Choose a reason for hiding this comment

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

Are the headers logged anywhere? It may be good to include, but it may also present a security risk to include the X_RH_IDENTITY header.

Copy link
Author

Choose a reason for hiding this comment

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

Inspecting X_RH_IDENTITY is a good idea, and can be done here. That's mainly the reason why this is a middleware (to access the request and headers).

Copy link

Choose a reason for hiding this comment

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

The headers may also include X_RH_REQUEST_ID (or something similar), which can be important to tracing uploads, etc. across services.

Copy link
Author

Choose a reason for hiding this comment

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

Awesome! I guess we can incorporate that in the second iteration.

Comment on lines +87 to +89
def fmt_params(params)
params.map { |e| ":#{e}" }.join(", ")
end
Copy link

Choose a reason for hiding this comment

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

Does it work with rails parameter filtering for sensitive params? We should verify this/add a test for this case.

https://guides.rubyonrails.org/action_controller_overview.html#log-filtering

Copy link
Author

Choose a reason for hiding this comment

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

This only lists the params, not their value. The code snippet is strait from Rails logger.

Copy link

Choose a reason for hiding this comment

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

Ah! I missed that. Is it possible to log the param values? I think it'd be really useful to be able to debug errors/malfunctions with requests, but we need to ensure they're filtered based on the built in log filtering.

Copy link
Author

Choose a reason for hiding this comment

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

It would be possible, but the notification does not provide them. https://guides.rubyonrails.org/active_support_instrumentation.html#unpermitted-parameters-action-controller

Please keep in mind that this is a audit log with a purpose of retracing steps one has taken. Mostly access/denial/changes to a resource, etc. If details would be necessary an app log can be of help. Hence the transation_ids. 😉

Copy link
Contributor

@slemrmartin slemrmartin left a comment

Choose a reason for hiding this comment

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

@vkrizan is the audit log intended only for APIs or also for all other services?


def init_logger(logger = nil)
logger ||= Logger.new($stdout)
logger.level = Logger::INFO
Copy link
Contributor

Choose a reason for hiding this comment

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

in topological/sources we are using ENV['LOG_LEVEL'] for ruby and ENV['RAILS_LOG_LEVEL'] for rails.
what about logger.level= ENV[...] || Logger::INFO

Copy link
Author

@vkrizan vkrizan Feb 9, 2021

Choose a reason for hiding this comment

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

The level is hardcoded deliberately, as this is not going to be configurable. The idea is to use info/warn/error for audit logs. The end result might not even use and/or format the level. The difference would be only whether it is a failure/success.

@vkrizan
Copy link
Author

vkrizan commented Feb 9, 2021

@slemrmartin It is a global thing, not directly related to APIs. I was operating on the fact that the main module is Insights::API::Common. Do you have any suggestion where to move it? Is there other common package for Insights where we can move it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants