-
Notifications
You must be signed in to change notification settings - Fork 25
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
base: master
Are you sure you want to change the base?
Conversation
428bc45
to
bc1470f
Compare
bc1470f
to
04d8aa0
Compare
@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! |
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 |
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.
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 |
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.
Seems like this should still be an H4. Same with Insights::Api::Common::AuditLog
above. See #### Sorting Results
below.
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 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.
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.
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.
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.
There are document composition guidelines, hence the structure (and order).
return unless block_given? | ||
|
||
yield | ||
Thread.current[:audit_account_number] = original |
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 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.
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.
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.
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 suggest setting the original account number in ensure
block in case of exception
def account_number | ||
Thread.current[:audit_account_number] | ||
end |
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 could just be an attr_accessor
on the Formatter and set by the AuditLog in with_account
.
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.
There is only a one instance of Formatter. Setting it on that instance would not work correctly on multi-threaded env.
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.
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.
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.
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.
@app.call(env).tap do |status, _headers, _body| | ||
@status = status | ||
response_finished | ||
end |
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.
Nice! 💯
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 |
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.
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.
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.
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).
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.
The headers may also include X_RH_REQUEST_ID
(or something similar), which can be important to tracing uploads, etc. across services.
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.
Awesome! I guess we can incorporate that in the second iteration.
def fmt_params(params) | ||
params.map { |e| ":#{e}" }.join(", ") | ||
end |
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.
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
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 only lists the params, not their value. The code snippet is strait from Rails logger.
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.
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.
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 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_id
s. 😉
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.
@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 |
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.
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
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.
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.
@slemrmartin It is a global thing, not directly related to APIs. I was operating on the fact that the main module is |
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:
Support within a Racecar(only general logging)Reference: RHICOMPL-1217