├── .gitignore ├── start.sh ├── git-shell-commands └── no-interactive-login ├── config ├── docker.json └── default.json ├── Makefile ├── Dockerfile ├── README.md ├── cmd └── authorized-keys │ └── main.go └── vendor └── vendor.json /.gitignore: -------------------------------------------------------------------------------- 1 | .container_id 2 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # -D flag avoids executing sshd as a daemon 3 | # -d flag enables the debug mode 4 | echo "Starting ssh daemon..." 5 | /usr/sbin/sshd -D 6 | -------------------------------------------------------------------------------- /git-shell-commands/no-interactive-login: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | printf '%s\n' "Welcome to git-server-docker!" 3 | printf '%s\n' "You've successfully authenticated, but I do not" 4 | printf '%s\n' "provide interactive shell access." 5 | exit 128 6 | -------------------------------------------------------------------------------- /config/docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "mongo": { 3 | "url": "mongodb://mongo:27017/git" 4 | }, 5 | "logger": { 6 | "dir": "/var/log/git", 7 | "level": "debug", 8 | "maxAge": "720h", 9 | "suffixPattern": ".%Y%m%d", 10 | "linkName": "access_log" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "mongo": { 3 | "url": "mongodb://localhost:27017/git" 4 | }, 5 | "logger": { 6 | "dir": "/var/log/git", 7 | "level": "debug", 8 | "maxAge": "720h", 9 | "suffixPattern": ".%Y%m%d", 10 | "linkName": "access_log" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SSH_FLAGS := -D -e 2 | 3 | 4 | DOCKER_NETWORK := docker 5 | 6 | 7 | GITORBIT_IMAGE := yoanlin/gitorbit 8 | GITORBIT_HOSTPORT := 2022 9 | GITORBIT_CONTAINER_NAME := gitorbit 10 | 11 | CID_FILE := .container_id 12 | 13 | configmap.yaml: 14 | kubectl create configmap git-server-config --from-file=mongo.json=config/k8s.json -o yaml --dry-run > $@ 15 | 16 | config: configmap.yaml 17 | kubectl create configmap git-server-config --from-file=mongo.json=config/k8s.json 18 | 19 | stop: 20 | [[ -f $(CID_FILE) ]] && docker stop $(GITORBIT_CONTAINER_NAME) $$(cat $(CID_FILE)) || true 21 | rm -f $(CID_FILE) 22 | 23 | push: 24 | docker push $(GITORBIT_IMAGE) 25 | 26 | build: 27 | docker build --build-arg CACHE=$$(date +%s) --tag $(GITORBIT_IMAGE) . 28 | 29 | run: stop 30 | # -e Write debug logs to standard error instead of the system log. 31 | docker network create $(DOCKER_NETWORK) || true 32 | docker run --name $(GITORBIT_CONTAINER_NAME) \ 33 | --network $(DOCKER_NETWORK) \ 34 | --detach \ 35 | --rm \ 36 | --publish $(GITORBIT_HOSTPORT):22 \ 37 | $(GITORBIT_IMAGE) /usr/sbin/sshd $(SSH_FLAGS) > $(CID_FILE) 38 | cat $(CID_FILE) 39 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10-alpine3.7 AS src 2 | RUN apk add --no-cache git 3 | WORKDIR /go/src 4 | RUN go get -u github.com/kardianos/govendor 5 | COPY cmd /go/src/cmd 6 | COPY vendor /go/src/vendor 7 | RUN govendor sync -v 8 | RUN go install ./cmd/authorized-keys 9 | 10 | FROM alpine:3.7 11 | RUN apk update 12 | RUN apk add openssh git shadow 13 | RUN apk add curl 14 | RUN apk add bash 15 | RUN apk add mongodb 16 | 17 | # Generate host keys 18 | RUN ssh-keygen -A 19 | WORKDIR /home/git 20 | 21 | # --create-home create the user's home directory 22 | # --user-group create a group with the same name as the user 23 | # RUN adduser -D -s /usr/bin/git-shell git \ 24 | RUN useradd --user-group --create-home --shell /bin/bash git \ 25 | && usermod -p '*' git \ 26 | && mkdir -p /home/git/keys /home/git/.ssh \ 27 | && chown -R git:git /home/git \ 28 | && chmod 700 /home/git/keys /home/git/.ssh \ 29 | && find /home/git/.ssh /home/git/keys -type f -exec chmod 600 {} \; 30 | 31 | ARG REPOS_DIR=/repositories 32 | RUN mkdir -p $REPOS_DIR \ 33 | && chmod 700 $REPOS_DIR \ 34 | && git init --bare $REPOS_DIR/test.git \ 35 | && chown -R git:git $REPOS_DIR \ 36 | && find $REPOS_DIR -type f -exec chmod 600 {} \; 37 | 38 | # This is a login shell for SSH accounts to provide restricted Git access. 39 | # It permits execution only of server-side Git commands implementing the 40 | # pull/push functionality, plus custom commands present in a subdirectory 41 | # named git-shell-commands in the user’s home directory. 42 | # More info: https://git-scm.com/docs/git-shell 43 | COPY git-shell-commands /home/git/git-shell-commands 44 | 45 | # COPY git-authorized-keys.sh /authorized-keys 46 | ARG CACHE 47 | COPY --from=src /go/bin/authorized-keys /authorized-keys 48 | 49 | COPY git-command.sh /git-command 50 | 51 | RUN mkdir -p /var/log/git && chown -R git:git /var/log/git 52 | ARG CONFIG=config/docker.json 53 | 54 | RUN mkdir -p /etc/gitorbit 55 | COPY $CONFIG /etc/gitorbit/authorized_keys.json 56 | 57 | # sshd_config file is edited for enable access key and disable access password 58 | # COPY sshd_config /etc/ssh/sshd_config 59 | COPY start.sh /start.sh 60 | 61 | # %f token passes the fingerprint SHA256:wi76P4RkpL9gWJx/p1Jr35r0Ri0/50NFPI4cVbT/4vc 62 | RUN sed -i -e "/AuthorizedKeysCommand none/c\AuthorizedKeysCommand /authorized-keys -config /etc/gitorbit/authorized_keys.json %f" /etc/ssh/sshd_config 63 | RUN sed -i -e "/AuthorizedKeysCommandUser /c\AuthorizedKeysCommandUser git" /etc/ssh/sshd_config 64 | 65 | # Disable password authentication 66 | RUN sed -i -e "/#PasswordAuthentication/c\PasswordAuthentication no" /etc/ssh/sshd_config 67 | 68 | # Use public key authentication 69 | RUN sed -i -e "/#PubkeyAuthentication/c\PubkeyAuthentication yes" /etc/ssh/sshd_config 70 | 71 | # Enable password-locked account 72 | # see also https://unix.stackexchange.com/questions/193066/how-to-unlock-account-for-public-key-ssh-authorization-but-not-for-password-aut 73 | # RUN sed -i -e "/#UsePAM/c\UsePAM yes" /etc/ssh/sshd_config 74 | 75 | RUN sed -i -e "/#LogLevel /c\LogLevel DEBUG" /etc/ssh/sshd_config 76 | 77 | EXPOSE 22 78 | CMD ["sh", "/start.sh"] 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitOrbit 2 | 3 | *WORKING IN PRRGRESS* 4 | 5 | ## Features 6 | 7 | - MongoDB-based ssh public key authentication. 8 | - Multiple ssh public key support for each user. 9 | - Kubernetes deployment support. 10 | - Support permission customization. 11 | 12 | - LDAP support (WIP) 13 | - Default Permission Configuration (WIP) 14 | 15 | ## Install 16 | 17 | ### Setup with Docker 18 | 19 | By default, the git-server image includes a config file: 20 | 21 | ```json 22 | { 23 | "mongo": { 24 | "url": "mongodb://mongo:27017/git" 25 | }, 26 | "logger": { 27 | "dir": "/var/log/git", 28 | "level": "debug", 29 | "maxAge": "720h", 30 | "suffixPattern": ".%Y%m%d", 31 | "linkName": "access_log" 32 | } 33 | } 34 | ``` 35 | 36 | Which uses `mongodb://mongo:27017/git` as the connection string for the mongo client. 37 | 38 | And so you will need to create a mongodb container instance with the name `mongo`, so that 39 | the client can connect to your mongodb server. 40 | 41 | First, you need to create a network for sharing the mongodb network connection: 42 | 43 | ```sh 44 | docker network create docker 45 | ``` 46 | 47 | Start the mongodb server with the network that we just created: 48 | 49 | ```sh 50 | docker run --name mongo \ 51 | --restart=always \ 52 | --publish 27018:27017 \ 53 | --network docker \ 54 | --detach mongo 55 | ``` 56 | 57 | Get your public key footprint and the key: 58 | 59 | ```sh 60 | SSH_PUBKEY=$(cat ~/.ssh/id_rsa.pub) 61 | SSH_PUBKEY_FINGERPRINT=$(ssh-keygen -lf ~/.ssh/id_rsa.pub | awk '{ print $2 }') 62 | GIT_USER_EMAIL=$(git config user.email) 63 | ``` 64 | 65 | Insert the user in the mongodb: 66 | 67 | ```sh 68 | mongo mongodb://localhost:27018/git --eval " 69 | db.users.insert({ 70 | email: \"$GIT_USER_EMAIL\", 71 | keys: [{ 72 | \"name\": \"dev\", 73 | \"fingerprint\": \"$SSH_PUBKEY_FINGERPRINT\", 74 | \"key\": \"$SSH_PUBKEY\" 75 | }] 76 | })" 77 | ``` 78 | 79 | Ensure that you can find the user by the fingerprint: 80 | 81 | ```sh 82 | mongo mongodb://localhost:27018/git --eval "db.users.find({ 83 | \"keys.fingerprint\": \"$SSH_PUBKEY_FINGERPRINT\" }).pretty()" 84 | ``` 85 | 86 | Build and Run the git server: 87 | 88 | ```sh 89 | make stop build run 90 | ``` 91 | 92 | Add an entry in your .ssh/config: 93 | 94 | ```ssh 95 | Host git-server 96 | HostName localhost 97 | User git 98 | Port 2022 99 | LogLevel INFO 100 | PreferredAuthentications publickey 101 | IdentityFile ~/.ssh/id_rsa 102 | ``` 103 | 104 | Now you can try git clone: 105 | 106 | ```sh 107 | git clone git@git-server:test.git 108 | ``` 109 | 110 | ### Kubernetes 111 | 112 | Copy the config from the template: 113 | 114 | ``` 115 | cp -v config/default.json k8s.json 116 | ``` 117 | 118 | Configure your settings: 119 | 120 | ``` 121 | vim k8s.json 122 | ``` 123 | 124 | Create configmap object in your cluster: 125 | 126 | ``` 127 | kubectl create configmap git-server-config --from-file=authorized_keys.json=k8s.json 128 | ``` 129 | 130 | To be continued. 131 | 132 | 133 | 134 | ## License 135 | 136 | MIT License 137 | 138 | ## Author 139 | 140 | Yo-An Lin 141 | -------------------------------------------------------------------------------- /cmd/authorized-keys/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | 10 | "github.com/c9s/ssh-authorizedkey" 11 | "github.com/linkernetworks/foundation/logger" 12 | "github.com/linkernetworks/foundation/service/mongo" 13 | 14 | "gopkg.in/mgo.v2" 15 | "gopkg.in/mgo.v2/bson" 16 | 17 | "golang.org/x/crypto/ssh" 18 | ) 19 | 20 | func fingerprintKeys(keys []PublicKey) map[string]ssh.PublicKey { 21 | // load the keys with fingerprints 22 | keyMap := map[string]ssh.PublicKey{} 23 | for _, key := range keys { 24 | // Parse the key, other info ignored 25 | pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key.Key)) 26 | if err != nil { 27 | panic(err) 28 | } 29 | // Get the fingerprint 30 | fp := ssh.FingerprintSHA256(pk) 31 | keyMap[fp] = pk 32 | } 33 | return keyMap 34 | } 35 | 36 | type PublicKey struct { 37 | // The name of the key 38 | Name string `json:"name"` 39 | 40 | // The base64 encoded public key 41 | Key string `json:"key"` 42 | 43 | // The fingerprint of the public key 44 | Fingerprint string `json:"fingerprint"` 45 | } 46 | 47 | type User struct { 48 | ID bson.ObjectId `bson:"_id" json:"_id"` 49 | Keys []PublicKey `bson:"keys" json:"keys"` 50 | } 51 | 52 | type MongoConfig struct { 53 | mongo.MongoConfig 54 | UserCollection string `json:"userCollection"` 55 | } 56 | 57 | type Config struct { 58 | Mongo MongoConfig `json:"mongo"` 59 | Logger logger.LoggerConfig `json:"logger"` 60 | } 61 | 62 | // To get your fingerprint via command-line 63 | // 64 | // ssh-keygen -lf ~/.ssh/id_rsa.pub 65 | // 66 | // Run this program: 67 | // 68 | // go run main.go SHA256:xxxxxx 69 | // go run main.go $(ssh-keygen -lf ~/.ssh/id_rsa.pub | awk '{ print $2 }') 70 | // 71 | func main() { 72 | var fingerprintInput string 73 | var configFile string 74 | var logDir string = "" 75 | var mongoURL string = "" 76 | var defaultMongoUserCollection string = "users" 77 | 78 | flag.StringVar(&fingerprintInput, "fingerprint", "", "ssh public key fingerprint") 79 | flag.StringVar(&configFile, "config", "", "config file") 80 | flag.StringVar(&mongoURL, "mongo", "", "mongourl") 81 | flag.StringVar(&logDir, "logDir", "", "log dir") 82 | flag.Parse() 83 | 84 | // fingerprint is a MUST 85 | if fingerprintInput == "" && len(flag.Args()) > 0 { 86 | fingerprintInput = flag.Arg(0) 87 | } 88 | 89 | if fingerprintInput == "" { 90 | logger.Fatal("fingerprint is required") 91 | } 92 | 93 | // config file is a MUST 94 | if configFile == "" { 95 | logger.Fatal("-config option is required.") 96 | } 97 | 98 | configJSON, err := ioutil.ReadFile(configFile) 99 | if err != nil { 100 | logger.Fatalf("Failed to read config file: %v", err) 101 | } 102 | 103 | var config Config 104 | if err := json.Unmarshal(configJSON, &config); err != nil { 105 | logger.Fatalf("Failed to parse config JSON: %v", err) 106 | } 107 | 108 | logger.Debugln("Setting logger config...") 109 | if logDir != "" { 110 | config.Logger.Dir = logDir 111 | } 112 | logger.Setup(config.Logger) 113 | 114 | if len(mongoURL) > 0 { 115 | logger.Debugf("Using mongo URL from option: %s", mongoURL) 116 | config.Mongo.Url = mongoURL 117 | } 118 | 119 | if config.Mongo.UserCollection == "" { 120 | config.Mongo.UserCollection = defaultMongoUserCollection 121 | } 122 | 123 | logger.Debugf("Connecting to mongo: %s", config.Mongo.Url) 124 | session, err := mgo.Dial(config.Mongo.Url) 125 | if err != nil { 126 | logger.Fatal(err) 127 | } 128 | logger.Debugf("Mongo connected") 129 | 130 | logger.Infof("Finding user key with fingerprint %s", fingerprintInput) 131 | var user User 132 | // session.DB("") 133 | if err := session.DB(config.Mongo.Database).C(config.Mongo.UserCollection).Find(bson.M{ 134 | "keys.fingerprint": fingerprintInput, 135 | }).One(&user); err != nil { 136 | logger.Fatal(err) 137 | } 138 | 139 | // load the entries 140 | logger.Infof("Found user keys: %d", len(user.Keys)) 141 | for fp, pk := range fingerprintKeys(user.Keys) { 142 | logger.Infof("Checking user key: %s", fp) 143 | if fingerprintInput == fp { 144 | logger.Infof("Matched user key: %s", fp) 145 | entry := authorizedkey.AuthorizedKeyEntry{ 146 | KeyType: pk.Type(), 147 | Key: base64.StdEncoding.EncodeToString(pk.Marshal()), 148 | Command: "/git-command " + user.ID.Hex(), 149 | // Environment: map[string]string{"foo": "bar"}, 150 | NoAgentForwarding: true, 151 | NoX11Forwarding: true, 152 | NoPty: true, 153 | NoPortForwarding: true, 154 | } 155 | fmt.Println(entry) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "mi3yJHxA/cvt5nTccQ9kSTvk7Dk=", 7 | "path": "github.com/c9s/ssh-authorizedkey", 8 | "revision": "fafa5e75503e83bde50e2dabaddda765a16c99e2", 9 | "revisionTime": "2018-07-31T01:11:26Z" 10 | }, 11 | { 12 | "checksumSHA1": "D5jVUTl7HrJrdH95IxfLeSZifKQ=", 13 | "path": "github.com/lestrrat/go-file-rotatelogs", 14 | "revision": "d3151e2a480fdcd05fb97102f5310a47d96274c4", 15 | "revisionTime": "2018-02-23T00:06:17Z" 16 | }, 17 | { 18 | "checksumSHA1": "VFdd7/gnXSPEQqUMSI9ufakRMnI=", 19 | "path": "github.com/lestrrat/go-strftime", 20 | "revision": "ba3bf9c1d0421aa146564a632931730344f1f9f1", 21 | "revisionTime": "2018-02-20T04:22:22Z" 22 | }, 23 | { 24 | "checksumSHA1": "05C/6F2zMBpmB50/FA3OeXbzVEA=", 25 | "path": "github.com/linkernetworks/foundation/logger", 26 | "revision": "2c3a871093e1486a54d56f2e38ed23b6908ac004", 27 | "revisionTime": "2018-07-20T04:12:22Z" 28 | }, 29 | { 30 | "checksumSHA1": "bBKttDjHaaiu9XdHE9N2gdFA+0M=", 31 | "path": "github.com/linkernetworks/foundation/service/mongo", 32 | "revision": "2c3a871093e1486a54d56f2e38ed23b6908ac004", 33 | "revisionTime": "2018-07-20T04:12:22Z" 34 | }, 35 | { 36 | "checksumSHA1": "SEnjvwVyfuU2xBaOfXfwPD5MZqk=", 37 | "path": "github.com/mattn/go-colorable", 38 | "revision": "efa589957cd060542a26d2dd7832fd6a6c6c3ade", 39 | "revisionTime": "2018-03-10T13:32:14Z" 40 | }, 41 | { 42 | "checksumSHA1": "w5RcOnfv5YDr3j2bd1YydkPiZx4=", 43 | "path": "github.com/mattn/go-isatty", 44 | "revision": "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c", 45 | "revisionTime": "2017-11-07T05:05:31Z" 46 | }, 47 | { 48 | "checksumSHA1": "CIK3BBNX3nuUQCmNqTQydNfMNKI=", 49 | "path": "github.com/mgutz/ansi", 50 | "revision": "9520e82c474b0a04dd04f8a40959027271bab992", 51 | "revisionTime": "2017-02-06T15:57:36Z" 52 | }, 53 | { 54 | "checksumSHA1": "ljd3FhYRJ91cLZz3wsH9BQQ2JbA=", 55 | "path": "github.com/pkg/errors", 56 | "revision": "816c9085562cd7ee03e7f8188a1cfd942858cded", 57 | "revisionTime": "2018-03-11T21:45:15Z" 58 | }, 59 | { 60 | "checksumSHA1": "sH2ehBZuEJh2Ro/Ro4BpehZmu9A=", 61 | "path": "github.com/rifflock/lfshook", 62 | "revision": "62f5e0662816a462697d29f396fd19376d17f65b", 63 | "revisionTime": "2018-03-13T18:34:24Z" 64 | }, 65 | { 66 | "checksumSHA1": "Jv7Wjp5oiDYAW/pGZzNPzs0yyOQ=", 67 | "path": "github.com/sirupsen/logrus", 68 | "revision": "a4096716b036d07e715731665d400a3bddbe9078", 69 | "revisionTime": "2018-07-29T08:37:48Z" 70 | }, 71 | { 72 | "checksumSHA1": "L1vKtjhZ2Lej0kfsLaTUm7ps/Yg=", 73 | "path": "github.com/x-cray/logrus-prefixed-formatter", 74 | "revision": "bb2702d423886830dee131692131d35648c382e2", 75 | "revisionTime": "2017-07-31T09:11:26Z" 76 | }, 77 | { 78 | "checksumSHA1": "IQkUIOnvlf0tYloFx9mLaXSvXWQ=", 79 | "path": "golang.org/x/crypto/curve25519", 80 | "revision": "c126467f60eb25f8f27e5a981f32a87e3965053f", 81 | "revisionTime": "2018-07-23T15:26:11Z" 82 | }, 83 | { 84 | "checksumSHA1": "2LpxYGSf068307b7bhAuVjvzLLc=", 85 | "path": "golang.org/x/crypto/ed25519", 86 | "revision": "c126467f60eb25f8f27e5a981f32a87e3965053f", 87 | "revisionTime": "2018-07-23T15:26:11Z" 88 | }, 89 | { 90 | "checksumSHA1": "0JTAFXPkankmWcZGQJGScLDiaN8=", 91 | "path": "golang.org/x/crypto/ed25519/internal/edwards25519", 92 | "revision": "c126467f60eb25f8f27e5a981f32a87e3965053f", 93 | "revisionTime": "2018-07-23T15:26:11Z" 94 | }, 95 | { 96 | "checksumSHA1": "cZxP1MWcUqAi4Q3AUMDE1uAWZNc=", 97 | "path": "golang.org/x/crypto/internal/chacha20", 98 | "revision": "c126467f60eb25f8f27e5a981f32a87e3965053f", 99 | "revisionTime": "2018-07-23T15:26:11Z" 100 | }, 101 | { 102 | "checksumSHA1": "/U7f2gaH6DnEmLguVLDbipU6kXU=", 103 | "path": "golang.org/x/crypto/internal/subtle", 104 | "revision": "c126467f60eb25f8f27e5a981f32a87e3965053f", 105 | "revisionTime": "2018-07-23T15:26:11Z" 106 | }, 107 | { 108 | "checksumSHA1": "vKbPb9fpjCdzuoOvajOJnYfHG2g=", 109 | "path": "golang.org/x/crypto/poly1305", 110 | "revision": "c126467f60eb25f8f27e5a981f32a87e3965053f", 111 | "revisionTime": "2018-07-23T15:26:11Z" 112 | }, 113 | { 114 | "checksumSHA1": "5UDaK1KsPOI7P/Q1b17FnNno36o=", 115 | "path": "golang.org/x/crypto/ssh", 116 | "revision": "c126467f60eb25f8f27e5a981f32a87e3965053f", 117 | "revisionTime": "2018-07-23T15:26:11Z" 118 | }, 119 | { 120 | "checksumSHA1": "BGm8lKZmvJbf/YOJLeL1rw2WVjA=", 121 | "path": "golang.org/x/crypto/ssh/terminal", 122 | "revision": "c126467f60eb25f8f27e5a981f32a87e3965053f", 123 | "revisionTime": "2018-07-23T15:26:11Z" 124 | }, 125 | { 126 | "checksumSHA1": "YVhxr50aj4r9QjM4CY52K45XOJY=", 127 | "path": "golang.org/x/sys/unix", 128 | "revision": "bd9dbc187b6e1dacfdd2722a87e83093c2d7bd6e", 129 | "revisionTime": "2018-07-27T21:32:08Z" 130 | }, 131 | { 132 | "checksumSHA1": "k3aXicSfO+3+JmTNstuZrsTCRyI=", 133 | "path": "golang.org/x/sys/windows", 134 | "revision": "bd9dbc187b6e1dacfdd2722a87e83093c2d7bd6e", 135 | "revisionTime": "2018-07-27T21:32:08Z" 136 | }, 137 | { 138 | "checksumSHA1": "bS5Kp6YjeXz4nvvS55CqIBP+HzM=", 139 | "path": "gopkg.in/mgo.v2", 140 | "revision": "9856a29383ce1c59f308dd1cf0363a79b5bef6b5", 141 | "revisionTime": "2018-07-05T11:36:04Z" 142 | }, 143 | { 144 | "checksumSHA1": "/xRHTpN8WOK4nmZjJ1f96ER1b/o=", 145 | "path": "gopkg.in/mgo.v2/bson", 146 | "revision": "9856a29383ce1c59f308dd1cf0363a79b5bef6b5", 147 | "revisionTime": "2018-07-05T11:36:04Z" 148 | }, 149 | { 150 | "checksumSHA1": "XQsrqoNT1U0KzLxOFcAZVvqhLfk=", 151 | "path": "gopkg.in/mgo.v2/internal/json", 152 | "revision": "9856a29383ce1c59f308dd1cf0363a79b5bef6b5", 153 | "revisionTime": "2018-07-05T11:36:04Z" 154 | }, 155 | { 156 | "checksumSHA1": "LEvMCnprte47qdAxWvQ/zRxVF1U=", 157 | "path": "gopkg.in/mgo.v2/internal/sasl", 158 | "revision": "9856a29383ce1c59f308dd1cf0363a79b5bef6b5", 159 | "revisionTime": "2018-07-05T11:36:04Z" 160 | }, 161 | { 162 | "checksumSHA1": "+1WDRPaOphSCmRMxVPIPBV4aubc=", 163 | "path": "gopkg.in/mgo.v2/internal/scram", 164 | "revision": "9856a29383ce1c59f308dd1cf0363a79b5bef6b5", 165 | "revisionTime": "2018-07-05T11:36:04Z" 166 | } 167 | ], 168 | "rootPath": "work/git-server" 169 | } 170 | --------------------------------------------------------------------------------