From 6b183e9e2a1fa47ccb2006a80812b5bbd1d506f8 Mon Sep 17 00:00:00 2001 From: Robert Roach Date: Wed, 4 Dec 2019 21:39:34 +0800 Subject: [PATCH 1/2] XACML policies and actions --- Dockerfile | 10 +- .../file_fingerprints_controller.rb | 2 +- .../controllers/policy_actions_controller.rb | 14 +++ .../service_policies_controller.rb | 14 +++ .../app/models/concerns/ros/tenant_concern.rb | 2 +- lib/core/app/models/policy_action.rb | 32 ++++++ lib/core/app/models/service_policy.rb | 34 ++++++ .../app/resources/policy_action_resource.rb | 6 + .../app/resources/service_policy_resource.rb | 6 + lib/core/config/routes.rb | 2 + lib/core/lib/ros/core.rb | 16 +++ lib/core/lib/ros/core/console.rb | 1 + lib/core/lib/ros/core/engine.rb | 9 ++ lib/core/lib/ros/healthz_middleware.rb | 15 +++ lib/core/lib/ros/iron_hide/rule.rb | 15 +++ .../ros/iron_hide/storage/service_adapter.rb | 21 ++++ lib/core/lib/ros/routes.rb | 11 -- lib/core/ros-core.gemspec | 1 + services/cognito/Gemfile.lock | 106 +++++++++--------- .../doc/policies/another_built_in_policy.json | 5 + .../doc/policies/cognito_super_user.json | 51 +++++++++ services/cognito/doc/policy_actions.json | 16 +++ services/cognito/lib/ros/cognito/engine.rb | 6 +- services/iam/Gemfile.lock | 18 ++- services/iam/lib/ros/iam/engine.rb | 2 +- 25 files changed, 338 insertions(+), 77 deletions(-) create mode 100644 lib/core/app/controllers/policy_actions_controller.rb create mode 100644 lib/core/app/controllers/service_policies_controller.rb create mode 100644 lib/core/app/models/policy_action.rb create mode 100644 lib/core/app/models/service_policy.rb create mode 100644 lib/core/app/resources/policy_action_resource.rb create mode 100644 lib/core/app/resources/service_policy_resource.rb create mode 100644 lib/core/lib/ros/healthz_middleware.rb create mode 100644 lib/core/lib/ros/iron_hide/rule.rb create mode 100644 lib/core/lib/ros/iron_hide/storage/service_adapter.rb create mode 100644 services/cognito/doc/policies/another_built_in_policy.json create mode 100644 services/cognito/doc/policies/cognito_super_user.json create mode 100644 services/cognito/doc/policy_actions.json diff --git a/Dockerfile b/Dockerfile index 84e4d85d2..98f90ae34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,14 +8,14 @@ WORKDIR /home/rails/services/app # Install gems that need compiling first b/c they can take a long time to compile RUN gem install \ bundler:2.0.2 \ - nokogiri:1.10.4 \ - ffi:1.11.1 \ - grpc:1.23.0 \ + nokogiri:1.10.5 \ + ffi:1.11.3 \ + grpc:1.25.0 \ mini_portile2:2.4.0 \ msgpack:1.3.1 \ pg:1.1.4 \ - nio4r:2.5.1 \ - puma:4.1.1 \ + nio4r:2.5.2 \ + puma:4.3.0 \ eventmachine:1.2.7 # NOTE: Copy in a generic Gemfile and the dependent gem's gemspecs so that their dependencies are also installed diff --git a/lib/core/app/controllers/file_fingerprints_controller.rb b/lib/core/app/controllers/file_fingerprints_controller.rb index 960dcb10c..8c898bb67 100644 --- a/lib/core/app/controllers/file_fingerprints_controller.rb +++ b/lib/core/app/controllers/file_fingerprints_controller.rb @@ -8,7 +8,7 @@ def index private def resources - # @TODO: Add proper filters based on resource class + # TODO: Add proper filters based on resource class if params.dig(:filter, :model_name) models.select! { |model| model.model_name.downcase == params[:filter][:model_name].downcase } end diff --git a/lib/core/app/controllers/policy_actions_controller.rb b/lib/core/app/controllers/policy_actions_controller.rb new file mode 100644 index 000000000..82289d397 --- /dev/null +++ b/lib/core/app/controllers/policy_actions_controller.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class PolicyActionsController < ApplicationController + def index + render json: json_resources(resource_class: PolicyActionResource, records: resources) + end + + private + + def resources + return [] unless (json = Settings.dig(:service, :policy_actions)) + json.map { |model| PolicyActionResource.new(PolicyAction.new(model), nil) } + end +end diff --git a/lib/core/app/controllers/service_policies_controller.rb b/lib/core/app/controllers/service_policies_controller.rb new file mode 100644 index 000000000..a2cf07e24 --- /dev/null +++ b/lib/core/app/controllers/service_policies_controller.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class ServicePoliciesController < ApplicationController + def index + render json: json_resources(resource_class: ServicePolicyResource, records: resources) + end + + private + + def resources + return [] unless (json = Settings.dig(:service, :policies)) + json.map { |model| ServicePolicyResource.new(ServicePolicy.new(model), nil) } + end +end diff --git a/lib/core/app/models/concerns/ros/tenant_concern.rb b/lib/core/app/models/concerns/ros/tenant_concern.rb index 59bcaa703..8a5ed9e29 100644 --- a/lib/core/app/models/concerns/ros/tenant_concern.rb +++ b/lib/core/app/models/concerns/ros/tenant_concern.rb @@ -19,7 +19,7 @@ def schema_name_from(account_id: nil, id: nil) end def account_id_to_schema(account_id) - account_id.to_i.zero? ? 'public' : account_id.to_s.scan(/.{3}/).join('_') + %w[public 0].include?(account_id.to_s) ? 'public' : account_id.to_s.scan(/.{3}/).join('_') end end diff --git a/lib/core/app/models/policy_action.rb b/lib/core/app/models/policy_action.rb new file mode 100644 index 000000000..790381ea8 --- /dev/null +++ b/lib/core/app/models/policy_action.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class PolicyAction + include ActiveModel::Model + attr_reader :id, :name, :actions + + def initialize(model) + @id = SecureRandom.uuid + @name = model['name'] + @actions = model['actions'] + end + + def readonly? + true + end + + def persisted? + false + end + + def self.all + new + end + + def order + [self] # following order collect is called on the result so return an array with self + end + + def count + 1 # count is called for meta + end +end diff --git a/lib/core/app/models/service_policy.rb b/lib/core/app/models/service_policy.rb new file mode 100644 index 000000000..a9e97d265 --- /dev/null +++ b/lib/core/app/models/service_policy.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class ServicePolicy + include ActiveModel::Model + attr_reader :id, :name, :description, :version, :rules + + def initialize(model) + @id = SecureRandom.uuid + @name = model['name'] + @description = model['description'] + @version = model['version'] + @rules = model['rules'] + end + + def readonly? + true + end + + def persisted? + false + end + + def self.all + new + end + + def order + [self] # following order collect is called on the result so return an array with self + end + + def count + 1 # count is called for meta + end +end diff --git a/lib/core/app/resources/policy_action_resource.rb b/lib/core/app/resources/policy_action_resource.rb new file mode 100644 index 000000000..899ab5d81 --- /dev/null +++ b/lib/core/app/resources/policy_action_resource.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class PolicyActionResource < JSONAPI::Resource + attributes :name, :actions + paginator :none +end diff --git a/lib/core/app/resources/service_policy_resource.rb b/lib/core/app/resources/service_policy_resource.rb new file mode 100644 index 000000000..e25eae59c --- /dev/null +++ b/lib/core/app/resources/service_policy_resource.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class ServicePolicyResource < JSONAPI::Resource + attributes :name, :description, :version, :rules + paginator :none +end diff --git a/lib/core/config/routes.rb b/lib/core/config/routes.rb index ddb3cc469..fc7333be2 100644 --- a/lib/core/config/routes.rb +++ b/lib/core/config/routes.rb @@ -10,6 +10,8 @@ [{ errors: [{ status: '404', title: 'Not found' }] }.to_json]] } jsonapi_resources :tenants + jsonapi_resources :service_policies, only: [:index] + jsonapi_resources :policy_actions, only: [:index] jsonapi_resources :file_fingerprints, only: [:index] jsonapi_resources :cloud_event_subjects, only: [:index] end diff --git a/lib/core/lib/ros/core.rb b/lib/core/lib/ros/core.rb index 40d9c9934..6936003d8 100644 --- a/lib/core/lib/ros/core.rb +++ b/lib/core/lib/ros/core.rb @@ -5,6 +5,7 @@ require 'attr_encrypted' require 'config' require 'hashids' +require 'iron_hide' require 'jsonapi-resources' require 'jsonapi/authorization' require 'jwt' @@ -20,6 +21,8 @@ require_relative 'api_token_strategy' require_relative 'dtrace_middleware' +require_relative 'iron_hide/rule' +require_relative 'iron_hide/storage/service_adapter' require_relative 'jsonapi_authorization/authorizer' require_relative 'jwt' require_relative '../migrations' @@ -69,6 +72,19 @@ def api_calls_enabled # By default all services exclude only the Tenant model from schemas def excluded_models; %w[Tenant] end + + def paths; @paths ||= HashWithIndifferentAccess.new(policies: [], policy_actions: []) end + + def load_policies + %i[policies policy_actions].each do |type| + Settings.service[type]= Ros.paths[type].each_with_object([]) do |path, ary| + path = path.to_s.end_with?('.json') ? path : "#{path}/*.json" + Dir[path].each { |policy_file| ary << JSON.parse(File.read(policy_file)) } + end.flatten + end + rescue JSON::ParserError + Rails.logger.warn('Error parsing security policies') + end end class AccessKey diff --git a/lib/core/lib/ros/core/console.rb b/lib/core/lib/ros/core/console.rb index 45c7f6f09..b5de0e38b 100644 --- a/lib/core/lib/ros/core/console.rb +++ b/lib/core/lib/ros/core/console.rb @@ -132,6 +132,7 @@ class Reload < Pry::ClassCommand def process Ros::Console::Methods.reset_shortcuts TOPLEVEL_BINDING.eval('self').reload! + Ros.load_policies Rails.configuration.x.memoized_shortcuts[:ct] = Tenant.find_by(schema_name: Apartment::Tenant.current) end diff --git a/lib/core/lib/ros/core/engine.rb b/lib/core/lib/ros/core/engine.rb index 900584733..89d7a58ae 100644 --- a/lib/core/lib/ros/core/engine.rb +++ b/lib/core/lib/ros/core/engine.rb @@ -72,6 +72,15 @@ class Engine < ::Rails::Engine end end + initializer 'ros_core.initialize_policies' do |_app| + Ros.paths[:policies] << root.join('doc/policies') + Ros.load_policies + IronHide.config do |config| + config.adapter = :service + config.namespace = Settings.service.policy_name + end + end + # Configure ActionMailer (used by Devise) based on our Settings.smtp initializer 'ros_core.initialize_action_mailer' do |app| # NOTE: Enabling the smtp is not enough. The service that enables diff --git a/lib/core/lib/ros/healthz_middleware.rb b/lib/core/lib/ros/healthz_middleware.rb new file mode 100644 index 000000000..6f3017db3 --- /dev/null +++ b/lib/core/lib/ros/healthz_middleware.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Ros + class HealthzMiddleware + def initialize(app) + @app = app + end + + def call(env) + # Respond with 200 to Kubernetes health check + return [200, { 'Content-Type' => 'text/plain' }, ['']] if env.fetch('PATH_INFO') == '/healthz' + @app.call(env) + end + end +end diff --git a/lib/core/lib/ros/iron_hide/rule.rb b/lib/core/lib/ros/iron_hide/rule.rb new file mode 100644 index 000000000..7861058c3 --- /dev/null +++ b/lib/core/lib/ros/iron_hide/rule.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module IronHide + class Rule + + def self.find(user, action, resource) + cache = IronHide.configuration.memoizer.new + ns_resource = "#{IronHide.configuration.namespace}::#{resource.class.name}" + # NOTE: modified to pass the user into the storage adapter + storage.where(user: user, resource: ns_resource, action: action).map do |json| + new(user, resource, json, cache) + end + end + end +end diff --git a/lib/core/lib/ros/iron_hide/storage/service_adapter.rb b/lib/core/lib/ros/iron_hide/storage/service_adapter.rb new file mode 100644 index 000000000..678833df0 --- /dev/null +++ b/lib/core/lib/ros/iron_hide/storage/service_adapter.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module IronHide + class Storage + class ServiceAdapter < FileAdapter + + def initialize + end + + def where(opts = {}) + # Settings.dig(:service, :policies)) + json.select { |p| p['name'].eql?('CognitoPowerUser') } + @rules = unfold(opts[:user].attached_policies) + self[opts[:resource]][opts[:action]] + end + end + end +end + +# Add adapter class to IronHide::Storage +IronHide::Storage::ADAPTERS.merge!(service: :ServiceAdapter) diff --git a/lib/core/lib/ros/routes.rb b/lib/core/lib/ros/routes.rb index e2d4ee60d..f0e083e90 100644 --- a/lib/core/lib/ros/routes.rb +++ b/lib/core/lib/ros/routes.rb @@ -5,16 +5,5 @@ module Routes def catch_not_found match '*path', controller: 'application', action: :not_found, via: :all end - # def cache(*resources) - # resources.each do |resource| - # get "#{resource}_cache", to: "#{resource}#cache" - # end - # end - - # def report(*resources) - # resources.each do |resource| - # get "#{resource}_reports", to: "#{resource}#reports" - # end - # end end end diff --git a/lib/core/ros-core.gemspec b/lib/core/ros-core.gemspec index 08314cdef..ccd02b34c 100644 --- a/lib/core/ros-core.gemspec +++ b/lib/core/ros-core.gemspec @@ -21,6 +21,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'aws-sdk-s3' spec.add_dependency 'config', '1.7.1' spec.add_dependency 'hashids', '1.0.5' + spec.add_dependency 'iron_hide', '0.4.1' spec.add_dependency 'grpc', '1.23.0' spec.add_dependency 'json_schemer', '0.2.6' spec.add_dependency 'jsonapi-authorization', '3.0.1' diff --git a/services/cognito/Gemfile.lock b/services/cognito/Gemfile.lock index aa5ba05e6..b6fa736ca 100644 --- a/services/cognito/Gemfile.lock +++ b/services/cognito/Gemfile.lock @@ -10,6 +10,7 @@ PATH config (= 1.7.1) grpc (= 1.23.0) hashids (= 1.0.5) + iron_hide (= 0.4.1) json_schemer (= 0.2.6) jsonapi-authorization (= 3.0.1) jsonapi-resources (= 0.9.10) @@ -148,7 +149,7 @@ GEM aws-eventstream (~> 1.0, >= 1.0.2) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - brakeman (4.6.1) + brakeman (4.7.2) builder (3.2.3) choice (0.2.0) coderay (1.1.2) @@ -159,14 +160,14 @@ GEM deep_merge (~> 1.2.1) dry-validation (>= 0.12.2) connection_pool (2.2.2) - crass (1.0.4) + crass (1.0.5) debug_inspector (0.0.3) deep_merge (1.2.1) diff-lcs (1.3) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.5) - dry-configurable (0.8.3) + dry-configurable (0.9.0) concurrent-ruby (~> 1.0) dry-core (~> 0.4, >= 0.4.7) dry-container (0.7.2) @@ -174,22 +175,22 @@ GEM dry-configurable (~> 0.1, >= 0.1.3) dry-core (0.4.9) concurrent-ruby (~> 1.0) - dry-equalizer (0.2.2) - dry-inflector (0.1.2) - dry-initializer (3.0.1) - dry-logic (1.0.3) + dry-equalizer (0.3.0) + dry-inflector (0.2.0) + dry-initializer (3.0.2) + dry-logic (1.0.5) concurrent-ruby (~> 1.0) dry-core (~> 0.2) dry-equalizer (~> 0.2) - dry-schema (1.3.4) + dry-schema (1.4.1) concurrent-ruby (~> 1.0) dry-configurable (~> 0.8, >= 0.8.3) dry-core (~> 0.4) dry-equalizer (~> 0.2) dry-initializer (~> 3.0) dry-logic (~> 1.0) - dry-types (~> 1.0) - dry-types (1.1.1) + dry-types (~> 1.2) + dry-types (1.2.1) concurrent-ruby (~> 1.0) dry-container (~> 0.3) dry-core (~> 0.4, >= 0.4.4) @@ -206,22 +207,22 @@ GEM ecma-re-validator (0.2.0) regexp_parser (~> 1.2) encryptor (3.0.0) - erubi (1.8.0) + erubi (1.9.0) et-orbi (1.2.2) tzinfo excon (0.69.1) - factory_bot (5.0.2) + factory_bot (5.1.1) activesupport (>= 4.2.0) - factory_bot_rails (5.0.2) - factory_bot (~> 5.0.2) + factory_bot_rails (5.1.1) + factory_bot (~> 5.1.0) railties (>= 4.2.0) - faker (2.3.0) - i18n (~> 1.6.0) - faraday (0.15.4) + faker (2.8.0) + i18n (>= 1.6, < 1.8) + faraday (0.17.1) multipart-post (>= 1.2, < 3) faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) - ffi (1.11.1) + ffi (1.11.3) fluent-logger (0.8.2) msgpack (>= 1.0.0, < 2) fugit (1.3.3) @@ -240,9 +241,12 @@ GEM http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) - i18n (1.6.0) + i18n (1.7.0) concurrent-ruby (~> 1.0) inifile (3.0.0) + iron_hide (0.4.1) + json_minify (~> 0.2) + multi_json jaro_winkler (1.5.4) jmespath (1.4.0) json_api_client (1.15.0) @@ -252,6 +256,8 @@ GEM faraday (~> 0.15, >= 0.15.2) faraday_middleware (~> 0.9) rack (>= 0.2) + json_minify (0.2.1) + multi_json (~> 1) json_schemer (0.2.6) ecma-re-validator (~> 0.2) hana (~> 1.3) @@ -271,7 +277,7 @@ GEM rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) - loofah (2.2.3) + loofah (2.4.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -281,23 +287,23 @@ GEM method_source (0.9.2) mime-types (3.3) mime-types-data (~> 3.2015) - mime-types-data (3.2019.0904) + mime-types-data (3.2019.1009) mimemagic (0.3.3) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.11.3) + minitest (5.13.0) msgpack (1.3.1) multi_json (1.14.1) multipart-post (2.1.1) netrc (0.11.0) - nio4r (2.5.1) - nokogiri (1.10.4) + nio4r (2.5.2) + nokogiri (1.10.5) mini_portile2 (~> 2.4.0) - parallel (1.17.0) + parallel (1.19.1) parser (2.6.5.0) ast (~> 2.4.0) - perx-rubocop (0.0.1.rc1) - rubocop (~> 0.74.0) + perx-rubocop (0.0.3) + rubocop (~> 0.77.0) rubocop-rails (~> 2.3.2) pg (1.1.4) prometheus_exporter (0.4.13) @@ -310,7 +316,7 @@ GEM binding_of_caller (>= 0.7) pry (>= 0.9.11) public_suffix (4.0.1) - puma (4.1.1) + puma (4.3.0) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) @@ -350,8 +356,8 @@ GEM activesupport (>= 4.2) choice (~> 0.2.0) ruby-graphviz (~> 1.2) - rails-html-sanitizer (1.2.0) - loofah (~> 2.2, >= 2.2.2) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) railties (6.0.0.rc2) actionpack (= 6.0.0.rc2) activesupport (= 6.0.0.rc2) @@ -359,7 +365,7 @@ GEM rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) rainbow (3.0.0) - rake (12.3.3) + rake (13.0.1) rb-fsevent (0.10.3) rb-inotify (0.10.0) ffi (~> 1.0) @@ -370,30 +376,30 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-core (3.8.2) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.4) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.0) + rspec-support (~> 3.9.0) + rspec-expectations (3.9.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.1) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-rails (3.8.2) + rspec-support (~> 3.9.0) + rspec-rails (3.9.0) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.2) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-support (~> 3.9.0) + rspec-support (3.9.0) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.74.0) + rubocop (0.77.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.6) @@ -425,7 +431,7 @@ GEM spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) - sprockets (3.7.2) + sprockets (4.0.0) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.2.1) @@ -451,7 +457,7 @@ GEM websocket-driver (0.7.1) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.4) - zeitwerk (2.1.10) + zeitwerk (2.2.2) zero-rails_openapi (2.1.0) activesupport (>= 4.1) colorize diff --git a/services/cognito/doc/policies/another_built_in_policy.json b/services/cognito/doc/policies/another_built_in_policy.json new file mode 100644 index 000000000..6a317bc2f --- /dev/null +++ b/services/cognito/doc/policies/another_built_in_policy.json @@ -0,0 +1,5 @@ +{ "name": "AnotherBuiltInPolicy", + "description": "Provides administrative access to existing Amazon Cognito resources", + "version": "1", + "rules": [] +} diff --git a/services/cognito/doc/policies/cognito_super_user.json b/services/cognito/doc/policies/cognito_super_user.json new file mode 100644 index 000000000..746a7ec8f --- /dev/null +++ b/services/cognito/doc/policies/cognito_super_user.json @@ -0,0 +1,51 @@ +{ "name": "CognitoPowerUser", + "description": "Provides administrative access to existing Amazon Cognito resources", + "version": "1", + "rules": [ + { + "description": "Allow owners to read their account", + "effect": "allow", + "action": ["cognito:read"], + "resource": "urn:perx:cognito::user", + "conditions": [ + { + "equal": { + "user::id": ["resource::id"] + } + } + ] + }, + { + "description": "Allow authors to read, write, and sell their books", + "effect": "allow", + "action": ["sell"], + "resource": "Iam::User", + "conditions": [ + { + "equal": { + "resource::id": ["user::id"] + } + } + ] + }, + { + "description": "Disallow users from writing books in languages they do not speak", + "effect": "deny", + "resource": "Iam::User", + "action": ["write"], + "conditions": [ + { + "not_equal": { + "resource::time_zone": ["user::time_zone"] + } + } + ] + }, + { + "description": "Allows users to borrow books from friendly users", + "effect": "allow", + "resource": "Iam::User", + "action": ["borrow_book"] + } + ] +} diff --git a/services/cognito/doc/policy_actions.json b/services/cognito/doc/policy_actions.json new file mode 100644 index 000000000..fcb499842 --- /dev/null +++ b/services/cognito/doc/policy_actions.json @@ -0,0 +1,16 @@ +[ + { + "name": "List", + "actions": [ + "DescribeRepositories", + "ListImages" + ] + }, + { + "name": "Read", + "actions": [ + "BatchCheckLayerAvailability", + "DescribeImages" + ] + } +] diff --git a/services/cognito/lib/ros/cognito/engine.rb b/services/cognito/lib/ros/cognito/engine.rb index 42f148011..a4e5991cc 100644 --- a/services/cognito/lib/ros/cognito/engine.rb +++ b/services/cognito/lib/ros/cognito/engine.rb @@ -40,8 +40,10 @@ class Engine < ::Rails::Engine end end - # initializer 'service.initialize_infra_services', after: 'ros_core.initialize_infra_services' do |app| - # end + initializer 'service.initialize_policies', before: 'ros_core.initialize_policies' do |_app| + Ros.paths[:policies] << root.join('doc/policies') + Ros.paths[:policy_actions] << root.join('doc/policy_actions.json') + end # initializer 'service.configure_console_methods', before: 'ros_core.configure_console_methods' do |_app| # if Rails.env.development? && !Rails.const_defined?('Server') diff --git a/services/iam/Gemfile.lock b/services/iam/Gemfile.lock index 19a761f0c..84440a60f 100644 --- a/services/iam/Gemfile.lock +++ b/services/iam/Gemfile.lock @@ -10,6 +10,7 @@ PATH config (= 1.7.1) grpc (= 1.23.0) hashids (= 1.0.5) + iron_hide (= 0.4.1) json_schemer (= 0.2.6) jsonapi-authorization (= 3.0.1) jsonapi-resources (= 0.9.10) @@ -135,8 +136,8 @@ GEM excon (~> 0.45) awesome_print (1.8.0) aws-eventstream (1.0.3) - aws-partitions (1.247.0) - aws-sdk-core (3.82.0) + aws-partitions (1.249.0) + aws-sdk-core (3.83.0) aws-eventstream (~> 1.0, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) @@ -144,7 +145,7 @@ GEM aws-sdk-kms (1.26.0) aws-sdk-core (~> 3, >= 3.71.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.57.0) + aws-sdk-s3 (1.58.0) aws-sdk-core (~> 3, >= 3.77.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) @@ -220,7 +221,7 @@ GEM erubi (1.9.0) et-orbi (1.2.2) tzinfo - excon (0.69.1) + excon (0.70.0) factory_bot (5.1.1) activesupport (>= 4.2.0) factory_bot_rails (5.1.1) @@ -240,10 +241,10 @@ GEM raabro (~> 1.1) globalid (0.4.2) activesupport (>= 4.2.0) - google-protobuf (3.11.0-x86_64-linux) + google-protobuf (3.11.1) googleapis-common-protos-types (1.0.4) google-protobuf (~> 3.0) - grpc (1.23.0-x86_64-linux) + grpc (1.23.0) google-protobuf (~> 3.8) googleapis-common-protos-types (~> 1.0) hana (1.3.5) @@ -254,6 +255,9 @@ GEM i18n (1.7.0) concurrent-ruby (~> 1.0) inifile (3.0.0) + iron_hide (0.4.1) + json_minify (~> 0.2) + multi_json jaro_winkler (1.5.4) jmespath (1.4.0) json_api_client (1.15.0) @@ -263,6 +267,8 @@ GEM faraday (~> 0.15, >= 0.15.2) faraday_middleware (~> 0.9) rack (>= 0.2) + json_minify (0.2.1) + multi_json (~> 1) json_schemer (0.2.6) ecma-re-validator (~> 0.2) hana (~> 1.3) diff --git a/services/iam/lib/ros/iam/engine.rb b/services/iam/lib/ros/iam/engine.rb index 6be945247..bbe97d207 100644 --- a/services/iam/lib/ros/iam/engine.rb +++ b/services/iam/lib/ros/iam/engine.rb @@ -13,7 +13,7 @@ class Engine < ::Rails::Engine settings_path = root.join('config/settings.yml') Settings.prepend_source!(settings_path) if File.exist? settings_path name = self.class.module_parent.name.demodulize.underscore - Settings.prepend_source!(service: { name: name, policy_name: name.capitalize }) + Settings.prepend_source!(service: { name: name, policy_name: name.camelize }) end # Adds this gem's db/migrations path to the enclosing application's migraations_path array From d6a2d52fe3b4cbb3f1354afa1dec6590e5e1ccd1 Mon Sep 17 00:00:00 2001 From: Robert Roach Date: Tue, 10 Dec 2019 18:51:20 +0800 Subject: [PATCH 2/2] test with urns --- .../controllers/policy_actions_controller.rb | 1 + .../service_policies_controller.rb | 1 + lib/core/lib/ros/core.rb | 2 +- lib/core/lib/ros/core/engine.rb | 3 ++- lib/core/lib/ros/healthz_middleware.rb | 1 + lib/core/lib/ros/iron_hide/rule.rb | 8 ++++--- .../ros/iron_hide/storage/service_adapter.rb | 12 +++++----- services/cognito/app/policies/pool_policy.rb | 11 ++++++++++ .../doc/policies/cognito_super_user.json | 22 +++++++++---------- services/cognito/lib/ros/cognito/engine.rb | 2 +- 10 files changed, 40 insertions(+), 23 deletions(-) diff --git a/lib/core/app/controllers/policy_actions_controller.rb b/lib/core/app/controllers/policy_actions_controller.rb index 82289d397..3c03e6ed1 100644 --- a/lib/core/app/controllers/policy_actions_controller.rb +++ b/lib/core/app/controllers/policy_actions_controller.rb @@ -9,6 +9,7 @@ def index def resources return [] unless (json = Settings.dig(:service, :policy_actions)) + json.map { |model| PolicyActionResource.new(PolicyAction.new(model), nil) } end end diff --git a/lib/core/app/controllers/service_policies_controller.rb b/lib/core/app/controllers/service_policies_controller.rb index a2cf07e24..00d7bdfce 100644 --- a/lib/core/app/controllers/service_policies_controller.rb +++ b/lib/core/app/controllers/service_policies_controller.rb @@ -9,6 +9,7 @@ def index def resources return [] unless (json = Settings.dig(:service, :policies)) + json.map { |model| ServicePolicyResource.new(ServicePolicy.new(model), nil) } end end diff --git a/lib/core/lib/ros/core.rb b/lib/core/lib/ros/core.rb index 6936003d8..ceeeaef81 100644 --- a/lib/core/lib/ros/core.rb +++ b/lib/core/lib/ros/core.rb @@ -77,7 +77,7 @@ def paths; @paths ||= HashWithIndifferentAccess.new(policies: [], policy_actions def load_policies %i[policies policy_actions].each do |type| - Settings.service[type]= Ros.paths[type].each_with_object([]) do |path, ary| + Settings.service[type] = Ros.paths[type].each_with_object([]) do |path, ary| path = path.to_s.end_with?('.json') ? path : "#{path}/*.json" Dir[path].each { |policy_file| ary << JSON.parse(File.read(policy_file)) } end.flatten diff --git a/lib/core/lib/ros/core/engine.rb b/lib/core/lib/ros/core/engine.rb index 89d7a58ae..928911625 100644 --- a/lib/core/lib/ros/core/engine.rb +++ b/lib/core/lib/ros/core/engine.rb @@ -77,7 +77,8 @@ class Engine < ::Rails::Engine Ros.load_policies IronHide.config do |config| config.adapter = :service - config.namespace = Settings.service.policy_name + # config.namespace = Settings.service.policy_name + config.namespace = Settings.service.name end end diff --git a/lib/core/lib/ros/healthz_middleware.rb b/lib/core/lib/ros/healthz_middleware.rb index 6f3017db3..95c97a70b 100644 --- a/lib/core/lib/ros/healthz_middleware.rb +++ b/lib/core/lib/ros/healthz_middleware.rb @@ -9,6 +9,7 @@ def initialize(app) def call(env) # Respond with 200 to Kubernetes health check return [200, { 'Content-Type' => 'text/plain' }, ['']] if env.fetch('PATH_INFO') == '/healthz' + @app.call(env) end end diff --git a/lib/core/lib/ros/iron_hide/rule.rb b/lib/core/lib/ros/iron_hide/rule.rb index 7861058c3..b8a7a69f6 100644 --- a/lib/core/lib/ros/iron_hide/rule.rb +++ b/lib/core/lib/ros/iron_hide/rule.rb @@ -2,14 +2,16 @@ module IronHide class Rule - def self.find(user, action, resource) cache = IronHide.configuration.memoizer.new - ns_resource = "#{IronHide.configuration.namespace}::#{resource.class.name}" + ns_resource = "#{ApplicationRecord.urn_base}*:*:#{resource.class.name.downcase}" + # ns_resource = resource.class.to_urn # NOTE: modified to pass the user into the storage adapter - storage.where(user: user, resource: ns_resource, action: action).map do |json| + ar = storage.where(user: user, resource: ns_resource, action: action).map do |json| new(user, resource, json, cache) end + binding.pry + ar end end end diff --git a/lib/core/lib/ros/iron_hide/storage/service_adapter.rb b/lib/core/lib/ros/iron_hide/storage/service_adapter.rb index 678833df0..efa4b783f 100644 --- a/lib/core/lib/ros/iron_hide/storage/service_adapter.rb +++ b/lib/core/lib/ros/iron_hide/storage/service_adapter.rb @@ -3,14 +3,14 @@ module IronHide class Storage class ServiceAdapter < FileAdapter - - def initialize - end + def initialize; end def where(opts = {}) - # Settings.dig(:service, :policies)) - json.select { |p| p['name'].eql?('CognitoPowerUser') } - @rules = unfold(opts[:user].attached_policies) + keys = opts[:user].attached_policies.keys + policy = Settings.dig(:service, :policies) + json = policy.select { |p| keys.include?(p['name']) }.map { |j| j['rules'] }.flatten + @rules = unfold(json) + # @rules = unfold(opts[:user].attached_policies) self[opts[:resource]][opts[:action]] end end diff --git a/services/cognito/app/policies/pool_policy.rb b/services/cognito/app/policies/pool_policy.rb index 2e9b1ae3d..4e202e6d3 100644 --- a/services/cognito/app/policies/pool_policy.rb +++ b/services/cognito/app/policies/pool_policy.rb @@ -1,4 +1,15 @@ # frozen_string_literal: true class PoolPolicy < Cognito::ApplicationPolicy + def show? + IronHide.can? user, :read, record + end + + def update? + IronHide.can? user, :write, record + end + + def index? + IronHide.can? user, :list, record + end end diff --git a/services/cognito/doc/policies/cognito_super_user.json b/services/cognito/doc/policies/cognito_super_user.json index 746a7ec8f..4d5be06b0 100644 --- a/services/cognito/doc/policies/cognito_super_user.json +++ b/services/cognito/doc/policies/cognito_super_user.json @@ -4,9 +4,9 @@ "rules": [ { "description": "Allow owners to read their account", + "resource": "urn:perx:cognito:*:*:user", + "action": ["read"], "effect": "allow", - "action": ["cognito:read"], - "resource": "urn:perx:cognito::user", "conditions": [ { "equal": { @@ -16,10 +16,10 @@ ] }, { - "description": "Allow authors to read, write, and sell their books", + "description": "Allow user to read, write, and list their pools", + "resource": "urn:perx:cognito:*:*:pool", + "action": ["read", "write", "list"], "effect": "allow", - "action": ["sell"], - "resource": "Iam::User", "conditions": [ { "equal": { @@ -30,22 +30,22 @@ }, { "description": "Disallow users from writing books in languages they do not speak", - "effect": "deny", - "resource": "Iam::User", + "resource": "urn:perx:cognito:*:*:pool", "action": ["write"], + "effect": "deny", "conditions": [ { "not_equal": { - "resource::time_zone": ["user::time_zone"] + "resource::id": ["user::id"] } } ] }, { "description": "Allows users to borrow books from friendly users", - "effect": "allow", - "resource": "Iam::User", - "action": ["borrow_book"] + "resource": "urn:perx:cognito:*:*:pool", + "action": ["write"], + "effect": "allow" } ] } diff --git a/services/cognito/lib/ros/cognito/engine.rb b/services/cognito/lib/ros/cognito/engine.rb index a4e5991cc..09dfa7d02 100644 --- a/services/cognito/lib/ros/cognito/engine.rb +++ b/services/cognito/lib/ros/cognito/engine.rb @@ -13,7 +13,7 @@ class Engine < ::Rails::Engine settings_path = root.join('config/settings.yml') Settings.prepend_source!(settings_path) if File.exist? settings_path name = self.class.module_parent.name.demodulize.underscore - Settings.prepend_source!(service: { name: name, policy_name: name.capitalize }) + Settings.prepend_source!(service: { name: name, policy_name: name.camelize }) end # Adds this gem's db/migrations path to the enclosing application's migraations_path array