diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2b53930016..bd8904b1ad 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -54,6 +54,7 @@ class UnprocessableEntity < RuntimeError rescue_from GatewayTimeout, with: :gateway_timeout rescue_from BadGateway, with: :bad_gateway rescue_from Exceptions::NotImplemented, with: :not_implemented + rescue_from Errors::Conjur::ReadOnlyModeMethodNotAllowed, with: :method_not_allowed rescue_from Sequel::ValidationFailed, with: :validation_failed rescue_from Sequel::NoMatchingRow, with: :no_matching_row rescue_from Sequel::ForeignKeyConstraintViolation, with: :foreign_key_constraint_violation diff --git a/app/controllers/concerns/prepend.rb b/app/controllers/concerns/prepend.rb new file mode 100644 index 0000000000..132c1fdc4d --- /dev/null +++ b/app/controllers/concerns/prepend.rb @@ -0,0 +1,14 @@ +module ReadOnlyPrepender + # Given a list of method symbols, preempt calls to them using a proxy that raises an error if read_only_api is enabled. + def read_safe(*method_names) + method_names.each do |m| + proxy = Module.new do + define_method(m) do |*args| + raise Errors::Conjur::ReadOnlyModeMethodNotAllowed.new() unless !Rails.configuration.read_only_api + super *args + end + end + self.prepend proxy + end + end + end diff --git a/app/controllers/policies_controller.rb b/app/controllers/policies_controller.rb index 18e3771912..a0073fd183 100644 --- a/app/controllers/policies_controller.rb +++ b/app/controllers/policies_controller.rb @@ -3,8 +3,11 @@ class PoliciesController < RestController include FindResource include AuthorizeResource + extend ReadOnlyPrepender + before_action :current_user before_action :find_or_create_root_policy + read_safe :put, :patch, :post after_action :publish_event, if: -> { response.successful? } rescue_from Sequel::UniqueConstraintViolation, with: :concurrent_load diff --git a/app/controllers/secrets_controller.rb b/app/controllers/secrets_controller.rb index 0030c5d1e9..f7ea2cf683 100644 --- a/app/controllers/secrets_controller.rb +++ b/app/controllers/secrets_controller.rb @@ -5,8 +5,10 @@ class SecretsController < RestController include FindResource include AuthorizeResource + extend ReadOnlyPrepender before_action :current_user + read_safe :create, :expire # TODO: should we disable the expire endpoint? def create authorize(:update) diff --git a/app/domain/errors.rb b/app/domain/errors.rb index c86787881d..82fcf524db 100644 --- a/app/domain/errors.rb +++ b/app/domain/errors.rb @@ -57,6 +57,11 @@ module Conjur msg: "Resource '{0-resource}' requested by role '{1-role}' not found", code: "CONJ00123E" ) + + ReadOnlyModeMethodNotAllowed = ::Util::TrackableErrorClass.new( + msg: "This action is not permitted when the server is in read-only mode", + code: "CONJ00153E" + ) end module Authorization diff --git a/config/initializers/read_only_api.rb b/config/initializers/read_only_api.rb new file mode 100644 index 0000000000..23348f2e9d --- /dev/null +++ b/config/initializers/read_only_api.rb @@ -0,0 +1,15 @@ +Rails.application.configure do + # Determines whether or not writable API endpoints are enabled. + # + # The `read_only_api` arguement is a boolean. By default, `read_only_api` is "Off". + # This means that any of the writable API endpoints will function as intended. + # + # A writable API endpoint is one whose controller method is decorated with the + # `@read_safe` decorator. When `read_only_api` is "On", such endpoints will return + # an HTTP 405 Method Not Allowed response code. + # + # TODO: + read_only_api = true + config.read_only_api = read_only_api # def enabled?() read_only_api end + end + \ No newline at end of file