Add warning for object storage misconfiguration (#24137)
This commit is contained in:
		| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| class Admin::SystemCheck | class Admin::SystemCheck | ||||||
|   ACTIVE_CHECKS = [ |   ACTIVE_CHECKS = [ | ||||||
|  |     Admin::SystemCheck::MediaPrivacyCheck, | ||||||
|     Admin::SystemCheck::DatabaseSchemaCheck, |     Admin::SystemCheck::DatabaseSchemaCheck, | ||||||
|     Admin::SystemCheck::SidekiqProcessCheck, |     Admin::SystemCheck::SidekiqProcessCheck, | ||||||
|     Admin::SystemCheck::RulesCheck, |     Admin::SystemCheck::RulesCheck, | ||||||
|   | |||||||
							
								
								
									
										105
									
								
								app/lib/admin/system_check/media_privacy_check.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								app/lib/admin/system_check/media_privacy_check.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | |||||||
|  | # frozen_string_literal: true | ||||||
|  |  | ||||||
|  | class Admin::SystemCheck::MediaPrivacyCheck < Admin::SystemCheck::BaseCheck | ||||||
|  |   include RoutingHelper | ||||||
|  |  | ||||||
|  |   def skip? | ||||||
|  |     !current_user.can?(:view_devops) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def pass? | ||||||
|  |     check_media_uploads! | ||||||
|  |     @failure_message.nil? | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def message | ||||||
|  |     Admin::SystemCheck::Message.new(@failure_message, @failure_value, @failure_action, true) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def check_media_uploads! | ||||||
|  |     if Rails.configuration.x.use_s3 | ||||||
|  |       check_media_listing_inaccessible_s3! | ||||||
|  |     else | ||||||
|  |       check_media_listing_inaccessible! | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def check_media_listing_inaccessible! | ||||||
|  |     full_url = full_asset_url(media_attachment.file.url(:original, false)) | ||||||
|  |  | ||||||
|  |     # Check if we can list the uploaded file. If true, that's an error | ||||||
|  |     directory_url = Addressable::URI.parse(full_url) | ||||||
|  |     directory_url.query = nil | ||||||
|  |     filename = directory_url.path.gsub(%r{.*/}, '') | ||||||
|  |     directory_url.path = directory_url.path.gsub(%r{/[^/]+\Z}, '/') | ||||||
|  |     Request.new(:get, directory_url, allow_local: true).perform do |res| | ||||||
|  |       if res.truncated_body&.include?(filename) | ||||||
|  |         @failure_message = use_storage? ? :upload_check_privacy_error_object_storage : :upload_check_privacy_error | ||||||
|  |         @failure_action = 'https://docs.joinmastodon.org/admin/optional/object-storage/#FS' | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   rescue | ||||||
|  |     nil | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def check_media_listing_inaccessible_s3! | ||||||
|  |     urls_to_check = [] | ||||||
|  |     paperclip_options = Paperclip::Attachment.default_options | ||||||
|  |     s3_protocol = paperclip_options[:s3_protocol] | ||||||
|  |     s3_host_alias = paperclip_options[:s3_host_alias] | ||||||
|  |     s3_host_name  = paperclip_options[:s3_host_name] | ||||||
|  |     bucket_name = paperclip_options.dig(:s3_credentials, :bucket) | ||||||
|  |  | ||||||
|  |     urls_to_check << "#{s3_protocol}://#{s3_host_alias}/" if s3_host_alias.present? | ||||||
|  |     urls_to_check << "#{s3_protocol}://#{s3_host_name}/#{bucket_name}/" | ||||||
|  |     urls_to_check.uniq.each do |full_url| | ||||||
|  |       check_s3_listing!(full_url) | ||||||
|  |       break if @failure_message.present? | ||||||
|  |     end | ||||||
|  |   rescue | ||||||
|  |     nil | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def check_s3_listing!(full_url) | ||||||
|  |     bucket_url = Addressable::URI.parse(full_url) | ||||||
|  |     bucket_url.path = bucket_url.path.delete_suffix(media_attachment.file.path(:original)) | ||||||
|  |     bucket_url.query = "max-keys=1&x-random=#{SecureRandom.hex(10)}" | ||||||
|  |     Request.new(:get, bucket_url, allow_local: true).perform do |res| | ||||||
|  |       if res.truncated_body&.include?('ListBucketResult') | ||||||
|  |         @failure_message = :upload_check_privacy_error_object_storage | ||||||
|  |         @failure_action  = 'https://docs.joinmastodon.org/admin/optional/object-storage/#S3' | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def media_attachment | ||||||
|  |     @media_attachment ||= begin | ||||||
|  |       attachment = Account.representative.media_attachments.first | ||||||
|  |       if attachment.present? | ||||||
|  |         attachment.touch # rubocop:disable Rails/SkipsModelValidations | ||||||
|  |         attachment | ||||||
|  |       else | ||||||
|  |         create_test_attachment! | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def create_test_attachment! | ||||||
|  |     Tempfile.create(%w(test-upload .jpg), binmode: true) do |tmp_file| | ||||||
|  |       tmp_file.write( | ||||||
|  |         Base64.decode64( | ||||||
|  |           '/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' \ | ||||||
|  |           'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' \ | ||||||
|  |           'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' \ | ||||||
|  |           'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/x' \ | ||||||
|  |           'ABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAA' \ | ||||||
|  |           'AAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==' | ||||||
|  |         ) | ||||||
|  |       ) | ||||||
|  |       tmp_file.flush | ||||||
|  |       Account.representative.media_attachments.create!(file: tmp_file) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -1,11 +1,12 @@ | |||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
|  |  | ||||||
| class Admin::SystemCheck::Message | class Admin::SystemCheck::Message | ||||||
|   attr_reader :key, :value, :action |   attr_reader :key, :value, :action, :critical | ||||||
|  |  | ||||||
|   def initialize(key, value = nil, action = nil) |   def initialize(key, value = nil, action = nil, critical = false) | ||||||
|     @key      = key |     @key      = key | ||||||
|     @value    = value |     @value    = value | ||||||
|     @action   = action |     @action   = action | ||||||
|  |     @critical = critical | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ | |||||||
| - unless @system_checks.empty? | - unless @system_checks.empty? | ||||||
|   .flash-message-stack |   .flash-message-stack | ||||||
|     - @system_checks.each do |message| |     - @system_checks.each do |message| | ||||||
|       .flash-message.warning |       .flash-message{ class: message.critical ? 'alert' : 'warning' } | ||||||
|         = t("admin.system_checks.#{message.key}.message_html", value: message.value ? content_tag(:strong, message.value) : nil) |         = t("admin.system_checks.#{message.key}.message_html", value: message.value ? content_tag(:strong, message.value) : nil) | ||||||
|         - if message.action |         - if message.action | ||||||
|           = link_to t("admin.system_checks.#{message.key}.action"), message.action |           = link_to t("admin.system_checks.#{message.key}.action"), message.action | ||||||
|   | |||||||
| @@ -805,6 +805,12 @@ en: | |||||||
|         message_html: You haven't defined any server rules. |         message_html: You haven't defined any server rules. | ||||||
|       sidekiq_process_check: |       sidekiq_process_check: | ||||||
|         message_html: No Sidekiq process running for the %{value} queue(s). Please review your Sidekiq configuration |         message_html: No Sidekiq process running for the %{value} queue(s). Please review your Sidekiq configuration | ||||||
|  |       upload_check_privacy_error: | ||||||
|  |         action: Check here for more information | ||||||
|  |         message_html: "<strong>Your web server is misconfigured. The privacy of your users is at risk.</strong>" | ||||||
|  |       upload_check_privacy_error_object_storage: | ||||||
|  |         action: Check here for more information | ||||||
|  |         message_html: "<strong>Your object storage is misconfigured. The privacy of your users is at risk.</strong>" | ||||||
|     tags: |     tags: | ||||||
|       review: Review status |       review: Review status | ||||||
|       updated_msg: Hashtag settings updated successfully |       updated_msg: Hashtag settings updated successfully | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user