Skip to content
Snippets Groups Projects
Commit ad05e488 authored by Brett Walker's avatar Brett Walker
Browse files

Add support for using a Camo proxy server

User images and videos will get proxied through
the Camo server in order to keep malicious
sites from collecting the IP address of users.
parent 892e4c0d
No related branches found
No related tags found
No related merge requests found
Showing
with 258 additions and 15 deletions
...@@ -164,6 +164,10 @@ def visible_attributes ...@@ -164,6 +164,10 @@ def visible_attributes
:allow_local_requests_from_system_hooks, :allow_local_requests_from_system_hooks,
:dns_rebinding_protection_enabled, :dns_rebinding_protection_enabled,
:archive_builds_in_human_readable, :archive_builds_in_human_readable,
:asset_proxy_enabled,
:asset_proxy_secret_key,
:asset_proxy_url,
:asset_proxy_whitelist,
:authorized_keys_enabled, :authorized_keys_enabled,
:auto_devops_enabled, :auto_devops_enabled,
:auto_devops_domain, :auto_devops_domain,
......
...@@ -18,12 +18,19 @@ class ApplicationSetting < ApplicationRecord ...@@ -18,12 +18,19 @@ class ApplicationSetting < ApplicationRecord
# fix a lot of tests using allow_any_instance_of # fix a lot of tests using allow_any_instance_of
include ApplicationSettingImplementation include ApplicationSettingImplementation
attr_encrypted :asset_proxy_secret_key,
mode: :per_attribute_iv,
insecure_mode: true,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
serialize :asset_proxy_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
ignore_column :koding_url ignore_column :koding_url
ignore_column :koding_enabled ignore_column :koding_enabled
...@@ -192,6 +199,17 @@ class ApplicationSetting < ApplicationRecord ...@@ -192,6 +199,17 @@ class ApplicationSetting < ApplicationRecord
allow_nil: true, allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 65536 } numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 65536 }
validates :asset_proxy_url,
presence: true,
allow_blank: false,
url: true,
if: :asset_proxy_enabled?
validates :asset_proxy_secret_key,
presence: true,
allow_blank: false,
if: :asset_proxy_enabled?
SUPPORTED_KEY_TYPES.each do |type| SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type } validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end end
......
...@@ -23,8 +23,9 @@ def defaults ...@@ -23,8 +23,9 @@ def defaults
akismet_enabled: false, akismet_enabled: false,
allow_local_requests_from_web_hooks_and_services: false, allow_local_requests_from_web_hooks_and_services: false,
allow_local_requests_from_system_hooks: true, allow_local_requests_from_system_hooks: true,
dns_rebinding_protection_enabled: true, asset_proxy_enabled: false,
authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
commit_email_hostname: default_commit_email_hostname,
container_registry_token_expire_delay: 5, container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days', default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'], default_branch_protection: Settings.gitlab['default_branch_protection'],
...@@ -33,7 +34,9 @@ def defaults ...@@ -33,7 +34,9 @@ def defaults
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'], default_projects_limit: Settings.gitlab['default_projects_limit'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
dns_rebinding_protection_enabled: true,
domain_whitelist: Settings.gitlab['domain_whitelist'], domain_whitelist: Settings.gitlab['domain_whitelist'],
dsa_key_restriction: 0, dsa_key_restriction: 0,
ecdsa_key_restriction: 0, ecdsa_key_restriction: 0,
...@@ -52,9 +55,11 @@ def defaults ...@@ -52,9 +55,11 @@ def defaults
housekeeping_gc_period: 200, housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10, housekeeping_incremental_repack_period: 10,
import_sources: Settings.gitlab['import_sources'], import_sources: Settings.gitlab['import_sources'],
local_markdown_version: 0,
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'], max_attachment_size: Settings.gitlab['max_attachment_size'],
mirror_available: true, mirror_available: true,
outbound_local_requests_whitelist: [],
password_authentication_enabled_for_git: true, password_authentication_enabled_for_git: true,
password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'], password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
performance_bar_allowed_group_id: nil, performance_bar_allowed_group_id: nil,
...@@ -63,6 +68,8 @@ def defaults ...@@ -63,6 +68,8 @@ def defaults
plantuml_url: nil, plantuml_url: nil,
polling_interval_multiplier: 1, polling_interval_multiplier: 1,
project_export_enabled: true, project_export_enabled: true,
protected_ci_variables: false,
raw_blob_request_limit: 300,
recaptcha_enabled: false, recaptcha_enabled: false,
repository_checks_enabled: true, repository_checks_enabled: true,
repository_storages: ['default'], repository_storages: ['default'],
...@@ -95,16 +102,10 @@ def defaults ...@@ -95,16 +102,10 @@ def defaults
user_default_internal_regex: nil, user_default_internal_regex: nil,
user_show_add_ssh_key_message: true, user_show_add_ssh_key_message: true,
usage_stats_set_by_user_id: nil, usage_stats_set_by_user_id: nil,
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
commit_email_hostname: default_commit_email_hostname,
snowplow_collector_hostname: nil, snowplow_collector_hostname: nil,
snowplow_cookie_domain: nil, snowplow_cookie_domain: nil,
snowplow_enabled: false, snowplow_enabled: false,
snowplow_site_id: nil, snowplow_site_id: nil
protected_ci_variables: false,
local_markdown_version: 0,
outbound_local_requests_whitelist: [],
raw_blob_request_limit: 300
} }
end end
...@@ -198,6 +199,15 @@ def outbound_local_requests_whitelist_arrays ...@@ -198,6 +199,15 @@ def outbound_local_requests_whitelist_arrays
end end
end end
def asset_proxy_whitelist=(values)
values = domain_strings_to_array(values) if values.is_a?(String)
# make sure we always whitelist the running host
values << Gitlab.config.gitlab.host unless values.include?(Gitlab.config.gitlab.host)
self[:asset_proxy_whitelist] = values
end
def repository_storages def repository_storages
Array(read_attribute(:repository_storages)) Array(read_attribute(:repository_storages))
end end
...@@ -306,6 +316,7 @@ def domain_strings_to_array(values) ...@@ -306,6 +316,7 @@ def domain_strings_to_array(values)
values values
.split(DOMAIN_LIST_SEPARATOR) .split(DOMAIN_LIST_SEPARATOR)
.map(&:strip)
.reject(&:empty?) .reject(&:empty?)
.uniq .uniq
end end
......
...@@ -6,6 +6,8 @@ class UpdateService < ApplicationSettings::BaseService ...@@ -6,6 +6,8 @@ class UpdateService < ApplicationSettings::BaseService
attr_reader :params, :application_setting attr_reader :params, :application_setting
MARKDOWN_CACHE_INVALIDATING_PARAMS = %w(asset_proxy_enabled asset_proxy_url asset_proxy_secret_key asset_proxy_whitelist).freeze
def execute def execute
validate_classification_label(application_setting, :external_authorization_service_default_label) validate_classification_label(application_setting, :external_authorization_service_default_label)
...@@ -25,7 +27,13 @@ def execute ...@@ -25,7 +27,13 @@ def execute
params[:usage_stats_set_by_user_id] = current_user.id params[:usage_stats_set_by_user_id] = current_user.id
end end
@application_setting.update(@params) @application_setting.assign_attributes(params)
if invalidate_markdown_cache?
@application_setting[:local_markdown_version] = @application_setting.local_markdown_version + 1
end
@application_setting.save
end end
private private
...@@ -41,6 +49,11 @@ def add_to_outbound_local_requests_whitelist(values) ...@@ -41,6 +49,11 @@ def add_to_outbound_local_requests_whitelist(values)
@application_setting.add_to_outbound_local_requests_whitelist(values_array) @application_setting.add_to_outbound_local_requests_whitelist(values_array)
end end
def invalidate_markdown_cache?
!params.key?(:local_markdown_version) &&
(@application_setting.changes.keys & MARKDOWN_CACHE_INVALIDATING_PARAMS).any?
end
def update_terms(terms) def update_terms(terms)
return unless terms.present? return unless terms.present?
......
---
title: Added image proxy to mitigate potential stealing of IP addresses
merge_request:
author:
type: security
#
# Asset proxy settings
#
ActiveSupport.on_load(:active_record) do
Banzai::Filter::AssetProxyFilter.initialize_settings
end
if Shard.connected? && !Gitlab::Database.read_only? # The `table_exists?` check is needed because during our migration rollback testing,
# `Shard.connected?` could be cached and return true even though the table doesn't exist
if Shard.connected? && Shard.table_exists? && !Gitlab::Database.read_only?
Shard.populate! Shard.populate!
end end
# frozen_string_literal: true
class AddAssetProxySettings < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
add_column :application_settings, :asset_proxy_enabled, :boolean, default: false, null: false
add_column :application_settings, :asset_proxy_url, :string
add_column :application_settings, :asset_proxy_whitelist, :text
add_column :application_settings, :encrypted_asset_proxy_secret_key, :text
add_column :application_settings, :encrypted_asset_proxy_secret_key_iv, :string
end
end
...@@ -278,6 +278,11 @@ ...@@ -278,6 +278,11 @@
t.boolean "allow_local_requests_from_system_hooks", default: true, null: false t.boolean "allow_local_requests_from_system_hooks", default: true, null: false
t.bigint "instance_administration_project_id" t.bigint "instance_administration_project_id"
t.string "snowplow_collector_hostname" t.string "snowplow_collector_hostname"
t.boolean "asset_proxy_enabled", default: false, null: false
t.string "asset_proxy_url"
t.text "asset_proxy_whitelist"
t.text "encrypted_asset_proxy_secret_key"
t.string "encrypted_asset_proxy_secret_key_iv"
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id" t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id" t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id" t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
......
...@@ -68,6 +68,9 @@ Example response: ...@@ -68,6 +68,9 @@ Example response:
"allow_local_requests_from_hooks_and_services": true, "allow_local_requests_from_hooks_and_services": true,
"allow_local_requests_from_web_hooks_and_services": true, "allow_local_requests_from_web_hooks_and_services": true,
"allow_local_requests_from_system_hooks": false "allow_local_requests_from_system_hooks": false
"asset_proxy_enabled": true,
"asset_proxy_url": "https://assets.example.com",
"asset_proxy_whitelist": ["example.com", "*.example.com", "your-instance.com"]
} }
``` ```
...@@ -141,6 +144,9 @@ Example response: ...@@ -141,6 +144,9 @@ Example response:
"user_show_add_ssh_key_message": true, "user_show_add_ssh_key_message": true,
"file_template_project_id": 1, "file_template_project_id": 1,
"local_markdown_version": 0, "local_markdown_version": 0,
"asset_proxy_enabled": true,
"asset_proxy_url": "https://assets.example.com",
"asset_proxy_whitelist": ["example.com", "*.example.com", "your-instance.com"],
"geo_node_allowed_ips": "0.0.0.0/0, ::/0", "geo_node_allowed_ips": "0.0.0.0/0, ::/0",
"allow_local_requests_from_hooks_and_services": true, "allow_local_requests_from_hooks_and_services": true,
"allow_local_requests_from_web_hooks_and_services": true, "allow_local_requests_from_web_hooks_and_services": true,
...@@ -186,6 +192,10 @@ are listed in the descriptions of the relevant settings. ...@@ -186,6 +192,10 @@ are listed in the descriptions of the relevant settings.
| `allow_local_requests_from_hooks_and_services` | boolean | no | (Deprecated: Use `allow_local_requests_from_web_hooks_and_services` instead) Allow requests to the local network from hooks and services. | | `allow_local_requests_from_hooks_and_services` | boolean | no | (Deprecated: Use `allow_local_requests_from_web_hooks_and_services` instead) Allow requests to the local network from hooks and services. |
| `allow_local_requests_from_web_hooks_and_services` | boolean | no | Allow requests to the local network from web hooks and services. | | `allow_local_requests_from_web_hooks_and_services` | boolean | no | Allow requests to the local network from web hooks and services. |
| `allow_local_requests_from_system_hooks` | boolean | no | Allow requests to the local network from system hooks. | | `allow_local_requests_from_system_hooks` | boolean | no | Allow requests to the local network from system hooks. |
| `asset_proxy_enabled` | boolean | no | (**If enabled, requires:** `asset_proxy_url`) Enable proxying of assets. GitLab restart is required to apply changes. |
| `asset_proxy_secret_key` | string | no | Shared secret with the asset proxy server. GitLab restart is required to apply changes. |
| `asset_proxy_url` | string | no | URL of the asset proxy server. GitLab restart is required to apply changes. |
| `asset_proxy_whitelist` | string or array of strings | no | Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted. GitLab restart is required to apply changes. |
| `authorized_keys_enabled` | boolean | no | By default, we write to the `authorized_keys` file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. | | `authorized_keys_enabled` | boolean | no | By default, we write to the `authorized_keys` file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. |
| `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. | | `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. |
| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It will automatically build, test, and deploy applications based on a predefined CI/CD configuration. | | `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It will automatically build, test, and deploy applications based on a predefined CI/CD configuration. |
......
...@@ -17,3 +17,4 @@ type: index ...@@ -17,3 +17,4 @@ type: index
- [Enforce Two-factor authentication](two_factor_authentication.md) - [Enforce Two-factor authentication](two_factor_authentication.md)
- [Send email confirmation on sign-up](user_email_confirmation.md) - [Send email confirmation on sign-up](user_email_confirmation.md)
- [Security of running jobs](https://docs.gitlab.com/runner/security/) - [Security of running jobs](https://docs.gitlab.com/runner/security/)
- [Proxying images](asset_proxy.md)
A possible security concern when managing a public facing GitLab instance is
the ability to steal a users IP address by referencing images in issues, comments, etc.
For example, adding `![Example image](http://example.com/example.png)` to
an issue description will cause the image to be loaded from the external
server in order to be displayed. However this also allows the external server
to log the IP address of the user.
One way to mitigate this is by proxying any external images to a server you
control. GitLab handles this by allowing you to run the "Camo" server
[cactus/go-camo](https://github.com/cactus/go-camo#how-it-works).
The image request is sent to the Camo server, which then makes the request for
the original image. This way an attacker only ever seems the IP address
of your Camo server.
Once you have your Camo server up and running, you can configure GitLab to
proxy image requests to it. The following settings are supported:
| Attribute | Description |
| ------------------------ | ----------- |
| `asset_proxy_enabled` | (**If enabled, requires:** `asset_proxy_url`) Enable proxying of assets. |
| `asset_proxy_secret_key` | Shared secret with the asset proxy server. |
| `asset_proxy_url` | URL of the asset proxy server. |
| `asset_proxy_whitelist` | Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted. |
These can be set via the [Application setting API](../api/settings.md)
Note that a GitLab restart is required to apply any changes.
...@@ -1174,6 +1174,9 @@ def self.exposed_attributes ...@@ -1174,6 +1174,9 @@ def self.exposed_attributes
attributes.delete(:performance_bar_enabled) attributes.delete(:performance_bar_enabled)
attributes.delete(:allow_local_requests_from_hooks_and_services) attributes.delete(:allow_local_requests_from_hooks_and_services)
# let's not expose the secret key in a response
attributes.delete(:asset_proxy_secret_key)
attributes attributes
end end
......
...@@ -36,6 +36,10 @@ def filter_attributes_using_license(attrs) ...@@ -36,6 +36,10 @@ def filter_attributes_using_license(attrs)
given akismet_enabled: ->(val) { val } do given akismet_enabled: ->(val) { val } do
requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com' requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com'
end end
optional :asset_proxy_enabled, type: Boolean, desc: 'Enable proxying of assets'
optional :asset_proxy_url, type: String, desc: 'URL of the asset proxy server'
optional :asset_proxy_secret_key, type: String, desc: 'Shared secret with the asset proxy server'
optional :asset_proxy_whitelist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted.'
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)' optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts" optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group' optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group'
...@@ -123,7 +127,7 @@ def filter_attributes_using_license(attrs) ...@@ -123,7 +127,7 @@ def filter_attributes_using_license(attrs)
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.' optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins' optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins'
optional :local_markdown_version, type: Integer, desc: "Local markdown version, increase this value when any cached markdown should be invalidated" optional :local_markdown_version, type: Integer, desc: 'Local markdown version, increase this value when any cached markdown should be invalidated'
optional :allow_local_requests_from_hooks_and_services, type: Boolean, desc: 'Deprecated: Use :allow_local_requests_from_web_hooks_and_services instead. Allow requests to the local network from hooks and services.' # support legacy names, can be removed in v5 optional :allow_local_requests_from_hooks_and_services, type: Boolean, desc: 'Deprecated: Use :allow_local_requests_from_web_hooks_and_services instead. Allow requests to the local network from hooks and services.' # support legacy names, can be removed in v5
optional :snowplow_enabled, type: Grape::API::Boolean, desc: 'Enable Snowplow tracking' optional :snowplow_enabled, type: Grape::API::Boolean, desc: 'Enable Snowplow tracking'
given snowplow_enabled: ->(val) { val } do given snowplow_enabled: ->(val) { val } do
......
# frozen_string_literal: true
module API
module Validations
module Types
class CommaSeparatedToArray
def self.coerce
lambda do |value|
case value
when String
value.split(',').map(&:strip)
when Array
value.map { |v| v.to_s.split(',').map(&:strip) }.flatten
else
[]
end
end
end
end
end
end
end
# frozen_string_literal: true
module Banzai
module Filter
# Proxy's images/assets to another server. Reduces mixed content warnings
# as well as hiding the customer's IP address when requesting images.
# Copies the original img `src` to `data-canonical-src` then replaces the
# `src` with a new url to the proxy server.
class AssetProxyFilter < HTML::Pipeline::CamoFilter
def initialize(text, context = nil, result = nil)
super
end
def validate
needs(:asset_proxy, :asset_proxy_secret_key) if asset_proxy_enabled?
end
def asset_host_whitelisted?(host)
context[:asset_proxy_domain_regexp] ? context[:asset_proxy_domain_regexp].match?(host) : false
end
def self.transform_context(context)
context[:disable_asset_proxy] = !Gitlab.config.asset_proxy.enabled
unless context[:disable_asset_proxy]
context[:asset_proxy_enabled] = !context[:disable_asset_proxy]
context[:asset_proxy] = Gitlab.config.asset_proxy.url
context[:asset_proxy_secret_key] = Gitlab.config.asset_proxy.secret_key
context[:asset_proxy_domain_regexp] = Gitlab.config.asset_proxy.domain_regexp
end
context
end
# called during an initializer. Caching the values in Gitlab.config significantly increased
# performance, rather than querying Gitlab::CurrentSettings.current_application_settings
# over and over. However, this does mean that the Rails servers need to get restarted
# whenever the application settings are changed
def self.initialize_settings
application_settings = Gitlab::CurrentSettings.current_application_settings
Gitlab.config['asset_proxy'] ||= Settingslogic.new({})
if application_settings.respond_to?(:asset_proxy_enabled)
Gitlab.config.asset_proxy['enabled'] = application_settings.asset_proxy_enabled
Gitlab.config.asset_proxy['url'] = application_settings.asset_proxy_url
Gitlab.config.asset_proxy['secret_key'] = application_settings.asset_proxy_secret_key
Gitlab.config.asset_proxy['whitelist'] = application_settings.asset_proxy_whitelist || [Gitlab.config.gitlab.host]
Gitlab.config.asset_proxy['domain_regexp'] = compile_whitelist(Gitlab.config.asset_proxy.whitelist)
else
Gitlab.config.asset_proxy['enabled'] = ::ApplicationSetting.defaults[:asset_proxy_enabled]
end
end
def self.compile_whitelist(domain_list)
return if domain_list.empty?
escaped = domain_list.map { |domain| Regexp.escape(domain).gsub('\*', '.*?') }
Regexp.new("^(#{escaped.join('|')})$", Regexp::IGNORECASE)
end
end
end
end
...@@ -14,10 +14,10 @@ def call ...@@ -14,10 +14,10 @@ def call
# such as on `mailto:` links. Since we've been using it, do an # such as on `mailto:` links. Since we've been using it, do an
# initial parse for validity and then use Addressable # initial parse for validity and then use Addressable
# for IDN support, etc # for IDN support, etc
uri = uri_strict(node['href'].to_s) uri = uri_strict(node_src(node))
if uri if uri
node.set_attribute('href', uri.to_s) node.set_attribute(node_src_attribute(node), uri.to_s)
addressable_uri = addressable_uri(node['href']) addressable_uri = addressable_uri(node_src(node))
else else
addressable_uri = nil addressable_uri = nil
end end
...@@ -35,6 +35,16 @@ def call ...@@ -35,6 +35,16 @@ def call
private private
# if this is a link to a proxied image, then `src` is already the correct
# proxied url, so work with the `data-canonical-src`
def node_src_attribute(node)
node['data-canonical-src'] ? 'data-canonical-src' : 'href'
end
def node_src(node)
node[node_src_attribute(node)]
end
def uri_strict(href) def uri_strict(href)
URI.parse(href) URI.parse(href)
rescue URI::Error rescue URI::Error
...@@ -72,7 +82,7 @@ def punycode_autolink_node!(uri, node) ...@@ -72,7 +82,7 @@ def punycode_autolink_node!(uri, node)
return unless uri return unless uri
return unless context[:emailable_links] return unless context[:emailable_links]
unencoded_uri_str = Addressable::URI.unencode(node['href']) unencoded_uri_str = Addressable::URI.unencode(node_src(node))
if unencoded_uri_str == node.content && idn?(uri) if unencoded_uri_str == node.content && idn?(uri)
node.content = uri.normalize node.content = uri.normalize
......
...@@ -18,6 +18,9 @@ def call ...@@ -18,6 +18,9 @@ def call
rel: 'noopener noreferrer' rel: 'noopener noreferrer'
) )
# make sure the original non-proxied src carries over to the link
link['data-canonical-src'] = img['data-canonical-src'] if img['data-canonical-src']
link.children = img.clone link.children = img.clone
img.replace(link) img.replace(link)
......
...@@ -23,6 +23,14 @@ def query ...@@ -23,6 +23,14 @@ def query
"'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})" "'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})"
end end
if context[:asset_proxy_enabled].present?
src_query.concat(
UploaderHelper::VIDEO_EXT.map do |ext|
"'.#{ext}' = substring(@data-canonical-src, string-length(@data-canonical-src) - #{ext.size})"
end
)
end
"descendant-or-self::img[not(ancestor::a) and (#{src_query.join(' or ')})]" "descendant-or-self::img[not(ancestor::a) and (#{src_query.join(' or ')})]"
end end
end end
...@@ -48,6 +56,13 @@ def video_node(doc, element) ...@@ -48,6 +56,13 @@ def video_node(doc, element)
target: '_blank', target: '_blank',
rel: 'noopener noreferrer', rel: 'noopener noreferrer',
title: "Download '#{element['title'] || element['alt']}'") title: "Download '#{element['title'] || element['alt']}'")
# make sure the original non-proxied src carries over
if element['data-canonical-src']
video['data-canonical-src'] = element['data-canonical-src']
link['data-canonical-src'] = element['data-canonical-src']
end
download_paragraph = doc.document.create_element('p') download_paragraph = doc.document.create_element('p')
download_paragraph.children = link download_paragraph.children = link
......
...@@ -6,12 +6,17 @@ class AsciiDocPipeline < BasePipeline ...@@ -6,12 +6,17 @@ class AsciiDocPipeline < BasePipeline
def self.filters def self.filters
FilterArray[ FilterArray[
Filter::AsciiDocSanitizationFilter, Filter::AsciiDocSanitizationFilter,
Filter::AssetProxyFilter,
Filter::SyntaxHighlightFilter, Filter::SyntaxHighlightFilter,
Filter::ExternalLinkFilter, Filter::ExternalLinkFilter,
Filter::PlantumlFilter, Filter::PlantumlFilter,
Filter::AsciiDocPostProcessingFilter Filter::AsciiDocPostProcessingFilter
] ]
end end
def self.transform_context(context)
Filter::AssetProxyFilter.transform_context(context)
end
end end
end end
end end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment