Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
Add warning for object storage misconfiguration (mastodon#24137)
Browse files Browse the repository at this point in the history
  • Loading branch information
ClearlyClaire authored Mar 16, 2023
1 parent 75e5a6e commit 8fdf49b
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 6 deletions.
1 change: 1 addition & 0 deletions app/lib/admin/system_check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

class Admin::SystemCheck
ACTIVE_CHECKS = [
Admin::SystemCheck::MediaPrivacyCheck,
Admin::SystemCheck::DatabaseSchemaCheck,
Admin::SystemCheck::SidekiqProcessCheck,
Admin::SystemCheck::RulesCheck,
Expand Down
105 changes: 105 additions & 0 deletions app/lib/admin/system_check/media_privacy_check.rb
Original file line number Diff line number Diff line change
@@ -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
11 changes: 6 additions & 5 deletions app/lib/admin/system_check/message.rb
Original file line number Diff line number Diff line change
@@ -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)
@key = key
@value = value
@action = action
def initialize(key, value = nil, action = nil, critical = false)
@key = key
@value = value
@action = action
@critical = critical
end
end
2 changes: 1 addition & 1 deletion app/views/admin/dashboard/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 8fdf49b

Please sign in to comment.