diff options
| -rwxr-xr-x | bin/leap | 1 | ||||
| -rw-r--r-- | lib/core_ext/string.rb | 14 | ||||
| -rw-r--r-- | lib/leap_cli.rb | 20 | ||||
| -rw-r--r-- | lib/leap_cli/commands/ca.rb | 255 | ||||
| -rw-r--r-- | lib/leap_cli/commands/compile.rb | 2 | ||||
| -rw-r--r-- | lib/leap_cli/commands/node.rb | 58 | ||||
| -rw-r--r-- | lib/leap_cli/commands/test.rb | 25 | ||||
| -rw-r--r-- | lib/leap_cli/remote/plugin.rb | 2 | ||||
| -rw-r--r-- | lib/lib_ext/gli.rb | 56 | ||||
| -rw-r--r-- | test/leap_platform/provider_base/services/openvpn.json | 2 | 
10 files changed, 253 insertions, 182 deletions
| @@ -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 '<node-name | "all">', :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 '<node-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 '<node-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 '<node-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'"    }  } | 
