Standardize SSH key storage
Summary: Ref T5833. This fixes a few weird things with this table: - A bunch of columns were nullable for no reason. - We stored an MD5 hash of the key (unusual) but never used it and callers were responsible for manually populating it. - We didn't perform known-key-text lookups by using an index. Test Plan: - Ran migrations. - Faked duplicate keys, saw them clean up correctly. - Added new keys. - Generated new keys. - Used `bin/auth-ssh` and `bin/auth-ssh-key`. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T5833 Differential Revision: https://secure.phabricator.com/D10805
This commit is contained in:
		
							
								
								
									
										2
									
								
								resources/sql/autopatches/20141107.ssh.1.colname.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								resources/sql/autopatches/20141107.ssh.1.colname.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | ALTER TABLE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   CHANGE userPHID objectPHID VARBINARY(64) NOT NULL; | ||||||
							
								
								
									
										2
									
								
								resources/sql/autopatches/20141107.ssh.2.keyhash.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								resources/sql/autopatches/20141107.ssh.2.keyhash.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | ALTER TABLE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   DROP COLUMN keyHash; | ||||||
							
								
								
									
										2
									
								
								resources/sql/autopatches/20141107.ssh.3.keyindex.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								resources/sql/autopatches/20141107.ssh.3.keyindex.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | ALTER TABLE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   ADD COLUMN keyIndex BINARY(12); | ||||||
							
								
								
									
										50
									
								
								resources/sql/autopatches/20141107.ssh.4.keymig.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								resources/sql/autopatches/20141107.ssh.4.keymig.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | $table = new PhabricatorAuthSSHKey(); | ||||||
|  | $conn_w = $table->establishConnection('w'); | ||||||
|  |  | ||||||
|  | echo "Updating SSH public key indexes...\n"; | ||||||
|  |  | ||||||
|  | $keys = new LiskMigrationIterator($table); | ||||||
|  | foreach ($keys as $key) { | ||||||
|  |   $id = $key->getID(); | ||||||
|  |  | ||||||
|  |   echo "Updating key {$id}...\n"; | ||||||
|  |  | ||||||
|  |   try { | ||||||
|  |     $hash = $key->toPublicKey()->getHash(); | ||||||
|  |   } catch (Exception $ex) { | ||||||
|  |     echo "Key has bad format! Removing key.\n"; | ||||||
|  |     queryfx( | ||||||
|  |       $conn_w, | ||||||
|  |       'DELETE FROM %T WHERE id = %d', | ||||||
|  |       $table->getTableName(), | ||||||
|  |       $id); | ||||||
|  |     continue; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   $collision = queryfx_all( | ||||||
|  |     $conn_w, | ||||||
|  |     'SELECT * FROM %T WHERE keyIndex = %s AND id < %d', | ||||||
|  |     $table->getTableName(), | ||||||
|  |     $hash, | ||||||
|  |     $key->getID()); | ||||||
|  |   if ($collision) { | ||||||
|  |     echo "Key is a duplicate! Removing key.\n"; | ||||||
|  |     queryfx( | ||||||
|  |       $conn_w, | ||||||
|  |       'DELETE FROM %T WHERE id = %d', | ||||||
|  |       $table->getTableName(), | ||||||
|  |       $id); | ||||||
|  |     continue; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   queryfx( | ||||||
|  |     $conn_w, | ||||||
|  |     'UPDATE %T SET keyIndex = %s WHERE id = %d', | ||||||
|  |     $table->getTableName(), | ||||||
|  |     $hash, | ||||||
|  |     $key->getID()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | echo "Done.\n"; | ||||||
							
								
								
									
										2
									
								
								resources/sql/autopatches/20141107.ssh.5.indexnull.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								resources/sql/autopatches/20141107.ssh.5.indexnull.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | ALTER TABLE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   CHANGE keyIndex keyIndex BINARY(12) NOT NULL; | ||||||
							
								
								
									
										2
									
								
								resources/sql/autopatches/20141107.ssh.6.indexkey.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								resources/sql/autopatches/20141107.ssh.6.indexkey.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | ALTER TABLE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   ADD UNIQUE KEY `key_unique` (keyIndex); | ||||||
							
								
								
									
										23
									
								
								resources/sql/autopatches/20141107.ssh.7.colnull.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								resources/sql/autopatches/20141107.ssh.7.colnull.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | UPDATE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   SET name = '' WHERE name IS NULL; | ||||||
|  |  | ||||||
|  | ALTER TABLE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   CHANGE name name VARCHAR(255) COLLATE {$COLLATE_TEXT} NOT NULL; | ||||||
|  |  | ||||||
|  | UPDATE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   SET keyType = '' WHERE keyType IS NULL; | ||||||
|  |  | ||||||
|  | ALTER TABLE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   CHANGE keyType keyType VARCHAR(255) COLLATE {$COLLATE_TEXT} NOT NULL; | ||||||
|  |  | ||||||
|  | UPDATE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   SET keyBody = '' WHERE keyBody IS NULL; | ||||||
|  |  | ||||||
|  | ALTER TABLE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   CHANGE keyBody keyBody LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL; | ||||||
|  |  | ||||||
|  | UPDATE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   SET keyComment = '' WHERE keyComment IS NULL; | ||||||
|  |  | ||||||
|  | ALTER TABLE {$NAMESPACE}_auth.auth_sshkey | ||||||
|  |   CHANGE keyComment keyComment VARCHAR(255) COLLATE {$COLLATE_TEXT} NOT NULL; | ||||||
| @@ -34,9 +34,15 @@ foreach ($keys as $ssh_key) { | |||||||
|  |  | ||||||
|   $type = $ssh_key->getKeyType(); |   $type = $ssh_key->getKeyType(); | ||||||
|   $type = preg_replace('@[\x00-\x20]+@', '', $type); |   $type = preg_replace('@[\x00-\x20]+@', '', $type); | ||||||
|  |   if (!strlen($type)) { | ||||||
|  |     continue; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   $key = $ssh_key->getKeyBody(); |   $key = $ssh_key->getKeyBody(); | ||||||
|   $key = preg_replace('@[\x00-\x20]+@', '', $key); |   $key = preg_replace('@[\x00-\x20]+@', '', $key); | ||||||
|  |   if (!strlen($key)) { | ||||||
|  |     continue; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   $options = array( |   $options = array( | ||||||
|     'command="'.$cmd.'"', |     'command="'.$cmd.'"', | ||||||
|   | |||||||
| @@ -73,21 +73,18 @@ final class PhabricatorAuthSSHKeyQuery | |||||||
|     if ($this->objectPHIDs !== null) { |     if ($this->objectPHIDs !== null) { | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn_r, |         $conn_r, | ||||||
|         'userPHID IN (%Ls)', |         'objectPHID IN (%Ls)', | ||||||
|         $this->objectPHIDs); |         $this->objectPHIDs); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($this->keys !== null) { |     if ($this->keys !== null) { | ||||||
|       // TODO: This could take advantage of a better key, and the hashing |  | ||||||
|       // scheme for this table is a bit nonstandard and questionable. |  | ||||||
|  |  | ||||||
|       $sql = array(); |       $sql = array(); | ||||||
|       foreach ($this->keys as $key) { |       foreach ($this->keys as $key) { | ||||||
|         $sql[] = qsprintf( |         $sql[] = qsprintf( | ||||||
|           $conn_r, |           $conn_r, | ||||||
|           '(keyType = %s AND keyBody = %s)', |           '(keyType = %s AND keyIndex = %s)', | ||||||
|           $key->getType(), |           $key->getType(), | ||||||
|           $key->getBody()); |           $key->getHash()); | ||||||
|       } |       } | ||||||
|       $where[] = implode(' OR ', $sql); |       $where[] = implode(' OR ', $sql); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -4,43 +4,45 @@ final class PhabricatorAuthSSHKey | |||||||
|   extends PhabricatorAuthDAO |   extends PhabricatorAuthDAO | ||||||
|   implements PhabricatorPolicyInterface { |   implements PhabricatorPolicyInterface { | ||||||
|  |  | ||||||
|   protected $userPHID; |   protected $objectPHID; | ||||||
|   protected $name; |   protected $name; | ||||||
|   protected $keyType; |   protected $keyType; | ||||||
|  |   protected $keyIndex; | ||||||
|   protected $keyBody; |   protected $keyBody; | ||||||
|   protected $keyHash; |   protected $keyComment = ''; | ||||||
|   protected $keyComment; |  | ||||||
|  |  | ||||||
|   private $object = self::ATTACHABLE; |   private $object = self::ATTACHABLE; | ||||||
|  |  | ||||||
|   public function getObjectPHID() { |  | ||||||
|     return $this->getUserPHID(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public function getConfiguration() { |   public function getConfiguration() { | ||||||
|     return array( |     return array( | ||||||
|       self::CONFIG_COLUMN_SCHEMA => array( |       self::CONFIG_COLUMN_SCHEMA => array( | ||||||
|         'keyHash' => 'bytes32', |         'name' => 'text255', | ||||||
|         'keyComment' => 'text255?', |         'keyType' => 'text255', | ||||||
|  |         'keyIndex' => 'bytes12', | ||||||
|         // T6203/NULLABILITY |         'keyBody' => 'text', | ||||||
|         // These seem like they should not be nullable. |         'keyComment' => 'text255', | ||||||
|         'name' => 'text255?', |  | ||||||
|         'keyType' => 'text255?', |  | ||||||
|         'keyBody' => 'text?', |  | ||||||
|       ), |       ), | ||||||
|       self::CONFIG_KEY_SCHEMA => array( |       self::CONFIG_KEY_SCHEMA => array( | ||||||
|         'userPHID' => array( |         'key_object' => array( | ||||||
|           'columns' => array('userPHID'), |           'columns' => array('objectPHID'), | ||||||
|         ), |         ), | ||||||
|         'keyHash' => array( |         'key_unique' => array( | ||||||
|           'columns' => array('keyHash'), |           'columns' => array('keyIndex'), | ||||||
|           'unique' => true, |           'unique' => true, | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|     ) + parent::getConfiguration(); |     ) + parent::getConfiguration(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public function save() { | ||||||
|  |     $this->setKeyIndex($this->toPublicKey()->getHash()); | ||||||
|  |     return parent::save(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function toPublicKey() { | ||||||
|  |     return PhabricatorAuthSSHPublicKey::newFromStoredKey($this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public function getEntireKey() { |   public function getEntireKey() { | ||||||
|     $parts = array( |     $parts = array( | ||||||
|       $this->getKeyType(), |       $this->getKeyType(), | ||||||
| @@ -60,6 +62,8 @@ final class PhabricatorAuthSSHKey | |||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /* -(  PhabricatorPolicyInterface  )----------------------------------------- */ | /* -(  PhabricatorPolicyInterface  )----------------------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,15 @@ final class PhabricatorAuthSSHPublicKey extends Phobject { | |||||||
|     // <internal> |     // <internal> | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public static function newFromStoredKey(PhabricatorAuthSSHKey $key) { | ||||||
|  |     $public_key = new PhabricatorAuthSSHPublicKey(); | ||||||
|  |     $public_key->type = $key->getKeyType(); | ||||||
|  |     $public_key->body = $key->getKeyBody(); | ||||||
|  |     $public_key->comment = $key->getKeyComment(); | ||||||
|  |  | ||||||
|  |     return $public_key; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public static function newFromRawKey($entire_key) { |   public static function newFromRawKey($entire_key) { | ||||||
|     $entire_key = trim($entire_key); |     $entire_key = trim($entire_key); | ||||||
|     if (!strlen($entire_key)) { |     if (!strlen($entire_key)) { | ||||||
| @@ -83,4 +92,11 @@ final class PhabricatorAuthSSHPublicKey extends Phobject { | |||||||
|     return $this->comment; |     return $this->comment; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public function getHash() { | ||||||
|  |     $body = $this->getBody(); | ||||||
|  |     $body = trim($body); | ||||||
|  |     $body = rtrim($body, '='); | ||||||
|  |     return PhabricatorHash::digestForIndex($body); | ||||||
|  |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -897,7 +897,7 @@ EOBODY; | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       $keys = id(new PhabricatorAuthSSHKey())->loadAllWhere( |       $keys = id(new PhabricatorAuthSSHKey())->loadAllWhere( | ||||||
|         'userPHID = %s', |         'objectPHID = %s', | ||||||
|         $this->getPHID()); |         $this->getPHID()); | ||||||
|       foreach ($keys as $key) { |       foreach ($keys as $key) { | ||||||
|         $key->delete(); |         $key->delete(); | ||||||
|   | |||||||
| @@ -44,8 +44,7 @@ final class PhabricatorSettingsPanelSSHKeys | |||||||
|       $this->getPanelURI()); |       $this->getPanelURI()); | ||||||
|  |  | ||||||
|     $id = nonempty($edit, $delete); |     $id = nonempty($edit, $delete); | ||||||
|  |     if ($id && (int)$id) { | ||||||
|     if ($id) { |  | ||||||
|       $key = id(new PhabricatorAuthSSHKeyQuery()) |       $key = id(new PhabricatorAuthSSHKeyQuery()) | ||||||
|         ->setViewer($viewer) |         ->setViewer($viewer) | ||||||
|         ->withIDs(array($id)) |         ->withIDs(array($id)) | ||||||
| @@ -59,8 +58,8 @@ final class PhabricatorSettingsPanelSSHKeys | |||||||
|         return new Aphront404Response(); |         return new Aphront404Response(); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       $key = new PhabricatorAuthSSHKey(); |       $key = id(new PhabricatorAuthSSHKey()) | ||||||
|       $key->setUserPHID($user->getPHID()); |         ->setObjectPHID($user->getPHID()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($delete) { |     if ($delete) { | ||||||
| @@ -89,7 +88,6 @@ final class PhabricatorSettingsPanelSSHKeys | |||||||
|  |  | ||||||
|           $key->setKeyType($type); |           $key->setKeyType($type); | ||||||
|           $key->setKeyBody($body); |           $key->setKeyBody($body); | ||||||
|           $key->setKeyHash(md5($body)); |  | ||||||
|           $key->setKeyComment($comment); |           $key->setKeyComment($comment); | ||||||
|  |  | ||||||
|           $e_key = null; |           $e_key = null; | ||||||
| @@ -309,11 +307,10 @@ final class PhabricatorSettingsPanelSSHKeys | |||||||
|       $body = $public_key->getBody(); |       $body = $public_key->getBody(); | ||||||
|  |  | ||||||
|       $key = id(new PhabricatorAuthSSHKey()) |       $key = id(new PhabricatorAuthSSHKey()) | ||||||
|         ->setUserPHID($user->getPHID()) |         ->setObjectPHID($user->getPHID()) | ||||||
|         ->setName('id_rsa_phabricator') |         ->setName('id_rsa_phabricator') | ||||||
|         ->setKeyType($type) |         ->setKeyType($type) | ||||||
|         ->setKeyBody($body) |         ->setKeyBody($body) | ||||||
|         ->setKeyHash(md5($body)) |  | ||||||
|         ->setKeyComment(pht('Generated')) |         ->setKeyComment(pht('Generated')) | ||||||
|         ->save(); |         ->save(); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 epriestley
					epriestley