Add warning for object storage misconfiguration (#24137)
This commit is contained in:
		
							parent
							
								
									75e5a6e437
								
							
						
					
					
						commit
						8fdf49b11d
					
				
					 5 changed files with 119 additions and 6 deletions
				
			
		|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| class Admin::SystemCheck | ||||
|   ACTIVE_CHECKS = [ | ||||
|     Admin::SystemCheck::MediaPrivacyCheck, | ||||
|     Admin::SystemCheck::DatabaseSchemaCheck, | ||||
|     Admin::SystemCheck::SidekiqProcessCheck, | ||||
|     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 | ||||
| 
 | ||||
| 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 | ||||
|     @value    = value | ||||
|     @action   = action | ||||
|     @critical = critical | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ | |||
| - unless @system_checks.empty? | ||||
|   .flash-message-stack | ||||
|     - @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) | ||||
|         - if message.action | ||||
|           = link_to t("admin.system_checks.#{message.key}.action"), message.action | ||||
|  |  | |||
|  | @ -812,6 +812,12 @@ en: | |||
|         message_html: You haven't defined any server rules. | ||||
|       sidekiq_process_check: | ||||
|         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: | ||||
|       review: Review status | ||||
|       updated_msg: Hashtag settings updated successfully | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue