Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
cleanup and document
  • Loading branch information
billiegoose committed Dec 25, 2018
commit bdcfd0143bcf322be201ad59dfeadc015109401d
39 changes: 34 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# git-http-mock-server
# git-http-mock-server / git-ssh-mock-server
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.amrom.workers.dev%2Fisomorphic-git%2Fgit-http-mock-server.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.amrom.workers.dev%2Fisomorphic-git%2Fgit-http-mock-server?ref=badge_shield)


Clone and push to git repository test fixtures over HTTP.
Clone and push to git repository test fixtures over HTTP or SSH.

## What it does

Expand All @@ -22,6 +21,12 @@ It also supports HTTP Basic Auth password protection of repos so you can test ho

Using `isomorphic-git` and testing things from browsers? Fear not, `git-http-mock-server` includes appropriate CORS headers.

`git-ssh-mock-server` is similar, but because authentication happens before the client can say which repo
they are interested in, the authentication can't be customized per repository.
By default it allows anonymous SSH access. You can disable anonymous access and activate password authentication by setting the `GIT_SSH_MOCK_SERVER_PASSWORD` evironment variable.
(When password auth is activated, any username will work as long as the password matches the environment variable.)
Alternatively, you can set the `GIT_SSH_MOCK_SERVER_PUBKEY` environment variable to true to disable anonymous access and activate Public Key authentication. What key to use is explained in detail later in this document.

## How to use

```sh
Expand All @@ -34,14 +39,16 @@ Now `cd` to a directory in which you have some bare git repos and run this serve
> cd __fixtures__
> ls
test-repo1.git test-repo2.git imaginatively-named-repo.git
> git-http-mock-server
> git-http-mock-server &
> git-ssh-mock-server &
```

Now in another shell, clone and push away...
```sh
> git clone http://localhost:8174/test-repo1.git
> git clone http://localhost:8174/test-repo2.git
> git clone http://localhost:8174/imaginatively-named-repo.git
> git clone ssh://localhost:2222/imaginatively-named-repo.git
```

## Run in the background
Expand All @@ -64,8 +71,13 @@ Just be sure to run `start` and `stop` from the same working directory.
- `GIT_HTTP_MOCK_SERVER_ROUTE` default is `/`
- `GIT_HTTP_MOCK_SERVER_ROOT` default is `process.cwd()`
- `GIT_HTTP_MOCK_SERVER_ALLOW_ORIGIN` default is `*` (used for CORS)
- `GIT_SSH_MOCK_SERVER_PORT` default is 2222
- `GIT_SSH_MOCK_SERVER_ROUTE` default is `/`
- `GIT_SSH_MOCK_SERVER_ROOT` default is `process.cwd()`
- `GIT_SSH_MOCK_SERVER_PASSWORD` activate Password Authentication and use this password (leave blank to allow anonymous SSH access.)
- `GIT_SSH_MOCK_SERVER_PUBKEY` activate PubKey Authentication using the self-generated keypair (leave blank to allow anonymous SSH access.)

### .htpasswd support
### .htpasswd support (http-only)

You can place an Apache-style `.htpasswd` file in a bare repo to protect it with Basic Authentication.

Expand All @@ -80,13 +92,30 @@ testuser:$apr1$BRdvH4Mu$3HrpeyBrWiS88GcSPidgq/
If you don't have `htpasswd` on your machine, you can use [htpasswd](https://npm.im/htpasswd) which is
a cross-platform Node implementation of `htpasswd`.

### Public Key Auth support (ssh-only)

`git-ssh-mock-server` generates its own keypair using the system's native `ssh-keygen` the first time it's run,
in order to create encrypted SSH connections.
This key can be used to authenticate with the server as well!

1. Run `GIT_SSH_MOCK_SERVER_PUBKEY=true git-ssh-mock-server`
2. Try cloning (e.g. `git clone ssh://localhost:2222/imaginatively-named-repo.git`). It shouldn't work.
2. Run `git-ssh-mock-server exportKeys` which will copy the key files to `./id_rsa` and `./id_rsa.pub` in the working directory with the correct file permissions (`600`).
3. Run `ssh-add ./id_rsa`
4. Now try cloning. It works!
5. To clear the key from the ssh-agent, use `ssh-add -d ./id_rsa`

You can use `GIT_SSH_MOCK_SERVER_PUBKEY` and `GIT_SSH_MOCK_SERVER_PASSWORD` together, but using either one disables anonymous SSH access.

## Dependencies

