UNPKG

aws-es-kibana-xt

Version:
272 lines (236 loc) 8.43 kB
#!/usr/bin/env node const AWS = require('aws-sdk'); const http = require('http'); const httpProxy = require('http-proxy'); const express = require('express'); const bodyParser = require('body-parser'); const stream = require('stream'); const basicAuth = require('express-basic-auth'); const compress = require('compression'); const os = require('os'); const figlet = require('figlet'); const yargs = require('yargs') .usage('usage: $0 [options] --region eu-west-1 search-mykibana-es-uat-testing.eu-west-1.es.amazonaws.com') .option('b', { alias: 'bind-address', default: process.env.BIND_ADDRESS || '127.0.0.1', demand: false, describe: 'the ip address to bind to', type: 'string', }) .option( 'e', { alias: 'endpoint', default: process.env.ES_ENDPOINT || '', demand: true, describe: 'the AWS ES cluster endpoint', type: 'string', } ) .option('p', { alias: 'port', default: process.env.PORT || 9200, demand: false, describe: 'the port to bind to', type: 'number', }) .option('r', { alias: 'region', default: process.env.AWS_REGION, demand: false, describe: 'the region of the Elasticsearch cluster', type: 'string', }) .option('u', { alias: 'user', default: process.env.AUTH_USER || process.env.USER, demand: false, describe: 'the username to access the proxy', }) .option('a', { alias: 'password', default: process.env.AUTH_PASSWORD || process.env.PASSWORD, demand: false, describe: 'the password to access the proxy', }) .option('s', { alias: 'silent', default: false, demand: false, describe: 'remove figlet banner', }) .option('H', { alias: 'health-path', default: process.env.HEALTH_PATH, demand: false, describe: 'URI path for health check', type: 'string', }) .option('l', { alias: 'limit', default: process.env.LIMIT || '10000kb', demand: false, describe: 'request limit', }) .option('debug', { alias: 'd', default: false, describe: 'Enable debugging mode', type: 'boolean', }) .help() .version() .strict(); const argv = yargs.argv; const ES_ENDPOINT = argv.endpoint || process.env.ES_ENDPOINT; const REGION = argv.r || process.env.AWS_REGION; // Define an array to store error messages const errors = []; // Check if ES_ENDPOINT is provided // Check if ES_ENDPOINT is provided if (!ES_ENDPOINT) { errors.push('Elasticsearch endpoint must be provided either through --endpoint argument or ES_ENDPOINT environment variable.'); } // Check if REGION is provided if (!REGION) { errors.push('Region must be provided either through --region argument or AWS_REGION environment variable.'); } // Check if AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are provided if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) { errors.push('AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be provided as environment variables.'); } // Check if AUTH_USER and AUTH_PASSWORD are provided together if ((process.env.AUTH_USER && !process.env.AUTH_PASSWORD) || (!process.env.AUTH_USER && process.env.AUTH_PASSWORD)) { errors.push('Both AUTH_USER and AUTH_PASSWORD must be provided or omitted together.'); } const BIND_ADDRESS = argv.b; const PORT = argv.p; const REQ_LIMIT = argv.l; // Check if PORT is a valid number if (isNaN(PORT) || PORT < 1 || PORT > 65535) { errors.push('Invalid PORT number. Please provide a valid port number between 1 and 65535.'); } // Check if LIMIT is a valid limit format if (!/^\d+(\.\d+)?[kKmMgG]?[bB]?$/.test(REQ_LIMIT)) { errors.push('Invalid request limit format. Please provide a valid limit (e.g., 10000kb).'); } // If there are errors, log them and exit if (errors.length > 0) { console.error('Errors:'); errors.forEach((error) => console.error(error)); yargs.showHelp(); process.exit(1); } // Check if ENDPOINT starts with 'http://' or 'https://' const TARGET = (ES_ENDPOINT.startsWith('http://') || ES_ENDPOINT.startsWith('https://')) ? ES_ENDPOINT : `https://${ES_ENDPOINT}`; // Assuming https by default if missing // ... (previous code) const credentials = { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, sessionToken: process.env.AWS_SESSION_TOKEN, // Only if you are using temporary session credentials }; const options = { target: TARGET, changeOrigin: true, secure: true, }; const proxy = httpProxy.createProxyServer(options); const app = express(); app.use(compress()); app.use(bodyParser.raw({ limit: REQ_LIMIT, type: () => true })); if (argv.debug) { console.log('Environmental Variables:'); console.log('ES_ENDPOINT:', ES_ENDPOINT); console.log('REGION:', REGION); console.log('AWS_ACCESS_KEY_ID:', process.env.AWS_ACCESS_KEY_ID); console.log('AWS_SECRET_ACCESS_KEY:', process.env.AWS_SECRET_ACCESS_KEY); console.log('AWS_SESSION_TOKEN:', process.env.AWS_SESSION_TOKEN); console.log('AUTH_USER:', process.env.AUTH_USER || process.env.USER); console.log('AUTH_PASSWORD:', process.env.AUTH_PASSWORD || process.env.PASSWORD); console.log('BIND_ADDRESS:', BIND_ADDRESS); console.log('PORT:', PORT); console.log('REQ_LIMIT:', REQ_LIMIT); console.log('TARGET:', TARGET); } if (argv.H) { app.get(argv.H, (req, res) => { res.setHeader('Content-Type', 'text/plain'); res.send('ok'); }); } if (argv.u && argv.a) { const users = {}; const user = process.env.USER || process.env.AUTH_USER; const pass = process.env.PASSWORD || process.env.AUTH_PASSWORD; users[user] = pass; app.use( basicAuth({ users: users, challenge: true, }) ); } app.use(async (req, res) => { let bufferStream; if (Buffer.isBuffer(req.body)) { bufferStream = new stream.PassThrough(); await bufferStream.end(req.body); } proxy.web(req, res, { buffer: bufferStream }); }); proxy.on('proxyReq', (proxyReq, req) => { const endpoint = new AWS.Endpoint(ES_ENDPOINT); const request = new AWS.HttpRequest(endpoint); request.method = proxyReq.method; request.path = proxyReq.path; request.region = REGION; if (Buffer.isBuffer(req.body)) request.body = req.body; if (!request.headers) request.headers = {}; request.headers['presigned-expires'] = false; request.headers['Host'] = endpoint.hostname; const signer = new AWS.Signers.V4(request, 'es'); signer.addAuthorization(credentials, new Date()); proxyReq.setHeader('Host', request.headers['Host']); proxyReq.setHeader('X-Amz-Date', request.headers['X-Amz-Date']); proxyReq.setHeader('Authorization', request.headers['Authorization']); if (request.headers['x-amz-security-token']) proxyReq.setHeader('x-amz-security-token', request.headers['x-amz-security-token']); }); proxy.on('proxyRes', (proxyReq, req, res) => { if (req.url.match(/\.(css|js|img|font)/)) { res.setHeader('Cache-Control', 'public, max-age=86400'); } }); const server = http.createServer(app); server.on('error', (error) => { console.error('Server error:', error); process.exit(1); }); const listener = server.listen(PORT, BIND_ADDRESS, () => { if (!argv.s) { console.log( figlet.textSync('AWS ES Proxy!', { font: 'Speed', horizontalLayout: 'default', verticalLayout: 'default', }) ); } console.log(`AWS ES cluster available at http://${BIND_ADDRESS}:${PORT}`); console.log(`Kibana available at http://${BIND_ADDRESS}:${PORT}/_plugin/kibana/`); if (argv.H) { console.log(`Health endpoint enabled at http://${BIND_ADDRESS}:${PORT}${argv.H}`); } }); process.on('SIGINT', gracefulShutdown); process.on('SIGTERM', gracefulShutdown); function gracefulShutdown() { console.log('Received SIGINT or SIGTERM. Exiting gracefully...'); // Close the server server.close(() => { console.log('Server has closed. Exiting gracefully.'); process.exit(0); }); }