diff --git a/config/locales/en.yml b/config/locales/en.yml index 63344b1fc29..0dfcf46bfcc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2197,17 +2197,14 @@ en: before: "must be before %{date}." before_or_equal_to: "must be before or equal to %{date}." blank: "can't be blank." - not_before_start_date: "must not be before the start date." - overlapping_range: "overlaps with an existing non-working day range." blank_nested: "needs to have the property '%{property}' set." cannot_delete_mapping: "is required. Cannot be deleted." - is_for_all_cannot_modify: "is for all projects and can therefore not be modified." cant_link_a_work_package_with_a_descendant: "A work package cannot be linked to one of its subtasks." circular_dependency: "This relation would create a circular dependency." confirmation: "doesn't match %{attribute}." could_not_be_copied: "%{dependency} could not be (fully) copied." + datetime_must_be_in_future: "must be in the future." does_not_exist: "does not exist." - user_already_in_department: "User %{user_id} is already a member of department %{department_id}." error_enterprise_only: "%{action} is only available in the OpenProject Enterprise edition." error_unauthorized: "may not be accessed." error_readonly: "was attempted to be written but is not writable." @@ -2228,36 +2225,38 @@ en: greater_than_or_equal_to: "must be greater than or equal to %{count}." greater_than_or_equal_to_start_date: "must be greater than or equal to the start date." greater_than_start_date: "must be greater than the start date." + hexcode_invalid: "is not a valid 6-digit hexadecimal color code." inclusion: "is not set to one of the allowed values." inclusion_nested: "is not set to one of the allowed values at path '%{path}'." invalid: "is invalid." invalid_uri: "must be a valid URI." invalid_url: "is not a valid URL." invalid_url_scheme: "is not a supported protocol (allowed: %{allowed_schemes})." + is_for_all_cannot_modify: "is for all projects and can therefore not be modified." less_than_or_equal_to: "must be less than or equal to %{count}." not_available: "is not available due to a system configuration." + not_before_start_date: "must not be before the start date." not_deletable: "cannot be deleted." not_editable: "cannot be edited because it is already in effect." not_current_user: "is not the current user." - system_wide_non_working_day_exists: "conflicts with an existing system-wide non-working day for this date." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." not_a_number: "is not a number." not_allowed: "is invalid because of missing permissions." - host_not_allowed: "is not an allowed host." not_json: "is not parseable as JSON." not_json_object: "is not a JSON object." not_an_integer: "is not an integer." not_an_iso_date: "is not a valid date. Required format: YYYY-MM-DD." not_same_project: "doesn't belong to the same project." - datetime_must_be_in_future: "must be in the future." odd: "must be odd." + overlapping_range: "overlaps with an existing non-working day range." regex_match_failed: "does not match the regular expression %{expression}." regex_invalid: "could not be validated with the associated regular expression." regex_list_invalid: "Lines %{invalid_lines} could not be parsed as regular expression." - hexcode_invalid: "is not a valid 6-digit hexadecimal color code." smaller_than_or_equal_to_max_length: "must be smaller than or equal to maximum length." + ssrf_filtered: "violates the SSRF policy of this OpenProject instance." + system_wide_non_working_day_exists: "conflicts with an existing system-wide non-working day for this date." taken: "has already been taken." too_long: "is too long (maximum is %{count} characters)." too_short: "is too short (minimum is %{count} characters)." @@ -2269,6 +2268,7 @@ en: unremovable: "cannot be removed." url_not_secure_context: > is not providing a "Secure Context". Either use HTTPS or a loopback address, such as localhost. + user_already_in_department: "User %{user_id} is already a member of department %{department_id}." wrong_length: "is the wrong length (should be %{count} characters)." models: group: diff --git a/docs/release-notes/17-6-0/README.md b/docs/release-notes/17-6-0/README.md index 7fe7625d46d..c08b6b3ce9e 100644 --- a/docs/release-notes/17-6-0/README.md +++ b/docs/release-notes/17-6-0/README.md @@ -19,7 +19,20 @@ In these Release Notes, we will give an overview of important feature changes. A ## Important updates and breaking changes - +### Integrations (e.g. Nextcloud and XWiki) respect global SSRF filters + +To increase the security of OpenProject installations, we've added protections against server-side request forgery in previous releases +of OpenProject. These prevent OpenProject from making network requests into private IP address space. + +Starting with OpenProject 17.6, these protections expand into the code that's responsible for web requests of storage and wiki integrations as well. +This means if you have a Nextcloud instance or an XWiki instance reachable via a private (i.e. not publicly routable) IP address, you need to +add it to the SSRF allowlist to be able to keep the integration working. This is usually achieved by defining the following environment variable: + +``` +OPENPROJECT_SSRF_PROTECTION_IP_ALLOWLIST=2001:db8:100::/48 +``` + +The list accepts one or multiple IP addresses or ranges (in CIDR notation) that shall be exempt from SSRF filtering. diff --git a/modules/openid_connect/app/services/openid_connect/providers/update_service.rb b/modules/openid_connect/app/services/openid_connect/providers/update_service.rb index 1c4ab6167d4..e29d05de295 100644 --- a/modules/openid_connect/app/services/openid_connect/providers/update_service.rb +++ b/modules/openid_connect/app/services/openid_connect/providers/update_service.rb @@ -114,7 +114,7 @@ module OpenIDConnect if host.present? && OpenProject::SsrfProtection.safe_ip?(host) true else - call.errors.add(:metadata_url, :host_not_allowed) + call.errors.add(:metadata_url, :ssrf_filtered) call.success = false false end diff --git a/modules/openid_connect/spec/services/openid_connect/providers/update_service_spec.rb b/modules/openid_connect/spec/services/openid_connect/providers/update_service_spec.rb index 8ce703e4ae3..7be98aad37f 100644 --- a/modules/openid_connect/spec/services/openid_connect/providers/update_service_spec.rb +++ b/modules/openid_connect/spec/services/openid_connect/providers/update_service_spec.rb @@ -92,7 +92,7 @@ RSpec.describe OpenIDConnect::Providers::UpdateService, type: :model do result = service_call expect(result).not_to be_success - expect(result.errors[:metadata_url]).to include("is not an allowed host.") + expect(result.errors[:metadata_url]).to include("violates the SSRF policy of this OpenProject instance.") expect(httpx_session).not_to have_received(:get) end end diff --git a/modules/storages/app/validator/nextcloud_compatible_host_validator.rb b/modules/storages/app/validator/nextcloud_compatible_host_validator.rb index aabdcbcf612..ef4760e1a38 100644 --- a/modules/storages/app/validator/nextcloud_compatible_host_validator.rb +++ b/modules/storages/app/validator/nextcloud_compatible_host_validator.rb @@ -50,7 +50,7 @@ class NextcloudCompatibleHostValidator < ActiveModel::EachValidator return false if host.blank? return true if OpenProject::SsrfProtection.safe_ip?(host) - contract.errors.add(attribute, :host_not_allowed) + contract.errors.add(attribute, :ssrf_filtered) false rescue URI::InvalidURIError false diff --git a/modules/storages/spec/contracts/storages/storages/shared_contract_examples.rb b/modules/storages/spec/contracts/storages/storages/shared_contract_examples.rb index 0b1f83ae507..d33470a736b 100644 --- a/modules/storages/spec/contracts/storages/storages/shared_contract_examples.rb +++ b/modules/storages/spec/contracts/storages/storages/shared_contract_examples.rb @@ -275,7 +275,7 @@ RSpec.shared_examples_for "nextcloud storage contract", :storage_server_helpers, context "when host is localhost" do let(:storage_host) { "http://localhost:1234" } - include_examples "contract is invalid", host: :host_not_allowed + include_examples "contract is invalid", host: :ssrf_filtered it "does not perform metadata discovery requests" do contract.validate @@ -288,7 +288,7 @@ RSpec.shared_examples_for "nextcloud storage contract", :storage_server_helpers, context "when host uses https protocol" do let(:storage_host) { "https://172.16.193.146" } - include_examples "contract is invalid", host: :host_not_allowed + include_examples "contract is invalid", host: :ssrf_filtered end end