mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-19 13:22:42 +01:00
Document how to register cluster devices with Almanac
Summary: Ref T4292. This is a required step in configuring a cluster: document and explain it. Previously `bin/almanac register` could //also// add and trust keys. I've removed this capability since I think it's needless and complicated. If there's some real use for it eventually, we could add a `bin/almanac add-key` or whatever. The workflow is simpler and has better guard rails that point you in the correct direction now. Test Plan: - Read documentation. - Ran `bin/almanac` with various good/bad flags. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4292 Differential Revision: https://secure.phabricator.com/D15795
This commit is contained in:
parent
550a82d438
commit
2c870bad86
2 changed files with 298 additions and 63 deletions
|
@ -19,13 +19,6 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
'param' => 'key',
|
'param' => 'key',
|
||||||
'help' => pht('Path to a private key for the host.'),
|
'help' => pht('Path to a private key for the host.'),
|
||||||
),
|
),
|
||||||
array(
|
|
||||||
'name' => 'allow-key-reuse',
|
|
||||||
'help' => pht(
|
|
||||||
'Register even if another host is already registered with this '.
|
|
||||||
'keypair. This is an advanced featuer which allows a pool of '.
|
|
||||||
'devices to share credentials.'),
|
|
||||||
),
|
|
||||||
array(
|
array(
|
||||||
'name' => 'identify-as',
|
'name' => 'identify-as',
|
||||||
'param' => 'name',
|
'param' => 'name',
|
||||||
|
@ -36,13 +29,13 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
array(
|
array(
|
||||||
'name' => 'force',
|
'name' => 'force',
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
'Register this host even if keys already exist.'),
|
'Register this host even if keys already exist on disk.'),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(PhutilArgumentParser $args) {
|
public function execute(PhutilArgumentParser $args) {
|
||||||
$console = PhutilConsole::getConsole();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$device_name = $args->getArg('device');
|
$device_name = $args->getArg('device');
|
||||||
if (!strlen($device_name)) {
|
if (!strlen($device_name)) {
|
||||||
|
@ -51,7 +44,7 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
}
|
}
|
||||||
|
|
||||||
$device = id(new AlmanacDeviceQuery())
|
$device = id(new AlmanacDeviceQuery())
|
||||||
->setViewer($this->getViewer())
|
->setViewer($viewer)
|
||||||
->withNames(array($device_name))
|
->withNames(array($device_name))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
if (!$device) {
|
if (!$device) {
|
||||||
|
@ -59,6 +52,23 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
pht('No such device "%s" exists!', $device_name));
|
pht('No such device "%s" exists!', $device_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$identify_as = $args->getArg('identify-as');
|
||||||
|
|
||||||
|
$raw_device = $device_name;
|
||||||
|
if (strlen($identify_as)) {
|
||||||
|
$raw_device = $identify_as;
|
||||||
|
}
|
||||||
|
|
||||||
|
$identity_device = id(new AlmanacDeviceQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withNames(array($raw_device))
|
||||||
|
->executeOne();
|
||||||
|
if (!$identity_device) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'No such device "%s" exists!', $raw_device));
|
||||||
|
}
|
||||||
|
|
||||||
$private_key_path = $args->getArg('private-key');
|
$private_key_path = $args->getArg('private-key');
|
||||||
if (!strlen($private_key_path)) {
|
if (!strlen($private_key_path)) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
|
@ -67,7 +77,7 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
|
|
||||||
if (!Filesystem::pathExists($private_key_path)) {
|
if (!Filesystem::pathExists($private_key_path)) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht('Private key "%s" does not exist!', $private_key_path));
|
pht('No private key exists at path "%s"!', $private_key_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
$raw_private_key = Filesystem::readFile($private_key_path);
|
$raw_private_key = Filesystem::readFile($private_key_path);
|
||||||
|
@ -85,8 +95,8 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
if ($err) {
|
if ($err) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht(
|
pht(
|
||||||
'Unable to change ownership of a file to daemon user "%s". Run '.
|
'Unable to change ownership of an identity file to daemon user '.
|
||||||
'this command as %s or root.',
|
'"%s". Run this command as %s or root.',
|
||||||
$phd_user,
|
$phd_user,
|
||||||
$phd_user));
|
$phd_user));
|
||||||
}
|
}
|
||||||
|
@ -133,43 +143,39 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
->withKeys(array($key_object))
|
->withKeys(array($key_object))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
|
|
||||||
if ($public_key) {
|
if (!$public_key) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'The public key corresponding to the given private key is not '.
|
||||||
|
'yet known to Phabricator. Associate the public key with an '.
|
||||||
|
'Almanac device in the web interface before registering hosts '.
|
||||||
|
'with it.'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($public_key->getObjectPHID() !== $device->getPHID()) {
|
if ($public_key->getObjectPHID() !== $device->getPHID()) {
|
||||||
|
$public_phid = $public_key->getObjectPHID();
|
||||||
|
$public_handles = $viewer->loadHandles(array($public_phid));
|
||||||
|
$public_handle = $public_handles[$public_phid];
|
||||||
|
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht(
|
pht(
|
||||||
'The public key corresponding to the given private key is '.
|
'The public key corresponding to the given private key is already '.
|
||||||
'already associated with an object other than the specified '.
|
'associated with an object ("%s") other than the specified '.
|
||||||
'device. You can not use a single private key to identify '.
|
'device ("%s"). You can not use a single private key to identify '.
|
||||||
'multiple devices or users.'));
|
'multiple devices or users.',
|
||||||
} else if (!$public_key->getIsTrusted()) {
|
$public_handle->getFullName(),
|
||||||
throw new PhutilArgumentUsageException(
|
$device->getName()));
|
||||||
pht(
|
|
||||||
'The public key corresponding to the given private key is '.
|
|
||||||
'already associated with the device, but is not trusted. '.
|
|
||||||
'Registering this key would trust the other entities which '.
|
|
||||||
'hold it. Use a unique key, or explicitly enable trust for the '.
|
|
||||||
'current key.'));
|
|
||||||
} else if (!$args->getArg('allow-key-reuse')) {
|
|
||||||
throw new PhutilArgumentUsageException(
|
|
||||||
pht(
|
|
||||||
'The public key corresponding to the given private key is '.
|
|
||||||
'already associated with the device. If you do not want to '.
|
|
||||||
'use a unique key, use --allow-key-reuse to permit '.
|
|
||||||
'reassociation.'));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$public_key = id(new PhabricatorAuthSSHKey())
|
|
||||||
->setObjectPHID($device->getPHID())
|
|
||||||
->attachObject($device)
|
|
||||||
->setName($device->getSSHKeyDefaultName())
|
|
||||||
->setKeyType($key_object->getType())
|
|
||||||
->setKeyBody($key_object->getBody())
|
|
||||||
->setKeyComment(pht('Registered'))
|
|
||||||
->setIsTrusted(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$public_key->getIsTrusted()) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'The public key corresponding to the given private key is '.
|
||||||
|
'properly associated with the device, but is not yet trusted. '.
|
||||||
|
'Trust this key before registering devices with it.'));
|
||||||
|
}
|
||||||
|
|
||||||
$console->writeOut(
|
echo tsprintf(
|
||||||
"%s\n",
|
"%s\n",
|
||||||
pht('Installing public key...'));
|
pht('Installing public key...'));
|
||||||
|
|
||||||
|
@ -179,18 +185,12 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
Filesystem::writeFile($tmp_public, $raw_public_key);
|
Filesystem::writeFile($tmp_public, $raw_public_key);
|
||||||
execx('mv -f %s %s', $tmp_public, $stored_public_path);
|
execx('mv -f %s %s', $tmp_public, $stored_public_path);
|
||||||
|
|
||||||
$console->writeOut(
|
echo tsprintf(
|
||||||
"%s\n",
|
"%s\n",
|
||||||
pht('Installing private key...'));
|
pht('Installing private key...'));
|
||||||
execx('mv -f %s %s', $tmp_private, $stored_private_path);
|
execx('mv -f %s %s', $tmp_private, $stored_private_path);
|
||||||
|
|
||||||
$raw_device = $device_name;
|
echo tsprintf(
|
||||||
$identify_as = $args->getArg('identify-as');
|
|
||||||
if (strlen($identify_as)) {
|
|
||||||
$raw_device = $identify_as;
|
|
||||||
}
|
|
||||||
|
|
||||||
$console->writeOut(
|
|
||||||
"%s\n",
|
"%s\n",
|
||||||
pht('Installing device %s...', $raw_device));
|
pht('Installing device %s...', $raw_device));
|
||||||
|
|
||||||
|
@ -202,14 +202,7 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
Filesystem::writeFile($tmp_device, $raw_device);
|
Filesystem::writeFile($tmp_device, $raw_device);
|
||||||
execx('mv -f %s %s', $tmp_device, $stored_device_path);
|
execx('mv -f %s %s', $tmp_device, $stored_device_path);
|
||||||
|
|
||||||
if (!$public_key->getID()) {
|
echo tsprintf(
|
||||||
$console->writeOut(
|
|
||||||
"%s\n",
|
|
||||||
pht('Registering device key...'));
|
|
||||||
$public_key->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
$console->writeOut(
|
|
||||||
"**<bg:green> %s </bg>** %s\n",
|
"**<bg:green> %s </bg>** %s\n",
|
||||||
pht('HOST REGISTERED'),
|
pht('HOST REGISTERED'),
|
||||||
pht(
|
pht(
|
||||||
|
|
242
src/docs/user/cluster/cluster_devices.diviner
Normal file
242
src/docs/user/cluster/cluster_devices.diviner
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
@title Cluster: Devices
|
||||||
|
@group cluster
|
||||||
|
|
||||||
|
Guide to configuring hosts to act as cluster devices.
|
||||||
|
|
||||||
|
Cluster Context
|
||||||
|
===============
|
||||||
|
|
||||||
|
This document describes a step in configuring Phabricator to run on
|
||||||
|
multiple hosts in a cluster configuration. This is an advanced feature. For
|
||||||
|
more information on clustering, see @{article:Clustering Introduction}.
|
||||||
|
|
||||||
|
In this context, device configuration is mostly relevant to configuring
|
||||||
|
repository services in a cluster. You can find more details about this in
|
||||||
|
@{article:Cluster: Repositories}.
|
||||||
|
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
Some cluster services need to be able to authenticate themselves and interact
|
||||||
|
with other services. For example, two repository hosts holding copies of the
|
||||||
|
same repository must be able to fetch changes from one another, even if the
|
||||||
|
repository is private.
|
||||||
|
|
||||||
|
Within a cluster, devices authenticate using SSH keys. Some operations happen
|
||||||
|
over SSH (using keys in a normal way, as you would when running `ssh` from the
|
||||||
|
command line), while others happen over HTTP (using SSH keys to sign requests).
|
||||||
|
|
||||||
|
Before hosts can authenticate to one another, you need to configure the
|
||||||
|
credentials so other devices know the keys can be trusted. Beyond establishing
|
||||||
|
trust, this configuration will establish //device identity//, so each host
|
||||||
|
knows which device it is explicitly.
|
||||||
|
|
||||||
|
Today, this is primarily necessary when configuring repository clusters.
|
||||||
|
|
||||||
|
|
||||||
|
Using Almanac
|
||||||
|
=============
|
||||||
|
|
||||||
|
The tool Phabricator uses to manage cluster devices is the **Almanac**
|
||||||
|
application, and most configuration will occur through the application's web
|
||||||
|
UI. If you are not familiar with it, see @{article:Almanac User Guide} first.
|
||||||
|
This document assumes you are familiar with Almanac concepts.
|
||||||
|
|
||||||
|
|
||||||
|
What Lies Ahead
|
||||||
|
===============
|
||||||
|
|
||||||
|
Here's a brief overview of the steps required to register cluster devices. The
|
||||||
|
remainder of this document walks through these points in more detail.
|
||||||
|
|
||||||
|
- Create an Almanac device record for each device.
|
||||||
|
- Generate, add, and trust SSH keys if necessary.
|
||||||
|
- Install Phabricator on the host.
|
||||||
|
- Use `bin/almanac register` from the host to register it as a device.
|
||||||
|
|
||||||
|
See below for guidance on each of these steps.
|
||||||
|
|
||||||
|
|
||||||
|
Individual vs Shared Keys
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Before getting started, you should choose how you plan to manage device SSH
|
||||||
|
keys. Trust and device identity are handled separately, and there are two ways
|
||||||
|
to set up SSH keys so that devices can authenticate with one another:
|
||||||
|
|
||||||
|
- you can generate a unique SSH key for each device; or
|
||||||
|
- you can generate one SSH key and share it across multiple devices.
|
||||||
|
|
||||||
|
Using **unique keys** allows the tools to do some more sanity/safety checks and
|
||||||
|
makes it a bit more difficult to misconfigure things, but you'll have to do
|
||||||
|
more work managing the actual keys. This may be a better choice if you are
|
||||||
|
setting up a small cluster (2-3 devices) for the first time.
|
||||||
|
|
||||||
|
Using **shared keys** makes key management easier but safety checks won't be
|
||||||
|
able to catch a few kinds of mistakes. This may be a better choice if you are
|
||||||
|
setting up a larger cluster, plan to expand the cluster later, or have
|
||||||
|
experience with Phabricator clustering.
|
||||||
|
|
||||||
|
Because all cluster keys are all-powerful, there is no material difference
|
||||||
|
between these methods from a security or trust viewpoint. Unique keys are just
|
||||||
|
potentially easier to administrate at small scales, while shared keys are
|
||||||
|
easier at larger scales.
|
||||||
|
|
||||||
|
|
||||||
|
Create Almanac Device Records
|
||||||
|
=============================
|
||||||
|
|
||||||
|
For each host you plan to make part of a Phabricator cluster, go to the
|
||||||
|
{nav Almanac} application and create a **device** record. For guidance on this
|
||||||
|
application, see @{article:Almanac User Guide}.
|
||||||
|
|
||||||
|
Add **interfaces** to each device record so Phabricator can tell how to
|
||||||
|
connect to these hosts. Normally, you'll add one HTTP interface (usually on
|
||||||
|
port 80) and one SSH interface (often on port 22) to each device:
|
||||||
|
|
||||||
|
For example, if you are building a two-host repository cluster, you may end
|
||||||
|
up with records that look like these:
|
||||||
|
|
||||||
|
- Device: `repo001.mycompany.net`
|
||||||
|
- Interface: `123.0.0.1:22`
|
||||||
|
- Interface: `123.0.0.1:80`
|
||||||
|
- Device: `repo002.mycopmany.net`
|
||||||
|
- Interface: `123.0.0.2:22`
|
||||||
|
- Interface: `123.0.0.2:80`
|
||||||
|
|
||||||
|
Note that these hosts will normally run two `sshd` ports: the standard `sshd`
|
||||||
|
which you connect to to operate and administrate the host, and the special
|
||||||
|
Phabricator `sshd` that you connect to to clone and push repositories.
|
||||||
|
|
||||||
|
You should specify the Phabricator `sshd` port, **not** the standard `sshd`
|
||||||
|
port.
|
||||||
|
|
||||||
|
If you're using **unique** SSH keys for each device, continue to the next step.
|
||||||
|
|
||||||
|
If you're using **shared** SSH keys, create a third device with no interfaces,
|
||||||
|
like `keywarden.mycompany.net`. This device will just be used as a container to
|
||||||
|
hold the trusted SSH key and is not a real device.
|
||||||
|
|
||||||
|
NOTE: Do **not** create a **service** record yet. Today, service records become
|
||||||
|
active immediately once they are created, and you haven't set things up yet.
|
||||||
|
|
||||||
|
|
||||||
|
Generate and Trust SSH Keys
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Next, you need to generate or upload SSH keys and mark them as trusted. Marking
|
||||||
|
a key as trusted gives it tremendous power.
|
||||||
|
|
||||||
|
If you're using **unique** SSH keys, upload or generate a key for each
|
||||||
|
individual device from the device detail screen in the Almanac web UI. Save the
|
||||||
|
private keys for the next step.
|
||||||
|
|
||||||
|
If you're using a **shared** SSH key, upload or generate a single key for
|
||||||
|
the keywarden device from the device detail screen in the Almanac web UI.
|
||||||
|
Save the private key for the next step.
|
||||||
|
|
||||||
|
Regardless of how many keys you generated, take the key IDs from the tables
|
||||||
|
in the web UI and run this command from the command line for each key, to mark
|
||||||
|
each key as trusted:
|
||||||
|
|
||||||
|
```
|
||||||
|
phabricator/ $ ./bin/almanac trust-key --id <key-id-1>
|
||||||
|
phabricator/ $ ./bin/almanac trust-key --id <key-id-2>
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The warnings this command emits are serious. The private keys are now trusted,
|
||||||
|
and allow any user or device possessing them to sign requests that bypass
|
||||||
|
policy checks without requiring additional credentials. Guard them carefully!
|
||||||
|
|
||||||
|
If you need to revoke trust for a key later, use `untrust-key`:
|
||||||
|
|
||||||
|
```
|
||||||
|
phabricator/ $ ./bin/almanac untrust-key --id <key-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the keys are trusted, continue to the next step.
|
||||||
|
|
||||||
|
|
||||||
|
Install Phabricator
|
||||||
|
===================
|
||||||
|
|
||||||
|
If you haven't already, install Phabricator on each device you plan to enroll
|
||||||
|
in the cluster. Cluster repository devices must provide services over both HTTP
|
||||||
|
and SSH, so you need to install and configure both a webserver and a
|
||||||
|
Phabricator `sshd` on these hosts.
|
||||||
|
|
||||||
|
Generally, you will follow whatever process you otherwise use when installing
|
||||||
|
Phabricator.
|
||||||
|
|
||||||
|
NOTE: Do not start the daemons on the new devices yet. They won't work properly
|
||||||
|
until you've finished configuring things.
|
||||||
|
|
||||||
|
Once Phabricator is installed, you can enroll the devices in the cluster by
|
||||||
|
registering them.
|
||||||
|
|
||||||
|
|
||||||
|
Register Devices
|
||||||
|
================
|
||||||
|
|
||||||
|
To register a host as an Almanac device, use `bin/almanac register`.
|
||||||
|
|
||||||
|
If you are using **unique** keys, run it like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/almanac register \
|
||||||
|
--device <device> \
|
||||||
|
--private-key <key>
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, you might run this command on `repo001` when using unique keys:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/almanac register \
|
||||||
|
--device repo001.mycompany.net \
|
||||||
|
--private-key /path/to/private.key
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using a **shared** key, this will be a little more complicated
|
||||||
|
because you need to override some checks that are intended to prevent mistakes.
|
||||||
|
Use the `--identify-as` flag to choose a device identity:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/almanac register \
|
||||||
|
--device <keywarden-device> \
|
||||||
|
--private-key <key> \
|
||||||
|
--identify-as <actual-device>
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, you might run this command on `repo001` when using a shared key:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/almanac register
|
||||||
|
--device keywarden.mycompany.net \
|
||||||
|
--private-key /path/to/private-key \
|
||||||
|
--identify-as repo001.mycompany.net
|
||||||
|
```
|
||||||
|
|
||||||
|
In particular, note that `--device` is always the **trusted** device associated
|
||||||
|
with the trusted key. The `--identify-as` flag allows several different hosts
|
||||||
|
to share the same key but still identify as different devices.
|
||||||
|
|
||||||
|
The overall effect of the `bin/almanac` command is to copy identity and key
|
||||||
|
files into `phabricator/conf/keys/`. You can inspect the results by examining
|
||||||
|
that directory. The helper script just catches potential mistakes and makes
|
||||||
|
sure the process is completed correctly.
|
||||||
|
|
||||||
|
Note that a copy of the active private key is stored in the `conf/keys/`
|
||||||
|
directory permanently.
|
||||||
|
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
|
Now that devices are registered, you can build cluster services from them.
|
||||||
|
Return to the relevant cluster service documentation to continue:
|
||||||
|
|
||||||
|
- build repository clusters with @{article:Cluster: Repositories};
|
||||||
|
- return to @{article:Clustering Introduction}; or
|
||||||
|
- review the Almanac application with @{article:Almanac User Guide}.
|
Loading…
Reference in a new issue