You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

KeysDispatcher.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /*
  2. * Copyright 2014 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.transport.ssh.keys;
  17. import java.io.IOException;
  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import org.kohsuke.args4j.Argument;
  21. import org.kohsuke.args4j.Option;
  22. import org.slf4j.Logger;
  23. import org.slf4j.LoggerFactory;
  24. import com.gitblit.Constants.AccessPermission;
  25. import com.gitblit.transport.ssh.IPublicKeyManager;
  26. import com.gitblit.transport.ssh.SshKey;
  27. import com.gitblit.transport.ssh.commands.CommandMetaData;
  28. import com.gitblit.transport.ssh.commands.DispatchCommand;
  29. import com.gitblit.transport.ssh.commands.SshCommand;
  30. import com.gitblit.transport.ssh.commands.UsageExample;
  31. import com.gitblit.utils.FlipTable;
  32. import com.gitblit.utils.FlipTable.Borders;
  33. import com.gitblit.utils.StringUtils;
  34. import com.google.common.base.Joiner;
  35. /**
  36. * The dispatcher and it's commands for SSH public key management.
  37. *
  38. * @author James Moger
  39. *
  40. */
  41. @CommandMetaData(name = "keys", description = "SSH public key management commands")
  42. public class KeysDispatcher extends DispatchCommand {
  43. @Override
  44. protected void setup() {
  45. register(AddKey.class);
  46. register(RemoveKey.class);
  47. register(ListKeys.class);
  48. register(WhichKey.class);
  49. register(CommentKey.class);
  50. register(PermissionKey.class);
  51. }
  52. @CommandMetaData(name = "add", description = "Add an SSH public key to your account")
  53. @UsageExample(syntax = "cat ~/.ssh/id_rsa.pub | ${ssh} ${cmd}", description = "Upload your SSH public key and add it to your account")
  54. public static class AddKey extends BaseKeyCommand {
  55. protected final Logger log = LoggerFactory.getLogger(getClass());
  56. @Argument(metaVar = "<STDIN>", usage = "the key to add")
  57. private List<String> addKeys = new ArrayList<String>();
  58. @Option(name = "--permission", aliases = { "-p" }, metaVar = "PERMISSION", usage = "set the key access permission")
  59. private String permission;
  60. @Override
  61. protected String getUsageText() {
  62. String permissions = Joiner.on(", ").join(AccessPermission.SSHPERMISSIONS);
  63. StringBuilder sb = new StringBuilder();
  64. sb.append("Valid SSH public key permissions are:\n ").append(permissions);
  65. return sb.toString();
  66. }
  67. @Override
  68. public void run() throws IOException, Failure {
  69. String username = getContext().getClient().getUsername();
  70. List<String> keys = readKeys(addKeys);
  71. if (keys.isEmpty()) {
  72. throw new UnloggedFailure("No public keys were read from STDIN!");
  73. }
  74. for (String key : keys) {
  75. SshKey sshKey = parseKey(key);
  76. try {
  77. // this method parses the rawdata and produces a public key
  78. // if it fails it will throw a Buffer.BufferException
  79. // the null check is a QC verification on top of that
  80. if (sshKey.getPublicKey() == null) {
  81. throw new RuntimeException();
  82. }
  83. } catch (RuntimeException e) {
  84. throw new UnloggedFailure("The data read from SDTIN can not be parsed as an SSH public key!");
  85. }
  86. if (!StringUtils.isEmpty(permission)) {
  87. AccessPermission ap = AccessPermission.fromCode(permission);
  88. if (ap.exceeds(AccessPermission.NONE)) {
  89. try {
  90. sshKey.setPermission(ap);
  91. } catch (IllegalArgumentException e) {
  92. throw new Failure(1, e.getMessage());
  93. }
  94. }
  95. }
  96. getKeyManager().addKey(username, sshKey);
  97. log.info("added SSH public key for {}", username);
  98. }
  99. }
  100. }
  101. @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove an SSH public key from your account")
  102. @UsageExample(syntax = "${cmd} 2", description = "Remove the SSH key identified as #2 in `keys list`")
  103. public static class RemoveKey extends BaseKeyCommand {
  104. protected final Logger log = LoggerFactory.getLogger(getClass());
  105. private final String ALL = "ALL";
  106. @Argument(metaVar = "<INDEX>|ALL", usage = "the key to remove", required = true)
  107. private List<String> keyParameters = new ArrayList<String>();
  108. @Override
  109. public void run() throws IOException, Failure {
  110. String username = getContext().getClient().getUsername();
  111. // remove a key that has been piped to the command
  112. // or remove all keys
  113. List<SshKey> registeredKeys = new ArrayList<SshKey>(getKeyManager().getKeys(username));
  114. if (registeredKeys.isEmpty()) {
  115. throw new UnloggedFailure(1, "There are no registered keys!");
  116. }
  117. if (keyParameters.contains(ALL)) {
  118. if (getKeyManager().removeAllKeys(username)) {
  119. stdout.println("Removed all keys.");
  120. log.info("removed all SSH public keys from {}", username);
  121. } else {
  122. log.warn("failed to remove all SSH public keys from {}", username);
  123. }
  124. } else {
  125. for (String keyParameter : keyParameters) {
  126. try {
  127. // remove a key by it's index (1-based indexing)
  128. int index = Integer.parseInt(keyParameter);
  129. if (index > registeredKeys.size()) {
  130. if (keyParameters.size() == 1) {
  131. throw new Failure(1, "Invalid index specified. There is only 1 registered key.");
  132. }
  133. throw new Failure(1, String.format("Invalid index specified. There are %d registered keys.", registeredKeys.size()));
  134. }
  135. SshKey sshKey = registeredKeys.get(index - 1);
  136. if (getKeyManager().removeKey(username, sshKey)) {
  137. stdout.println(String.format("Removed %s", sshKey.getFingerprint()));
  138. } else {
  139. throw new Failure(1, String.format("failed to remove #%s: %s", keyParameter, sshKey.getFingerprint()));
  140. }
  141. } catch (NumberFormatException e) {
  142. log.warn("failed to remove SSH public key {} from {}", keyParameter, username);
  143. throw new Failure(1, String.format("failed to remove key %s", keyParameter));
  144. }
  145. }
  146. }
  147. }
  148. }
  149. @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your registered SSH public keys")
  150. public static class ListKeys extends SshCommand {
  151. @Option(name = "-L", usage = "list complete public key parameters")
  152. private boolean showRaw;
  153. @Override
  154. public void run() {
  155. IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager();
  156. String username = getContext().getClient().getUsername();
  157. List<SshKey> keys = keyManager.getKeys(username);
  158. if (showRaw) {
  159. asRaw(keys);
  160. } else {
  161. asTable(keys);
  162. }
  163. }
  164. /* output in the same format as authorized_keys */
  165. protected void asRaw(List<SshKey> keys) {
  166. if (keys == null) {
  167. return;
  168. }
  169. for (SshKey key : keys) {
  170. stdout.println(key.getRawData());
  171. }
  172. }
  173. protected void asTable(List<SshKey> keys) {
  174. String[] headers = { "#", "Fingerprint", "Comment", "Permission", "Type" };
  175. int len = keys == null ? 0 : keys.size();
  176. Object[][] data = new Object[len][];
  177. for (int i = 0; i < len; i++) {
  178. // show 1-based index numbers with the fingerprint
  179. // this is useful for comparing with "ssh-add -l"
  180. SshKey k = keys.get(i);
  181. data[i] = new Object[] { (i + 1), k.getFingerprint(), k.getComment(),
  182. k.getPermission(), k.getAlgorithm() };
  183. }
  184. stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS));
  185. }
  186. }
  187. @CommandMetaData(name = "which", description = "Display the SSH public key used for this session")
  188. public static class WhichKey extends SshCommand {
  189. @Option(name = "-L", usage = "list complete public key parameters")
  190. private boolean showRaw;
  191. @Override
  192. public void run() throws UnloggedFailure {
  193. SshKey key = getContext().getClient().getKey();
  194. if (key == null) {
  195. throw new UnloggedFailure(1, "You have not authenticated with an SSH public key.");
  196. }
  197. if (showRaw) {
  198. stdout.println(key.getRawData());
  199. } else {
  200. final String username = getContext().getClient().getUsername();
  201. List<SshKey> keys = getContext().getGitblit().getPublicKeyManager().getKeys(username);
  202. int index = 0;
  203. for (int i = 0; i < keys.size(); i++) {
  204. if (key.equals(keys.get(i))) {
  205. index = i + 1;
  206. break;
  207. }
  208. }
  209. asTable(index, key);
  210. }
  211. }
  212. protected void asTable(int index, SshKey key) {
  213. String[] headers = { "#", "Fingerprint", "Comment", "Permission", "Type" };
  214. Object[][] data = new Object[1][];
  215. data[0] = new Object[] { index, key.getFingerprint(), key.getComment(), key.getPermission(), key.getAlgorithm() };
  216. stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS));
  217. }
  218. }
  219. @CommandMetaData(name = "comment", description = "Set the comment for an SSH public key")
  220. @UsageExample(syntax = "${cmd} 3 Home workstation", description = "Set the comment for key #3")
  221. public static class CommentKey extends SshCommand {
  222. @Argument(index = 0, metaVar = "INDEX", usage = "the key index", required = true)
  223. private int index;
  224. @Argument(index = 1, metaVar = "COMMENT", usage = "the new comment", required = true)
  225. private List<String> values = new ArrayList<String>();
  226. @Override
  227. public void run() throws Failure {
  228. final String username = getContext().getClient().getUsername();
  229. IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager();
  230. List<SshKey> keys = keyManager.getKeys(username);
  231. if (index > keys.size()) {
  232. throw new UnloggedFailure(1, "Invalid key index!");
  233. }
  234. String comment = Joiner.on(" ").join(values);
  235. SshKey key = keys.get(index - 1);
  236. key.setComment(comment);
  237. if (keyManager.addKey(username, key)) {
  238. stdout.println(String.format("Updated the comment for key #%d.", index));
  239. } else {
  240. throw new Failure(1, String.format("Failed to update the comment for key #%d!", index));
  241. }
  242. }
  243. }
  244. @CommandMetaData(name = "permission", description = "Set the permission of an SSH public key")
  245. @UsageExample(syntax = "${cmd} 3 RW", description = "Set the permission for key #3 to PUSH (PW)")
  246. public static class PermissionKey extends SshCommand {
  247. @Argument(index = 0, metaVar = "INDEX", usage = "the key index", required = true)
  248. private int index;
  249. @Argument(index = 1, metaVar = "PERMISSION", usage = "the new permission", required = true)
  250. private String value;
  251. @Override
  252. public void run() throws Failure {
  253. final String username = getContext().getClient().getUsername();
  254. IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager();
  255. List<SshKey> keys = keyManager.getKeys(username);
  256. if (index > keys.size()) {
  257. throw new UnloggedFailure(1, "Invalid key index!");
  258. }
  259. SshKey key = keys.get(index - 1);
  260. AccessPermission permission = AccessPermission.fromCode(value);
  261. if (permission.exceeds(AccessPermission.NONE)) {
  262. try {
  263. key.setPermission(permission);
  264. } catch (IllegalArgumentException e) {
  265. throw new Failure(1, e.getMessage());
  266. }
  267. }
  268. if (keyManager.addKey(username, key)) {
  269. stdout.println(String.format("Updated the permission for key #%d.", index));
  270. } else {
  271. throw new Failure(1, String.format("Failed to update the comment for key #%d!", index));
  272. }
  273. }
  274. }
  275. }