]> source.dussan.org Git - gitblit.git/commitdiff
Support specifying permission levels for SSH public keys
authorJames Moger <james.moger@gitblit.com>
Wed, 2 Apr 2014 13:41:23 +0000 (09:41 -0400)
committerJames Moger <james.moger@gitblit.com>
Thu, 10 Apr 2014 23:00:52 +0000 (19:00 -0400)
src/main/java/com/gitblit/Constants.java
src/main/java/com/gitblit/transport/ssh/FileKeyManager.java
src/main/java/com/gitblit/transport/ssh/SshKey.java
src/main/java/com/gitblit/transport/ssh/git/Receive.java
src/main/java/com/gitblit/transport/ssh/git/Upload.java
src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java

index 56dfec064561ff23f765571451b7c04b2d07da34..26e0de3c9a8498f9e3a42b5c9658e24ae4895d30 100644 (file)
@@ -423,6 +423,8 @@ public class Constants {
 \r
                public static final AccessPermission [] NEWPERMISSIONS = { EXCLUDE, VIEW, CLONE, PUSH, CREATE, DELETE, REWIND };\r
 \r
+               public static final AccessPermission [] SSHPERMISSIONS = { VIEW, CLONE, PUSH };\r
+\r
                public static AccessPermission LEGACY = REWIND;\r
 \r
                public final String code;\r
index 77f818c37fec5a10fa78b1a9b855a335a55f8634..ae4588ae940f8d60352771a34dce2e301af666c8 100644 (file)
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Keys;
 import com.gitblit.manager.IRuntimeManager;
 import com.google.common.base.Charsets;
@@ -105,8 +106,18 @@ public class FileKeyManager extends IPublicKeyManager {
                                                // skip comments
                                                continue;
                                        }
-                                       SshKey key = new SshKey(entry);
-                                       list.add(key);
+                                       String [] parts = entry.split(" ", 2);
+                                       AccessPermission perm = AccessPermission.fromCode(parts[0]);
+                                       if (perm.equals(AccessPermission.NONE)) {
+                                               // ssh-rsa DATA COMMENT
+                                               SshKey key = new SshKey(entry);
+                                               list.add(key);
+                                       } else if (perm.exceeds(AccessPermission.NONE)) {
+                                               // PERMISSION ssh-rsa DATA COMMENT
+                                               SshKey key = new SshKey(parts[1]);
+                                               key.setPermission(perm);
+                                               list.add(key);
+                                       }
                                }
 
                                if (list.isEmpty()) {
@@ -129,7 +140,6 @@ public class FileKeyManager extends IPublicKeyManager {
        @Override
        public boolean addKey(String username, SshKey key) {
                try {
-                       String newKey = stripCommentFromKey(key.getRawData());
                        boolean replaced = false;
                        List<String> lines = new ArrayList<String>();
                        File keystore = getKeystore(username);
@@ -147,10 +157,10 @@ public class FileKeyManager extends IPublicKeyManager {
                                                continue;
                                        }
 
-                                       String oldKey = stripCommentFromKey(line);
-                                       if (newKey.equals(oldKey)) {
+                                       SshKey oldKey = parseKey(line);
+                                       if (key.equals(oldKey)) {
                                                // replace key
-                                               lines.add(key.getRawData());
+                                               lines.add(key.getPermission() + " " + key.getRawData());
                                                replaced = true;
                                        } else {
                                                // retain key
@@ -161,7 +171,7 @@ public class FileKeyManager extends IPublicKeyManager {
 
                        if (!replaced) {
                                // new key, append
-                               lines.add(key.getRawData());
+                               lines.add(key.getPermission() + " " + key.getRawData());
                        }
 
                        // write keystore
@@ -182,8 +192,6 @@ public class FileKeyManager extends IPublicKeyManager {
        @Override
        public boolean removeKey(String username, SshKey key) {
                try {
-                       String rmKey = stripCommentFromKey(key.getRawData());
-
                        File keystore = getKeystore(username);
                        if (keystore.exists()) {
                                List<String> lines = new ArrayList<String>();
@@ -201,8 +209,8 @@ public class FileKeyManager extends IPublicKeyManager {
                                        }
 
                                        // only include keys that are NOT rmKey
-                                       String oldKey = stripCommentFromKey(line);
-                                       if (!rmKey.equals(oldKey)) {
+                                       SshKey oldKey = parseKey(line);
+                                       if (!key.equals(oldKey)) {
                                                lines.add(entry);
                                        }
                                }
@@ -242,10 +250,18 @@ public class FileKeyManager extends IPublicKeyManager {
                return keys;
        }
 
-       /* Strips the comment from the key data and eliminates whitespace diffs */
-       protected String stripCommentFromKey(String data) {
-               String [] cols = data.split(" ", 3);
-               String key = Joiner.on(" ").join(cols[0], cols[1]);
-               return key;
+       protected SshKey parseKey(String line) {
+               String [] parts = line.split(" ", 2);
+               AccessPermission perm = AccessPermission.fromCode(parts[0]);
+               if (perm.equals(AccessPermission.NONE)) {
+                       // ssh-rsa DATA COMMENT
+                       SshKey key = new SshKey(line);
+                       return key;
+               } else {
+                       // PERMISSION ssh-rsa DATA COMMENT
+                       SshKey key = new SshKey(parts[1]);
+                       key.setPermission(perm);
+                       return key;
+               }
        }
 }
index cb5ee097cca6aa2eb0cc0c1954cab2210e2dcd32..6ac0cdcb793444a53a759641222f154749663cbc 100644 (file)
@@ -2,12 +2,15 @@ package com.gitblit.transport.ssh;
 
 import java.io.Serializable;
 import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.List;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.util.Buffer;
 import org.eclipse.jgit.lib.Constants;
 
+import com.gitblit.Constants.AccessPermission;
 import com.gitblit.utils.StringUtils;
 
 /**
@@ -30,13 +33,17 @@ public class SshKey implements Serializable {
 
        private String toString;
 
+       private AccessPermission permission;
+
        public SshKey(String data) {
                this.rawData = data;
+               this.permission = AccessPermission.PUSH;
        }
 
        public SshKey(PublicKey key) {
                this.publicKey = key;
                this.comment = "";
+               this.permission = AccessPermission.PUSH;
        }
 
        public PublicKey getPublicKey() {
@@ -78,6 +85,46 @@ public class SshKey implements Serializable {
                }
        }
 
+       /**
+        * Returns true if this key may be used to clone or fetch.
+        *
+        * @return true if this key can be used to clone or fetch
+        */
+       public boolean canClone() {
+               return permission.atLeast(AccessPermission.CLONE);
+       }
+
+       /**
+        * Returns true if this key may be used to push changes.
+        *
+        * @return true if this key can be used to push changes
+        */
+       public boolean canPush() {
+               return permission.atLeast(AccessPermission.PUSH);
+       }
+
+       /**
+        * Returns the access permission for the key.
+        *
+        * @return the access permission for the key
+        */
+       public AccessPermission getPermission() {
+               return permission;
+       }
+
+       /**
+        * Control the access permission assigned to this key.
+        *
+        * @param value
+        */
+       public void setPermission(AccessPermission value) throws IllegalArgumentException {
+               List<AccessPermission> permitted = Arrays.asList(AccessPermission.SSHPERMISSIONS);
+               if (!permitted.contains(value)) {
+                       throw new IllegalArgumentException("Illegal SSH public key permission specified: " + value);
+               }
+               this.permission = value;
+       }
+
        public String getRawData() {
                if (rawData == null && publicKey != null) {
                        // build the raw data manually from the public key
index 94d099857fb5116a84bcfc3e0b68286caf40ab12..f0d86f0da07ce110ac6d0dd052a2a7342eb47b4e 100644 (file)
@@ -17,12 +17,17 @@ package com.gitblit.transport.ssh.git;
 
 import org.eclipse.jgit.transport.ReceivePack;
 
+import com.gitblit.transport.ssh.SshKey;
 import com.gitblit.transport.ssh.commands.CommandMetaData;
 
 @CommandMetaData(name = "git-receive-pack", description = "Receives pushes from a client", hidden = true)
 public class Receive extends BaseGitCommand {
        @Override
        protected void runImpl() throws Failure {
+               SshKey key = getContext().getClient().getKey();
+               if (key != null && !key.canPush()) {
+                       throw new Failure(1, "Sorry, your SSH public key is not allowed to push changes!");
+               }
                try {
                        ReceivePack rp = receivePackFactory.create(getContext().getClient(), repo);
                        rp.receive(in, out, null);
index c4dfa8086fb6f372912bb318de3309039232829b..11a33cefe8f23c29475792327cf190bec6c648c9 100644 (file)
@@ -17,6 +17,7 @@ package com.gitblit.transport.ssh.git;
 
 import org.eclipse.jgit.transport.UploadPack;
 
+import com.gitblit.transport.ssh.SshKey;
 import com.gitblit.transport.ssh.commands.CommandMetaData;
 
 @CommandMetaData(name = "git-upload-pack", description = "Sends packs to a client for clone and fetch", hidden = true)
@@ -24,6 +25,10 @@ public class Upload extends BaseGitCommand {
        @Override
        protected void runImpl() throws Failure {
                try {
+                       SshKey key = getContext().getClient().getKey();
+                       if (key != null && !key.canClone()) {
+                               throw new Failure(1, "Sorry, your SSH public key is not allowed to clone!");
+                       }
                        UploadPack up = uploadPackFactory.create(getContext().getClient(), repo);
                        up.upload(in, out, null);
                } catch (Exception e) {
index ad373060cfc5638e52c1118a3e34ec6b0b753f2d..62daec6a2a2b9278fc4462040484088925597a5f 100644 (file)
@@ -24,6 +24,7 @@ import org.kohsuke.args4j.Option;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.gitblit.Constants.AccessPermission;
 import com.gitblit.models.UserModel;
 import com.gitblit.transport.ssh.IPublicKeyManager;
 import com.gitblit.transport.ssh.SshKey;
@@ -33,6 +34,7 @@ import com.gitblit.transport.ssh.commands.SshCommand;
 import com.gitblit.transport.ssh.commands.UsageExample;
 import com.gitblit.utils.FlipTable;
 import com.gitblit.utils.FlipTable.Borders;
+import com.gitblit.utils.StringUtils;
 import com.google.common.base.Joiner;
 
 /**
@@ -54,7 +56,7 @@ public class KeysDispatcher extends DispatchCommand {
        }
 
        @CommandMetaData(name = "add", description = "Add an SSH public key to your account")
-       @UsageExample(syntax = "cat ~/.ssh/id_rsa.pub | ${ssh} ${cmd} -", description = "Upload your SSH public key and add it to your account")
+       @UsageExample(syntax = "cat ~/.ssh/id_rsa.pub | ${ssh} ${cmd}", description = "Upload your SSH public key and add it to your account")
        public static class AddKey extends BaseKeyCommand {
 
                protected final Logger log = LoggerFactory.getLogger(getClass());
@@ -62,12 +64,33 @@ public class KeysDispatcher extends DispatchCommand {
                @Argument(metaVar = "<KEY>", usage = "the key(s) to add")
                private List<String> addKeys = new ArrayList<String>();
 
+               @Option(name = "--permission", aliases = { "-p" }, metaVar = "PERMISSION", usage = "set the key access permission")
+               private String permission;
+
+               @Override
+               protected String getUsageText() {
+                       String permissions = Joiner.on(", ").join(AccessPermission.SSHPERMISSIONS);
+                       StringBuilder sb = new StringBuilder();
+                       sb.append("Valid SSH public key permissions are:\n   ").append(permissions);
+                       return sb.toString();
+               }
+
                @Override
                public void run() throws IOException, UnloggedFailure {
                        String username = getContext().getClient().getUsername();
                        List<String> keys = readKeys(addKeys);
                        for (String key : keys) {
                                SshKey sshKey = parseKey(key);
+                               if (!StringUtils.isEmpty(permission)) {
+                                       AccessPermission ap = AccessPermission.fromCode(permission);
+                                       if (ap.exceeds(AccessPermission.NONE)) {
+                                               try {
+                                                       sshKey.setPermission(ap);
+                                               } catch (IllegalArgumentException e) {
+                                                       throw new UnloggedFailure(1, e.getMessage());
+                                               }
+                                       }
+                               }
                                getKeyManager().addKey(username, sshKey);
                                log.info("added SSH public key for {}", username);
                        }
@@ -167,14 +190,15 @@ public class KeysDispatcher extends DispatchCommand {
                }
 
                protected void asTable(List<SshKey> keys) {
-                       String[] headers = { "#", "Fingerprint", "Comment", "Type" };
+                       String[] headers = { "#", "Fingerprint", "Comment", "Permission", "Type" };
                        int len = keys == null ? 0 : keys.size();
                        Object[][] data = new Object[len][];
                        for (int i = 0; i < len; i++) {
                                // show 1-based index numbers with the fingerprint
                                // this is useful for comparing with "ssh-add -l"
                                SshKey k = keys.get(i);
-                               data[i] = new Object[] { (i + 1), k.getFingerprint(), k.getComment(), k.getAlgorithm() };
+                               data[i] = new Object[] { (i + 1), k.getFingerprint(), k.getComment(),
+                                               k.getPermission(), k.getAlgorithm() };
                        }
 
                        stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS));
@@ -211,9 +235,9 @@ public class KeysDispatcher extends DispatchCommand {
                }
 
                protected void asTable(int index, SshKey key) {
-                       String[] headers = { "#", "Fingerprint", "Comment", "Type" };
+                       String[] headers = { "#", "Fingerprint", "Comment", "Permission", "Type" };
                        Object[][] data = new Object[1][];
-                       data[0] = new Object[] { index, key.getFingerprint(), key.getComment(), key.getAlgorithm() };
+                       data[0] = new Object[] { index, key.getFingerprint(), key.getComment(), key.getPermission(), key.getAlgorithm() };
 
                        stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS));
                }