-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
My basic idea, moved from audit_couchdb
- Loading branch information
Showing
8 changed files
with
506 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// The probe_couchdb API | ||
// | ||
|
||
module.exports = { "CouchDB" : require('./couch').CouchDB | ||
, "Database" : require('./db').Database | ||
, "DesignDocument": require('./ddoc').DesignDocument | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
#!/usr/bin/env node | ||
// The probe_couchdb command-line interface. | ||
// | ||
|
||
var fs = require('fs') | ||
, assert = require('assert') | ||
, probe_couchdb = require('./api') | ||
; | ||
|
||
function usage() { | ||
console.log([ 'usage: probe_couchdb <URL>' | ||
, '' | ||
].join("\n")); | ||
} | ||
|
||
var couch_url = process.argv[2]; | ||
if(!couch_url) { | ||
usage(); | ||
process.exit(1); | ||
} | ||
|
||
if(!/^https?:\/\//.test(couch_url)) | ||
couch_url = 'http://' + couch_url; | ||
|
||
var couch = new probe_couchdb.CouchDB(); | ||
couch.url = couch_url; | ||
couch.log.setLevel('debug'); | ||
|
||
var count = 0; | ||
function line() { | ||
count += 1; | ||
var parts = [count].concat(Array.prototype.slice.apply(arguments)); | ||
console.log(parts.join("\t")); | ||
} | ||
|
||
function handler_for(ev_name) { | ||
return function event_handler(obj) { | ||
line(ev_name, JSON.stringify(obj)); | ||
} | ||
} | ||
|
||
; ['couchdb', 'dbs', 'session', 'config'].forEach(function(ev_name) { | ||
couch.on(ev_name, handler_for(ev_name)); | ||
}) | ||
|
||
couch.on('users', function show_users(users) { | ||
line('users', '(' + users.length + ' users, including the anonymous user)'); | ||
}) | ||
|
||
couch.on('db', function(db) { | ||
; ['metadata', 'security', 'ddoc_ids'].forEach(function(ev_name) { | ||
db.on(ev_name, handler_for([ev_name, db.name].join(':'))); | ||
}) | ||
|
||
db.on('ddoc', function(ddoc) { | ||
var path = [db.name, ddoc.id].join('/'); | ||
|
||
; ['info'].forEach(function(ev_name) { | ||
ddoc.on(ev_name, handler_for([ev_name, path].join(':'))); | ||
}) | ||
|
||
ddoc.on('body', function show_ddoc_body(body) { | ||
line(['body', path].join(':'), '(' + JSON.stringify(body).length + ' characters; ' + Object.keys(body).length + ' top-level keys)'); | ||
}) | ||
}) | ||
}) | ||
|
||
line("Number", "Event", "Data"); | ||
couch.start(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// Probe all details about a CouchDB. | ||
// | ||
|
||
var lib = require('./lib') | ||
, util = require('util') | ||
, Emitter = require('./emitter').Emitter | ||
, Database = require('./db').Database | ||
; | ||
|
||
var MAX_USER_DEFAULT = 1000; | ||
|
||
function CouchDB () { | ||
var self = this; | ||
Emitter.call(self); | ||
|
||
self.url = null; | ||
self.only_dbs = null; | ||
self.max_users = MAX_USER_DEFAULT; | ||
|
||
self.on('start', function ping_root() { | ||
self.log.debug("Pinging: " + self.url); | ||
self.request({uri:self.url}, function(er, resp, body) { | ||
if(er) | ||
throw er; | ||
else if(resp.statusCode !== 200 || body.couchdb !== "Welcome") | ||
throw new Error("Bad welcome from " + self.url + ": " + JSON.stringify(body)); | ||
else | ||
self.x_emit('couchdb', body); | ||
}) | ||
}) | ||
|
||
self.on('couchdb', function probe_databases(hello) { | ||
var all_dbs = lib.join(self.url, '/_all_dbs'); | ||
self.log.debug("Scanning databases: " + all_dbs); | ||
self.request({uri:all_dbs}, function(er, resp, body) { | ||
if(er) throw er; | ||
if(resp.statusCode !== 200 || !Array.isArray(body)) | ||
throw new Error("Bad _all_dbs from " + all_dbs + ": " + JSON.stringify(body)); | ||
|
||
self.log.debug(self.url + ' has ' + body.length + ' databases'); | ||
var dbs = body.filter(function(db) { return !self.only_dbs || self.only_dbs.indexOf(db) !== -1 }); | ||
self.x_emit('dbs', dbs); | ||
}) | ||
}) | ||
|
||
self.on('dbs', function emit_db_probes(dbs) { | ||
self.log.debug('Creating probes for ' + dbs.length + ' dbs'); | ||
dbs.forEach(function(db_name) { | ||
var db = new Database; | ||
db.couch = self.url; | ||
db.name = db_name; | ||
self.x_emit('db', db); | ||
db.start(); | ||
}) | ||
}) | ||
|
||
self.on('couchdb', function probe_session(hello) { | ||
var session_url = lib.join(self.url, '/_session'); | ||
self.log.debug("Checking login session: " + session_url); | ||
self.request({uri:session_url}, function(er, resp, session) { | ||
if(er) throw er; | ||
if(resp.statusCode !== 200 || (!session) || session.ok !== true) | ||
throw new Error("Bad _session from " + session_url + ": " + JSON.stringify(session)); | ||
|
||
self.log.debug("Received session: " + JSON.stringify(session)); | ||
if( ((session.userCtx || {}).roles || []).indexOf('_admin') === -1 ) | ||
self.log.warn("Results will be incomplete without _admin access"); | ||
self.x_emit('session', session); | ||
}) | ||
}) | ||
|
||
self.on('couchdb', function(hello) { | ||
var config_url = lib.join(self.url, '/_config'); | ||
self.log.debug("Checking config: " + config_url); | ||
self.request({uri:config_url}, function(er, resp, config) { | ||
if(er) throw er; | ||
if(resp.statusCode !== 200 || (typeof config !== 'object')) { | ||
self.log.debug("Bad config response: " + JSON.stringify(config)); | ||
config = null; | ||
} | ||
self.x_emit('config', config); | ||
}) | ||
}) | ||
|
||
self.on('config', function(config) { | ||
// Once the config is known, the list of users can be established. | ||
var auth_db = config && config.couch_httpd_auth && config.couch_httpd_auth.authentication_db; | ||
if(!auth_db) { | ||
auth_db = '_users'; | ||
self.log.warn('authentication_db not found in config; trying ' + JSON.stringify(auth_db)); | ||
} | ||
|
||
// Of course, the anonymous user is always known to exist. | ||
var anonymous_users = [ { name:null, roles: [] } ]; | ||
|
||
var auth_db_url = lib.join(self.url, encodeURIComponent(auth_db).replace(/^_design%2[fF]/, '_design/')); | ||
self.log.debug("Checking auth_db: " + auth_db_url); | ||
self.request({uri:auth_db_url}, function(er, resp, body) { | ||
if(er) throw er; | ||
if(resp.statusCode !== 200 || typeof config !== 'object') { | ||
self.log.warn("Can not access authentication_db: " + auth_db_url); | ||
// Signal the end of the users discovery. | ||
self.x_emit('users', anonymous_users); | ||
} else if(body.doc_count > self.max_users) { | ||
throw new Error("Too many users; you must add a view to process them"); | ||
// TODO | ||
} else { | ||
var users_query = lib.join(auth_db_url, '/_all_docs' | ||
+ '?include_docs=true' | ||
+ '&startkey=' + encodeURIComponent(JSON.stringify("org.couchdb.user:")) | ||
+ '&endkey=' + encodeURIComponent(JSON.stringify("org.couchdb.user;")) | ||
); | ||
self.log.debug("Fetching all users: " + users_query); | ||
self.request({uri:users_query}, function(er, resp, body) { | ||
if(er) throw er; | ||
if(resp.statusCode !== 200 || !Array.isArray(body.rows)) | ||
throw new Error("Failed to fetch user listing from " + users_query + ": " + JSON.stringify(body)); | ||
|
||
var users = body.rows.map(function(row) { return row.doc }); | ||
self.log.debug("Found " + (users.length+1) + " users (including anonymous): " + auth_db_url); | ||
self.x_emit('users', anonymous_users.concat(users)); | ||
}) | ||
} | ||
}) | ||
}) | ||
} // CouchDB | ||
|
||
util.inherits(CouchDB, Emitter); | ||
|
||
CouchDB.prototype.start = function() { | ||
var self = this; | ||
|
||
if(!self.url) | ||
throw new Error("url required"); | ||
|
||
self.x_emit('start'); | ||
} | ||
|
||
module.exports = { "CouchDB": CouchDB | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Probe all details about a CouchDB database. | ||
// | ||
|
||
var lib = require('./lib') | ||
, util = require('util') | ||
, assert = require('assert') | ||
, Emitter = require('./emitter').Emitter | ||
, DesignDocument = require('./ddoc').DesignDocument | ||
; | ||
|
||
function Database () { | ||
var self = this; | ||
Emitter.call(self); | ||
|
||
self.couch = null; | ||
self.name = null; | ||
self.url = null; | ||
|
||
self.on('start', function probe_metadata() { | ||
self.log.debug("Fetching db metadata: " + self.url); | ||
self.request({uri:self.url}, function(er, resp, info) { | ||
if(er) | ||
throw er; | ||
else if(resp.statusCode === 401 && typeof info === 'object' && info.error === 'unauthorized') | ||
// Indicate no read permission. | ||
self.x_emit('metadata', null); | ||
else if(resp.statusCode === 200 && typeof info === 'object') | ||
self.x_emit('metadata', info); | ||
else | ||
throw new Error("Unknown db responses: " + JSON.stringify(info)); | ||
}) | ||
}) | ||
|
||
self.on('start', function probe_security_object() { | ||
var sec_url = lib.join(self.url, '/_security'); | ||
self.log.debug("Fetching db security data: " + sec_url); | ||
self.request({uri:sec_url}, function(er, resp, security) { | ||
if(er) | ||
throw er; | ||
else if(resp.statusCode === 401 && typeof security === 'object' && security.error === 'unauthorized') | ||
// Indicate no read permission. | ||
self.x_emit('security', null); | ||
else if(resp.statusCode === 200 && typeof security === 'object') | ||
self.x_emit('security', security); | ||
else | ||
throw new Error("Unknown db responses: " + JSON.stringify(security)); | ||
}) | ||
}) | ||
|
||
self.on('start', function probe_ddocs() { | ||
var view = lib.join(self.url, '/_all_docs' | ||
+ '?include_docs=false' | ||
+ '&startkey=' + encodeURIComponent(JSON.stringify("_design/")) | ||
+ '&endkey=' + encodeURIComponent(JSON.stringify("_design0")) | ||
); | ||
self.log.debug("Scanning for design documents: " + self.name); | ||
self.request({uri:view}, function(er, resp, body) { | ||
if(er) | ||
throw er; | ||
else if(resp.statusCode === 401 && typeof body === 'object' && body.error === 'unauthorized') { | ||
// Indicate no read permisssion. | ||
self.x_emit('ddoc_ids', null); | ||
} else if(resp.statusCode === 200 && ("rows" in body)) { | ||
var ids = body.rows.map(function(row) { return row.id }); | ||
self.log.debug(self.name + ' has ' + ids.length + ' design documents: ' + ids.join(', ')); | ||
self.x_emit('ddoc_ids', ids); | ||
} else | ||
throw new Error("Bad ddoc response from " + view + ": " + JSON.stringify({code:resp.statusCode, body:body})); | ||
}) | ||
}) | ||
|
||
self.on('ddoc_ids', function emit_ddoc_probes(ids) { | ||
self.log.debug('Creating probes for ' + ids.length + ' ddocs'); | ||
ids.forEach(function(id) { | ||
var ddoc = new DesignDocument; | ||
ddoc.db = self.url; | ||
ddoc.id = id; | ||
self.x_emit('ddoc', ddoc); | ||
ddoc.start(); | ||
}) | ||
}) | ||
} // Database | ||
util.inherits(Database, Emitter); | ||
|
||
Database.prototype.start = function() { | ||
var self = this; | ||
|
||
if(!self.couch) | ||
throw new Error("Couch URL required"); | ||
if(!self.name) | ||
throw new Error("Database name required"); | ||
|
||
self.url = lib.join(self.couch, encodeURIComponent(self.name)); | ||
self.x_emit('start'); | ||
} | ||
|
||
module.exports = { "Database": Database | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// Probe all details about a CouchDB design document | ||
// | ||
|
||
var lib = require('./lib') | ||
, util = require('util') | ||
, assert = require('assert') | ||
, Emitter = require('./emitter').Emitter | ||
; | ||
|
||
function DesignDocument () { | ||
var self = this; | ||
Emitter.call(self); | ||
|
||
self.db = null; | ||
self.id = null; | ||
self.url = null; | ||
|
||
self.on('start', function probe_doc() { | ||
self.log.debug("Fetching design document: " + self.url); | ||
self.request({uri:self.url}, function(er, resp, body) { | ||
if(er) | ||
throw er; | ||
else if(resp.statusCode !== 200 || typeof body !== 'object') | ||
throw new Error("Bad ddoc response from " + self.url + ": " + JSON.stringify({code:resp.statusCode, body:body})); | ||
|
||
self.x_emit('body', body); | ||
}) | ||
}) | ||
|
||
self.on('start', function probe_info() { | ||
var info_url = lib.join(self.url, '/_info'); | ||
self.log.debug("Fetching ddoc info: " + info_url); | ||
self.request({uri:info_url}, function(er, resp, body) { | ||
if(er) | ||
throw er; | ||
else if(resp.statusCode !== 200 || typeof body !== 'object') | ||
throw new Error("Bad ddoc response from " + info_url + ": " + JSON.stringify({code:resp.statusCode, body:body})); | ||
|
||
self.x_emit('info', body); | ||
}) | ||
}) | ||
|
||
} // DesignDocument | ||
util.inherits(DesignDocument, Emitter); | ||
|
||
DesignDocument.prototype.start = function() { | ||
var self = this; | ||
|
||
if(!self.db) | ||
throw new Error("Couch database URL required"); | ||
if(!self.id) | ||
throw new Error("Document ID required"); | ||
|
||
// Since this is a design document, the slash must be kept. | ||
self.url = lib.join(self.db, encodeURIComponent(self.id).replace(/^_design%2[fF]/, '_design/')); | ||
self.x_emit('start'); | ||
} | ||
|
||
module.exports = { "DesignDocument": DesignDocument | ||
}; |
Oops, something went wrong.