From: Vsevolod Stakhov Date: Fri, 26 Apr 2019 17:11:30 +0000 (+0100) Subject: [Project] Add initial version of the vault management tool X-Git-Tag: 1.9.3~64 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a0df170f2703a0e9d7c42947b94e5199daeec930;p=rspamd.git [Project] Add initial version of the vault management tool --- diff --git a/lualib/rspamadm/vault.lua b/lualib/rspamadm/vault.lua new file mode 100644 index 000000000..3d875fb39 --- /dev/null +++ b/lualib/rspamadm/vault.lua @@ -0,0 +1,267 @@ +--[[ +Copyright (c) 2019, Vsevolod Stakhov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +]]-- + + +local rspamd_logger = require "rspamd_logger" +local ansicolors = require "ansicolors" +local ucl = require "ucl" +local argparse = require "argparse" +local fun = require "fun" +local rspamd_http = require "rspamd_http" +local cr = require "rspamd_cryptobox" + +local parser = argparse() + :name "rspamadm vault" + :description "Perform Hashicorp Vault management" + :help_description_margin(32) + :command_target("command") + :require_command(true) + +parser:flag "-s --silent" + :description "Do not output extra information" +parser:option "-a --addr" + :description "Vault address (if not defined in VAULT_ADDR env)" +parser:option "-t --token" + :description "Vault token (not recommended, better define VAULT_TOKEN env)" +parser:option "-p --path" + :description "Path to work with in the vault" + :default "dkim" +parser:option "-o --output" + :description "Output format ('ucl', 'json', 'json-compact', 'yaml')" + :argname("") + :convert { + ucl = "ucl", + json = "json", + ['json-compact'] = "json-compact", + yaml = "yaml", + } + :default "ucl" + +parser:command "list ls l" + :description "List elements in the vault" + +local show = parser:command "show get" + :description "Extract element from the vault" +show:option "-d --domain" + :description "Domain to create key for" + :count "1" + +local newkey = parser:command "newkey new create" + :description "Add new key to the vault" +newkey:option "-d --domain" + :description "Domain to create key for" + :count "1" +newkey:option "-s --selector" + :description "Selector to use" + :count "1" +newkey:option "-A --algorithm" + :argname("") + :convert { + rsa = "rsa", + ed25519 = "ed25519", + } + :default "rsa" +newkey:option "-b --bits" + :argname("") + :convert(tonumber) + :default "1024" +newkey:flag "-r --rewrite" + + +local function printf(fmt, ...) + if fmt then + io.write(rspamd_logger.slog(fmt, ...)) + end + io.write('\n') +end + +local function maybe_printf(opts, fmt, ...) + if not opts.silent then + printf(fmt, ...) + end +end + +local function highlight(str, color) + return ansicolors[color or 'white'] .. str .. ansicolors.reset +end + +local function vault_url(opts, path) + if path then + return string.format('%s/v1/%s/%s', opts.addr, opts.path, path) + end + + return string.format('%s/v1/%s', opts.addr, opts.path) +end + +local function maybe_print_vault_data(opts, data, func) + local p = ucl.parser() + local res,parser_err = p:parse_string(data) + + if not res then + printf('vault reply for cannot be parsed: %s', parser_err) + else + local obj = p:get_object() + + if func then + printf(ucl.to_format(func(obj), opts.output)) + else + printf(ucl.to_format(obj, opts.output)) + end + end +end + +local function show_handler(opts) + local uri = vault_url(opts, opts.domain) + local err,data = rspamd_http.request{ + config = rspamd_config, + ev_base = rspamadm_ev_base, + session = rspamadm_session, + resolver = rspamadm_resolver, + url = uri, + headers = { + ['X-Vault-Token'] = opts.token + } + } + + if err then + printf('cannot get request to the vault (%s), HTTP error code %s', uri, data.code) + maybe_print_vault_data(opts, err) + os.exit(1) + else + maybe_print_vault_data(opts, data.content, function(obj) + return obj.data.selectors + end) + end +end + +local function list_handler(opts) + local uri = vault_url(opts) + local err,data = rspamd_http.request{ + config = rspamd_config, + ev_base = rspamadm_ev_base, + session = rspamadm_session, + resolver = rspamadm_resolver, + url = uri .. '?list=true', + headers = { + ['X-Vault-Token'] = opts.token + } + } + + if err then + printf('cannot get request to the vault (%s), HTTP error code %s', uri, data.code) + maybe_print_vault_data(opts, err) + os.exit(1) + else + maybe_print_vault_data(opts, data.content, function(obj) + return obj.data.keys + end) + end +end + +-- Returns pair privkey+pubkey +local function genkey(opts) + return cr.gen_dkim_keypair(opts.algorithm, opts.bits) +end + +local function newkey_handler(opts) + local uri = vault_url(opts, opts.domain) + local err,data = rspamd_http.request{ + config = rspamd_config, + ev_base = rspamadm_ev_base, + session = rspamadm_session, + resolver = rspamadm_resolver, + url = uri, + method = 'get', + headers = { + ['X-Vault-Token'] = opts.token + } + } + + if err or not data.content.data then + local sk,pk = genkey(opts) + + local res = { + selectors = { + [1] = { + selector = opts.selector, + domain = opts.domain, + key = sk + } + } + } + + ret,data = rspamd_http.request{ + config = rspamd_config, + ev_base = rspamadm_ev_base, + session = rspamadm_session, + resolver = rspamadm_resolver, + url = uri, + method = 'put', + headers = { + ['X-Vault-Token'] = opts.token + }, + body = { + ucl.to_format(res, 'json-compact') + }, + } + + if not ret then + printf('cannot get request to the vault (%s), HTTP error code %s', uri, data.code) + maybe_print_vault_data(opts, data.content) + os.exit(1) + end + else + -- Existing data + end +end + +local function handler(args) + local opts = parser:parse(args) + + if not opts.addr then + opts.addr = os.getenv('VAULT_ADDR') + end + + if not opts.token then + opts.token = os.getenv('VAULT_TOKEN') + else + maybe_printf(opts, 'defining token via command line is insecure, define it via environment variable %s', + highlight('VAULT_TOKEN', 'red')) + end + + if not opts.token or not opts.addr then + printf('no token or/and vault addr has been specified, exiting') + os.exit(1) + end + + local command = opts.command + + if command == 'list' then + list_handler(opts) + elseif command == 'show' then + show_handler(opts) + elseif command == 'newkey' then + newkey_handler(opts) + else + parser:error(string.format('command %s is not implemented', command)) + end +end + +return { + handler = handler, + description = parser._description, + name = 'vault' +} \ No newline at end of file