mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-18 21:02:41 +01:00
Add cluster.addresses
and require membership before accepting cluster authentication tokens
Summary: Ref T2783. Ref T6706. - Add `cluster.addresses`. This is a whitelist of CIDR blocks which define cluster hosts. - When we recieve a request that has a cluster-based authentication token, require the cluster to be configured and require the remote address to be a cluster member before we accept it. - This provides a general layer of security for these mechanisms. - In particular, it means they do not work by default on unconfigured hosts. - When cluster addresses are configured, and we receive a request //to// an address not on the list, reject it. - This provides a general layer of security for getting the Ops side of cluster configuration correct. - If cluster nodes have public IPs and are listening on them, we'll reject requests. - Basically, this means that any requests which bypass the LB get rejected. Test Plan: - With addresses not configured, tried to make requests; rejected for using a cluster auth mechanism. - With addresses configred wrong, tried to make requests; rejected for sending from (or to) an address outside of the cluster. - With addresses configured correctly, made valid requests. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6706, T2783 Differential Revision: https://secure.phabricator.com/D11159
This commit is contained in:
parent
c84b9d408c
commit
fa7bb8ff7a
6 changed files with 157 additions and 2 deletions
|
@ -1440,6 +1440,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorChatLogDAO' => 'applications/chatlog/storage/PhabricatorChatLogDAO.php',
|
||||
'PhabricatorChatLogEvent' => 'applications/chatlog/storage/PhabricatorChatLogEvent.php',
|
||||
'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php',
|
||||
'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php',
|
||||
'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php',
|
||||
'PhabricatorCommitCustomField' => 'applications/repository/customfield/PhabricatorCommitCustomField.php',
|
||||
'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php',
|
||||
|
@ -4593,6 +4594,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorCommitBranchesField' => 'PhabricatorCommitCustomField',
|
||||
'PhabricatorCommitCustomField' => 'PhabricatorCustomField',
|
||||
'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
|
|
|
@ -275,6 +275,50 @@ abstract class AphrontApplicationConfiguration {
|
|||
final public function buildController() {
|
||||
$request = $this->getRequest();
|
||||
|
||||
// If we're configured to operate in cluster mode, reject requests which
|
||||
// were not received on a cluster interface.
|
||||
//
|
||||
// For example, a host may have an internal address like "170.0.0.1", and
|
||||
// also have a public address like "51.23.95.16". Assuming the cluster
|
||||
// is configured on a range like "170.0.0.0/16", we want to reject the
|
||||
// requests received on the public interface.
|
||||
//
|
||||
// Ideally, nodes in a cluster should only be listening on internal
|
||||
// interfaces, but they may be configured in such a way that they also
|
||||
// listen on external interfaces, since this is easy to forget about or
|
||||
// get wrong. As a broad security measure, reject requests received on any
|
||||
// interfaces which aren't on the whitelist.
|
||||
|
||||
$cluster_addresses = PhabricatorEnv::getEnvConfig('cluster.addresses');
|
||||
if ($cluster_addresses) {
|
||||
$server_addr = idx($_SERVER, 'SERVER_ADDR');
|
||||
if (!$server_addr) {
|
||||
if (php_sapi_name() == 'cli') {
|
||||
// This is a command line script (probably something like a unit
|
||||
// test) so it's fine that we don't have SERVER_ADDR defined.
|
||||
} else {
|
||||
throw new AphrontUsageException(
|
||||
pht('No SERVER_ADDR'),
|
||||
pht(
|
||||
'Phabricator is configured to operate in cluster mode, but '.
|
||||
'SERVER_ADDR is not defined in the request context. Your '.
|
||||
'webserver configuration needs to forward SERVER_ADDR to '.
|
||||
'PHP so Phabricator can reject requests received on '.
|
||||
'external interfaces.'));
|
||||
}
|
||||
} else {
|
||||
if (!PhabricatorEnv::isClusterAddress($server_addr)) {
|
||||
throw new AphrontUsageException(
|
||||
pht('External Interface'),
|
||||
pht(
|
||||
'Phabricator is configured in cluster mode and the address '.
|
||||
'this request was received on ("%s") is not whitelisted as '.
|
||||
'a cluster address.',
|
||||
$server_addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PhabricatorEnv::getEnvConfig('security.require-https')) {
|
||||
if (!$request->isHTTPS()) {
|
||||
$https_uri = $request->getRequestURI();
|
||||
|
|
|
@ -274,8 +274,16 @@ final class PhabricatorConduitAPIController
|
|||
);
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
pht('Not Implemented: Would authenticate Almanac device.'));
|
||||
if (!PhabricatorEnv::isClusterRemoteAddress()) {
|
||||
return array(
|
||||
'ERR-INVALID-AUTH',
|
||||
pht(
|
||||
'This request originates from outside of the Phabricator '.
|
||||
'cluster address range. Requests signed with trusted '.
|
||||
'device keys must originate from within the cluster.'),);
|
||||
}
|
||||
|
||||
$user = PhabricatorUser::getOmnipotentUser();
|
||||
}
|
||||
|
||||
return $this->validateAuthenticatedUser(
|
||||
|
@ -361,6 +369,19 @@ final class PhabricatorConduitAPIController
|
|||
}
|
||||
}
|
||||
|
||||
// If this is a "clr-" token, Phabricator must be configured in cluster
|
||||
// mode and the remote address must be a cluster node.
|
||||
if ($token->getTokenType() == PhabricatorConduitToken::TYPE_CLUSTER) {
|
||||
if (!PhabricatorEnv::isClusterRemoteAddress()) {
|
||||
return array(
|
||||
'ERR-INVALID-AUTH',
|
||||
pht(
|
||||
'This request originates from outside of the Phabricator '.
|
||||
'cluster address range. Requests signed with cluster API '.
|
||||
'tokens must originate from within the cluster.'),);
|
||||
}
|
||||
}
|
||||
|
||||
$user = $token->getObject();
|
||||
if (!($user instanceof PhabricatorUser)) {
|
||||
return array(
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorClusterConfigOptions
|
||||
extends PhabricatorApplicationConfigOptions {
|
||||
|
||||
public function getName() {
|
||||
return pht('Cluster Setup');
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return pht('Configure Phabricator to run on a cluster of hosts.');
|
||||
}
|
||||
|
||||
public function getOptions() {
|
||||
return array(
|
||||
$this->newOption('cluster.addresses', 'list<string>', array())
|
||||
->setLocked(true)
|
||||
->setSummary(pht('Address ranges of cluster hosts.'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'To allow Phabricator nodes to communicate with other nodes '.
|
||||
'in the cluster, provide an address whitelist of hosts that '.
|
||||
'are part of the cluster.'.
|
||||
"\n\n".
|
||||
'Hosts on this whitelist are permitted to use special cluster '.
|
||||
'mechanisms to authenticate requests. By default, these '.
|
||||
'mechanisms are disabled.'.
|
||||
"\n\n".
|
||||
'Define a list of CIDR blocks which whitelist all hosts in the '.
|
||||
'cluster. See the examples below for details.',
|
||||
"\n\n".
|
||||
'When cluster addresses are defined, Phabricator hosts will also '.
|
||||
'reject requests to interfaces which are not whitelisted.'))
|
||||
->addExample(
|
||||
array(
|
||||
'23.24.25.80/32',
|
||||
'23.24.25.81/32',
|
||||
),
|
||||
pht('Whitelist Specific Addresses'))
|
||||
->addExample(
|
||||
array(
|
||||
'1.2.3.0/24',
|
||||
),
|
||||
pht('Whitelist 1.2.3.*'))
|
||||
->addExample(
|
||||
array(
|
||||
'1.2.0.0/16',
|
||||
),
|
||||
pht('Whitelist 1.2.*.*'))
|
||||
->addExample(
|
||||
array(
|
||||
'0.0.0.0/0',
|
||||
),
|
||||
pht('Allow Any Host (Insecure!)')),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -82,6 +82,10 @@ final class PhabricatorUser
|
|||
* @return bool True if this is a standard, usable account.
|
||||
*/
|
||||
public function isUserActivated() {
|
||||
if ($this->isOmnipotent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->getIsDisabled()) {
|
||||
return false;
|
||||
}
|
||||
|
|
26
src/infrastructure/env/PhabricatorEnv.php
vendored
26
src/infrastructure/env/PhabricatorEnv.php
vendored
|
@ -528,6 +528,32 @@ final class PhabricatorEnv {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static function isClusterRemoteAddress() {
|
||||
$address = idx($_SERVER, 'REMOTE_ADDR');
|
||||
if (!$address) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to test remote address against cluster whitelist: '.
|
||||
'REMOTE_ADDR is not defined.'));
|
||||
}
|
||||
|
||||
return self::isClusterAddress($address);
|
||||
}
|
||||
|
||||
public static function isClusterAddress($address) {
|
||||
$cluster_addresses = PhabricatorEnv::getEnvConfig('cluster.addresses');
|
||||
if (!$cluster_addresses) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Phabricator is not configured to serve cluster requests. '.
|
||||
'Set `cluster.addresses` in the configuration to whitelist '.
|
||||
'cluster hosts before sending requests that use a cluster '.
|
||||
'authentication mechanism.'));
|
||||
}
|
||||
|
||||
return PhutilCIDRList::newList($cluster_addresses)
|
||||
->containsAddress($address);
|
||||
}
|
||||
|
||||
/* -( Internals )---------------------------------------------------------- */
|
||||
|
||||
|
|
Loading…
Reference in a new issue