From e2c31618b6f70d86c55c348436dd600b2e4ace21 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 28 Nov 2012 14:08:39 -0800 Subject: command name shuffle -- grouped more commands together as subcommands --- bin/leap | 1 + lib/core_ext/string.rb | 14 ++ lib/leap_cli.rb | 20 +- lib/leap_cli/commands/ca.rb | 255 +++++++++++---------- lib/leap_cli/commands/compile.rb | 2 +- lib/leap_cli/commands/node.rb | 58 ++--- lib/leap_cli/commands/test.rb | 25 +- lib/leap_cli/remote/plugin.rb | 2 +- lib/lib_ext/gli.rb | 56 +++++ .../provider_base/services/openvpn.json | 2 +- 10 files changed, 253 insertions(+), 182 deletions(-) create mode 100644 lib/core_ext/string.rb create mode 100644 lib/lib_ext/gli.rb diff --git a/bin/leap b/bin/leap index 461ff3c..690a560 100755 --- a/bin/leap +++ b/bin/leap @@ -26,6 +26,7 @@ end require 'gli' require 'highline' require 'forwardable' +require 'lib_ext/gli' # our custom extensions to gli # # Typically, GLI and Highline methods are loaded into the global namespace. diff --git a/lib/core_ext/string.rb b/lib/core_ext/string.rb new file mode 100644 index 0000000..07af8e5 --- /dev/null +++ b/lib/core_ext/string.rb @@ -0,0 +1,14 @@ +# +# make ruby 1.9 act more like ruby 1.8 +# +unless String.method_defined?(:to_a) + class String + def to_a; [self]; end + end +end + +unless String.method_defined?(:any?) + class String + def any?; self.chars.any?; end + end +end diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb index 74c5832..3f35cd4 100644 --- a/lib/leap_cli.rb +++ b/lib/leap_cli.rb @@ -5,6 +5,7 @@ require 'leap_cli/requirements.rb' require 'core_ext/hash' require 'core_ext/boolean' require 'core_ext/nil' +require 'core_ext/string' require 'leap_cli/log' require 'leap_cli/init' @@ -22,25 +23,12 @@ require 'leap_cli/config/manager' module LeapCli::Commands; end +# +# allow everyone easy access to log() command. +# module LeapCli Util.send(:extend, LeapCli::Log) Commands.send(:extend, LeapCli::Log) Config::Manager.send(:include, LeapCli::Log) extend LeapCli::Log end - -# -# make ruby 1.9 act more like ruby 1.8 -# -unless String.method_defined?(:to_a) - class String - def to_a; [self]; end - end -end - -unless String.method_defined?(:any?) - class String - def any?; self.chars.any?; end - end -end - diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb index 05bdb2b..f471b5a 100644 --- a/lib/leap_cli/commands/ca.rb +++ b/lib/leap_cli/commands/ca.rb @@ -5,150 +5,155 @@ require 'digest/md5' module LeapCli; module Commands - desc 'Creates the public and private key for your Certificate Authority.' - command :'init-ca' do |c| - c.action do |global_options,options,args| - assert_files_missing! :ca_cert, :ca_key - assert_config! 'provider.ca.name' - assert_config! 'provider.ca.bit_size' - assert_config! 'provider.ca.life_span' - - provider = manager.provider - root = CertificateAuthority::Certificate.new - - # set subject - root.subject.common_name = provider.ca.name - possible = ['country', 'state', 'locality', 'organization', 'organizational_unit', 'email_address'] - provider.ca.keys.each do |key| - if possible.include?(key) - root.subject.send(key + '=', provider.ca[key]) + desc "Manage X.509 certificates" + #long_desc "" + command :cert do |c| + + c.desc 'Creates a Certificate Authority (private key and CA certificate)' + c.command :ca do |c| + c.action do |global_options,options,args| + assert_files_missing! :ca_cert, :ca_key + assert_config! 'provider.ca.name' + assert_config! 'provider.ca.bit_size' + assert_config! 'provider.ca.life_span' + + provider = manager.provider + root = CertificateAuthority::Certificate.new + + # set subject + root.subject.common_name = provider.ca.name + possible = ['country', 'state', 'locality', 'organization', 'organizational_unit', 'email_address'] + provider.ca.keys.each do |key| + if possible.include?(key) + root.subject.send(key + '=', provider.ca[key]) + end end - end - # set expiration - root.not_before = today - root.not_after = years_from_today(provider.ca.life_span.to_i) + # set expiration + root.not_before = today + root.not_after = years_from_today(provider.ca.life_span.to_i) - # generate private key - root.serial_number.number = 1 - root.key_material.generate_key(provider.ca.bit_size) + # generate private key + root.serial_number.number = 1 + root.key_material.generate_key(provider.ca.bit_size) - # sign self - root.signing_entity = true - root.parent = root - root.sign!(ca_root_signing_profile) + # sign self + root.signing_entity = true + root.parent = root + root.sign!(ca_root_signing_profile) - # save - write_file!(:ca_key, root.key_material.private_key.to_pem) - write_file!(:ca_cert, root.to_pem) + # save + write_file!(:ca_key, root.key_material.private_key.to_pem) + write_file!(:ca_cert, root.to_pem) + end end - end - desc 'Creates or renews a X.509 certificate/key pair for a single node or all nodes' - arg_name '', :optional => false, :multiple => false - command :'update-cert' do |c| - c.action do |global_options,options,args| - assert_files_exist! :ca_cert, :ca_key, :msg => 'Run init-ca to create them' - assert_config! 'provider.ca.server_certificates.bit_size' - assert_config! 'provider.ca.server_certificates.digest' - assert_config! 'provider.ca.server_certificates.life_span' - assert_config! 'common.x509.use' - - if args.first == 'all' || args.empty? - manager.each_node do |node| - if cert_needs_updating?(node) - generate_cert_for_node(node) + c.desc 'Creates or renews a X.509 certificate/key pair for a single node or all nodes' + c.arg_name 'node-name', :optional => false + c.command :update do |c| + c.action do |global_options,options,args| + assert_files_exist! :ca_cert, :ca_key, :msg => 'Run `leap cert ca` to create them' + assert_config! 'provider.ca.server_certificates.bit_size' + assert_config! 'provider.ca.server_certificates.digest' + assert_config! 'provider.ca.server_certificates.life_span' + assert_config! 'common.x509.use' + + if args.first == 'all' || args.empty? + manager.each_node do |node| + if cert_needs_updating?(node) + generate_cert_for_node(node) + end end + else + generate_cert_for_node(get_node_from_args(args)) end - else - generate_cert_for_node(get_node_from_args(args)) end end - end - desc 'Generates Diffie-Hellman parameter file (needed for server-side of TLS connections)' - command :'init-dh' do |c| - c.action do |global_options,options,args| - long_running do - if cmd_exists?('certtool') - log 0, 'Generating DH parameters (takes a long time)...' - output = assert_run!('certtool --generate-dh-params --sec-param high') - output.sub! /.*(-----BEGIN DH PARAMETERS-----.*-----END DH PARAMETERS-----).*/m, '\1' - output << "\n" - write_file!(:dh_params, output) - else - log 0, 'Generating DH parameters (takes a REALLY long time)...' - output = OpenSSL::PKey::DH.generate(3248).to_pem - write_file!(:dh_params, output) + c.desc 'Creates a Diffie-Hellman parameter file' # (needed for server-side of some TLS connections) + c.command :dh do |c| + c.action do |global_options,options,args| + long_running do + if cmd_exists?('certtool') + log 0, 'Generating DH parameters (takes a long time)...' + output = assert_run!('certtool --generate-dh-params --sec-param high') + output.sub! /.*(-----BEGIN DH PARAMETERS-----.*-----END DH PARAMETERS-----).*/m, '\1' + output << "\n" + write_file!(:dh_params, output) + else + log 0, 'Generating DH parameters (takes a REALLY long time)...' + output = OpenSSL::PKey::DH.generate(3248).to_pem + write_file!(:dh_params, output) + end end end end - end - - # - # hints: - # - # inspect CSR: - # openssl req -noout -text -in files/cert/x.csr - # - # generate CSR with openssl to see how it compares: - # openssl req -sha256 -nodes -newkey rsa:2048 -keyout example.key -out example.csr - # - # validate a CSR: - # http://certlogik.com/decoder/ - # - # nice details about CSRs: - # http://www.redkestrel.co.uk/Articles/CSR.html - # - desc 'Creates a Certificate Signing Request for use in purchasing a commercial x509 certificate' - command :'init-csr' do |c| - #c.switch 'sign', :desc => 'additionally creates a cert that is signed by your own CA (recommended only for testing)', :negatable => false - c.action do |global_options,options,args| - assert_config! 'provider.domain' - assert_config! 'provider.name' - assert_config! 'provider.default_language' - assert_config! 'provider.ca.server_certificates.bit_size' - assert_config! 'provider.ca.server_certificates.digest' - assert_files_missing! [:commercial_key, manager.provider.domain], [:commercial_csr, manager.provider.domain], :msg => 'If you really want to create a new key and CSR, remove these files first.' - if options[:sign] - assert_files_exist! :ca_cert, :ca_key, :msg => 'Run init-ca to create them' - end - # RSA key - keypair = CertificateAuthority::MemoryKeyMaterial.new - log :generating, "%s bit RSA key" % manager.provider.ca.server_certificates.bit_size do - keypair.generate_key(manager.provider.ca.server_certificates.bit_size) - write_file! [:commercial_key, manager.provider.domain], keypair.private_key.to_pem - end + # + # hints: + # + # inspect CSR: + # openssl req -noout -text -in files/cert/x.csr + # + # generate CSR with openssl to see how it compares: + # openssl req -sha256 -nodes -newkey rsa:2048 -keyout example.key -out example.csr + # + # validate a CSR: + # http://certlogik.com/decoder/ + # + # nice details about CSRs: + # http://www.redkestrel.co.uk/Articles/CSR.html + # + c.desc 'Creates a CSR for use in buying a commercial X.509 certificate' + c.command :csr do |c| + #c.switch 'sign', :desc => 'additionally creates a cert that is signed by your own CA (recommended only for testing)', :negatable => false + c.action do |global_options,options,args| + assert_config! 'provider.domain' + assert_config! 'provider.name' + assert_config! 'provider.default_language' + assert_config! 'provider.ca.server_certificates.bit_size' + assert_config! 'provider.ca.server_certificates.digest' + assert_files_missing! [:commercial_key, manager.provider.domain], [:commercial_csr, manager.provider.domain], :msg => 'If you really want to create a new key and CSR, remove these files first.' + if options[:sign] + assert_files_exist! :ca_cert, :ca_key, :msg => 'Run `leap cert ca` to create them' + end - # CSR - dn = CertificateAuthority::DistinguishedName.new - csr = CertificateAuthority::SigningRequest.new - dn.common_name = manager.provider.domain - dn.organization = manager.provider.name[manager.provider.default_language] - log :generating, "CSR with commonName => '%s', organization => '%s'" % [dn.common_name, dn.organization] do - csr.distinguished_name = dn - csr.key_material = keypair - csr.digest = manager.provider.ca.server_certificates.digest - request = csr.to_x509_csr - write_file! [:commercial_csr, manager.provider.domain], csr.to_pem - end + # RSA key + keypair = CertificateAuthority::MemoryKeyMaterial.new + log :generating, "%s bit RSA key" % manager.provider.ca.server_certificates.bit_size do + keypair.generate_key(manager.provider.ca.server_certificates.bit_size) + write_file! [:commercial_key, manager.provider.domain], keypair.private_key.to_pem + end - # Sign using our own CA, for use in testing but hopefully not production. - # It is not that commerical CAs are so secure, it is just that signing your own certs is - # a total drag for the user because they must click through dire warnings. - #if options[:sign] - log :generating, "self-signed x509 server certificate for testing purposes" do - cert = csr.to_cert - cert.serial_number.number = cert_serial_number(manager.provider.domain) - cert.not_before = today - cert.not_after = years_from_today(1) - cert.parent = ca_root - cert.sign! domain_test_signing_profile - write_file! [:commercial_cert, manager.provider.domain], cert.to_pem - log "please replace this file with the real certificate you get from a CA using #{Path.relative_path([:commercial_csr, manager.provider.domain])}" + # CSR + dn = CertificateAuthority::DistinguishedName.new + csr = CertificateAuthority::SigningRequest.new + dn.common_name = manager.provider.domain + dn.organization = manager.provider.name[manager.provider.default_language] + log :generating, "CSR with commonName => '%s', organization => '%s'" % [dn.common_name, dn.organization] do + csr.distinguished_name = dn + csr.key_material = keypair + csr.digest = manager.provider.ca.server_certificates.digest + request = csr.to_x509_csr + write_file! [:commercial_csr, manager.provider.domain], csr.to_pem end - #end + + # Sign using our own CA, for use in testing but hopefully not production. + # It is not that commerical CAs are so secure, it is just that signing your own certs is + # a total drag for the user because they must click through dire warnings. + #if options[:sign] + log :generating, "self-signed x509 server certificate for testing purposes" do + cert = csr.to_cert + cert.serial_number.number = cert_serial_number(manager.provider.domain) + cert.not_before = today + cert.not_after = years_from_today(1) + cert.parent = ca_root + cert.sign! domain_test_signing_profile + write_file! [:commercial_cert, manager.provider.domain], cert.to_pem + log "please replace this file with the real certificate you get from a CA using #{Path.relative_path([:commercial_csr, manager.provider.domain])}" + end + #end + end end end diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb index 9882e6a..45e4f2b 100644 --- a/lib/leap_cli/commands/compile.rb +++ b/lib/leap_cli/commands/compile.rb @@ -2,7 +2,7 @@ module LeapCli module Commands - desc 'Compile json files to hiera configs' + desc 'Compiles node configuration files into hiera files used for deployment' command :compile do |c| c.action do |global_options,options,args| # these must come first diff --git a/lib/leap_cli/commands/node.rb b/lib/leap_cli/commands/node.rb index 28e250a..678bebd 100644 --- a/lib/leap_cli/commands/node.rb +++ b/lib/leap_cli/commands/node.rb @@ -6,41 +6,43 @@ module LeapCli; module Commands ## ## COMMANDS ## - - desc 'not yet implemented... Create a new configuration for a node' - command :'add-node' do |c| - c.action do |global_options,options,args| + desc 'Node management' + command :node do |c| + c.desc 'Create a new configuration file for a node' + c.command :add do |c| + c.action do |global_options,options,args| + end end - end - desc 'Bootstraps a node, setting up ssh keys and installing prerequisites' - arg_name '', :optional => false, :multiple => false - command :'init-node' do |c| - c.switch 'echo', :desc => 'if set, passwords are visible as you type them (default is hidden)', :negatable => false - c.action do |global_options,options,args| - node = get_node_from_args(args) - ping_node(node) - save_public_host_key(node) - update_compiled_ssh_configs - ssh_connect(node, :bootstrap => true, :echo => options[:echo]) do |ssh| - ssh.install_authorized_keys - ssh.install_prerequisites + c.desc 'Bootstraps a node, setting up ssh keys and installing prerequisites' + c.arg_name 'node-name', :optional => false, :multiple => false + c.command :init do |c| + c.switch 'echo', :desc => 'if set, passwords are visible as you type them (default is hidden)', :negatable => false + c.action do |global_options,options,args| + node = get_node_from_args(args) + ping_node(node) + save_public_host_key(node) + update_compiled_ssh_configs + ssh_connect(node, :bootstrap => true, :echo => options[:echo]) do |ssh| + ssh.install_authorized_keys + ssh.install_prerequisites + end + log :completed, "node init #{node.name}" end - log :completed, "init-node #{node.name}" end - end - desc 'not yet implemented' - command :'rename-node' do |c| - c.action do |global_options,options,args| + c.desc 'Renames a node file, and all its related files' + c.command :mv do |c| + c.action do |global_options,options,args| + end end - end - desc 'not yet implemented' - arg_name '', :optional => false, :multiple => false - command :'rm-node' do |c| - c.action do |global_options,options,args| - remove_file!() + c.desc 'Removes a node file, and all its related files' + c.arg_name '', :optional => false, :multiple => false + c.command :rm do |c| + c.action do |global_options,options,args| + remove_file!() + end end end diff --git a/lib/leap_cli/commands/test.rb b/lib/leap_cli/commands/test.rb index dc08652..dd505b6 100644 --- a/lib/leap_cli/commands/test.rb +++ b/lib/leap_cli/commands/test.rb @@ -1,18 +1,23 @@ module LeapCli; module Commands - desc 'Creates files needed to run tests' - command :'init-test' do |c| - c.action do |global_options,options,args| - generate_test_client_cert - generate_test_client_openvpn_config - end - end - desc 'Run tests' command :test do |c| - c.action do |global_options,options,args| - log 'not yet implemented' + c.desc 'Creates files needed to run tests' + c.command :init do |c| + c.action do |global_options,options,args| + generate_test_client_cert + generate_test_client_openvpn_config + end end + + c.desc 'Run tests' + c.command :run do |c| + c.action do |global_options,options,args| + log 'not yet implemented' + end + end + + c.default_command :run end private diff --git a/lib/leap_cli/remote/plugin.rb b/lib/leap_cli/remote/plugin.rb index 6dafbd8..803ebf9 100644 --- a/lib/leap_cli/remote/plugin.rb +++ b/lib/leap_cli/remote/plugin.rb @@ -29,7 +29,7 @@ module LeapCli; module Remote; module Plugin rescue Capistrano::CommandError => exc LeapCli::Util.bail! do exc.hosts.each do |host| - LeapCli::Util.log :error, "running deploy: node not initialized. Run 'leap init-node #{host}'", :host => host + LeapCli::Util.log :error, "running deploy: node not initialized. Run 'leap node init #{host}'", :host => host end end end diff --git a/lib/lib_ext/gli.rb b/lib/lib_ext/gli.rb new file mode 100644 index 0000000..0bfdbc1 --- /dev/null +++ b/lib/lib_ext/gli.rb @@ -0,0 +1,56 @@ +# +# print subcommands indented in the main global help screen +# + +module GLI + module Commands + module HelpModules + class GlobalHelpFormat + SUB_CMD_INDENT = " " + def format + program_desc = @app.program_desc + program_long_desc = @app.program_long_desc + if program_long_desc + wrapper = @wrapper_class.new(Terminal.instance.size[0],4) + program_long_desc = "\n #{wrapper.wrap(program_long_desc)}\n\n" if program_long_desc + else + program_long_desc = "\n" + end + + # build a list of commands, sort them so the commands with subcommands are at the bottom + commands = @sorter.call(@app.commands_declaration_order.reject(&:nodoc)).sort do |a,b| + if a.commands.any? && b.commands.any?; a.name <=> b.name + elsif a.commands.any?; 1 + elsif b.commands.any?; -1 + else; a.name <=> b.name + end + end + + # build a list of command info ([name, description]), including subcommands if appropriate + command_info_list = [] + commands.each do |command| + name = [command.name, Array(command.aliases)].flatten.join(', ') + command_info_list << [name, command.description] + if command.commands.any? + @sorter.call(command.commands_declaration_order).each do |cmd| + if command.get_default_command == cmd.name + command_info_list << [SUB_CMD_INDENT + cmd.names,cmd.description + " (default)"] + else + command_info_list << [SUB_CMD_INDENT + cmd.names,cmd.description] + end + end + end + end + + # display + command_formatter = ListFormatter.new(command_info_list, @wrapper_class) + stringio = StringIO.new + command_formatter.output(stringio) + commands = stringio.string + global_option_descriptions = OptionsFormatter.new(global_flags_and_switches,@wrapper_class).format + GLOBAL_HELP.result(binding) + end + end + end + end +end diff --git a/test/leap_platform/provider_base/services/openvpn.json b/test/leap_platform/provider_base/services/openvpn.json index 65e7cad..8c0965e 100644 --- a/test/leap_platform/provider_base/services/openvpn.json +++ b/test/leap_platform/provider_base/services/openvpn.json @@ -13,6 +13,6 @@ "nat": true, "ca_crt": "= file :ca_cert", "ca_key": "= file :ca_key", - "dh": "= file :dh_params, :missing => 'Diffie-Hellman parameters. Run `leap init-dh`'" + "dh": "= file :dh_params, :missing => 'Diffie-Hellman parameters. Run `leap cert dh` to create it'" } } -- cgit v1.2.3