3 final class AlmanacManagementRegisterWorkflow
4 extends AlmanacManagementWorkflow
{
6 protected function didConstruct() {
9 ->setSynopsis(pht('Register this host as an Almanac device.'))
15 'help' => pht('Almanac device name to register.'),
18 'name' => 'private-key',
20 'help' => pht('Path to a private key for the host.'),
23 'name' => 'identify-as',
26 'Specify an alternate host identity. This is an advanced '.
27 'feature which allows a pool of devices to share credentials.'),
32 'Register this host even if keys already exist on disk.'),
37 public function execute(PhutilArgumentParser
$args) {
38 $viewer = $this->getViewer();
40 $device_name = $args->getArg('device');
41 if (!strlen($device_name)) {
42 throw new PhutilArgumentUsageException(
43 pht('Specify a device with --device.'));
46 $device = id(new AlmanacDeviceQuery())
48 ->withNames(array($device_name))
51 throw new PhutilArgumentUsageException(
52 pht('No such device "%s" exists!', $device_name));
55 $identify_as = $args->getArg('identify-as');
57 $raw_device = $device_name;
58 if (strlen($identify_as)) {
59 $raw_device = $identify_as;
62 $identity_device = id(new AlmanacDeviceQuery())
64 ->withNames(array($raw_device))
66 if (!$identity_device) {
67 throw new PhutilArgumentUsageException(
69 'No such device "%s" exists!', $raw_device));
72 $private_key_path = $args->getArg('private-key');
73 if (!strlen($private_key_path)) {
74 throw new PhutilArgumentUsageException(
75 pht('Specify a private key with --private-key.'));
78 if (!Filesystem
::pathExists($private_key_path)) {
79 throw new PhutilArgumentUsageException(
80 pht('No private key exists at path "%s"!', $private_key_path));
83 $raw_private_key = Filesystem
::readFile($private_key_path);
85 $phd_user = PhabricatorEnv
::getEnvConfig('phd.user');
87 throw new PhutilArgumentUsageException(
89 'Config option "phd.user" is not set. You must set this option '.
90 'so the private key can be stored with the correct permissions.'));
93 $tmp = new TempFile();
94 list($err) = exec_manual('chown %s %s', $phd_user, $tmp);
96 throw new PhutilArgumentUsageException(
98 'Unable to change ownership of an identity file to daemon user '.
99 '"%s". Run this command as %s or root.',
104 $stored_public_path = AlmanacKeys
::getKeyPath('device.pub');
105 $stored_private_path = AlmanacKeys
::getKeyPath('device.key');
106 $stored_device_path = AlmanacKeys
::getKeyPath('device.id');
108 if (!$args->getArg('force')) {
109 if (Filesystem
::pathExists($stored_public_path)) {
110 throw new PhutilArgumentUsageException(
112 'This host already has a registered public key ("%s"). '.
113 'Remove this key before registering the host, or use '.
114 '--force to overwrite it.',
115 Filesystem
::readablePath($stored_public_path)));
118 if (Filesystem
::pathExists($stored_private_path)) {
119 throw new PhutilArgumentUsageException(
121 'This host already has a registered private key ("%s"). '.
122 'Remove this key before registering the host, or use '.
123 '--force to overwrite it.',
124 Filesystem
::readablePath($stored_private_path)));
128 // NOTE: We're writing the private key here so we can change permissions
129 // on it without causing weird side effects to the file specified with
130 // the `--private-key` flag. The file needs to have restrictive permissions
131 // before `ssh-keygen` will willingly operate on it.
132 $tmp_private = new TempFile();
133 Filesystem
::changePermissions($tmp_private, 0600);
134 execx('chown %s %s', $phd_user, $tmp_private);
135 Filesystem
::writeFile($tmp_private, $raw_private_key);
137 list($raw_public_key) = execx('ssh-keygen -y -f %s', $tmp_private);
139 $key_object = PhabricatorAuthSSHPublicKey
::newFromRawKey($raw_public_key);
141 $public_key = id(new PhabricatorAuthSSHKeyQuery())
142 ->setViewer($this->getViewer())
143 ->withKeys(array($key_object))
148 throw new PhutilArgumentUsageException(
150 'The public key corresponding to the given private key is unknown. '.
151 'Associate the public key with an Almanac device in the web '.
152 'interface before registering hosts with it.'));
155 if ($public_key->getObjectPHID() !== $device->getPHID()) {
156 $public_phid = $public_key->getObjectPHID();
157 $public_handles = $viewer->loadHandles(array($public_phid));
158 $public_handle = $public_handles[$public_phid];
160 throw new PhutilArgumentUsageException(
162 'The public key corresponding to the given private key is already '.
163 'associated with an object ("%s") other than the specified '.
164 'device ("%s"). You can not use a single private key to identify '.
165 'multiple devices or users.',
166 $public_handle->getFullName(),
167 $device->getName()));
170 if (!$public_key->getIsTrusted()) {
171 throw new PhutilArgumentUsageException(
173 'The public key corresponding to the given private key is '.
174 'properly associated with the device, but is not yet trusted. '.
175 'Trust this key before registering devices with it.'));
180 pht('Installing public key...'));
182 $tmp_public = new TempFile();
183 Filesystem
::changePermissions($tmp_public, 0600);
184 execx('chown %s %s', $phd_user, $tmp_public);
185 Filesystem
::writeFile($tmp_public, $raw_public_key);
186 execx('mv -f %s %s', $tmp_public, $stored_public_path);
190 pht('Installing private key...'));
191 execx('mv -f %s %s', $tmp_private, $stored_private_path);
195 pht('Installing device %s...', $raw_device));
197 // The permissions on this file are more open because the webserver also
199 $tmp_device = new TempFile();
200 Filesystem
::changePermissions($tmp_device, 0644);
201 execx('chown %s %s', $phd_user, $tmp_device);
202 Filesystem
::writeFile($tmp_device, $raw_device);
203 execx('mv -f %s %s', $tmp_device, $stored_device_path);
206 "**<bg:green> %s </bg>** %s\n",
207 pht('HOST REGISTERED'),
209 'This host has been registered as "%s" and a trusted keypair '.
210 'has been installed.',