@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+157 -2
+2
src/__phutil_library_map__.php
··· 1440 1440 'PhabricatorChatLogDAO' => 'applications/chatlog/storage/PhabricatorChatLogDAO.php', 1441 1441 'PhabricatorChatLogEvent' => 'applications/chatlog/storage/PhabricatorChatLogEvent.php', 1442 1442 'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php', 1443 + 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', 1443 1444 'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php', 1444 1445 'PhabricatorCommitCustomField' => 'applications/repository/customfield/PhabricatorCommitCustomField.php', 1445 1446 'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php', ··· 4593 4594 'PhabricatorPolicyInterface', 4594 4595 ), 4595 4596 'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4597 + 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', 4596 4598 'PhabricatorCommitBranchesField' => 'PhabricatorCommitCustomField', 4597 4599 'PhabricatorCommitCustomField' => 'PhabricatorCustomField', 4598 4600 'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine',
+44
src/aphront/configuration/AphrontApplicationConfiguration.php
··· 275 275 final public function buildController() { 276 276 $request = $this->getRequest(); 277 277 278 + // If we're configured to operate in cluster mode, reject requests which 279 + // were not received on a cluster interface. 280 + // 281 + // For example, a host may have an internal address like "170.0.0.1", and 282 + // also have a public address like "51.23.95.16". Assuming the cluster 283 + // is configured on a range like "170.0.0.0/16", we want to reject the 284 + // requests received on the public interface. 285 + // 286 + // Ideally, nodes in a cluster should only be listening on internal 287 + // interfaces, but they may be configured in such a way that they also 288 + // listen on external interfaces, since this is easy to forget about or 289 + // get wrong. As a broad security measure, reject requests received on any 290 + // interfaces which aren't on the whitelist. 291 + 292 + $cluster_addresses = PhabricatorEnv::getEnvConfig('cluster.addresses'); 293 + if ($cluster_addresses) { 294 + $server_addr = idx($_SERVER, 'SERVER_ADDR'); 295 + if (!$server_addr) { 296 + if (php_sapi_name() == 'cli') { 297 + // This is a command line script (probably something like a unit 298 + // test) so it's fine that we don't have SERVER_ADDR defined. 299 + } else { 300 + throw new AphrontUsageException( 301 + pht('No SERVER_ADDR'), 302 + pht( 303 + 'Phabricator is configured to operate in cluster mode, but '. 304 + 'SERVER_ADDR is not defined in the request context. Your '. 305 + 'webserver configuration needs to forward SERVER_ADDR to '. 306 + 'PHP so Phabricator can reject requests received on '. 307 + 'external interfaces.')); 308 + } 309 + } else { 310 + if (!PhabricatorEnv::isClusterAddress($server_addr)) { 311 + throw new AphrontUsageException( 312 + pht('External Interface'), 313 + pht( 314 + 'Phabricator is configured in cluster mode and the address '. 315 + 'this request was received on ("%s") is not whitelisted as '. 316 + 'a cluster address.', 317 + $server_addr)); 318 + } 319 + } 320 + } 321 + 278 322 if (PhabricatorEnv::getEnvConfig('security.require-https')) { 279 323 if (!$request->isHTTPS()) { 280 324 $https_uri = $request->getRequestURI();
+23 -2
src/applications/conduit/controller/PhabricatorConduitAPIController.php
··· 274 274 ); 275 275 } 276 276 277 - throw new Exception( 278 - pht('Not Implemented: Would authenticate Almanac device.')); 277 + if (!PhabricatorEnv::isClusterRemoteAddress()) { 278 + return array( 279 + 'ERR-INVALID-AUTH', 280 + pht( 281 + 'This request originates from outside of the Phabricator '. 282 + 'cluster address range. Requests signed with trusted '. 283 + 'device keys must originate from within the cluster.'),); 284 + } 285 + 286 + $user = PhabricatorUser::getOmnipotentUser(); 279 287 } 280 288 281 289 return $this->validateAuthenticatedUser( ··· 358 366 $token->setExpires(null); 359 367 $token->save(); 360 368 unset($unguarded); 369 + } 370 + } 371 + 372 + // If this is a "clr-" token, Phabricator must be configured in cluster 373 + // mode and the remote address must be a cluster node. 374 + if ($token->getTokenType() == PhabricatorConduitToken::TYPE_CLUSTER) { 375 + if (!PhabricatorEnv::isClusterRemoteAddress()) { 376 + return array( 377 + 'ERR-INVALID-AUTH', 378 + pht( 379 + 'This request originates from outside of the Phabricator '. 380 + 'cluster address range. Requests signed with cluster API '. 381 + 'tokens must originate from within the cluster.'),); 361 382 } 362 383 } 363 384
+58
src/applications/config/option/PhabricatorClusterConfigOptions.php
··· 1 + <?php 2 + 3 + final class PhabricatorClusterConfigOptions 4 + extends PhabricatorApplicationConfigOptions { 5 + 6 + public function getName() { 7 + return pht('Cluster Setup'); 8 + } 9 + 10 + public function getDescription() { 11 + return pht('Configure Phabricator to run on a cluster of hosts.'); 12 + } 13 + 14 + public function getOptions() { 15 + return array( 16 + $this->newOption('cluster.addresses', 'list<string>', array()) 17 + ->setLocked(true) 18 + ->setSummary(pht('Address ranges of cluster hosts.')) 19 + ->setDescription( 20 + pht( 21 + 'To allow Phabricator nodes to communicate with other nodes '. 22 + 'in the cluster, provide an address whitelist of hosts that '. 23 + 'are part of the cluster.'. 24 + "\n\n". 25 + 'Hosts on this whitelist are permitted to use special cluster '. 26 + 'mechanisms to authenticate requests. By default, these '. 27 + 'mechanisms are disabled.'. 28 + "\n\n". 29 + 'Define a list of CIDR blocks which whitelist all hosts in the '. 30 + 'cluster. See the examples below for details.', 31 + "\n\n". 32 + 'When cluster addresses are defined, Phabricator hosts will also '. 33 + 'reject requests to interfaces which are not whitelisted.')) 34 + ->addExample( 35 + array( 36 + '23.24.25.80/32', 37 + '23.24.25.81/32', 38 + ), 39 + pht('Whitelist Specific Addresses')) 40 + ->addExample( 41 + array( 42 + '1.2.3.0/24', 43 + ), 44 + pht('Whitelist 1.2.3.*')) 45 + ->addExample( 46 + array( 47 + '1.2.0.0/16', 48 + ), 49 + pht('Whitelist 1.2.*.*')) 50 + ->addExample( 51 + array( 52 + '0.0.0.0/0', 53 + ), 54 + pht('Allow Any Host (Insecure!)')), 55 + ); 56 + } 57 + 58 + }
+4
src/applications/people/storage/PhabricatorUser.php
··· 82 82 * @return bool True if this is a standard, usable account. 83 83 */ 84 84 public function isUserActivated() { 85 + if ($this->isOmnipotent()) { 86 + return true; 87 + } 88 + 85 89 if ($this->getIsDisabled()) { 86 90 return false; 87 91 }
+26
src/infrastructure/env/PhabricatorEnv.php
··· 528 528 return true; 529 529 } 530 530 531 + public static function isClusterRemoteAddress() { 532 + $address = idx($_SERVER, 'REMOTE_ADDR'); 533 + if (!$address) { 534 + throw new Exception( 535 + pht( 536 + 'Unable to test remote address against cluster whitelist: '. 537 + 'REMOTE_ADDR is not defined.')); 538 + } 539 + 540 + return self::isClusterAddress($address); 541 + } 542 + 543 + public static function isClusterAddress($address) { 544 + $cluster_addresses = PhabricatorEnv::getEnvConfig('cluster.addresses'); 545 + if (!$cluster_addresses) { 546 + throw new Exception( 547 + pht( 548 + 'Phabricator is not configured to serve cluster requests. '. 549 + 'Set `cluster.addresses` in the configuration to whitelist '. 550 + 'cluster hosts before sending requests that use a cluster '. 551 + 'authentication mechanism.')); 552 + } 553 + 554 + return PhutilCIDRList::newList($cluster_addresses) 555 + ->containsAddress($address); 556 + } 531 557 532 558 /* -( Internals )---------------------------------------------------------- */ 533 559