Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[RubyRussia 2019] Welcome, or access denied

[RubyRussia 2019] Welcome, or access denied

Video (RU): https://www.youtube.com/watch?v=y15a2g7v8i0

Popular Ruby frameworks bring us a lot of useful tools out-of-the-box, but there are missing parts too.
For example, for such essential task as authorization we are on our own.
The variety of open source solutions comes with the problem of choice—there is no silver bullet.

Nevertheless, it's possible to extract common patterns of designing authorization systems and define common technical problems, such as:
performance, code maintainability and testability, integration with client-side applications.

This talk aims to shed light on both theoretical and practical problems: from different authorization models to useful code techniques I came up with while working
on the Action Policy framework (https://actionpolicy.evilmartians.io) .

Vladimir Dementyev

September 28, 2019
Tweet

More Decks by Vladimir Dementyev

Other Decks in Programming

Transcript

  1. palkan_tula palkan AUTHORIZATION Is system allows to do that? 17

    SYSTEM CONSTRAINTS ПО ПОМЫТОМУ НЕ ХОДИТЬ! * No step on washed floor
  2. palkan_tula palkan BY EXAMPLE 18 class ReposController < AppController def

    create if current_account.available_repos.zero? head :payment_required end # ... end end constraint check
  3. palkan_tula palkan AUTHORIZATION 19 Authorization model How to grant/revoke access?

    (roles, permission, accesses) Authorization layer How to verify access? (policies, rules, helpers)
  4. palkan_tula palkan DAC 24 Discretionary # => granting permissions @stove.permissions.create!(user:

    @vovka, activity: :cook) # => checking permissions @stove.permissions.exists?(user: @vovka, activity: :cook)
  5. palkan_tula palkan MAC 27 Mandatory # => checking permissions @report.security_level

    <= @colleague.security_clearance # => MUST have security level >= user clearance @colleague.reports.create!(params)
  6. palkan_tula palkan MAC 28 Bell & LaPadula (Multi-level security) Chinese

    Wall http://www.cs.cornell.edu/courses/cs5430/2015sp/notes/mac.php
  7. palkan_tula palkan 32 ABAC Attribute-based class Ability def initialize(user) can

    :sleep, Bed, user_id: user.id end end class BedPolicy def sleep? bed.user_id == user.id end end
  8. palkan_tula palkan 33 <xacml3:Rule RuleId="c01d7519-be21-4985-88d8-10941f44590a" Effect="Permit"> <xacml3:Description>Allow if time between

    9 and 5 </xacml3:Description> <xacml3:Target> <xacml3:AnyOf> <xacml3:AllOf> <xacml3:Match MatchId="urn:oasis:names:tc:xacml:1.0:function:time-greater-than"> <xacml3:AttributeValue DataType="http: // www.w3.org/2001/XMLSchema#time">09:00:00 </xacml3:AttributeValue> <xacml3:AttributeDesignator Category="urn:oasis:names:tc:xacml:3.0:attribute-category:environment" AttributeId="urn:oasis:names:tc:xacml:1.0:environment:current-time" MustBePresent="false" DataType="http: // www.w3.org/2001/XMLSchema#time" /> </xacml3:Match> </xacml3:AllOf> </xacml3:AnyOf> <xacml3:AnyOf> <xacml3:AllOf> <xacml3:Match MatchId="urn:oasis:names:tc:xacml:1.0:function:time-less-than"> <xacml3:AttributeValue DataType="http: // www.w3.org/2001/XMLSchema#time">17:00:00 </xacml3:AttributeValue> <xacml3:AttributeDesignator Category="urn:oasis:names:tc:xacml:3.0:attribute-category:environment" AttributeId="urn:oasis:names:tc:xacml:1.0:environment:current-time" MustBePresent="false" DataType="http: // www.w3.org/2001/XMLSchema#time" /> </xacml3:Match> </xacml3:AllOf> </xacml3:AnyOf> </xacml3:Target> </xacml3:Rule> XACML eXtensible Access Control Markup Language
  9. palkan_tula palkan 34 namespace exampleBoolean { policy article{ target clause

    userRole == “editor" and actionId == "edit" and itemType == "article" apply firstApplicable rule publishedArticles { target clause published == true permit } } } ALFA (XACML) Abbreviated Language For Authorization
  10. palkan_tula palkan 35 [ { "resource": “TaleApp ::Bed", "action": ["sleep"],

    "description": "Allow owners to sleep in their beds”, "effect": "allow", "conditions": [ { "equal": { "resource ::owner ::id": ["user ::id"] } } ] } ] https://github.com/TheClimateCorporation/iron_hide ABAC Attribute-based
  11. palkan_tula palkan class CoursePolicy < ApplicationPolicy def show? user.permissions.view_courses? &&

    (record.account_id == user.account_id) && ( record.owner_id == user.id || record.visibility_level <= user.visibility || user.assigned_courses.where(id: record.id).exists? ) end end IN REAL LIFE 38
  12. palkan_tula palkan class CoursePolicy < ApplicationPolicy def show? user.permissions.view_courses? &&

    (record.account_id == user.account_id) && ( record.owner_id == user.id || record.visibility_level <= user.visibility || user.assigned_courses.where(id: record.id).exists? ) end end IN REAL LIFE 39 RBAC
  13. palkan_tula palkan class CoursePolicy < ApplicationPolicy def show? user.permissions.view_courses? &&

    (record.account_id == user.account_id) && ( record.owner_id == user.id || record.visibility_level <= user.visibility || user.assigned_courses.where(id: record.id).exists? ) end end IN REAL LIFE 40 ABAC
  14. palkan_tula palkan class CoursePolicy < ApplicationPolicy def show? user.permissions.view_courses? &&

    (record.account_id == user.account_id) && ( record.owner_id == user.id || record.visibility_level <= user.visibility || user.assigned_courses.where(id: record.id).exists? ) end end IN REAL LIFE 41 MAC
  15. palkan_tula palkan class CoursePolicy < ApplicationPolicy def show? user.permissions.view_courses? &&

    (record.account_id == user.account_id) && ( record.owner_id == user.id || record.visibility_level <= user.visibility || user.assigned_courses.where(id: record.id).exists? ) end end IN REAL LIFE 42 DAC
  16. palkan_tula palkan AUTHORIZATION MODEL 43 Could not be generalized well

    Should reflects your business domain rules => No “right tool for the job”
  17. palkan_tula palkan AUTHORIZATION 44 Authorization model How to grant/revoke access?

    (roles, permission, accesses) Authorization layer How to verify access? (policies, rules, helpers) ✅
  18. palkan_tula palkan AUTHORIZATION LAYER 46 Controllers (Rails), Actions (Hanami) Views

    (templates, serializers) Channels (Action Cable) GraphQL mutations and types
  19. palkan_tula palkan CANCAN(-CAN) 50 class Ability include CanCan ::Ability def

    user_abilities can :create, [Question, Answer] can :update, [Question, Answer], user_id: user.id can :destroy, [Question, Answer], user_id: user.id can :destroy, Attachment, attachable: { user_id: user.id } can [:vote_up, :vote_down], [Question, Answer] do |resource| resource.user_id != user.id end end end
  20. palkan_tula palkan PUNDIT 52 class QuestionPolicy def index? true end

    def create? true end def update? user.admin? || (user.id == target.user_id) end end
  21. palkan_tula palkan THE EVOLUTION 54 Start with CanCan Migrate to

    Pundit Customize Pundit Customize Pundit…
  22. palkan_tula palkan THE EVOLUTION 57 Start with CanCan Migrate to

    Pundit Write your own framework — Action Policy
  23. palkan_tula palkan class ProductsController < ApplicationController before_action :load_product, except: [:index,

    :new, :create] def load_product @product = current_account.products.find(params[:id]) # auto-infer policy and rule authorize! @product # explicit rule and policy authorize! @product, to: :manage?, with: SpecialProductPolicy end end ACTION POLICY 60
  24. palkan_tula palkan class ProductPolicy < ApplicationPolicy relation_scope do |rel| next

    rel if user.manager? rel.where(owner_id: user.id) end def create? user.manager? end def show? record.account_id == user.account_id end end ACTION POLICY 61
  25. palkan_tula palkan INSTRUMENTATION 70 Instrument policy checks (both raising and

    non-raising) Instrument authorize! calls separately => Detect missing checks, monitor high “Access denied” rate, etc.
  26. palkan_tula palkan N+1 AUTHORIZATION 71 14 Mar 2018 14:08:30.722241 <190>1

    2018-03-14T11:08:30.349156+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] Started GET "/test_account/openings/public-funnel/dashboard" for 185.13.112.107 at 2018-03-14 11:08:30 +0000 14 Mar 2018 14:08:30.722186 <190>1 2018-03-14T11:08:30.358367+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] Processing by DashboardsController#show as HTML 14 Mar 2018 14:08:30.722216 <190>1 2018-03-14T11:08:30.358391+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] Parameters: {"account_slug"=>"test_account", "opening_id"=>"public-funnel"} 14 Mar 2018 14:08:30.722174 <190>1 2018-03-14T11:08:30.368142+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.scope.hit=0ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.399348+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.scope.miss=6ms 14 Mar 2018 14:08:30.722176 <190>1 2018-03-14T11:08:30.407044+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.scope.miss=21ms 14 Mar 2018 14:08:30.722174 <190>1 2018-03-14T11:08:30.441693+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.scope.hit=0ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.471756+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=2ms 14 Mar 2018 14:08:30.722174 <190>1 2018-03-14T11:08:30.474080+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.504566+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.505893+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.507222+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.508522+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.509846+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.511189+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.796175 <190>1 2018-03-14T11:08:30.512857+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.796174 <190>1 2018-03-14T11:08:30.523828+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:30.796174 <190>1 2018-03-14T11:08:30.527719+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:30.796174 <190>1 2018-03-14T11:08:30.529970+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:30.928734+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.039388+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.040101+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.137807+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.138677+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.139952+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.140577+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.141658+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.142342+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.143228+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.143837+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.144657+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.145339+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.146206+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.146852+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.147671+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.148303+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.149119+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.149849+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.150702+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.151359+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.152254+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.152879+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.153761+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.154353+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.370174 <190>1 2018-03-14T11:08:31.155193+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.370174 <190>1 2018-03-14T11:08:31.155826+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.370174 <190>1 2018-03-14T11:08:31.156625+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.370174 <190>1 2018-03-14T11:08:31.157286+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 45 policy checks per request
  27. palkan_tula palkan 72 N+1 AUTHORIZATION <% @course.materials.each do |material| %>

    <li><%= material.title %> <% if allowed_to?(:edit?, @course) %> = link_to "Edit", material <% end %> </li> <% end %>
  28. palkan_tula palkan THE SURVEY 84 % of answers 0 17.5

    35 52.5 70 requests/controllers tests unit tests (e.g. policy tests) system tests other Where do you test authorization logic?
  29. palkan_tula palkan IN REAL LIFE 85 describe "GET #index" do

    subject { get :index, params: {account_id: account.id} } include_examples "account access" include_examples "permission access", "view_reports" end Testing in controllers
  30. palkan_tula palkan 88 describe "GET #index" do subject { get

    :index, params: {account_slug: account.slug} } it "is authorized" do policy = instance_double(ApplicantPolicy, index ?: false) expect(ApplicantPolicy).to receive(:new) .with(user, Applicant) { policy } expect { subject } .to raise_error Pundit ::NotAuthorizedError end end PUNDIT
  31. palkan_tula palkan 89 describe "GET #index" do subject { get

    :index, params: {account_slug: account.slug} } it "is authorized" do expect { subject } .to be_authorized_to(:index?).with(ApplicantPolicy) end end ACTION POLICY
  32. palkan_tula palkan AUTHORIZATION TESTS 90 Test that the required authorization

    has been performed — just one test! Test that the required scoping has been applied
  33. palkan_tula palkan ACTION POLICY 91 @posts = authorized_scope(Post.all) expect {

    subject }.to have_authorized_scope(:relation) .with_policy(PostPolicy) .with_target { |target| expect(target).to eq(Post.all) }
  34. palkan_tula palkan AUTHORIZATION TESTS 92 Test that the required authorization

    has been performed — just one test! Test that the required scoping has been applied Test authorization rules (policy classes) separately
  35. palkan_tula palkan 95 def rsvp? rsvp_opened? && show? && (seats_available?

    || rsvp_to_pack?) end EXAMPLE Why can this check fail?
  36. palkan_tula palkan 96 def rsvp? binding.irb rsvp_opened? && show? &&

    (seats_available? || rsvp_to_pack?) end EXAMPLE Why can this check fail? MAYBE BREAK?
  37. palkan_tula palkan DEBUGGABILITY 99 Debugging PORO is much easier than

    debugging DSL Provide debugging helpers => make developers happier
  38. palkan_tula palkan GRAPHQL CASE 104 class EventPolicy < ApplicationPolicy def

    rsvp? # checks end end One rule to render the button
  39. palkan_tula palkan GRAPHQL CASE 105 class EventPolicy < ApplicationPolicy def

    rsvp? check?(:no_rsvp_manager?) && check?(:rsvp_opened?) && show? && check?(:seats_available?) && allowed_to?(:rsvp_to_pack?) end end different reasons => different messages
  40. palkan_tula palkan REASONS 107 Provide information on why action is

    not allowed Allow generate actionable/meaningful errors
  41. palkan_tula palkan 108 class ApplicantPolicy < ApplicationPolicy def show? allowed_to?(:view?)

    && allowed_to?(:show?, stage) end def view? user.has_permission?(:view_applicants) end end Wrap nested checks to track their results ACTION POLICY: REASONS
  42. palkan_tula palkan 109 # in controller rescue_from ActionPolicy ::Unauthorized do

    |ex| # either p exception.reasons.details # => { stage: [:show?] } # or p exception.reasons.details # => { applicant: [:view?] } end ACTION POLICY: REASONS
  43. palkan_tula palkan 115 true/false is not enough Provide human-readable feedback

    Provide machine-readable details BACKEND VS FRONTEND
  44. palkan_tula palkan AUTHORIZATION IS… 118 …a critical part of your

    application requiring special attention …a model you need to choose or design yourself …a mechanism to enforce the policies (authorization layer)