- [basic-auth](https://ghub.io/basic-auth): node.js basic auth parser
- [buffer-equal-constant-time](https://ghub.io/buffer-equal-constant-time): Constant-time comparison of Buffers
- [chalk](https://ghub.io/chalk): Terminal string styling done right
- [fixturez](https://ghub.io/fixturez): Easily create and maintain test fixtures in the file system
- [git-http-backend](https://ghub.io/git-http-backend): serve a git repository over http
- [htpasswd-js](https://ghub.io/htpasswd-js): Pure JS htpasswd authentication
- [ssh2](https://ghub.io/ssh2): SSH2 client and server modules written in pure JavaScript for node.js

originally inspired by '[git-http-server](https://github.com/bahamas10/node-git-http-server)'

Expand Down
Empty file modified bin.js
100644 → 100755
Empty file.
70 changes: 44 additions & 26 deletions ssh-server.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const { spawn, spawnSync } = require('child_process')
var crypto = require('crypto')
var fs = require('fs')
var path = require('path')
var inspect = require('util').inspect

var buffersEqual = require('buffer-equal-constant-time')
var fixturez = require('fixturez')
Expand All @@ -21,38 +20,55 @@ new Promise((resolve, reject) => {
let pubKey = fs.readFileSync(path.join(__dirname, 'id_rsa.pub'))
return resolve({key, pubKey})
} catch (err) {
let proc = spawnSync('ssh-keygen', ['-C', '"git-ssh-mock-server@localhost"', '-N', '""', '-f', 'id_rsa'], {
cwd: __dirname,
shell: true
})
console.log(proc.stdout.toString('utf8'))
let key = fs.readFileSync(path.join(__dirname, 'id_rsa'))
let pubKey = fs.readFileSync(path.join(__dirname, 'id_rsa.pub'))
return resolve({key, pubKey})
try {
// Note: PEM is to workaround https://github.com/mscdex/ssh2/issues/746
let proc = spawnSync('ssh-keygen', ['-m', 'PEM', '-C', '"git-ssh-mock-server@localhost"', '-N', '""', '-f', 'id_rsa'], {
cwd: __dirname,
shell: true
})
console.log(proc.stdout.toString('utf8'))
let key = fs.readFileSync(path.join(__dirname, 'id_rsa'))
let pubKey = fs.readFileSync(path.join(__dirname, 'id_rsa.pub'))
return resolve({key, pubKey})
} catch (err) {
reject(err)
}
}
})
.then(keypair => {
if (process.argv[2] === 'exportKeys') {
fs.writeFileSync(path.join(process.cwd(), 'id_rsa'), keypair.key, { mode: 0o600, flag: 'wx' })
fs.writeFileSync(path.join(process.cwd(), 'id_rsa.pub'), keypair.pubKey, { mode: 0o600, flag: 'wx' })
process.exit()
}
var pubKey = ssh2.utils.genPublicKey(ssh2.utils.parseKey(keypair.pubKey))
var f = fixturez(config.root, {root: process.cwd(), glob: config.glob})

const PASSWORD_BUFFER = Buffer.from(process.env.GIT_SSH_MOCK_SERVER_PASSWORD || '')

new ssh2.Server({ hostKeys: [keypair.key] }, function (client) {
console.log('client connected')

client
.on('authentication', function (ctx) {
if (
ctx.method === 'password' &&
// Note: Don't do this in production code, see
// https://www.brendanlong.com/timing-attacks-and-usernames.html
// In node v6.0.0+, you can use `crypto.timingSafeEqual()` to safely
// compare two values.
ctx.username === 'foo' &&
ctx.password === 'bar'
) { ctx.accept() } else if (
ctx.method === 'publickey' &&
ctx.key.algo === pubKey.fulltype &&
buffersEqual(ctx.key.data, pubKey.public)
) {
ctx.method === 'none' &&
!process.env.GIT_SSH_MOCK_SERVER_PASSWORD &&
!process.env.GIT_SSH_MOCK_SERVER_PUBKEY
) {
ctx.accept()
} else if (
ctx.method === 'password' &&
process.env.GIT_SSH_MOCK_SERVER_PASSWORD &&
// After much thought... screw usernames.
buffersEqual(Buffer.from(ctx.password || ''), PASSWORD_BUFFER)
) {
ctx.accept()
} else if (
ctx.method === 'publickey' &&
ctx.key.algo === pubKey.fulltype &&
process.env.GIT_SSH_MOCK_SERVER_PUBKEY &&
buffersEqual(ctx.key.data, pubKey.public)
) {
if (ctx.signature) {
var verifier = crypto.createVerify(ctx.sigAlgo)
verifier.update(ctx.blob)
Expand All @@ -63,7 +79,9 @@ new Promise((resolve, reject) => {
// the validity of the given public key
ctx.accept()
}
} else ctx.reject()
} else {
ctx.reject()
}
})
.on('ready', function () {
console.log('client authenticated')
Expand All @@ -75,6 +93,7 @@ new Promise((resolve, reject) => {
let [_, command, gitdir] = info.command.match(/^([a-z-]+) '(.*)'/)
// Only allow these two commands to be executed
if (command !== 'git-upload-pack' && command !== 'git-receive-pack') {
console.log('invalid command:', command)
return reject()
}
if (gitdir !== path.posix.normalize(gitdir)) {
Expand Down Expand Up @@ -111,11 +130,10 @@ new Promise((resolve, reject) => {
})
})
.on('end', function () {
console.log('Client disconnected')
console.log('client disconnected')
})
}
).listen(process.env.GIT_SSH_MOCK_SERVER_PORT || 2222, '127.0.0.1', function () {
console.log('Listening on port ' + this.address().port)
})

})
})