diff options
113 files changed, 2369 insertions, 3268 deletions
@@ -1,11 +1,6 @@  Gemfile.lock  pkg  junk -test/provider/hiera -test/provider/files/nodes/ -test/provider/files/ca/ -test/provider/files/ssh/ -test/provider/files/users/  .vagrant  Vagrantfile  .reviewboardrc diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..5aca7d3 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,38 @@ +image: leapcode/ruby:2.1-slim + +stages: +  - build +  - test +  - trigger + +build: +  stage: build +  script: +    - "rake build" +    - "gem install --user-install pkg/leap_cli-*.gem" +    - export PATH="$PATH:$(ruby -e 'puts Gem.user_dir')/bin" +    - leap +  artifacts: +    paths: +      - pkg/leap_cli-*.gem +    name: "leap_cli_${CI_BUILD_REF_NAME}_${CI_BUILD_REF}" +    expire_in: 3 month + +test: +  stage: test +  script: +#   - apt-get install --yes pkg-config +#   - bundle config build.nokogiri --use-system-libraries +    - apt-get install rake +    - bundle install --path vendor/bundle --with test +    - git clone https://leap.se/git/leap_platform.git -b develop +    - chmod -R a+rwX test/provider +    - useradd -ms /bin/bash testuser +    - su -c "PLATFORM_DIR=$(readlink -e leap_platform) bundle exec rake test" testuser + +# trigger leap_platform pipeline +trigger: +  stage: trigger +  type: deploy +  script: +    - "curl -s -X POST -F token=${PLATFORM_TRIGGER_TOKEN} -F ref=develop https://0xacab.org/api/v3/projects/129/trigger/builds" @@ -1,14 +1,14 @@  About LEAP command line interface  =================================================== -This gem installs an executable 'leap' that allows you to manage servers using the LEAP platform. You can read about the [platform on-line](https://leap.se). +This gem installs an executable 'leap' that allows you to manage servers using the LEAP platform. You can read about the [platform on-line](https://leap.se/docs).  Installation  ===================================================  Install prerequisites: -    sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake +    sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake gcc make zlib1g-dev  NOTE: leap_cli requires ruby 1.9 or later. @@ -16,31 +16,18 @@ Optionally install Vagrant in order to be able to test with local virtual machin      sudo apt-get install vagrant virtualbox zlib1g-dev -NOTE: the packaged virtualbox and vagrant that comes with Debian and Ubuntu are rather ancient. Most people have better luck by downloading these packages from the upstream: +Install the `leap` command system-wide: -* https://downloads.vagrantup.com/ -* https://www.virtualbox.org/wiki/Downloads +    sudo gem install leap_cli -Install the `leap` command: +Alternately, you can install just for your user: -    sudo apt-get install rake -    git clone https://leap.se/git/leap_cli.git -    cd leap_cli -    rake build - -Install as root user (recommended): - -    sudo rake install - -Install as unprivileged user: - -    rake install -    # watch out for the directory leap is installed to, then i.e. -    sudo ln -s ~/.gem/ruby/1.9.1/bin/leap /usr/local/bin/leap +    gem install --user-install leap_cli +    [ $(which ruby) ] && PATH="$PATH:$(ruby -e 'puts Gem.user_dir')/bin" -With both methods, you can use now /usr/local/bin/leap, which in most cases will be in your $PATH. +The `--user-install` option for `gem` will install gems to a location in your home directory (handy!) but this directory is not in your PATH (not handy!). Add the second line to your `.bashrc` file so that all your shells will have `leap` in PATH. -To run directly from a clone of the git repo, see "Development", below. +For other methods of installing `leap_cli`, see below.  Usage  =================================================== @@ -56,35 +43,42 @@ How to set up your environment for developing the ``leap`` command.  Prerequisites  --------------------------------------------------- -Debian Squeeze - -    sudo apt-get install git ruby ruby-dev rubygems -    sudo gem install bundler rake -    export PATH=$PATH:/var/lib/gems/1.8/bin +Debian & Ubuntu -Debian Wheezy +    sudo apt-get install git ruby ruby-dev rake bundler -    sudo apt-get install git ruby ruby-dev bundler +Install from git +--------------------------------------------------- -Ubuntu +Download the source: -    sudo apt-get install git ruby ruby-dev -    sudo gem install bundler +    cd leap_cli -Install from git +Installing from the source  --------------------------------------------------- -Download the source: +Build the gem: -    git clone https://github.com/leapcode/leap_cli.git +    git clone https://leap.se/git/leap_cli.git      cd leap_cli +    rake build + +Install as root user: + +    sudo rake install + +Alternately, install as unprivileged user: + +    rake install +    PATH="$PATH:$(ruby -e 'puts Gem.user_dir')/bin" -Running from the source directory +Running directly from the source directory  ---------------------------------------------------  To run the ``leap`` command directly from the source tree, you need to install  the required gems using ``bundle`` and symlink ``bin/leap`` into your path: +    git clone https://leap.se/git/leap_cli.git      cd leap_cli      bundle                        # install required gems      ln -s `pwd`/bin/leap ~/bin    # link executable somewhere in your bin path @@ -99,32 +93,3 @@ working directory is under leap_cli. Because the point is to be able to run ``le  other places, it is easier to create the symlink. If you run ``leap`` directly, and not via  the command launcher that rubygems installs, leap will run in a mode that simulates  ``bundle exec leap`` (i.e. only gems included in Gemfile are allowed to be loaded). - -Changes -==================================================== - -1.7 - -* requires platform 0.7 -* deployment logging (see /var/log/leap) -* compatible with new tapicero -* selectively destroy some dbs with `leap db destroy` -* faster apt-get update -* added `leap scp` command -* bug fixes - -1.6.2 - -* auto generate certs on compile -* use internal ruby md5sum for compatibility on mac -* may override or customize tests by putting tests in `files/tests` -* bug fixes - -1.6.1 - -* requires platform 0.6 -* better `leap test run` -* added `leap tunnel` command -* only print stack trace if `--debug` flag was specified -* prompt user to upgrade host ssh key if a better one exists -* bug fixes @@ -1,4 +1,3 @@ -require "rubygems"  require "pty"  require "fileutils"  require "rake/testtask" @@ -37,7 +36,7 @@ end  desc "Build #{$spec.name}-#{$spec.version}.gem into the pkg directory"  task 'build' do    FileUtils.mkdir_p(File.join($base_dir, 'pkg')) -  FileUtils.rm($gem_path) if File.exists?($gem_path) +  FileUtils.rm($gem_path) if File.exist?($gem_path)    run "gem build -V '#{$spec_path}'"    file_name = File.basename(built_gem_path)    FileUtils.mv(built_gem_path, 'pkg') @@ -46,7 +45,7 @@ end  desc "Install #{$spec.name}-#{$spec.version}.gem into either system-wide or user gems"  task 'install' do -  if !File.exists?($gem_path) +  if !File.exist?($gem_path)      puts("Could not file #{$gem_path}. Try running 'rake build'")    else      options = '--verbose --conservative --no-rdoc --no-ri' @@ -83,6 +82,7 @@ end  Rake::TestTask.new do |t|    t.pattern = "test/unit/*_test.rb" +  t.warning = false  end  task :default => :test @@ -14,6 +14,7 @@ else    $VERBOSE=nil    DEBUG=false  end +TEST = false  LEAP_CLI_BASE_DIR = File.expand_path('..', File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__)) @@ -27,8 +28,6 @@ rescue LoadError  end  require 'gli' -require 'highline' -require 'forwardable'  require 'leap_cli/lib_ext/gli' # our custom extensions to gli  # @@ -40,16 +39,6 @@ require 'leap_cli/lib_ext/gli' # our custom extensions to gli  #  module LeapCli::Commands    extend GLI::App -  extend Forwardable - -  # delegate highline methods to make them available to sub-commands -  @terminal = HighLine.new -  def_delegator :@terminal, :ask,    'self.ask' -  def_delegator :@terminal, :agree,  'self.agree' -  def_delegator :@terminal, :choose, 'self.choose' -  def_delegator :@terminal, :say,    'self.say' -  def_delegator :@terminal, :color,  'self.color' -  def_delegator :@terminal, :list,   'self.list'    # make config manager available as 'manager'    def self.manager @@ -90,6 +79,9 @@ module LeapCli::Commands    # run command    begin +    if ARGV.any? +      LeapCli.log_raw(:log, nil, "COMMAND") { 'leap ' + ARGV.join(' ') } +    end      exit_status = run(ARGV)      exit(LeapCli::Util.exit_status || exit_status)    rescue StandardError => exc @@ -102,6 +94,8 @@ module LeapCli::Commands      end      if DEBUG        raise exc +    else +      exit(1)      end    end  end diff --git a/leap_cli.gemspec b/leap_cli.gemspec index beeb0a4..8a38893 100644 --- a/leap_cli.gemspec +++ b/leap_cli.gemspec @@ -44,38 +44,25 @@ spec = Gem::Specification.new do |s|    # test    s.add_development_dependency('minitest', '~> 5.0') - -  #s.add_development_dependency('rdoc') -  #s.add_development_dependency('aruba') +  s.add_development_dependency('rake', '~> 11.0')    # console gems    s.add_runtime_dependency('gli','~> 2.12', '>= 2.12.0')    # note: gli version is also pinned in leap_cli.rb. -  s.add_runtime_dependency('command_line_reporter', '~> 3.3') -  s.add_runtime_dependency('highline', '~> 1.6') -  s.add_runtime_dependency('paint', '~> 0.9')    # network gems -  s.add_runtime_dependency('net-ssh', '~> 2.7') -  # ^^ we can upgrade once we get off broken capistrano -  # https://github.com/net-ssh/net-ssh/issues/145 -  s.add_runtime_dependency('capistrano', '~> 2.15') +  s.add_runtime_dependency('sshkit', '~> 1.11') +  s.add_runtime_dependency('fog-aws', '~> 0.11')    # crypto gems -  #s.add_runtime_dependency('certificate_authority', '>= 0.2.0') -  # ^^ currently vendored    # s.add_runtime_dependency('gpgme')    # << does not build on debian jessie, so now optional.                                           # also, there is a ruby-gpgme package anyway. +  # acme-client is vendored for now, we need pre-lease version +  # s.add_runtime_dependency('acme-client', '~> 0.4.2') +  s.add_runtime_dependency('faraday', '~> 0.9', '>= 0.9.1') # for acme-client +    # misc gems    s.add_runtime_dependency('ya2yaml', '~> 0.31')    # pure ruby yaml, so we can better control output. see https://github.com/afunai/ya2yaml    s.add_runtime_dependency('json_pure', '~> 1.8')   # pure ruby json, so we can better control output. -  s.add_runtime_dependency('base32', '~> 0.3')      # base32 encoding - -  ## -  ## DEPENDENCIES for VENDORED GEMS -  ## - -  # certificate_authority -  s.add_runtime_dependency("activemodel", '~> 3.0', ">= 3.0.6")  end diff --git a/lib/leap/platform.rb b/lib/leap/platform.rb deleted file mode 100644 index 9112ef3..0000000 --- a/lib/leap/platform.rb +++ /dev/null @@ -1,90 +0,0 @@ -module Leap - -  class Platform -    class << self -      # -      # configuration -      # - -      attr_reader :version -      attr_reader :compatible_cli -      attr_accessor :facts -      attr_accessor :paths -      attr_accessor :node_files -      attr_accessor :monitor_username -      attr_accessor :reserved_usernames - -      attr_accessor :hiera_dir -      attr_accessor :hiera_path -      attr_accessor :files_dir -      attr_accessor :leap_dir -      attr_accessor :init_path - -      attr_accessor :default_puppet_tags - -      def define(&block) -        # some defaults: -        @reserved_usernames = [] -        @hiera_dir  = '/etc/leap' -        @hiera_path = '/etc/leap/hiera.yaml' -        @leap_dir   = '/srv/leap' -        @files_dir  = '/srv/leap/files' -        @init_path  = '/srv/leap/initialized' -        @default_puppet_tags = [] - -        self.instance_eval(&block) - -        @version ||= Gem::Version.new("0.0") -      end - -      def version=(version) -        @version = Gem::Version.new(version) -      end - -      def compatible_cli=(range) -        @compatible_cli = range -        @minimum_cli_version = Gem::Version.new(range.first) -        @maximum_cli_version = Gem::Version.new(range.last) -      end - -      # -      # return true if the cli_version is compatible with this platform. -      # -      def compatible_with_cli?(cli_version) -        cli_version = Gem::Version.new(cli_version) -        cli_version >= @minimum_cli_version && cli_version <= @maximum_cli_version -      end - -      # -      # return true if the platform version is within the specified range. -      # -      def version_in_range?(range) -        if range.is_a? String -          range = range.split('..') -        end -        minimum_platform_version = Gem::Version.new(range.first) -        maximum_platform_version = Gem::Version.new(range.last) -        @version >= minimum_platform_version && @version <= maximum_platform_version -      end - -      def major_version -        if @version.segments.first == 0 -          @version.segments[0..1].join('.') -        else -          @version.segments.first -        end -      end - -      def method_missing(method, *args) -        puts -        puts "WARNING:" -        puts "  leap_cli is out of date and does not understand `#{method}`." -        puts "  called from: #{caller.first}" -        puts "  please upgrade to a newer leap_cli" -      end - -    end - -  end - -end
\ No newline at end of file diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb index 0563327..c0e139e 100644 --- a/lib/leap_cli.rb +++ b/lib/leap_cli.rb @@ -1,6 +1,6 @@  module LeapCli -  module Commands; end  # for commands in leap_cli/commands -  module Macro; end     # for macros in leap_platform/provider_base/lib/macros +  module Commands; end  # for commands in leap_platform/lib/leap_cli/commands +  module Macro; end     # for macros in leap_platform/lib/leap_cli/macros  end  $ruby_version = RUBY_VERSION.split('.').collect{ |i| i.to_i }.extend(Comparable) @@ -11,11 +11,8 @@ $:.unshift(File.expand_path('../leap_cli/override',__FILE__))  # for a few gems, things will break if using earlier versions.  # enforce the compatible versions here:  require 'rubygems' -gem 'net-ssh', '~> 2.7'  gem 'gli', '~> 2.12', '>= 2.12.0' -require 'leap/platform' -  require 'leap_cli/version'  require 'leap_cli/exceptions' @@ -32,30 +29,13 @@ require 'leap_cli/core_ext/yaml'  require 'leap_cli/log'  require 'leap_cli/path'  require 'leap_cli/util' -require 'leap_cli/util/secret' -require 'leap_cli/util/remote_command' -require 'leap_cli/util/x509' -require 'leap_cli/logger'  require 'leap_cli/bootstrap' -require 'leap_cli/ssh_key' -require 'leap_cli/config/object' -require 'leap_cli/config/node' -require 'leap_cli/config/tag' -require 'leap_cli/config/provider' -require 'leap_cli/config/secrets' -require 'leap_cli/config/object_list' -require 'leap_cli/config/filter' -require 'leap_cli/config/environment' -require 'leap_cli/config/manager' -  require 'leap_cli/markdown_document_listener'  #  # allow everyone easy access to log() command.  #  module LeapCli -  Util.send(:extend, LeapCli::Log) -  Config::Manager.send(:include, LeapCli::Log) -  extend LeapCli::Log +  extend LeapCli::LogCommand  end diff --git a/lib/leap_cli/bootstrap.rb b/lib/leap_cli/bootstrap.rb index b7bc8e9..75edf5b 100644 --- a/lib/leap_cli/bootstrap.rb +++ b/lib/leap_cli/bootstrap.rb @@ -5,8 +5,8 @@  module LeapCli    module Bootstrap -    extend LeapCli::Log      extend self +    extend LeapCli::LogCommand      #      # the argument leapfile_path is only used for tests @@ -36,9 +36,11 @@ module LeapCli      # called from leap executable.      #      def load_libraries(app) -      if LeapCli.log_level >= 2 +      if LeapCli.logger.log_level >= 2          log_version        end +      add_platform_lib_to_path +      load_platform_libraries        load_commands(app)        load_macros      end @@ -72,14 +74,14 @@ module LeapCli        options = parse_logging_options(argv)        verbose = (options[:verbose] || 1).to_i        if verbose -        LeapCli.set_log_level(verbose) +        LeapCli.logger.log_level = verbose        end        if options[:log] -        LeapCli.log_file = options[:log] -        LeapCli::Util.log_raw(:log) { $0 + ' ' + argv.join(' ')} +        LeapCli.logger.log_file = options[:log] +        LeapCli.logger.log_raw(:log) { $0 + ' ' + argv.join(' ')}        end        unless options[:color].nil? -        LeapCli.log_in_color = options[:color] +        LeapCli.logger.log_in_color = options[:color]        end      end @@ -97,17 +99,16 @@ module LeapCli          if !Path.platform || !File.directory?(Path.platform)            bail! { log :missing, "platform directory '#{Path.platform}'" }          end -        if LeapCli.log_file.nil? && LeapCli.leapfile.log -          LeapCli.log_file = LeapCli.leapfile.log +        if LeapCli.logger.log_file.nil? && LeapCli.leapfile.log +          LeapCli.logger.log_file = LeapCli.leapfile.log          end        elsif !leapfile_optional?(argv)          puts          puts " =" -        log :note, "There is no `Leapfile` in this directory, or any parent directory.\n"+ -                   " =       "+ +        log :NOTE, "There is no `Leapfile` in this directory, or any parent directory.\n"+ +                   " =      "+                     "Without this file, most commands will not be available."          puts " =" -        puts        end      end @@ -158,7 +159,9 @@ module LeapCli      # Yes, hacky.      #      def leapfile_optional?(argv) -      if argv.include?('--version') +      if TEST +        return true +      elsif argv.include?('--version')          return true        else          without_flags = argv.select {|i| i !~ /^-/} @@ -193,5 +196,26 @@ module LeapCli        end      end +    # +    # makes all the ruby libraries in the leap_platform/lib directory +    # available for inclusion. +    # +    def add_platform_lib_to_path +      if Path.platform +        path = File.join(Path.platform, 'lib') +        $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path) +      end +    end + +    # +    # loads libraries that live in the platform and should +    # always be available. +    # +    def load_platform_libraries +      if Path.platform +        require 'leap_cli/load_libraries' +      end +    end +    end  end diff --git a/lib/leap_cli/commands/common.rb b/lib/leap_cli/commands/common.rb index 7bf49db..3dab2a0 100644 --- a/lib/leap_cli/commands/common.rb +++ b/lib/leap_cli/commands/common.rb @@ -1,61 +1,103 @@ -# -# Some common helpers available to all LeapCli::Commands -# -# This also includes utility methods, and makes all instance -# methods available as class methods. -# +require 'readline' -module LeapCli -  module Commands +module LeapCli; module Commands -    extend self -    extend LeapCli::Log -    extend LeapCli::Util -    extend LeapCli::Util::RemoteCommand +  extend LeapCli::LogCommand +  extend LeapCli::Util -    protected - -    def path(name) -      Path.named_path(name) -    end +  def path(name) +    Path.named_path(name) +  end -    # -    # keeps prompting the user for a numbered choice, until they pick a good one or bail out. -    # -    # block is yielded and is responsible for rendering the choices. -    # -    def numbered_choice_menu(msg, items, &block) -      while true -        say("\n" + msg + ':') -        items.each_with_index &block -        say("q. quit") -        index = ask("number 1-#{items.length}> ") -        if index.empty? -          next -        elsif index =~ /q/ +  # +  # keeps prompting the user for a numbered choice, until they pick a good one or bail out. +  # +  # block is yielded and is responsible for rendering the choices. +  # +  def numbered_choice_menu(msg, items, &block) +    while true +      say("\n" + msg + ':') +      items.each_with_index(&block) +      say("q. quit") +      index = ask("number 1-#{items.length}> ") +      if index.nil? || index.empty? +        next +      elsif index =~ /q/ +        bail! +      else +        i = index.to_i - 1 +        if i < 0 || i >= items.length            bail!          else -          i = index.to_i - 1 -          if i < 0 || i >= items.length -            bail! -          else -            return i -          end +          return i          end        end      end +  end + +  def parse_node_list(nodes) +    if nodes.is_a? Config::Object +      Config::ObjectList.new(nodes) +    elsif nodes.is_a? Config::ObjectList +      nodes +    elsif nodes.is_a? String +      manager.filter!(nodes) +    else +      bail! "argument error" +    end +  end + +  def say(statement) +    if ends_in_whitespace?(statement) +      $stdout.print(statement) +      $stdout.flush +    else +      $stdout.puts(statement) +    end +  end + +  def ask(question, options={}) +    default = options[:default] +    if default +      if ends_in_whitespace?(question) +        question = question + "|" + default + "| " +      else +        question = question + "|" + default + "|" +      end +    end +    response = Readline.readline(question, true) # set to false if ever reading passwords. +    if response +      response = response.strip +      if response.empty? +        return default +      else +        return response +      end +    else +      return default +    end +  end -    def parse_node_list(nodes) -      if nodes.is_a? Config::Object -        Config::ObjectList.new(nodes) -      elsif nodes.is_a? Config::ObjectList -        nodes -      elsif nodes.is_a? String -        manager.filter!(nodes) +  def agree(question, options={}) +    while true +      response = ask(question, options) +      if response.nil? +        say('Please enter "yes" or "no".') +      elsif ["y","yes", "ye"].include?(response.downcase) +        return true +      elsif ["n", "no"].include?(response.downcase) +        return false        else -        bail! "argument error" +        say('Please enter "yes" or "no".')        end      end +  end +  private + +  # true if str ends in whitespace before a color escape code. +  def ends_in_whitespace?(str) +    /[ \t](\e\[\d+(;\d+)*m)?\Z/ =~ str    end -end
\ No newline at end of file + +end; end diff --git a/lib/leap_cli/commands/new.rb b/lib/leap_cli/commands/new.rb index 838b80e..6b60e7d 100644 --- a/lib/leap_cli/commands/new.rb +++ b/lib/leap_cli/commands/new.rb @@ -4,7 +4,6 @@ module LeapCli; module Commands    desc 'Creates a new provider instance in the specified directory, creating it if necessary.'    arg_name 'DIRECTORY' -  #skips_pre    command :new do |c|      c.flag 'name', :desc => "The name of the provider." #, :default_value => 'Example'      c.flag 'domain', :desc => "The primary domain of the provider." #, :default_value => 'example.org' @@ -12,19 +11,7 @@ module LeapCli; module Commands      c.flag 'contacts', :desc => "Default email address contacts." #, :default_value => 'root'      c.action do |global, options, args| -      unless args.first -        # this should not be needed, but GLI is not making it required. -        bail! "Argument DIRECTORY is required." -      end -      directory = File.expand_path(args.first) -      create_provider_directory(global, directory) -      options[:domain]   ||= ask_string("The primary domain of the provider: ") {|q| q.default = 'example.org'} -      options[:name]     ||= ask_string("The name of the provider: ") {|q| q.default = 'Example'} -      options[:platform] ||= ask_string("File path of the leap_platform directory: ") {|q| q.default = File.expand_path('../leap_platform', directory)} -      options[:platform] = "./" + options[:platform] unless options[:platform] =~ /^\// -      options[:contacts] ||= ask_string("Default email address contacts: ") {|q| q.default = 'root@' + options[:domain]} -      options[:platform] = relative_path(options[:platform]) -      create_initial_provider_files(directory, global, options) +      new_provider_action(global, options, args)      end    end @@ -32,13 +19,33 @@ module LeapCli; module Commands    DEFAULT_REPO = 'https://leap.se/git/leap_platform.git' +  def new_provider_action(global, options, args) +    unless args.first +      # this should not be needed, but GLI is not making it required. +      bail! "Argument DIRECTORY is required." +    end +    directory = File.expand_path(args.first) +    create_provider_directory(global, directory) +    options[:domain]   ||= ask_string("The primary domain of the provider: ", +                                      default: 'example.org') +    options[:name]     ||= ask_string("The name of the provider: ", +                                      default: 'Example') +    options[:platform] ||= ask_string("File path of the leap_platform directory: ", +                                      default: File.expand_path('../leap_platform', directory)) +    options[:platform] = "./" + options[:platform] unless options[:platform] =~ /^\// +    options[:contacts] ||= ask_string("Default email address contacts: ", +                                       default: 'root@' + options[:domain]) +    options[:platform] = relative_path(options[:platform]) +    create_initial_provider_files(directory, global, options) +  end +    #    # don't let the user specify any of the following: y, yes, n, no    # they must actually input a real string    # -  def ask_string(str, &block) +  def ask_string(str, options={})      while true -      value = ask(str, &block) +      value = ask(str, options)        if value =~ /^(y|yes|n|no)$/i          say "`#{value}` is not a valid value. Try again"        else @@ -54,7 +61,7 @@ module LeapCli; module Commands      unless directory && directory.any?        help! "Directory name is required."      end -    unless File.exists?(directory) +    unless File.exist?(directory)        if global[:yes] || agree("Create directory #{directory}? ")          ensure_dir directory        else diff --git a/lib/leap_cli/commands/pre.rb b/lib/leap_cli/commands/pre.rb index f4bf7bb..0b7e98b 100644 --- a/lib/leap_cli/commands/pre.rb +++ b/lib/leap_cli/commands/pre.rb @@ -1,9 +1,11 @@ -  #  # check to make sure we can find the root directory of the platform  #  module LeapCli; module Commands +  extend self # this is a trick to make all instance methods +              # available as class methods. +    desc 'Verbosity level 0..5'    arg_name 'LEVEL'    default_value '1' diff --git a/lib/leap_cli/config/environment.rb b/lib/leap_cli/config/environment.rb deleted file mode 100644 index df4b56c..0000000 --- a/lib/leap_cli/config/environment.rb +++ /dev/null @@ -1,180 +0,0 @@ -# -# All configurations files can be isolated into separate environments. -# -# Each config json in each environment inherits from the default environment, -# which in term inherits from the "_base_" environment: -# -# _base_             -- base provider in leap_platform -# '- default         -- environment in provider dir when no env is set -#    '- production   -- example environment -# - -module LeapCli; module Config - -  class Environment -    # the String name of the environment -    attr_accessor :name - -    # the shared Manager object -    attr_accessor :manager - -    # hashes of {name => Config::Object} -    attr_accessor :services, :tags, :partials - -    # a Config::Provider -    attr_accessor :provider - -    # a Config::Object -    attr_accessor :common - -    # shared, non-inheritable -    def nodes; @@nodes; end -    def secrets; @@secrets; end - -    def initialize(manager, name, search_dir, parent, options={}) -      @@nodes ||= nil -      @@secrets ||= nil - -      @manager = manager -      @name    = name - -      load_provider_files(search_dir, options) - -      if parent -        @services.inherit_from! parent.services, self -            @tags.inherit_from! parent.tags    , self -        @partials.inherit_from! parent.partials, self -          @common.inherit_from! parent.common -        @provider.inherit_from! parent.provider -      end - -      if @provider -        @provider.set_env(name) -        @provider.validate! -      end -    end - -    def load_provider_files(search_dir, options) -      # -      # load empty environment if search_dir doesn't exist -      # -      if search_dir.nil? || !Dir.exist?(search_dir) -        @services = Config::ObjectList.new -        @tags     = Config::ObjectList.new -        @partials = Config::ObjectList.new -        @provider = Config::Provider.new -        @common   = Config::Object.new -        return -      end - -      # -      # inheritable -      # -      if options[:scope] -        scope = options[:scope] -        @services = load_all_json(Path.named_path([:service_env_config, '*', scope],  search_dir), Config::Tag, options) -        @tags     = load_all_json(Path.named_path([:tag_env_config, '*', scope],      search_dir), Config::Tag, options) -        @partials = load_all_json(Path.named_path([:service_env_config, '_*', scope], search_dir), Config::Tag, options) -        @provider = load_json(    Path.named_path([:provider_env_config, scope],      search_dir), Config::Provider, options) -        @common   = load_json(    Path.named_path([:common_env_config, scope],        search_dir), Config::Object, options) -      else -        @services = load_all_json(Path.named_path([:service_config, '*'],  search_dir), Config::Tag, options) -        @tags     = load_all_json(Path.named_path([:tag_config, '*'],      search_dir), Config::Tag, options) -        @partials = load_all_json(Path.named_path([:service_config, '_*'], search_dir), Config::Tag, options) -        @provider = load_json(    Path.named_path(:provider_config,        search_dir), Config::Provider, options) -        @common   = load_json(    Path.named_path(:common_config,          search_dir), Config::Object, options) -      end - -      # remove 'name' from partials, since partials get merged with nodes -      @partials.values.each {|partial| partial.delete('name'); } - -      # -      # shared: currently non-inheritable -      # load the first ones we find, and only those. -      # -      if @@nodes.nil? || @@nodes.empty? -        @@nodes = load_all_json(Path.named_path([:node_config, '*'], search_dir), Config::Node, options) -      end -      if @@secrets.nil? || @@secrets.empty? -        @@secrets = load_json(Path.named_path(:secrets_config, search_dir), Config::Secrets, options) -      end -    end - -    # -    # Loads a json template file as a Hash (used only when creating a new node .json -    # file for the first time). -    # -    def template(template) -      path = Path.named_path([:template_config, template], Path.provider_base) -      if File.exists?(path) -        return load_json(path, Config::Object) -      else -        return nil -      end -    end - -    private - -    def load_all_json(pattern, object_class, options={}) -      results = Config::ObjectList.new -      Dir.glob(pattern).each do |filename| -        next if options[:no_dots] && File.basename(filename) !~ /^[^\.]*\.json$/ -        obj = load_json(filename, object_class) -        if obj -          name = File.basename(filename).force_encoding('utf-8').sub(/^([^\.]+).*\.json$/,'\1') -          obj['name'] ||= name -          if options[:env] -            obj.environment = options[:env] -          end -          results[name] = obj -        end -      end -      results -    end - -    def load_json(filename, object_class, options={}) -      if !File.exists?(filename) -        return object_class.new(self) -      end - -      Util::log :loading, filename, 3 - -      # -      # Read a JSON file, strip out comments. -      # -      # UTF8 is the default encoding for JSON, but others are allowed: -      # https://www.ietf.org/rfc/rfc4627.txt -      # -      buffer = StringIO.new -      File.open(filename, "rb", :encoding => 'UTF-8') do |f| -        while (line = f.gets) -          next if line =~ /^\s*\/\// -          buffer << line -        end -      end - -      # -      # force UTF-8 -      # -      if $ruby_version >= [1,9] -        string = buffer.string.force_encoding('utf-8') -      else -        string = Iconv.conv("UTF-8//IGNORE", "UTF-8", buffer.string) -      end - -      # parse json -      begin -        hash = JSON.parse(string, :object_class => Hash, :array_class => Array) || {} -      rescue SyntaxError, JSON::ParserError => exc -        Util::log 0, :error, 'in file "%s":' % filename -        Util::log 0, exc.to_s, :indent => 1 -        return nil -      end -      object = object_class.new(self) -      object.deep_merge!(hash) -      return object -    end - -  end # end Environment - -end; end
\ No newline at end of file diff --git a/lib/leap_cli/config/filter.rb b/lib/leap_cli/config/filter.rb deleted file mode 100644 index 2c80be8..0000000 --- a/lib/leap_cli/config/filter.rb +++ /dev/null @@ -1,178 +0,0 @@ -# -# Many leap_cli commands accept a list of filters to select a subset of nodes for the command to -# be applied to. This class is a helper for manager to run these filters. -# -# Classes other than Manager should not use this class. -# -# Filter rules: -# -# * A filter consists of a list of tokens -# * A token may be a service name, tag name, environment name, or node name. -# * Each token may be optionally prefixed with a plus sign. -# * Multiple tokens with a plus are treated as an OR condition, -#   but treated as an AND condition with the plus sign. -# -# For example -# -# * openvpn +development => all nodes with service 'openvpn' AND environment 'development' -# * openvpn seattle => all nodes with service 'openvpn' OR tag 'seattle'. -# -# There can only be one environment specified. Typically, there are also tags -# for each environment name. These name are treated as environments, not tags. -# -module LeapCli -  module Config -    class Filter - -      # -      # filter -- array of strings, each one a filter -      # options -- hash, possible keys include -      #   :nopin -- disregard environment pinning -      #   :local -- if false, disallow local nodes -      # -      # A nil value in the filters array indicates -      # the default environment. This is in order to support -      # calls like `manager.filter(environments)` -      # -      def initialize(filters, options, manager) -        @filters = filters.nil? ? [] : filters.dup -        @environments = [] -        @options = options -        @manager = manager - -        # split filters by pulling out items that happen -        # to be environment names. -        if LeapCli.leapfile.environment.nil? || @options[:nopin] -          @environments = [] -        else -          @environments = [LeapCli.leapfile.environment] -        end -        @filters.select! do |filter| -          if filter.nil? -            @environments << nil unless @environments.include?(nil) -            false -          else -            filter_text = filter.sub(/^\+/,'') -            if is_environment?(filter_text) -              if filter_text == LeapCli.leapfile.environment -                # silently ignore already pinned environments -              elsif (filter =~ /^\+/ || @filters.first == filter) && !@environments.empty? -                LeapCli::Util.bail! do -                  LeapCli::Util.log "Environments are exclusive: no node is in two environments." do -                    LeapCli::Util.log "Tried to filter on '#{@environments.join('\' AND \'')}' AND '#{filter_text}'" -                  end -                end -              else -                @environments << filter_text -              end -              false -            else -              true -            end -          end -        end - -        # don't let the first filter have a + prefix -        if @filters[0] =~ /^\+/ -          @filters[0] = @filters[0][1..-1] -        end -      end - -      # actually run the filter, returns a filtered list of nodes -      def nodes() -        if @filters.empty? -          return nodes_for_empty_filter -        else -          return nodes_for_filter -        end -      end - -      private - -      def nodes_for_empty_filter -        node_list = @manager.nodes -        if @environments.any? -          node_list = node_list[ @environments.collect{|e|[:environment, env_to_filter(e)]} ] -        end -        if @options[:local] === false -          node_list = node_list[:environment => '!local'] -        end -        if @options[:disabled] === false -          node_list = node_list[:environment => '!disabled'] -        end -        node_list -      end - -      def nodes_for_filter -        node_list = Config::ObjectList.new -        @filters.each do |filter| -          if filter =~ /^\+/ -            keep_list = nodes_for_name(filter[1..-1]) -            node_list.delete_if do |name, node| -              if keep_list[name] -                false -              else -                true -              end -            end -          else -            node_list.merge!(nodes_for_name(filter)) -          end -        end -        node_list -      end - -      private - -      # -      # returns a set of nodes corresponding to a single name, -      # where name could be a node name, service name, or tag name. -      # -      # For services and tags, we only include nodes for the -      # environments that are active -      # -      def nodes_for_name(name) -        if node = @manager.nodes[name] -          return Config::ObjectList.new(node) -        elsif @environments.empty? -          if @manager.services[name] -            return @manager.env('_all_').services[name].node_list -          elsif @manager.tags[name] -            return @manager.env('_all_').tags[name].node_list -          else -            LeapCli::Util.log :warning, "filter '#{name}' does not match any node names, tags, services, or environments." -            return Config::ObjectList.new -          end -        else -          node_list = Config::ObjectList.new -          if @manager.services[name] -            @environments.each do |env| -              node_list.merge!(@manager.env(env).services[name].node_list) -            end -          elsif @manager.tags[name] -            @environments.each do |env| -              node_list.merge!(@manager.env(env).tags[name].node_list) -            end -          else -            LeapCli::Util.log :warning, "filter '#{name}' does not match any node names, tags, services, or environments." -          end -          return node_list -        end -      end - -      # -      # when pinning, we use the name 'default' to specify nodes -      # without an environment set, but when filtering, we need to filter -      # on :environment => nil. -      # -      def env_to_filter(environment) -        environment == 'default' ? nil : environment -      end - -      def is_environment?(text) -        text == 'default' || @manager.environment_names.include?(text) -      end - -    end -  end -end diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb deleted file mode 100644 index ecc59f3..0000000 --- a/lib/leap_cli/config/manager.rb +++ /dev/null @@ -1,419 +0,0 @@ -# encoding: utf-8 - -require 'json/pure' - -if $ruby_version < [1,9] -  require 'iconv' -end - -module LeapCli -  module Config - -    # -    # A class to manage all the objects in all the configuration files. -    # -    class Manager - -      def initialize -        @environments = {} # hash of `Environment` objects, keyed by name. -        Config::Object.send(:include, LeapCli::Macro) -      end - -      ## -      ## ATTRIBUTES -      ## - -      # -      # returns the Hash of the contents of facts.json -      # -      def facts -        @facts ||= begin -          content = Util.read_file(:facts) -          if !content || content.empty? -            content = "{}" -          end -          JSON.parse(content) -        rescue SyntaxError, JSON::ParserError => exc -          Util::bail! "Could not parse facts.json -- #{exc}" -        end -      end - -      # -      # returns an Array of all the environments defined for this provider. -      # the returned array includes nil (for the default environment) -      # -      def environment_names -        @environment_names ||= begin -          [nil] + (env.tags.field('environment') + env.nodes.field('environment')).compact.uniq -        end -      end - -      # -      # Returns the appropriate environment variable -      # -      def env(env=nil) -        @environments[env || 'default'] -      end - -      # -      # The default accessors -      # -      # For these defaults, use 'default' environment, or whatever -      # environment is pinned. -      # -      # I think it might be an error that these are ever used -      # and I would like to get rid of them. -      # -      def services; env(default_environment).services; end -      def tags;     env(default_environment).tags;     end -      def partials; env(default_environment).partials; end -      def provider; env(default_environment).provider; end -      def common;   env(default_environment).common;   end -      def secrets;  env(default_environment).secrets;  end -      def nodes;    env(default_environment).nodes;    end -      def template(*args) -        self.env.template(*args) -      end - -      def default_environment -        LeapCli.leapfile.environment -      end - -      ## -      ## IMPORT EXPORT -      ## - -      def add_environment(args) -        if args[:inherit] -          parent = @environments[args.delete(:inherit)] -        else -          parent = nil -        end -        @environments[args[:name]] = Environment.new( -          self, -          args.delete(:name), -          args.delete(:dir), -          parent, -          args -        ) -      end - -      # -      # load .json configuration files -      # -      def load(options = {}) -        @provider_dir = Path.provider - -        # load base -        add_environment(name: '_base_', dir: Path.provider_base) - -        # load provider -        Util::assert_files_exist!(Path.named_path(:provider_config, @provider_dir)) -        add_environment(name: 'default', dir: @provider_dir, -          inherit: '_base_', no_dots: true) - -        # create a special '_all_' environment, used for tracking -        # the union of all the environments -        add_environment(name: '_all_', inherit: 'default') - -        # load environments -        environment_names.each do |ename| -          if ename -            log 3, :loading, '%s environment...' % ename -            add_environment(name: ename, dir: @provider_dir, -              inherit: 'default', scope: ename) -          end -        end - -        # apply inheritance -        env.nodes.each do |name, node| -          Util::assert! name =~ /^[0-9a-z-]+$/, "Illegal character(s) used in node name '#{name}'" -          env.nodes[name] = apply_inheritance(node) -        end - -        # do some node-list post-processing -        cleanup_node_lists(options) - -        # apply control files -        env.nodes.each do |name, node| -          control_files(node).each do |file| -            begin -              node.eval_file file -            rescue ConfigError => exc -              if options[:continue_on_error] -                exc.log -              else -                raise exc -              end -            end -          end -        end -      end - -      # -      # save compiled hiera .yaml files -      # -      # if a node_list is specified, only update those .yaml files. -      # otherwise, update all files, destroying files that are no longer used. -      # -      def export_nodes(node_list=nil) -        updated_hiera = [] -        updated_files = [] -        existing_hiera = nil -        existing_files = nil - -        unless node_list -          node_list = env.nodes -          existing_hiera = Dir.glob(Path.named_path([:hiera, '*'], @provider_dir)) -          existing_files = Dir.glob(Path.named_path([:node_files_dir, '*'], @provider_dir)) -        end - -        node_list.each_node do |node| -          filepath = Path.named_path([:node_files_dir, node.name], @provider_dir) -          hierapath = Path.named_path([:hiera, node.name], @provider_dir) -          Util::write_file!(hierapath, node.dump_yaml) -          updated_files << filepath -          updated_hiera << hierapath -        end - -        if @disabled_nodes -          # make disabled nodes appear as if they are still active -          @disabled_nodes.each_node do |node| -            updated_files << Path.named_path([:node_files_dir, node.name], @provider_dir) -            updated_hiera << Path.named_path([:hiera, node.name], @provider_dir) -          end -        end - -        # remove files that are no longer needed -        if existing_hiera -          (existing_hiera - updated_hiera).each do |filepath| -            Util::remove_file!(filepath) -          end -        end -        if existing_files -          (existing_files - updated_files).each do |filepath| -            Util::remove_directory!(filepath) -          end -        end -      end - -      def export_secrets(clean_unused_secrets = false) -        if env.secrets.any? -          Util.write_file!([:secrets_config, @provider_dir], env.secrets.dump_json(clean_unused_secrets) + "\n") -        end -      end - -      ## -      ## FILTERING -      ## - -      # -      # returns a node list consisting only of nodes that satisfy the filter criteria. -      # -      # filter: condition [condition] [condition] [+condition] -      # condition: [node_name | service_name | tag_name | environment_name] -      # -      # if conditions is prefixed with +, then it works like an AND. Otherwise, it works like an OR. -      # -      # args: -      # filter -- array of filter terms, one per item -      # -      # options: -      # :local -- if :local is false and the filter is empty, then local nodes are excluded. -      # :nopin -- if true, ignore environment pinning -      # -      def filter(filters=nil, options={}) -        Filter.new(filters, options, self).nodes() -      end - -      # -      # same as filter(), but exits if there is no matching nodes -      # -      def filter!(filters, options={}) -        node_list = filter(filters, options) -        Util::assert! node_list.any?, "Could not match any nodes from '#{filters.join ' '}'" -        return node_list -      end - -      # -      # returns a single Config::Object that corresponds to a Node. -      # -      def node(name) -        if name =~ /\./ -          # probably got a fqdn, since periods are not allowed in node names. -          # so, take the part before the first period as the node name -          name = name.split('.').first -        end -        env.nodes[name] -      end - -      # -      # returns a single node that is disabled -      # -      def disabled_node(name) -        @disabled_nodes[name] -      end - -      # -      # yields each node, in sorted order -      # -      def each_node(&block) -        env.nodes.each_node &block -      end - -      def reload_node!(node) -        env.nodes[node.name] = apply_inheritance!(node) -      end - -      ## -      ## CONNECTIONS -      ## - -      class ConnectionList < Array -        def add(data={}) -          self << { -            "from" => data[:from], -            "to" => data[:to], -            "port" => data[:port] -          } -        end -      end - -      def connections -        @connections ||= ConnectionList.new -      end - -      ## -      ## PRIVATE -      ## - -      private - -      # -      # makes a node inherit options from appropriate the common, service, and tag json files. -      # -      def apply_inheritance(node, throw_exceptions=false) -        new_node = Config::Node.new(nil) -        name     = node.name -        node_env = guess_node_env(node) -        new_node.set_environment(node_env, new_node) - -        # inherit from common -        new_node.deep_merge!(node_env.common) - -        # inherit from services -        if node['services'] -          node['services'].to_a.each do |node_service| -            service = node_env.services[node_service] -            if service.nil? -              msg = 'in node "%s": the service "%s" does not exist.' % [node['name'], node_service] -              log 0, :error, msg -              raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions -            else -              new_node.deep_merge!(service) -            end -          end -        end - -        # inherit from tags -        if node.vagrant? -          node['tags'] = (node['tags'] || []).to_a + ['local'] -        end -        if node['tags'] -          node['tags'].to_a.each do |node_tag| -            tag = node_env.tags[node_tag] -            if tag.nil? -              msg = 'in node "%s": the tag "%s" does not exist.' % [node['name'], node_tag] -              log 0, :error, msg -              raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions -            else -              new_node.deep_merge!(tag) -            end -          end -        end - -        # inherit from node -        new_node.deep_merge!(node) -        return new_node -      end - -      def apply_inheritance!(node) -        apply_inheritance(node, true) -      end - -      # -      # Guess the environment of the node from the tag names. -      # -      # Technically, this is wrong: a tag that sets the environment might not be -      # named the same as the environment. This code assumes that it is. -      # -      # Unfortunately, it is a chicken and egg problem. We need to know the nodes -      # likely environment in order to apply the inheritance that will actually -      # determine the node's properties. -      # -      def guess_node_env(node) -        environment = self.env(default_environment) -        if node['tags'] -          node['tags'].to_a.each do |tag| -            if self.environment_names.include?(tag) -              environment = self.env(tag) -            end -          end -        end -        return environment -      end - -      # -      # does some final clean at the end of loading nodes. -      # this includes removing disabled nodes, and populating -      # the services[x].node_list and tags[x].node_list -      # -      def cleanup_node_lists(options) -        @disabled_nodes = Config::ObjectList.new -        env.nodes.each do |name, node| -          if node.enabled || options[:include_disabled] -            if node['services'] -              node['services'].to_a.each do |node_service| -                env(node.environment).services[node_service].node_list.add(node.name, node) -                env('_all_').services[node_service].node_list.add(node.name, node) -              end -            end -            if node['tags'] -              node['tags'].to_a.each do |node_tag| -                env(node.environment).tags[node_tag].node_list.add(node.name, node) -                env('_all_').tags[node_tag].node_list.add(node.name, node) -              end -            end -          elsif !options[:include_disabled] -            log 2, :skipping, "disabled node #{name}." -            env.nodes.delete(name) -            @disabled_nodes[name] = node -          end -        end -      end - -      # -      # returns a list of 'control' files for this node. -      # a control file is like a service or a tag JSON file, but it contains -      # raw ruby code that gets evaluated in the context of the node. -      # Yes, this entirely breaks our functional programming model -      # for JSON generation. -      # -      def control_files(node) -        files = [] -        [Path.provider_base, @provider_dir].each do |provider_dir| -          [['services', :service_config], ['tags', :tag_config]].each do |attribute, path_sym| -            node[attribute].each do |attr_value| -              path = Path.named_path([path_sym, "#{attr_value}.rb"], provider_dir).sub(/\.json$/,'') -              if File.exists?(path) -                files << path -              end -            end -          end -        end -        return files -      end - -    end -  end -end diff --git a/lib/leap_cli/config/node.rb b/lib/leap_cli/config/node.rb deleted file mode 100644 index 65735d5..0000000 --- a/lib/leap_cli/config/node.rb +++ /dev/null @@ -1,77 +0,0 @@ -# -# Configuration for a 'node' (a server in the provider's infrastructure) -# - -require 'ipaddr' - -module LeapCli; module Config - -  class Node < Object -    attr_accessor :file_paths - -    def initialize(environment=nil) -      super(environment) -      @node = self -      @file_paths = [] -    end - -    # -    # returns true if this node has an ip address in the range of the vagrant network -    # -    def vagrant? -      begin -        vagrant_range = IPAddr.new LeapCli.leapfile.vagrant_network -      rescue ArgumentError => exc -        Util::bail! { Util::log :invalid, "ip address '#{@node.ip_address}' vagrant.network" } -      end - -      begin -        ip_address = IPAddr.new @node.get('ip_address') -      rescue ArgumentError => exc -        Util::log :warning, "invalid ip address '#{@node.get('ip_address')}' for node '#{@node.name}'" -      end -      return vagrant_range.include?(ip_address) -    end - -    # -    # Return a hash table representation of ourselves, with the key equal to the @node.name, -    # and the value equal to the fields specified in *keys. -    # -    # Also, the result is flattened to a single hash, so a key of 'a.b' becomes 'a_b' -    # -    # compare to Object#pick(*keys). This method is the sames as Config::ObjectList#pick_fields, -    # but works on a single node. -    # -    # Example: -    # -    #  node.pick('domain.internal') => -    # -    #    { -    #      'node1': { -    #        'domain_internal': 'node1.example.i' -    #      } -    #    } -    # -    def pick_fields(*keys) -      {@node.name => self.pick(*keys)} -    end - -    # -    # can be overridden by the platform. -    # returns a list of node names that should be tested before this node -    # -    def test_dependencies -      [] -    end - -    # returns a string list of supported ssh host key algorithms for this node. -    # or an empty string if it could not be determined -    def supported_ssh_host_key_algorithms -      @host_key_algo ||= SshKey.supported_host_key_algorithms( -        Util.read_file([:node_ssh_pub_key, @node.name]) -      ) -    end - -  end - -end; end diff --git a/lib/leap_cli/config/object.rb b/lib/leap_cli/config/object.rb deleted file mode 100644 index b117c2f..0000000 --- a/lib/leap_cli/config/object.rb +++ /dev/null @@ -1,428 +0,0 @@ -# encoding: utf-8 - -require 'erb' -require 'json/pure'  # pure ruby implementation is required for our sorted trick to work. - -if $ruby_version < [1,9] -  $KCODE = 'UTF8' -end -require 'ya2yaml' # pure ruby yaml - -module LeapCli -  module Config - -    # -    # This class represents the configuration for a single node, service, or tag. -    # Also, all the nested hashes are also of this type. -    # -    # It is called 'object' because it corresponds to an Object in JSON. -    # -    class Object < Hash - -      attr_reader :env -      attr_reader :node - -      def initialize(environment=nil, node=nil) -        raise ArgumentError unless environment.nil? || environment.is_a?(Config::Environment) -        @env = environment -        # an object that is a node as @node equal to self, otherwise all the -        # child objects point back to the top level node. -        @node = node || self -      end - -      def manager -        @env.manager -      end - -      # -      # TODO: deprecate node.global() -      # -      def global -        @env -      end - -      def environment=(e) -        self.store('environment', e) -      end - -      def environment -        self['environment'] -      end - -      def duplicate(env) -        new_object = self.deep_dup -        new_object.set_environment(env, new_object) -      end - -      # -      # export YAML -      # -      # We use pure ruby yaml exporter ya2yaml instead of SYCK or PSYCH because it -      # allows us greater compatibility regardless of installed ruby version and -      # greater control over how the yaml is exported (sorted keys, in particular). -      # -      def dump_yaml -        evaluate(@node) -        sorted_ya2yaml(:syck_compatible => true) -      end - -      # -      # export JSON -      # -      def dump_json(options={}) -        evaluate(@node) -        if options[:format] == :compact -          return self.to_json -        else -          excluded = {} -          if options[:exclude] -            options[:exclude].each do |key| -              excluded[key] = self[key] -              self.delete(key) -            end -          end -          json_str = JSON.sorted_generate(self) -          if excluded.any? -            self.merge!(excluded) -          end -          return json_str -        end -      end - -      def evaluate(context=@node) -        evaluate_everything(context) -        late_evaluate_everything(context) -      end - -      ## -      ## FETCHING VALUES -      ## - -      def [](key) -        get(key) -      end - -      # Overrride some default methods in Hash that are likely to -      # be used as attributes. -      alias_method :hkey, :key -      def key; get('key'); end - -      # -      # make hash addressable like an object (e.g. obj['name'] available as obj.name) -      # -      def method_missing(method, *args, &block) -        get!(method) -      end - -      def get(key) -        begin -          get!(key) -        rescue NoMethodError -          nil -        end -      end - -      # override behavior of #default() from Hash -      def default -        get!('default') -      end - -      # -      # Like a normal Hash#[], except: -      # -      # (1) lazily eval dynamic values when we encounter them. (i.e. strings that start with "= ") -      # -      # (2) support for nested references in a single string (e.g. ['a.b'] is the same as ['a']['b']) -      #     the dot path is always absolute, starting at the top-most object. -      # -      def get!(key) -        key = key.to_s -        if self.has_key?(key) -          fetch_value(key) -        elsif key =~ /\./ -          # for keys with with '.' in them, we start from the root object (@node). -          keys = key.split('.') -          value = self.get!(keys.first) -          if value.is_a? Config::Object -            value.get!(keys[1..-1].join('.')) -          else -            value -          end -        else -          raise NoMethodError.new(key, "No method '#{key}' for #{self.class}") -        end -      end - -      ## -      ## COPYING -      ## - -      # -      # A deep (recursive) merge with another Config::Object. -      # -      # If prefer_self is set to true, the value from self will be picked when there is a conflict -      # that cannot be merged. -      # -      # Merging rules: -      # -      # - If a value is a hash, we recursively merge it. -      # - If the value is simple, like a string, the new one overwrites the value. -      # - If the value is an array: -      #   - If both old and new values are arrays, the new one replaces the old. -      #   - If one of the values is simple but the other is an array, the simple is added to the array. -      # -      def deep_merge!(object, prefer_self=false) -        object.each do |key,new_value| -          if self.has_key?('+'+key) -            mode = :add -            old_value = self.fetch '+'+key, nil -            self.delete('+'+key) -          elsif self.has_key?('-'+key) -            mode = :subtract -            old_value = self.fetch '-'+key, nil -            self.delete('-'+key) -          elsif self.has_key?('!'+key) -            mode = :replace -            old_value = self.fetch '!'+key, nil -            self.delete('!'+key) -          else -            mode = :normal -            old_value = self.fetch key, nil -          end - -          # clean up boolean -          new_value = true  if new_value == "true" -          new_value = false if new_value == "false" -          old_value = true  if old_value == "true" -          old_value = false if old_value == "false" - -          # force replace? -          if mode == :replace && prefer_self -            value = old_value - -          # merge hashes -          elsif old_value.is_a?(Hash) || new_value.is_a?(Hash) -            value = Config::Object.new(@env, @node) -            old_value.is_a?(Hash) ? value.deep_merge!(old_value) : (value[key] = old_value if !old_value.nil?) -            new_value.is_a?(Hash) ? value.deep_merge!(new_value, prefer_self) : (value[key] = new_value if !new_value.nil?) - -          # merge nil -          elsif new_value.nil? -            value = old_value -          elsif old_value.nil? -            value = new_value - -          # merge arrays when one value is not an array -          elsif old_value.is_a?(Array) && !new_value.is_a?(Array) -            (value = (old_value.dup << new_value).compact.uniq).delete('REQUIRED') -          elsif new_value.is_a?(Array) && !old_value.is_a?(Array) -            (value = (new_value.dup << old_value).compact.uniq).delete('REQUIRED') - -          # merge two arrays -          elsif old_value.is_a?(Array) && new_value.is_a?(Array) -            if mode == :add -              value = (old_value + new_value).sort.uniq -            elsif mode == :subtract -              value = new_value - old_value -            elsif prefer_self -              value = old_value -            else -              value = new_value -            end - -          # catch errors -          elsif type_mismatch?(old_value, new_value) -            raise 'Type mismatch. Cannot merge %s (%s) with %s (%s). Key is "%s", name is "%s".' % [ -              old_value.inspect, old_value.class, -              new_value.inspect, new_value.class, -              key, self.class -            ] - -          # merge simple strings & numbers -          else -            if prefer_self -              value = old_value -            else -              value = new_value -            end -          end - -          # save value -          self[key] = value -        end -        self -      end - -      def set_environment(env, node) -        @env = env -        @node = node -        self.each do |key, value| -          if value.is_a?(Config::Object) -            value.set_environment(env, node) -          end -        end -      end - -      # -      # like a reverse deep merge -      # (self takes precedence) -      # -      def inherit_from!(object) -        self.deep_merge!(object, true) -      end - -      # -      # Make a copy of ourselves, except only including the specified keys. -      # -      # Also, the result is flattened to a single hash, so a key of 'a.b' becomes 'a_b' -      # -      def pick(*keys) -        keys.map(&:to_s).inject(self.class.new(@manager)) do |hsh, key| -          value = self.get(key) -          if !value.nil? -            hsh[key.gsub('.','_')] = value -          end -          hsh -        end -      end - -      def eval_file(filename) -        evaluate_ruby(filename, File.read(filename)) -      end - -      protected - -      # -      # walks the object tree, eval'ing all the attributes that are dynamic ruby (e.g. value starts with '= ') -      # -      def evaluate_everything(context) -        keys.each do |key| -          obj = fetch_value(key, context) -          if is_required_value_not_set?(obj) -            Util::log 0, :warning, "required property \"#{key}\" is not set in node \"#{node.name}\"." -          elsif obj.is_a? Config::Object -            obj.evaluate_everything(context) -          end -        end -      end - -      # -      # some keys need to be evaluated 'late', after all the other keys have been evaluated. -      # -      def late_evaluate_everything(context) -        if @late_eval_list -          @late_eval_list.each do |key, value| -            self[key] = context.evaluate_ruby(key, value) -            if is_required_value_not_set?(self[key]) -              Util::log 0, :warning, "required property \"#{key}\" is not set in node \"#{node.name}\"." -            end -          end -        end -        values.each do |obj| -          if obj.is_a? Config::Object -            obj.late_evaluate_everything(context) -          end -        end -      end - -      # -      # evaluates the string `value` as ruby in the context of self. -      # (`key` is just passed for debugging purposes) -      # -      def evaluate_ruby(key, value) -        self.instance_eval(value, key, 1) -      rescue ConfigError => exc -        raise exc # pass through -      rescue SystemStackError => exc -        Util::log 0, :error, "while evaluating node '#{self.name}'" -        Util::log 0, "offending key: #{key}", :indent => 1 -        Util::log 0, "offending string: #{value}", :indent => 1 -        Util::log 0, "STACK OVERFLOW, BAILING OUT. There must be an eval loop of death (variables with circular dependencies).", :indent => 1 -        raise SystemExit.new(1) -      rescue FileMissing => exc -        Util::bail! do -          if exc.options[:missing] -            Util::log :missing, exc.options[:missing].gsub('$node', self.name).gsub('$file', exc.path) -          else -            Util::log :error, "while evaluating node '#{self.name}'" -            Util::log "offending key: #{key}", :indent => 1 -            Util::log "offending string: #{value}", :indent => 1 -            Util::log "error message: no file '#{exc}'", :indent => 1 -          end -          raise exc if DEBUG -        end -      rescue AssertionFailed => exc -        Util.bail! do -          Util::log :failed, "assertion while evaluating node '#{self.name}'" -          Util::log 'assertion: %s' % exc.assertion, :indent => 1 -          Util::log "offending key: #{key}", :indent => 1 -          raise exc if DEBUG -        end -      rescue SyntaxError, StandardError => exc -        Util::bail! do -          Util::log :error, "while evaluating node '#{self.name}'" -          Util::log "offending key: #{key}", :indent => 1 -          Util::log "offending string: #{value}", :indent => 1 -          Util::log "error message: #{exc.inspect}", :indent => 1 -          raise exc if DEBUG -        end -      end - -      private - -      # -      # fetches the value for the key, evaluating the value as ruby if it begins with '=' -      # -      def fetch_value(key, context=@node) -        value = fetch(key, nil) -        if value.is_a?(String) && value =~ /^=/ -          if value =~ /^=> (.*)$/ -            value = evaluate_later(key, $1) -          elsif value =~ /^= (.*)$/ -            value = context.evaluate_ruby(key, $1) -          end -          self[key] = value -        end -        return value -      end - -      def evaluate_later(key, value) -        @late_eval_list ||= [] -        @late_eval_list << [key, value] -        '<evaluate later>' -      end - -      # -      # when merging, we raise an error if this method returns true for the two values. -      # -      def type_mismatch?(old_value, new_value) -        if old_value.is_a?(Boolean) && new_value.is_a?(Boolean) -          # note: FalseClass and TrueClass are different classes -          # so we can't do old_value.class == new_value.class -          return false -        elsif old_value.is_a?(String) && old_value =~ /^=/ -          # pass through macros, since we don't know what the type will eventually be. -          return false -        elsif new_value.is_a?(String) && new_value =~ /^=/ -          return false -        elsif old_value.class == new_value.class -          return false -        else -          return true -        end -      end - -      # -      # returns true if the value has not been changed and the default is "REQUIRED" -      # -      def is_required_value_not_set?(value) -        if value.is_a? Array -          value == ["REQUIRED"] -        else -          value == "REQUIRED" -        end -      end - -    end # class -  end # module -end # module
\ No newline at end of file diff --git a/lib/leap_cli/config/object_list.rb b/lib/leap_cli/config/object_list.rb deleted file mode 100644 index f9299a6..0000000 --- a/lib/leap_cli/config/object_list.rb +++ /dev/null @@ -1,209 +0,0 @@ -require 'tsort' - -module LeapCli -  module Config -    # -    # A list of Config::Object instances (internally stored as a hash) -    # -    class ObjectList < Hash -      include TSort - -      def initialize(config=nil) -        if config -          self.add(config['name'], config) -        end -      end - -      # -      # If the key is a string, the Config::Object it references is returned. -      # -      # If the key is a hash, we treat it as a condition and filter all the Config::Objects using the condition. -      # A new ObjectList is returned. -      # -      # Examples: -      # -      # nodes['vpn1'] -      #   node named 'vpn1' -      # -      # nodes[:public_dns => true] -      #   all nodes with public dns -      # -      # nodes[:services => 'openvpn', 'location.country_code' => 'US'] -      #   all nodes with services containing 'openvpn' OR country code of US -      # -      # Sometimes, you want to do an OR condition with multiple conditions -      # for the same field. Since hash keys must be unique, you can use -      # an array representation instead: -      # -      # nodes[[:services, 'openvpn'], [:services, 'tor']] -      #   nodes with openvpn OR tor service -      # -      # nodes[:services => 'openvpn'][:tags => 'production'] -      #   nodes with openvpn AND are production -      # -      def [](key) -        if key.is_a?(Hash) || key.is_a?(Array) -          filter(key) -        else -          super key.to_s -        end -      end - -      def exclude(node) -        list = self.dup -        list.delete(node.name) -        return list -      end - -      def each_node(&block) -        self.keys.sort.each do |node_name| -          yield self[node_name] -        end -      end - -      # -      # filters this object list, producing a new list. -      # filter is an array or a hash. see [] -      # -      def filter(filter) -        results = Config::ObjectList.new -        filter.each do |field, match_value| -          field = field.is_a?(Symbol) ? field.to_s : field -          match_value = match_value.is_a?(Symbol) ? match_value.to_s : match_value -          if match_value.is_a?(String) && match_value =~ /^!/ -            operator = :not_equal -            match_value = match_value.sub(/^!/, '') -          else -            operator = :equal -          end -          each do |name, config| -            value = config[field] -            if value.is_a? Array -              if operator == :equal && value.include?(match_value) -                results[name] = config -              elsif operator == :not_equal && !value.include?(match_value) -                results[name] = config -              end -            else -              if operator == :equal && value == match_value -                results[name] = config -              elsif operator == :not_equal && value != match_value -                results[name] = config -              end -            end -          end -        end -        results -      end - -      def add(name, object) -        self[name] = object -      end - -      # -      # converts the hash of configs into an array of hashes, with ONLY the specified fields -      # -      def fields(*fields) -        result = [] -        keys.sort.each do |name| -          result << self[name].pick(*fields) -        end -        result -      end - -      # -      # like fields(), but returns an array of values instead of an array of hashes. -      # -      def field(field) -        field = field.to_s -        result = [] -        keys.sort.each do |name| -          result << self[name].get(field) -        end -        result -      end - -      # -      # pick_fields(field1, field2, ...) -      # -      # generates a Hash from the object list, but with only the fields that are picked. -      # -      # If there are more than one field, then the result is a Hash of Hashes. -      # If there is just one field, it is a simple map to the value. -      # -      # For example: -      # -      #   "neighbors" = "= nodes_like_me[:services => :couchdb].pick_fields('domain.full', 'ip_address')" -      # -      # generates this: -      # -      #   neighbors: -      #     couch1: -      #       domain_full: couch1.bitmask.net -      #       ip_address: "10.5.5.44" -      #     couch2: -      #       domain_full: couch2.bitmask.net -      #       ip_address: "10.5.5.52" -      # -      # But this: -      # -      #   "neighbors": "= nodes_like_me[:services => :couchdb].pick_fields('domain.full')" -      # -      # will generate this: -      # -      #   neighbors: -      #     couch1: couch1.bitmask.net -      #     couch2: couch2.bitmask.net -      # -      def pick_fields(*fields) -        self.values.inject({}) do |hsh, node| -          value = self[node.name].pick(*fields) -          if fields.size == 1 -            value = value.values.first -          end -          hsh[node.name] = value -          hsh -        end -      end - -      # -      # Applies inherit_from! to all objects. -      # -      # 'env' specifies what environment should be for -      # each object in the list. -      # -      def inherit_from!(object_list, env) -        object_list.each do |name, object| -          if self[name] -            self[name].inherit_from!(object) -          else -            self[name] = object.duplicate(env) -          end -        end -      end - -      # -      # topographical sort based on test dependency -      # -      def tsort_each_node(&block) -        self.each_key(&block) -      end - -      def tsort_each_child(node_name, &block) -        if self[node_name] -          self[node_name].test_dependencies.each do |test_me_first| -            if self[test_me_first] # TODO: in the future, allow for ability to optionally pull in all dependencies. -                                   # not just the ones that pass the node filter. -              yield(test_me_first) -            end -          end -        end -      end - -      def names_in_test_dependency_order -        self.tsort -      end - -    end -  end -end diff --git a/lib/leap_cli/config/provider.rb b/lib/leap_cli/config/provider.rb deleted file mode 100644 index 0d8bc1f..0000000 --- a/lib/leap_cli/config/provider.rb +++ /dev/null @@ -1,22 +0,0 @@ -# -# Configuration class for provider.json -# - -module LeapCli; module Config -  class Provider < Object -    attr_reader :environment -    def set_env(e) -      if e == 'default' -        @environment = nil -      else -        @environment = e -      end -    end -    def provider -      self -    end -    def validate! -      # nothing here yet :( -    end -  end -end; end diff --git a/lib/leap_cli/config/secrets.rb b/lib/leap_cli/config/secrets.rb deleted file mode 100644 index ca851c7..0000000 --- a/lib/leap_cli/config/secrets.rb +++ /dev/null @@ -1,87 +0,0 @@ -# encoding: utf-8 -# -# A class for the secrets.json file -# - -module LeapCli; module Config - -  class Secrets < Object -    attr_reader :node_list - -    def initialize(manager=nil) -      super(manager) -      @discovered_keys = {} -    end - -    # we can't use fetch() or get(), since those already have special meanings -    def retrieve(key, environment) -      environment ||= 'default' -      self.fetch(environment, {})[key.to_s] -    end - -    def set(*args, &block) -      if block_given? -        set_with_block(*args, &block) -      else -        set_without_block(*args) -      end -    end - -    # searches over all keys matching the regexp, checking to see if the value -    # has been already used by any of them. -    def taken?(regexp, value, environment) -      self.keys.grep(regexp).each do |key| -        return true if self.retrieve(key, environment) == value -      end -      return false -    end - -    def set_without_block(key, value, environment) -      set_with_block(key, environment) {value} -    end - -    def set_with_block(key, environment, &block) -      environment ||= 'default' -      key = key.to_s -      @discovered_keys[environment] ||= {} -      @discovered_keys[environment][key] = true -      self[environment] ||= {} -      self[environment][key] ||= yield -    end - -    # -    # if clean is true, then only secrets that have been discovered -    # during this run will be exported. -    # -    # if environment is also pinned, then we will clean those secrets -    # just for that environment. -    # -    # the clean argument should only be used when all nodes have -    # been processed, otherwise secrets that are actually in use will -    # get mistakenly removed. -    # -    def dump_json(clean=false) -      pinned_env = LeapCli.leapfile.environment -      if clean -        self.each_key do |environment| -          if pinned_env.nil? || pinned_env == environment -            env = self[environment] -            if env.nil? -              raise StandardError.new("secrets.json file seems corrupted. No such environment '#{environment}'") -            end -            env.each_key do |key| -              unless @discovered_keys[environment] && @discovered_keys[environment][key] -                self[environment].delete(key) -              end -            end -            if self[environment].empty? -              self.delete(environment) -            end -          end -        end -      end -      super() -    end -  end - -end; end diff --git a/lib/leap_cli/config/sources.rb b/lib/leap_cli/config/sources.rb deleted file mode 100644 index aee860d..0000000 --- a/lib/leap_cli/config/sources.rb +++ /dev/null @@ -1,11 +0,0 @@ -# encoding: utf-8 -# -# A class for the sources.json file -# - -module LeapCli -  module Config -    class Sources < Object -    end -  end -end diff --git a/lib/leap_cli/config/tag.rb b/lib/leap_cli/config/tag.rb deleted file mode 100644 index 6bd8d1e..0000000 --- a/lib/leap_cli/config/tag.rb +++ /dev/null @@ -1,25 +0,0 @@ -# -# -# A class for node services or node tags. -# -# - -module LeapCli; module Config - -  class Tag < Object -    attr_reader :node_list - -    def initialize(environment=nil) -      super(environment) -      @node_list = Config::ObjectList.new -    end - -    # don't copy the node list pointer when this object is dup'ed. -    def initialize_copy(orig) -      super -      @node_list = Config::ObjectList.new -    end - -  end - -end; end diff --git a/lib/leap_cli/core_ext/hash.rb b/lib/leap_cli/core_ext/hash.rb index 7df33b2..4eb3af3 100644 --- a/lib/leap_cli/core_ext/hash.rb +++ b/lib/leap_cli/core_ext/hash.rb @@ -32,4 +32,23 @@ class Hash      replace(deep_merge(other_hash))    end +  # +  # A recursive symbolize_keys +  # +  unless Hash.method_defined?(:symbolize_keys) +    def symbolize_keys +      self.inject({}) {|result, (key, value)| +        new_key = case key +                  when String then key.to_sym +                  else key +                  end +        new_value = case value +                    when Hash then symbolize_keys(value) +                    else value +                    end +        result[new_key] = new_value +        result +      } +    end +  end  end diff --git a/lib/leap_cli/leapfile.rb b/lib/leap_cli/leapfile.rb index 9164d0a..e526703 100644 --- a/lib/leap_cli/leapfile.rb +++ b/lib/leap_cli/leapfile.rb @@ -3,6 +3,8 @@  #  # It is akin to a Gemfile, Rakefile, or Capfile (e.g. it is a ruby file that gets eval'ed)  # +# Additional configuration options are defined in platform's leapfile_extensions.rb +#  module LeapCli    def self.leapfile @@ -10,17 +12,12 @@ module LeapCli    end    class Leapfile -    attr_accessor :platform_directory_path -    attr_accessor :provider_directory_path -    attr_accessor :custom_vagrant_vm_line -    attr_accessor :leap_version -    attr_accessor :log -    attr_accessor :vagrant_network -    attr_accessor :vagrant_basebox -    attr_accessor :environment +    attr_reader :platform_directory_path +    attr_reader :provider_directory_path +    attr_reader :environment +    attr_reader :valid      def initialize -      @vagrant_network = '10.5.5.0/24'      end      # @@ -61,25 +58,44 @@ module LeapCli          #          # load the platform          # -        platform_file = "#{@platform_directory_path}/platform.rb" -        unless File.exists?(platform_file) +        platform_class = "#{@platform_directory_path}/lib/leap/platform" +        platform_definition = "#{@platform_directory_path}/platform.rb" +        unless File.exist?(platform_definition)            Util.bail! "ERROR: The file `#{platform_file}` does not exist. Please check the value of `@platform_directory_path` in `Leapfile` or `~/.leaprc`."          end -        require "#{@platform_directory_path}/platform.rb" -        if !Leap::Platform.compatible_with_cli?(LeapCli::VERSION) || -           !Leap::Platform.version_in_range?(LeapCli::COMPATIBLE_PLATFORM_VERSION) -          Util.bail! "This leap command (v#{LeapCli::VERSION}) " + -                     "is not compatible with the platform #{@platform_directory_path} (v#{Leap::Platform.version}).\n   " + -                     "You need either leap command #{Leap::Platform.compatible_cli.first} to #{Leap::Platform.compatible_cli.last} or " + -                     "platform version #{LeapCli::COMPATIBLE_PLATFORM_VERSION.first} to #{LeapCli::COMPATIBLE_PLATFORM_VERSION.last}" +        begin +          require platform_class +          require platform_definition +        rescue LoadError +          Util.log "The leap_platform at #{platform_directory_path} is not compatible with this version of leap_cli" +          Util.log "You can either:" do +            Util.log "Upgrade leap_platform to version " + LeapCli::COMPATIBLE_PLATFORM_VERSION.first +            Util.log "Or, downgrade leap_cli to version 1.8" +          end +          Util.bail! +        rescue StandardError => exc +          Util.bail! exc.to_s          end -        unless @allow_production_deploy.nil? -          Util::log 0, :warning, "in Leapfile: @allow_production_deploy is no longer supported." +        begin +          Leap::Platform.validate!(LeapCli::VERSION, LeapCli::COMPATIBLE_PLATFORM_VERSION, self) +        rescue StandardError => exc +          Util.bail! exc.to_s          end -        unless @platform_branch.nil? -          Util::log 0, :warning, "in Leapfile: @platform_branch is no longer supported." +        leapfile_extensions = "#{@platform_directory_path}/lib/leap_cli/leapfile_extensions.rb" +        if File.exist?(leapfile_extensions) +          require leapfile_extensions          end -        @valid = true + +        # +        # validate +        # +        instance_variables.each do |var| +          var = var.to_s.sub('@', '') +          if !self.respond_to?(var) +            LeapCli.log :warning, "the variable `#{var}` is set in .leaprc or Leapfile, but it is not supported." +          end +        end +        @valid = validate          return @valid        end      end @@ -105,7 +121,7 @@ module LeapCli      def edit_leaprc(property, value=nil)        file_path = leaprc_path        lines = [] -      if File.exists?(file_path) +      if File.exist?(file_path)          regexp = /self\.#{Regexp.escape(property)} = .*? if @provider_directory_path == '#{Regexp.escape(@provider_directory_path)}'/          File.readlines(file_path).each do |line|            unless line =~ regexp @@ -128,10 +144,9 @@ module LeapCli      end      def read_settings(file) -      if File.exists? file -        Util::log 2, :read, file +      if File.exist? file +        LeapCli.log 2, :read, file          instance_eval(File.read(file), file) -        validate(file)        end      end @@ -146,11 +161,11 @@ module LeapCli        return search_dir      end -    PRIVATE_IP_RANGES = /(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/ - -    def validate(file) -      Util::assert! vagrant_network =~ PRIVATE_IP_RANGES do -        Util::log 0, :error, "in #{file}: vagrant_network is not a local private network" +    def method_missing(method, *args) +      if method =~ /=$/ +        self.instance_variable_set('@' + method.to_s.sub('=',''), args.first) +      else +        self.instance_variable_get('@' + method.to_s)        end      end diff --git a/lib/leap_cli/lib_ext/capistrano_connections.rb b/lib/leap_cli/lib_ext/capistrano_connections.rb deleted file mode 100644 index c46455f..0000000 --- a/lib/leap_cli/lib_ext/capistrano_connections.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Capistrano -  class Configuration -    module Connections -      def failed!(server) -        @failure_callback.call(server) if @failure_callback -        Thread.current[:failed_sessions] << server -      end - -      def call_on_failure(&block) -        @failure_callback = block -      end -    end -  end -end - - diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 6589ad4..af2fae7 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -1,58 +1,84 @@ -require 'paint' -  ##  ## LOGGING  ## -## Ugh. This class does not work well with multiple threads! -##  module LeapCli -  extend self +  module LogCommand +    @@logger = nil -  attr_accessor :log_in_color +    def log(*args, &block) +      logger.log(*args, &block) +    end -  # logging options -  def log_level -    @log_level ||= 1 -  end -  def set_log_level(value) -    @log_level = value -  end +    def log_raw(*args, &block) +      logger.log_raw(*args, &block) +    end -  def indent_level -    @indent_level ||= 0 -  end -  def indent_level=(value) -    @indent_level = value -  end +    # global shared logger +    def logger +      @@logger ||= LeapCli::LeapLogger.new +    end -  def log_file -    @log_file -  end -  def log_file=(value) -    @log_file = value -    if @log_file -      if !File.directory?(File.dirname(@log_file)) -        Util.bail!('Invalid log file "%s", directory "%s" does not exist' % [@log_file, File.dirname(@log_file)]) -      end -      @log_output_stream = File.open(@log_file, 'a') +    # thread safe logger +    def new_logger +      logger.dup      end -  end -  def log_output_stream -    @log_output_stream -  end +    # deprecated +    def log_level +      logger.log_level +    end + +    # +    # These probably should have been part of the logger originally, +    # but they are made available here for convenience: +    # + +    def bail!(*args, &block) +      Util.bail!(*args, &block) +    end +    def assert!(*args, &block) +      Util.assert!(*args, &block) +    end + +  end  end  module LeapCli -  module Log +  class LeapLogger      #      # these are log titles typically associated with files      # -    FILE_TITLES = [:updated, :created, :removed, :missing, :nochange, :loading] +    FILE_TITLES = %w(updated created removed missing nochange loading) + +    # TODO: use these +    IMPORTANT = 0 +    INFO      = 1 +    DEBUG     = 2 +    TRACE     = 3 +    attr_reader :log_output_stream, :log_file +    attr_accessor :indent_level, :log_level, :log_in_color + +    def initialize() +      @log_level = 1 +      @indent_level = 0 +      @log_file = nil +      @log_output_stream = nil +      @log_in_color = true +    end + +    def log_file=(value) +      @log_file = value +      if @log_file +        if !File.directory?(File.dirname(@log_file)) +          Util.bail!('Invalid log file "%s", directory "%s" does not exist' % [@log_file, File.dirname(@log_file)]) +        end +        @log_output_stream = File.open(@log_file, 'a') +      end +    end      #      # master logging function. @@ -63,74 +89,95 @@ module LeapCli      # * Integer: the log level (0, 1, 2)      # * Symbol: the prefix title to colorize. may be one of      #   [:error, :warning, :info, :updated, :created, :removed, :no_change, :missing] -    # * Hash: a hash of options. so far, only :indent is supported. +    # * Hash: a hash of options. +    #     :wrap -- if true, appy intend to each line in message. +    #     :color -- apply color to message or prefix +    #     :style -- apply style to message or prefix      # -      def log(*args)        level   = args.grep(Integer).first || 1        title   = args.grep(Symbol).first        message = args.grep(String).first        options = args.grep(Hash).first || {} -      unless message && LeapCli.log_level >= level +      host    = options[:host] +      if title +        title = title.to_s +      end +      if @log_level < level || (title.nil? && message.nil?)          return        end -      # prefix -      clear_prefix = colored_prefix = "" -      if title -        prefix_options = case title -          when :error     then ['error', :red, :bold] -          when :fatal_error then ['fatal error:', :red, :bold] -          when :warning   then ['warning:', :yellow, :bold] -          when :info      then ['info', :cyan, :bold] -          when :note      then ['NOTE:', :cyan, :bold] -          when :updated   then ['updated', :cyan, :bold] -          when :updating  then ['updating', :cyan, :bold] -          when :created   then ['created', :green, :bold] -          when :removed   then ['removed', :red, :bold] -          when :nochange  then ['no change', :magenta] -          when :loading   then ['loading', :magenta] -          when :missing   then ['missing', :yellow, :bold] -          when :skipping  then ['skipping', :yellow, :bold] -          when :run       then ['run', :magenta] -          when :failed    then ['FAILED', :red, :bold] -          when :completed then ['completed', :green, :bold] -          when :ran       then ['ran', :green, :bold] -          when :bail      then ['bailing out', :red, :bold] -          when :invalid   then ['invalid', :red, :bold] -          else [title.to_s, :cyan, :bold] -        end -        if options[:host] -          clear_prefix = "[%s] %s " % [options[:host], prefix_options[0]] -          colored_prefix = "[%s] %s " % [Paint[options[:host], prefix_options[1], prefix_options[2]], prefix_options[0]] +      # +      # transform absolute path names +      # +      if title && FILE_TITLES.include?(title) && message =~ /^\// +        message = LeapCli::Path.relative_path(message) +      end + +      # +      # apply filters +      # LogFilter will not be defined if no platform was loaded. +      # +      if defined?(LeapCli::LogFilter) +        if title +          title, filter_flags = LogFilter.apply_title_filters(title)          else -          clear_prefix = "%s " % prefix_options[0] -          colored_prefix = "%s " % Paint[prefix_options[0], prefix_options[1], prefix_options[2]] +          message, filter_flags = LogFilter.apply_message_filters(message) +          return if message.nil?          end -      elsif options[:host] -        clear_prefix = colored_prefix =  "[%s] " % options[:host] +        options = options.merge(filter_flags)        end -      # transform absolute path names -      if title && FILE_TITLES.include?(title) && message =~ /^\// -        message = LeapCli::Path.relative_path(message) +      # +      # set line prefix +      # +      if (host) +        host = host.split('.').first        end +      prefix = prefix_str(host, title) -      log_raw(:log, nil)                   { [clear_prefix, message].join } -      if LeapCli.log_in_color -        log_raw(:stdout, options[:indent]) { [colored_prefix, message].join } -      else -        log_raw(:stdout, options[:indent]) { [clear_prefix, message].join } +      # +      # write to the log file, always +      # +      log_raw(:log, nil, prefix) { message } + +      # +      # log to stdout, maybe in color +      # +      if @log_in_color +        if options[:wrap] +          message = message.split("\n") +        end +        if options[:color] +          if host +            host = colorize(host, options[:color], options[:style]) +          elsif title +            title = colorize(title, options[:color], options[:style]) +          else +            message = colorize(message, options[:color], options[:style]) +          end +        elsif title +          title = colorize(title, :cyan, :bold) +        end +        # new colorized prefix: +        prefix = prefix_str(host, title)        end +      log_raw(:stdout, options[:indent], prefix) { message } -      # run block, if given +      # +      # run block indented, if given +      #        if block_given? -        LeapCli.indent_level += 1 +        @indent_level += 1          yield -        LeapCli.indent_level -= 1 +        @indent_level -= 1        end      end +    def debug(*args) +      self.log(3, *args) +    end +      #      # Add a raw log entry, without any modifications (other than indent).      # Content to be logged is yielded by the block. @@ -139,23 +186,26 @@ module LeapCli      # if mode == :stdout, output is sent to STDOUT.      # if mode == :log, output is sent to log file, if present.      # -    def log_raw(mode, indent=nil, &block) -      # NOTE: print message (using 'print' produces better results than 'puts' when multiple threads are logging) +    def log_raw(mode, indent=nil, prefix=nil, &block) +      # NOTE: using 'print' produces better results than 'puts' +      # when multiple threads are logging)        if mode == :log -        if LeapCli.log_output_stream +        if @log_output_stream            messages = [yield].compact.flatten            if messages.any?              timestamp = Time.now.strftime("%b %d %H:%M:%S")              messages.each do |message| -              LeapCli.log_output_stream.print("#{timestamp} #{message}\n") +              message = message.rstrip +              next if message.empty? +              @log_output_stream.print("#{timestamp} #{prefix} #{message}\n")              end -            LeapCli.log_output_stream.flush +            @log_output_stream.flush            end          end        elsif mode == :stdout          messages = [yield].compact.flatten          if messages.any? -          indent ||= LeapCli.indent_level +          indent ||= @indent_level            indent_str = ""            indent_str += "  " * indent.to_i            if indent.to_i > 0 @@ -163,12 +213,70 @@ module LeapCli            else              indent_str += ' = '            end +          indent_str += prefix if prefix            messages.each do |message| +            message = message.rstrip +            next if message.empty?              STDOUT.print("#{indent_str}#{message}\n")            end          end        end      end +    def colorize(str, color, style=nil) +      codes = [FG_COLORS[color] || FG_COLORS[:default]] +      if style +        codes << EFFECTS[style] || EFFECTS[:nothing] +      end +      if str.is_a?(String) +        ["\033[%sm" % codes.join(';'), str, NO_COLOR].join +      elsif str.is_a?(Array) +        str.map { |s| +          ["\033[%sm" % codes.join(';'), s, NO_COLOR].join +        } +      end +    end + +    private + +    def prefix_str(host, title) +      prefix = "" +      prefix += "[" + host + "] " if host +      prefix += title + " " if title +      prefix += " " if !prefix.empty? && prefix !~ / $/ +      return prefix +    end + +    EFFECTS = { +      :reset         => 0,  :nothing         => 0, +      :bright        => 1,  :bold            => 1, +      :underline     => 4, +      :inverse       => 7,  :swap            => 7, +    } +    NO_COLOR = "\033[0m" +    FG_COLORS = { +      :black   => 30, +      :red     => 31, +      :green   => 32, +      :yellow  => 33, +      :blue    => 34, +      :magenta => 35, +      :cyan    => 36, +      :white   => 37, +      :default => 39, +    } +    BG_COLORS = { +      :black   => 40, +      :red     => 41, +      :green   => 42, +      :yellow  => 43, +      :blue    => 44, +      :magenta => 45, +      :cyan    => 46, +      :white   => 47, +      :default => 49, +    } +    end -end
\ No newline at end of file +end + diff --git a/lib/leap_cli/logger.rb b/lib/leap_cli/logger.rb deleted file mode 100644 index 9e98321..0000000 --- a/lib/leap_cli/logger.rb +++ /dev/null @@ -1,237 +0,0 @@ -# -# A drop in replacement for Capistrano::Logger that integrates better with LEAP CLI. -# - -require 'capistrano/logger' - -# -# from Capistrano::Logger -# ========================= -# -# IMPORTANT = 0 -# INFO      = 1 -# DEBUG     = 2 -# TRACE     = 3 -# MAX_LEVEL = 3 -# COLORS = { -#   :none     => "0", -#   :black    => "30", -#   :red      => "31", -#   :green    => "32", -#   :yellow   => "33", -#   :blue     => "34", -#   :magenta  => "35", -#   :cyan     => "36", -#   :white    => "37" -# } -# STYLES = { -#   :bright     => 1, -#   :dim        => 2, -#   :underscore => 4, -#   :blink      => 5, -#   :reverse    => 7, -#   :hidden     => 8 -# } -# - -module LeapCli -  class Logger < Capistrano::Logger - -    def initialize(options={}) -      @options = options -      @level = options[:level] || 0 -      @message_buffer = nil -    end - -    def log(level, message, line_prefix=nil, options={}) -      if message !~ /\n$/ && level <= 2 && line_prefix.is_a?(String) -        # in some cases, when the message doesn't end with a return, we buffer it and -        # wait until we encounter the return before we log the message out. -        @message_buffer ||= "" -        @message_buffer += message -        return -      elsif @message_buffer -        message = @message_buffer + message -        @message_buffer = nil -      end - -      options[:level] ||= level -      [:stdout, :log].each do |mode| -        LeapCli::log_raw(mode) do -          message_lines(mode, message, line_prefix, options) -        end -      end -    end - -    private - -    def message_lines(mode, message, line_prefix, options) -      formatted_message, formatted_prefix, message_options = apply_formatting(mode, message, line_prefix, options) -      if message_options[:level] <= self.level && formatted_message && formatted_message.chars.any? -        if formatted_prefix -          formatted_message.lines.collect { |line| -            "[#{formatted_prefix}] #{line.sub(/\s+$/, '')}" -          } -        else -          formatted_message.lines.collect {|line| line.sub(/\s+$/, '')} -        end -      else -        nil -      end -    end - -    ## -    ## FORMATTING -    ## - -    # -    # options for formatters: -    # -    # :match       =>  regexp for matching a log line -    # :color       => what color the line should be -    # :style       => what style the line should be -    # :priority    => what order the formatters are applied in. higher numbers first. -    # :match_level => only apply filter at the specified log level -    # :level       => make this line visible at this log level or higher -    # :replace     => replace the matched text -    # :exit        => force the exit code to be this (does not interrupt program, just -    #                 ensures a specific exit code when the program eventually exits) -    # -    @formatters = [ -      # TRACE -      { :match => /command finished/,          :color => :white,   :style => :dim, :match_level => 3, :priority => -10 }, -      { :match => /executing locally/,         :color => :yellow,  :match_level => 3, :priority => -20 }, - -      # DEBUG -      #{ :match => /executing .*/,             :color => :green,   :match_level => 2, :priority => -10, :timestamp => true }, -      #{ :match => /.*/,                        :color => :yellow,  :match_level => 2, :priority => -30 }, -      { :match => /^transaction:/,             :level => 3 }, - -      # INFO -      { :match => /.*out\] (fatal:|ERROR:).*/, :color => :red,     :match_level => 1, :priority => -10 }, -      { :match => /Permission denied/,         :color => :red,     :match_level => 1, :priority => -20 }, -      { :match => /sh: .+: command not found/, :color => :magenta, :match_level => 1, :priority => -30 }, - -      # IMPORTANT -      { :match => /^(E|e)rr ::/,                   :color => :red,     :match_level => 0, :priority => -10, :exit => 1}, -      { :match => /^ERROR:/,                   :color => :red,                        :priority => -10, :exit => 1}, -      { :match => /.*/,                        :color => :blue,    :match_level => 0, :priority => -20 }, - -      # CLEANUP -      { :match => /\s+$/,                      :replace => '', :priority => 0}, - -      # DEBIAN PACKAGES -      { :match => /^(Hit|Ign) /,                :color => :green,   :priority => -20}, -      { :match => /^Err /,                      :color => :red,     :priority => -20}, -      { :match => /^W(ARNING)?: /,              :color => :yellow,  :priority => -20}, -      { :match => /^E: /,                       :color => :red,     :priority => -20}, -      { :match => /already the newest version/, :color => :green,   :priority => -20}, -      { :match => /WARNING: The following packages cannot be authenticated!/, :color => :red, :level => 0, :priority => -10}, - -      # PUPPET -      { :match => /^(W|w)arning: Not collecting exported resources without storeconfigs/, :level => 2, :color => :yellow, :priority => -10}, -      { :match => /^(W|w)arning: Found multiple default providers for vcsrepo:/,          :level => 2, :color => :yellow, :priority => -10}, -      { :match => /^(W|w)arning: .*is deprecated.*$/, :level => 2, :color => :yellow, :priority => -10}, -      { :match => /^(W|w)arning: Scope.*$/,           :level => 2, :color => :yellow, :priority => -10}, -      { :match => /^(N|n)otice:/,                     :level => 1, :color => :cyan,   :priority => -20}, -      { :match => /^(N|n)otice:.*executed successfully$/, :level => 2, :color => :cyan, :priority => -15}, -      { :match => /^(W|w)arning:/,                    :level => 0, :color => :yellow, :priority => -20}, -      { :match => /^Duplicate declaration:/,          :level => 0, :color => :red,    :priority => -20}, -      { :match => /Finished catalog run/,             :level => 0, :color => :green,  :priority => -10}, -      { :match => /^APPLY COMPLETE \(changes made\)/, :level => 0, :color => :green,  :priority => -10}, -      { :match => /^APPLY COMPLETE \(no changes\)/,   :level => 0, :color => :green,  :priority => -10}, - -      # PUPPET FATAL ERRORS -      { :match => /^(E|e)rr(or|):/,                :level => 0, :color => :red, :priority => -1, :exit => 1}, -      { :match => /^Wrapped exception:/,           :level => 0, :color => :red, :priority => -1, :exit => 1}, -      { :match => /^Failed to parse template/,     :level => 0, :color => :red, :priority => -1, :exit => 1}, -      { :match => /^Execution of.*returned/,     :level => 0, :color => :red, :priority => -1, :exit => 1}, -      { :match => /^Parameter matches failed:/,    :level => 0, :color => :red, :priority => -1, :exit => 1}, -      { :match => /^Syntax error/,                 :level => 0, :color => :red, :priority => -1, :exit => 1}, -      { :match => /^Cannot reassign variable/,     :level => 0, :color => :red, :priority => -1, :exit => 1}, -      { :match => /^Could not find template/,      :level => 0, :color => :red, :priority => -1, :exit => 1}, -      { :match => /^APPLY COMPLETE.*fail/,         :level => 0, :color => :red, :priority => -1, :exit => 1}, - -      # TESTS -      { :match => /^PASS: /,                :color => :green,   :priority => -20}, -      { :match => /^(FAIL|ERROR): /,        :color => :red,     :priority => -20}, -      { :match => /^(SKIP|WARN): /,         :color => :yellow,  :priority => -20}, -      { :match => /\d+ tests: \d+ passes, \d+ skips, 0 warnings, 0 failures, 0 errors/, :color => :blue, :priority => -20}, - -      # LOG SUPPRESSION -      { :match => /^(W|w)arning: You cannot collect without storeconfigs being set/, :level => 2, :priority => 10}, -      { :match => /^(W|w)arning: You cannot collect exported resources without storeconfigs being set/, :level => 2, :priority => 10} -    ] - -    def self.sorted_formatters -      # Sort matchers in reverse order so we can break if we found a match. -      @sorted_formatters ||= @formatters.sort_by { |i| -(i[:priority] || i[:prio] || 0) } -    end - -    @prefix_formatters = [ -      { :match => /(err|out) :: /,             :replace => '', :priority => 0}, -      { :match => /\s+$/,                      :replace => '', :priority => 0} -    ] -    def self.prefix_formatters; @prefix_formatters; end - -    def apply_formatting(mode, message, line_prefix = nil, options={}) -      message = message.dup -      options = options.dup -      if !line_prefix.nil? -        if !line_prefix.is_a?(String) -          line_prefix = line_prefix.to_s.dup -        else -          line_prefix = line_prefix.dup -        end -      end -      color = options[:color] || :none -      style = options[:style] - -      if line_prefix -        self.class.prefix_formatters.each do |formatter| -          if line_prefix =~ formatter[:match] && formatter[:replace] -            line_prefix.gsub!(formatter[:match], formatter[:replace]) -          end -        end -      end - -      self.class.sorted_formatters.each do |formatter| -        if (formatter[:match_level] == level || formatter[:match_level].nil?) -          if message =~ formatter[:match] -            options[:level] = formatter[:level] if formatter[:level] -            color = formatter[:color] if formatter[:color] -            style = formatter[:style] || formatter[:attribute] # (support original cap colors) - -            message.gsub!(formatter[:match], formatter[:replace]) if formatter[:replace] -            message.replace(formatter[:prepend] + message) unless formatter[:prepend].nil? -            message.replace(message + formatter[:append])  unless formatter[:append].nil? -            message.replace(Time.now.strftime('%Y-%m-%d %T') + ' ' + message) if formatter[:timestamp] - -            if formatter[:exit] -              LeapCli::Util.exit_status(formatter[:exit]) -            end - -            # stop formatting, unless formatter was just for string replacement -            break unless formatter[:replace] -          end -        end -      end - -      if color == :hide -        return nil -      elsif mode == :log || (color == :none && style.nil?) || !LeapCli.log_in_color -        return [message, line_prefix, options] -      else -        term_color = COLORS[color] -        term_style = STYLES[style] -        if line_prefix.nil? -          message.replace format(message, term_color, term_style) -        else -          line_prefix.replace format(line_prefix, term_color, term_style).strip # format() appends a \n -        end -        return [message, line_prefix, options] -      end -    end - -  end -end diff --git a/lib/leap_cli/path.rb b/lib/leap_cli/path.rb index fd2e3fc..a78dbd2 100644 --- a/lib/leap_cli/path.rb +++ b/lib/leap_cli/path.rb @@ -3,7 +3,7 @@ require 'fileutils'  module LeapCli; module Path    def self.platform -    @platform +    @platform ||= nil    end    def self.provider_base @@ -40,14 +40,14 @@ module LeapCli; module Path      [Path.provider, Path.provider_base].each do |base|        if arg.is_a?(Symbol) || arg.is_a?(Array)          named_path(arg, base).tap {|path| -          return path if File.exists?(path) +          return path if File.exist?(path)          }        else          File.join(base, arg).tap {|path| -          return path if File.exists?(path) +          return path if File.exist?(path)          }          File.join(base, 'files', arg).tap {|path| -          return path if File.exists?(path) +          return path if File.exist?(path)          }        end      end @@ -83,7 +83,7 @@ module LeapCli; module Path    end    def self.exists?(name, provider_dir=nil) -    File.exists?(named_path(name, provider_dir)) +    File.exist?(named_path(name, provider_dir))    end    def self.defined?(name) diff --git a/lib/leap_cli/remote/leap_plugin.rb b/lib/leap_cli/remote/leap_plugin.rb deleted file mode 100644 index b48f433..0000000 --- a/lib/leap_cli/remote/leap_plugin.rb +++ /dev/null @@ -1,192 +0,0 @@ -# -# these methods are made available in capistrano tasks as 'leap.method_name' -# (see RemoteCommand::new_capistrano) -# - -module LeapCli; module Remote; module LeapPlugin - -  def required_packages -    "puppet rsync lsb-release locales" -  end - -  def log(*args, &block) -    LeapCli::Util::log(*args, &block) -  end - -  # -  # creates directories that are owned by root and 700 permissions -  # -  def mkdirs(*dirs) -    raise ArgumentError.new('illegal dir name') if dirs.grep(/[\' ]/).any? -    run dirs.collect{|dir| "mkdir -m 700 -p #{dir}; "}.join -  end - -  # -  # echos "ok" if the node has been initialized and the required packages are installed, bails out otherwise. -  # -  def assert_initialized -    begin -      test_initialized_file = "test -f #{Leap::Platform.init_path}" -      check_required_packages = "! dpkg-query -W --showformat='${Status}\n' #{required_packages} 2>&1 | grep -q -E '(deinstall|no packages)'" -      run "#{test_initialized_file} && #{check_required_packages} && echo ok" -    rescue Capistrano::CommandError => exc -      LeapCli::Util.bail! do -        exc.hosts.each do |host| -          node = host.to_s.split('.').first -          LeapCli::Util.log :error, "running deploy: node not initialized. Run 'leap node init #{node}'", :host => host -        end -      end -    end -  end - -  # -  # bails out the deploy if the file /etc/leap/no-deploy exists. -  # This kind of sucks, because it would be better to skip over nodes that have no-deploy set instead -  # halting the entire deploy. As far as I know, with capistrano, there is no way to close one of the -  # ssh connections in the pool and make sure it gets no further commands. -  # -  def check_for_no_deploy -    begin -      run "test ! -f /etc/leap/no-deploy" -    rescue Capistrano::CommandError => exc -      LeapCli::Util.bail! do -        exc.hosts.each do |host| -          LeapCli::Util.log "Can't continue because file /etc/leap/no-deploy exists", :host => host -        end -      end -    end -  end - -  # -  # dumps debugging information -  # # -  def debug -    run "#{Leap::Platform.leap_dir}/bin/debug.sh" -  end - -  # -  # dumps the recent deploy history to the console -  # -  def history(lines) -    command = "(test -s /var/log/leap/deploy-summary.log && tail -n #{lines} /var/log/leap/deploy-summary.log) || (test -s /var/log/leap/deploy-summary.log.1 && tail -n #{lines} /var/log/leap/deploy-summary.log.1) || (echo 'no history')" -    run command -  end - -  # -  # This is a hairy ugly hack, exactly the kind of stuff that makes ruby -  # dangerous and too much fun for its own good. -  # -  # In most places, we run remote ssh without a current 'task'. This works fine, -  # except that in a few places, the behavior of capistrano ssh is controlled by -  # the options of the current task. -  # -  # We don't want to create an actual current task, because tasks are no fun -  # and can't take arguments or return values. So, when we need to configure -  # things that can only be configured in a task, we use this handy hack to -  # fake the current task. -  # -  # This is NOT thread safe, but could be made to be so with some extra work. -  # -  def with_task(name) -    task = @config.tasks[name] -    @config.class.send(:alias_method, :original_current_task, :current_task) -    @config.class.send(:define_method, :current_task, Proc.new(){ task }) -    begin -      yield -    ensure -      @config.class.send(:remove_method, :current_task) -      @config.class.send(:alias_method, :current_task, :original_current_task) -    end -  end - -  # -  # similar to run(cmd, &block), but with: -  # -  # * exit codes -  # * stdout and stderr are combined -  # -  def stream(cmd, &block) -    command = '%s 2>&1; echo "exitcode=$?"' % cmd -    run(command) do |channel, stream, data| -      exitcode = nil -      if data =~ /exitcode=(\d+)\n/ -        exitcode = $1.to_i -        data.sub!(/exitcode=(\d+)\n/,'') -      end -      yield({:host => channel[:host], :data => data, :exitcode => exitcode}) -    end -  end - -  # -  # like stream, but capture all the output before returning -  # -  def capture(cmd, &block) -    command = '%s 2>&1; echo "exitcode=$?" 2>&1;' % cmd -    host_data = {} -    run(command) do |channel, stream, data| -      host_data[channel[:host]] ||= "" -      if data =~ /exitcode=(\d+)\n/ -        exitcode = $1.to_i -        data.sub!(/exitcode=(\d+)\n/,'') -        host_data[channel[:host]] += data -        yield({:host => channel[:host], :data => host_data[channel[:host]], :exitcode => exitcode}) -      else -        host_data[channel[:host]] += data -      end -    end -  end - -  # -  # Run a command, with a nice status report and progress indicator. -  # Only successful results are returned, errors are printed. -  # -  # For each successful run on each host, block is yielded with a hash like so: -  # -  # {:host => 'bluejay', :exitcode => 0, :data => 'shell output'} -  # -  def run_with_progress(cmd, &block) -    ssh_failures = [] -    exitcode_failures = [] -    succeeded = [] -    task = LeapCli.log_level > 1 ? :standard_task : :skip_errors_task -    with_task(task) do -      log :querying, 'facts' do -        progress "   " -        call_on_failure do |host| -          ssh_failures << host -          progress 'F' -        end -        capture(cmd) do |response| -          if response[:exitcode] == 0 -            progress '.' -            yield response -          else -            exitcode_failures << response -            progress 'F' -          end -        end -      end -    end -    puts "done" -    if ssh_failures.any? -      log :failed, 'to connect to nodes: ' + ssh_failures.join(' ') -    end -    if exitcode_failures.any? -      log :failed, 'to run successfully:' do -        exitcode_failures.each do |response| -          log "[%s] exit %s - %s" % [response[:host], response[:exitcode], response[:data].strip] -        end -      end -    end -  rescue Capistrano::RemoteError => err -    log :error, err.to_s -  end - -  private - -  def progress(str='.') -    print str -    STDOUT.flush -  end - -end; end; end diff --git a/lib/leap_cli/remote/puppet_plugin.rb b/lib/leap_cli/remote/puppet_plugin.rb deleted file mode 100644 index 5a6e908..0000000 --- a/lib/leap_cli/remote/puppet_plugin.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# these methods are made available in capistrano tasks as 'puppet.method_name' -# (see RemoteCommand::new_capistrano) -# - -module LeapCli; module Remote; module PuppetPlugin - -  def apply(options) -    run "#{Leap::Platform.leap_dir}/bin/puppet_command set_hostname apply #{flagize(options)}" -  end - -  private - -  def flagize(hsh) -    hsh.inject([]) {|str, item| -      if item[1] === false -        str -      elsif item[1] === true -        str << "--" + item[0].to_s -      else -        str << "--" + item[0].to_s + " " + item[1].inspect -      end -    }.join(' ') -  end - -end; end; end diff --git a/lib/leap_cli/remote/rsync_plugin.rb b/lib/leap_cli/remote/rsync_plugin.rb deleted file mode 100644 index a6708f4..0000000 --- a/lib/leap_cli/remote/rsync_plugin.rb +++ /dev/null @@ -1,35 +0,0 @@ -# -# these methods are made available in capistrano tasks as 'rsync.method_name' -# (see RemoteCommand::new_capistrano) -# - -autoload :RsyncCommand, 'rsync_command' - -module LeapCli; module Remote; module RsyncPlugin - -  # -  # takes a block, yielded a server, that should return a hash with various rsync options. -  # supported options include: -  # -  #   {:source => '', :dest => '', :flags => '', :includes => [], :excludes => []} -  # -  def update -    rsync = RsyncCommand.new(:logger => logger) -    rsync.asynchronously(find_servers) do |server| -      options = yield server -      next unless options -      remote_user = server.user || fetch(:user, ENV['USER']) -      src = options[:source] -      dest = {:user => remote_user, :host => server.host, :path => options[:dest]} -      options[:ssh] = ssh_options.merge(server.options[:ssh_options]||{}) -      options[:chdir] ||= Path.provider -      rsync.exec(src, dest, options) -    end -    if rsync.failed? -      LeapCli::Util.bail! do -        LeapCli::Util.log :failed, "to rsync to #{rsync.failures.map{|f|f[:dest][:host]}.join(' ')}" -      end -    end -  end - -end; end; end diff --git a/lib/leap_cli/remote/tasks.rb b/lib/leap_cli/remote/tasks.rb deleted file mode 100644 index d08d19a..0000000 --- a/lib/leap_cli/remote/tasks.rb +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is evaluated just the same as a typical capistrano "deploy.rb" -# For DSL manual, see https://github.com/capistrano/capistrano/wiki -# - -MAX_HOSTS = 10 - -task :install_authorized_keys, :max_hosts => MAX_HOSTS do -  leap.log :updating, "authorized_keys" do -    leap.mkdirs '/root/.ssh' -    upload LeapCli::Path.named_path(:authorized_keys), '/root/.ssh/authorized_keys', :mode => '600' -  end -end - -# -# for vagrant nodes, we install insecure vagrant key to authorized_keys2, since deploy -# will overwrite authorized_keys. -# -# why force the insecure vagrant key? -#   if we don't do this, then first time initialization might fail if the user has many keys -#   (ssh will bomb out before it gets to the vagrant key). -#   and it really doesn't make sense to ask users to pin the insecure vagrant key in their -#   .ssh/config files. -# -task :install_insecure_vagrant_key, :max_hosts => MAX_HOSTS do -  leap.log :installing, "insecure vagrant key" do -    leap.mkdirs '/root/.ssh' -    upload LeapCli::Path.vagrant_ssh_pub_key_file, '/root/.ssh/authorized_keys2', :mode => '600' -  end -end - -task :install_prerequisites, :max_hosts => MAX_HOSTS do -  bin_dir = File.join(Leap::Platform.leap_dir, 'bin') -  node_init_path = File.join(bin_dir, 'node_init') - -  leap.log :running, "node_init script" do -    leap.mkdirs bin_dir -    upload LeapCli::Path.node_init_script, node_init_path, :mode => '500' -    run node_init_path -  end -end - -# -# just dummies, used to capture task options -# - -task :skip_errors_task, :on_error => :continue, :max_hosts => MAX_HOSTS do -end - -task :standard_task, :max_hosts => MAX_HOSTS do -end diff --git a/lib/leap_cli/ssh_key.rb b/lib/leap_cli/ssh_key.rb deleted file mode 100644 index 138f444..0000000 --- a/lib/leap_cli/ssh_key.rb +++ /dev/null @@ -1,195 +0,0 @@ -# -# A wrapper around OpenSSL::PKey::RSA instances to provide a better api for dealing with SSH keys. -# -# cipher 'ssh-ed25519' not supported yet because we are waiting for support in Net::SSH -# - -require 'net/ssh' -require 'forwardable' - -module LeapCli -  class SshKey -    extend Forwardable - -    attr_accessor :filename -    attr_accessor :comment - -    # supported ssh key types, in order of preference -    SUPPORTED_TYPES = ['ssh-rsa', 'ecdsa-sha2-nistp256'] -    SUPPORTED_TYPES_RE = /(#{SUPPORTED_TYPES.join('|')})/ - -    ## -    ## CLASS METHODS -    ## - -    def self.load(arg1, arg2=nil) -      key = nil -      if arg1.is_a? OpenSSL::PKey::RSA -        key = SshKey.new arg1 -      elsif arg1.is_a? String -        if arg1 =~ /^ssh-/ -          type, data = arg1.split(' ') -          key = SshKey.new load_from_data(data, type) -        elsif File.exists? arg1 -          key = SshKey.new load_from_file(arg1) -          key.filename = arg1 -        else -          key = SshKey.new load_from_data(arg1, arg2) -        end -      end -      return key -    rescue StandardError => exc -    end - -    def self.load_from_file(filename) -      public_key = nil -      private_key = nil -      begin -        public_key = Net::SSH::KeyFactory.load_public_key(filename) -      rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError -        begin -          private_key = Net::SSH::KeyFactory.load_private_key(filename) -        rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError -        end -      end -      public_key || private_key -    end - -    def self.load_from_data(data, type='ssh-rsa') -      public_key = nil -      private_key = nil -      begin -        public_key = Net::SSH::KeyFactory.load_data_public_key("#{type} #{data}") -      rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError -        begin -          private_key = Net::SSH::KeyFactory.load_data_private_key("#{type} #{data}") -        rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError -        end -      end -      public_key || private_key -    end - -    # -    # Picks one key out of an array of keys that we think is the "best", -    # based on the order of preference in SUPPORTED_TYPES -    # -    # Currently, this does not take bitsize into account. -    # -    def self.pick_best_key(keys) -      keys.select {|k| -        SUPPORTED_TYPES.include?(k.type) -      }.sort {|a,b| -        SUPPORTED_TYPES.index(a.type) <=> SUPPORTED_TYPES.index(b.type) -      }.first -    end - -    # -    # takes a string with one or more ssh keys, one key per line, -    # and returns an array of SshKey objects. -    # -    # the lines should be in one of these formats: -    # -    # 1. <hostname> <key-type> <key> -    # 2. <key-type> <key> -    # -    def self.parse_keys(string) -      keys = [] -      lines = string.split("\n").grep(/^[^#]/) -      lines.each do |line| -        if line =~ / #{SshKey::SUPPORTED_TYPES_RE} / -          # <hostname> <key-type> <key> -          keys << line.split(' ')[1..2] -        elsif line =~ /^#{SshKey::SUPPORTED_TYPES_RE} / -          # <key-type> <key> -          keys << line.split(' ') -        end -      end -      return keys.map{|k| SshKey.load(k[1], k[0])} -    end - -    # -    # takes a string with one or more ssh keys, one key per line, -    # and returns a string that specified the ssh key algorithms -    # that are supported by the keys, in order of preference. -    # -    # eg: ecdsa-sha2-nistp256,ssh-rsa,ssh-ed25519 -    # -    def self.supported_host_key_algorithms(string) -      if string -        self.parse_keys(string).map {|key| -          key.type -        }.join(',') -      else -        "" -      end -    end - -    ## -    ## INSTANCE METHODS -    ## - -    public - -    def initialize(rsa_key) -      @key = rsa_key -    end - -    def_delegator :@key, :fingerprint, :fingerprint -    def_delegator :@key, :public?, :public? -    def_delegator :@key, :private?, :private? -    def_delegator :@key, :ssh_type, :type -    def_delegator :@key, :public_encrypt, :public_encrypt -    def_delegator :@key, :public_decrypt, :public_decrypt -    def_delegator :@key, :private_encrypt, :private_encrypt -    def_delegator :@key, :private_decrypt, :private_decrypt -    def_delegator :@key, :params, :params -    def_delegator :@key, :to_text, :to_text - -    def public_key -      SshKey.new(@key.public_key) -    end - -    def private_key -      SshKey.new(@key.private_key) -    end - -    # -    # not sure if this will always work, but is seems to for now. -    # -    def bits -      Net::SSH::Buffer.from(:key, @key).to_s.split("\001\000").last.size * 8 -    end - -    def summary -      if self.filename -        "%s %s %s (%s)" % [self.type, self.bits, self.fingerprint, File.basename(self.filename)] -      else -        "%s %s %s" % [self.type, self.bits, self.fingerprint] -      end -    end - -    def to_s -      self.type + " " + self.key -    end - -    def key -      [Net::SSH::Buffer.from(:key, @key).to_s].pack("m*").gsub(/\s/, "") -    end - -    def ==(other_key) -      return false if other_key.nil? -      return false if self.class != other_key.class -      return self.to_text == other_key.to_text -    end - -    def in_known_hosts?(*identifiers) -      identifiers.each do |identifier| -        Net::SSH::KnownHosts.search_for(identifier).each do |key| -          return true if self == key -        end -      end -      return false -    end - -  end -end diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index 5014238..ae73731 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -10,6 +10,10 @@ module LeapCli      @@exit_status = nil +    def log(*args, &block) +      LeapCli.log(*args, &block) +    end +      ##      ## QUITTING      ## @@ -36,15 +40,14 @@ module LeapCli      #      # exit with error code and with a message that we are bailing out.      # -    def bail!(*message) -      if block_given? -        LeapCli.set_log_level(3) -        yield -      elsif message -        log 0, *message +    def bail!(*message, &block) +      LeapCli.logger.log_level = 3 if LeapCli.logger.log_level < 3 +      if message.any? +        log(0, *message, &block) +      else +        log(0, :bailing, "out", :color => :red, :style => :bold, &block)        end -      log 0, :bail, "" -      raise SystemExit.new(@exit_status || 1) +      raise SystemExit.new(exit_status || 1)      end      # @@ -52,7 +55,7 @@ module LeapCli      #      def quit!(message='')        puts(message) -      raise SystemExit.new(@exit_status || 0) +      raise SystemExit.new(exit_status || 0)      end      # @@ -119,7 +122,7 @@ module LeapCli        base = options[:base] || Path.provider        file_list = files.collect { |file_path|          file_path = Path.named_path(file_path, base) -        File.exists?(file_path) ? Path.relative_path(file_path, base) : nil +        File.exist?(file_path) ? Path.relative_path(file_path, base) : nil        }.compact        if file_list.length > 1          bail! do @@ -138,7 +141,7 @@ module LeapCli        options = files.last.is_a?(Hash) ? files.pop : {}        file_list = files.collect { |file_path|          file_path = Path.named_path(file_path) -        !File.exists?(file_path) ? Path.relative_path(file_path) : nil +        !File.exist?(file_path) ? Path.relative_path(file_path) : nil        }.compact        if file_list.length > 1          bail! do @@ -157,7 +160,7 @@ module LeapCli      def file_exists?(*files)        files.each do |file_path|          file_path = Path.named_path(file_path) -        if !File.exists?(file_path) +        if !File.exist?(file_path)            return false          end        end @@ -233,7 +236,7 @@ module LeapCli      #      def replace_file!(filepath, &block)        filepath = Path.named_path(filepath) -      if !File.exists?(filepath) +      if !File.exist?(filepath)          content = yield(nil)          unless content.nil?            write_file!(filepath, content) @@ -258,7 +261,7 @@ module LeapCli      def remove_file!(filepath)        filepath = Path.named_path(filepath) -      if File.exists?(filepath) +      if File.exist?(filepath)          if File.directory?(filepath)            remove_directory!(filepath)          else @@ -298,7 +301,7 @@ module LeapCli      def write_file!(filepath, contents)        filepath = Path.named_path(filepath)        ensure_dir File.dirname(filepath) -      existed = File.exists?(filepath) +      existed = File.exist?(filepath)        if existed          if file_content_equals?(filepath, contents)            log :nochange, filepath, 2 @@ -320,11 +323,11 @@ module LeapCli      def rename_file!(oldpath, newpath)        oldpath = Path.named_path(oldpath)        newpath = Path.named_path(newpath) -      if File.exists? newpath +      if File.exist? newpath          log :skipping, "#{Path.relative_path(newpath)}, file already exists"          return        end -      if !File.exists? oldpath +      if !File.exist? oldpath          log :skipping, "#{Path.relative_path(oldpath)}, file is missing"          return        end @@ -425,11 +428,18 @@ module LeapCli        end      end +    def is_git_subrepo?(dir) +      Dir.chdir(dir) do +        `ls .gitrepo 2>/dev/null` +        return $? == 0 +      end +    end +      def current_git_branch(dir)        Dir.chdir(dir) do          branch = `git symbolic-ref HEAD 2>/dev/null`.strip          if branch.chars.any? -          branch.sub /^refs\/heads\//, '' +          branch.sub(/^refs\/heads\//, '')          else            nil          end diff --git a/lib/leap_cli/util/remote_command.rb b/lib/leap_cli/util/remote_command.rb deleted file mode 100644 index 10a5ca8..0000000 --- a/lib/leap_cli/util/remote_command.rb +++ /dev/null @@ -1,158 +0,0 @@ -module LeapCli; module Util; module RemoteCommand -  extend self - -  # -  # FYI -  #  Capistrano::Logger::IMPORTANT = 0 -  #  Capistrano::Logger::INFO      = 1 -  #  Capistrano::Logger::DEBUG     = 2 -  #  Capistrano::Logger::TRACE     = 3 -  # -  def ssh_connect(nodes, options={}, &block) -    options ||= {} -    node_list = parse_node_list(nodes) - -    cap = new_capistrano -    cap.logger = LeapCli::Logger.new(:level => [LeapCli.log_level,3].min) -    user = options[:user] || 'root' -    cap.set :user, user -    cap.set :ssh_options, ssh_options # ssh options common to all nodes -    cap.set :use_sudo, false          # we may want to change this in the future - -    # Allow password authentication when we are bootstraping a single node -    # (and key authentication fails). -    if options[:bootstrap] && node_list.size == 1 -      hostname = node_list.values.first.name -      if options[:echo] -        cap.set(:password) { ask "Root SSH password for #{user}@#{hostname}> " } -      else -        cap.set(:password) { Capistrano::CLI.password_prompt " * Typed password will be hidden (use --echo to make it visible)\nRoot SSH password for #{user}@#{hostname}> " } -      end -    end - -    node_list.each do |name, node| -      cap.server node.domain.full, :dummy_arg, node_options(node, options[:ssh_options]) -    end - -    yield cap -  rescue Capistrano::ConnectionError => exc -    # not sure if this will work if english is not the locale?? -    if exc.message =~ /Too many authentication failures/ -      at_exit {ssh_config_help_message} -    end -    raise exc -  end - -  private - -  # -  # For available options, see http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start -  # -  # Capistrano has some very evil behavior in it's ssh.rb: -  # -  #   ssh_options = Net::SSH.configuration_for( -  #     server.host, ssh_options.fetch(:config, true) -  #   ).merge(ssh_options) -  #   # Once we've loaded the config, we don't need Net::SSH to do it again. -  #   ssh_options[:config] = false -  # -  # Net:SSH is supposed to call Net::SSH.configuration_for, but Capistrano is doing it -  # in advance and then disabling loading of configs. -  # -  # The result of this is the following: if you have IdentityFile in your ~/.ssh/config -  # file, then the above code will transform the ssh_options by reading ~/.ssh/config -  # and adding the keys specified via IdentityFile to ssh_options... -  # AND IT WILL SET :keys_only TO TRUE. -  # -  # The problem is that :keys_only will disable Net:SSH's ability to use ssh-agent. -  # With :keys_only set to true, it will not consult the ssh-agent at all. -  # -  # So nice of capistrano to parse ~/.ssh/config for us, but then add flags to the -  # ssh_options that prevent's these options from being useful. -  # -  # The current hackaround is to force :keys_only to be false. This allows the config -  # to be read and also allows ssh-agent to still be used. -  # -  def ssh_options -    { -      :keys_only => false, # Don't you dare change this. -      :global_known_hosts_file => path(:known_hosts), -      :user_known_hosts_file => '/dev/null', -      :paranoid => true, -      :verbose => net_ssh_log_level -    } -  end - -  def net_ssh_log_level -    if DEBUG -      case LeapCli.log_level -        when 1 then 3 -        when 2 then 2 -        when 3 then 1 -        else 0 -      end -    else -      nil -    end -  end - -  # -  # For notes on advanced ways to set server-specific options, see -  # http://railsware.com/blog/2011/11/02/advanced-server-definitions-in-capistrano/ -  # -  # if, in the future, we want to do per-node password options, it would be done like so: -  # -  #  password_proc = Proc.new {Capistrano::CLI.password_prompt "Root SSH password for #{node.name}"} -  #  return {:password => password_proc} -  # -  def node_options(node, ssh_options_override=nil) -    { -      :ssh_options => { -        # :host_key_alias => node.name, << incompatible with ports in known_hosts -        :host_name => node.ip_address, -        :port => node.ssh.port -      }.merge(contingent_ssh_options_for_node(node)).merge(ssh_options_override||{}) -    } -  end - -  def new_capistrano -    # load once the library files -    @capistrano_enabled ||= begin -      require 'capistrano' -      require 'capistrano/cli' -      require 'leap_cli/lib_ext/capistrano_connections' -      require 'leap_cli/remote/leap_plugin' -      require 'leap_cli/remote/puppet_plugin' -      require 'leap_cli/remote/rsync_plugin' -      Capistrano.plugin :leap, LeapCli::Remote::LeapPlugin -      Capistrano.plugin :puppet, LeapCli::Remote::PuppetPlugin -      Capistrano.plugin :rsync, LeapCli::Remote::RsyncPlugin -      true -    end - -    # create capistrano instance -    cap = Capistrano::Configuration.new - -    # add tasks to capistrano instance -    cap.load File.dirname(__FILE__) + '/../remote/tasks.rb' - -    return cap -  end - -  def contingent_ssh_options_for_node(node) -    opts = {} -    if node.vagrant? -      opts[:keys] = [vagrant_ssh_key_file] -      opts[:keys_only] = true # only use the keys specified above, and ignore whatever keys the ssh-agent is aware of. -      opts[:paranoid] = false # we skip host checking for vagrant nodes, because fingerprint is different for everyone. -      if LeapCli::log_level <= 1 -        opts[:verbose] = :error # suppress all the warnings about adding host keys to known_hosts, since it is not actually doing that. -      end -    end -    if !node.supported_ssh_host_key_algorithms.empty? -      opts[:host_key] = node.supported_ssh_host_key_algorithms -    end -    return opts -  end - -end; end; end
\ No newline at end of file diff --git a/lib/leap_cli/util/secret.rb b/lib/leap_cli/util/secret.rb deleted file mode 100644 index 749b959..0000000 --- a/lib/leap_cli/util/secret.rb +++ /dev/null @@ -1,55 +0,0 @@ -# encoding: utf-8 -# -# A simple secret generator -# -# Uses OpenSSL random number generator instead of Ruby's rand function -# -autoload :OpenSSL, 'openssl' - -module LeapCli; module Util -  class Secret -    CHARS = (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a) - "i1loO06G".split(//u) -    HEX = (0..9).to_a + ('a'..'f').to_a - -    # -    # generate a secret with with no ambiguous characters. -    # -    # +length+ is in chars -    # -    # Only alphanumerics are allowed, in order to make these passwords work -    # for REST url calls and to allow you to easily copy and paste them. -    # -    def self.generate(length = 16) -      seed -      OpenSSL::Random.random_bytes(length).bytes.to_a.collect { |byte| -        CHARS[ byte % CHARS.length ] -      }.join -    end - -    # -    # generates a hex secret, instead of an alphanumeric on. -    # -    # length is in bits -    # -    def self.generate_hex(length = 128) -      seed -      OpenSSL::Random.random_bytes(length/4).bytes.to_a.collect { |byte| -        HEX[ byte % HEX.length ] -      }.join -    end - -    private - -    def self.seed -      @pid ||= 0 -      pid = $$ -      if @pid != pid -        now = Time.now -        ary = [now.to_i, now.nsec, @pid, pid] -        OpenSSL::Random.seed(ary.to_s) -        @pid = pid -      end -    end - -  end -end; end diff --git a/lib/leap_cli/util/x509.rb b/lib/leap_cli/util/x509.rb deleted file mode 100644 index 787fdfa..0000000 --- a/lib/leap_cli/util/x509.rb +++ /dev/null @@ -1,33 +0,0 @@ -autoload :OpenSSL, 'openssl' -autoload :CertificateAuthority, 'certificate_authority' - -require 'digest' -require 'digest/md5' -require 'digest/sha1' - -module LeapCli; module X509 -  extend self - -  # -  # returns a fingerprint of a x509 certificate -  # -  def fingerprint(digest, cert_file) -    if cert_file.is_a? String -      cert = OpenSSL::X509::Certificate.new(Util.read_file!(cert_file)) -    elsif cert_file.is_a? OpenSSL::X509::Certificate -      cert = cert_file -    elsif cert_file.is_a? CertificateAuthority::Certificate -      cert = cert_file.openssl_body -    end -    digester = case digest -      when "MD5" then Digest::MD5.new -      when "SHA1" then Digest::SHA1.new -      when "SHA256" then Digest::SHA256.new -      when "SHA384" then Digest::SHA384.new -      when "SHA512" then Digest::SHA512.new -    end -    digester.hexdigest(cert.to_der) -  end - - -end; end diff --git a/lib/leap_cli/version.rb b/lib/leap_cli/version.rb index 475cdcc..bb8b57c 100644 --- a/lib/leap_cli/version.rb +++ b/lib/leap_cli/version.rb @@ -1,9 +1,14 @@  module LeapCli    unless defined?(LeapCli::VERSION) -    VERSION = '1.8.1' -    COMPATIBLE_PLATFORM_VERSION = '0.8'..'0.8.99' +    VERSION = '1.9' +    COMPATIBLE_PLATFORM_VERSION = '0.9'..'0.99'      SUMMARY = 'Command line interface to the LEAP platform'      DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.' -    LOAD_PATHS = ['lib', 'vendor/certificate_authority/lib', 'vendor/rsync_command/lib'] +    LOAD_PATHS = ['lib', +      'vendor/certificate_authority/lib', +      'vendor/rsync_command/lib', +      'vendor/base32/lib', +      'vendor/acme-client/lib' +    ]    end  end diff --git a/test/provider/Leapfile b/test/provider/Leapfile index abab946..71af4f9 100644 --- a/test/provider/Leapfile +++ b/test/provider/Leapfile @@ -1 +1 @@ -@platform_directory_path = '../../../leap_platform'
\ No newline at end of file +@platform_directory_path = ENV['PLATFORM_DIR'] || '../../../leap_platform' diff --git a/test/provider/files/ca/ca.crt b/test/provider/files/ca/ca.crt new file mode 100644 index 0000000..765b61d --- /dev/null +++ b/test/provider/files/ca/ca.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICbDCCAdWgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRAwDgYDVQQKDAdFeGFt +cGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3JnMRgwFgYDVQQDDA9FeGFt +cGxlIFJvb3QgQ0EwIBcNMTYwOTI4MDAwMDAwWhgPMjExNjA5MjgwMDAwMDBaMEox +EDAOBgNVBAoMB0V4YW1wbGUxHDAaBgNVBAsME2h0dHBzOi8vZXhhbXBsZS5vcmcx +GDAWBgNVBAMMD0V4YW1wbGUgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEA2qvO4cFgWRuMMgubaTP8L6ygeBPvHQrK0ZbM3MRJxtBUKfF0uT/+Y8CH +XtQ9Jz+uy4+0n/W/BvExOilY/A/S7cmdD/xdRl7IxSmpCpvQmuhtgV48wSvC9E1v +TB9pSZBJtfYiLqf6WXncSe/3AbjsF+z1id3Ye/4wcbY77MeSaRMCAwEAAaNgMF4w +HQYDVR0OBBYEFLWsA8r7sW8c87nGA4JQcSbYlZGBMA4GA1UdDwEB/wQEAwICBDAM +BgNVHRMEBTADAQH/MB8GA1UdIwQYMBaAFLWsA8r7sW8c87nGA4JQcSbYlZGBMA0G +CSqGSIb3DQEBDQUAA4GBAEeo1alEkXmugRJHjczC7od50zZxaoG/1fIfXqhTGPs7 +99FuXnlKKeFjQOUN1V0Ef5m3MOaTVk4Zx8zyZ9ybljriVS6Dwf2AdMUkOppYe4wp +soLvZ2y0bY//F279Xn/GvmHHLfA722mPJ/Z7csirWHaEhqp9prBXN5Fqin8mNCiF +-----END CERTIFICATE----- diff --git a/test/provider/files/ca/ca.key b/test/provider/files/ca/ca.key new file mode 100644 index 0000000..b0a6550 --- /dev/null +++ b/test/provider/files/ca/ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDaq87hwWBZG4wyC5tpM/wvrKB4E+8dCsrRlszcxEnG0FQp8XS5 +P/5jwIde1D0nP67Lj7Sf9b8G8TE6KVj8D9LtyZ0P/F1GXsjFKakKm9Ca6G2BXjzB +K8L0TW9MH2lJkEm19iIup/pZedxJ7/cBuOwX7PWJ3dh7/jBxtjvsx5JpEwIDAQAB +AoGBAIbgKAf5RZtQsYWAwUf/h5JEUOofqYHpUTY7ZHrbG4JkpzUDuHI29YrDivvD +v0CBOChYqBlt83ittiZgsIEwpXFf36q9Xz80yySXHsjEhOVUPIdKZt95n0VZmwQn +y5PdsGgaWXkD58HSuSaa6CyMH0O50iwqJQiRy71VRr0A6LJZAkEA74dUYD/hJieH +OA0FDw0z5BaB6QTLtKZogOQ/g6Ju1PmhqcXvbMhv4hZ5DDXEwkVb/5qaFbAMmxL5 +QRkTw0Kx3wJBAOm1TWIauB0siNSGnESGSiZxsDGzfd6GbztC3E7E0tupAk0l+HuK +PA76vs76QgJPxRjLSn6A6mhGStwSnUk0N00CQHJ9/2jaX+Z68nlqT8a4Ctu1nnch +YbWB7WXetDVZiRyoDgw2npEi5cft8gJSGTC7MpRk8832DrB5S0dAk1+8G4UCQHQa +e90XBQyJSVi7nvpz9HZw2GV4lDluc+fu6V/AbDhwGBKXoIBPRlLywsQ0k4Jueq48 +oD+Eb+9prFr0bGsno6kCQQDqCigukRwPvpNyq5fMS4d7Rs0N5HlaSUdi0QYWQ38i +144eEq0NswCDQt025bEw/dzZZIqS3JSUbx3ZGOiUD3bp +-----END RSA PRIVATE KEY----- diff --git a/test/provider/files/ca/client_ca.crt b/test/provider/files/ca/client_ca.crt new file mode 100644 index 0000000..accc0cf --- /dev/null +++ b/test/provider/files/ca/client_ca.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICpDCCAg2gAwIBAgIBATANBgkqhkiG9w0BAQ0FADBmMRAwDgYDVQQKDAdFeGFt +cGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3JnMTQwMgYDVQQDDCtFeGFt +cGxlIFJvb3QgQ0EgKGNsaWVudCBjZXJ0aWZpY2F0ZXMgb25seSEpMCAXDTE2MDky +ODAwMDAwMFoYDzIxMTYwOTI4MDAwMDAwWjBmMRAwDgYDVQQKDAdFeGFtcGxlMRww +GgYDVQQLDBNodHRwczovL2V4YW1wbGUub3JnMTQwMgYDVQQDDCtFeGFtcGxlIFJv +b3QgQ0EgKGNsaWVudCBjZXJ0aWZpY2F0ZXMgb25seSEpMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDrtlWEw/XMV0p4+R9fDEMKm4kBmN+F29qdA3cQssZkZBRj +UAbpwIk+wZXJuukwoQHY9bwobr85rEf6UiEi0e3sxD4yN4GEU+rX9JVHgGUmbi0f +Wmu6YHMRfnRKOu8IMu50Ry+oPIwHpzSek6IfYKI1D+484UBJ1sMESQyo3V47rQID +AQABo2AwXjAdBgNVHQ4EFgQUz+7haong5OegkFFugOHX4oRoJCowDgYDVR0PAQH/ +BAQDAgIEMAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUz+7haong5OegkFFugOHX +4oRoJCowDQYJKoZIhvcNAQENBQADgYEAzV/AUYmkxsnnbHExdePYceBeQ8mMGaqy +JQx4UstHEqUq5IXz346saQcXHELq2QHX/JgGC7crUsjICglqq1XeVJ7ULlmIRVoe +6iSG0sdAulji4sdIidXJ/AluBdUE9iPbmgKQWn9YD17j85QBQEa+M5G52gb0Ul8q +oMQRQRQ5X7I= +-----END CERTIFICATE----- diff --git a/test/provider/files/ca/client_ca.key b/test/provider/files/ca/client_ca.key new file mode 100644 index 0000000..f9e2f27 --- /dev/null +++ b/test/provider/files/ca/client_ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDrtlWEw/XMV0p4+R9fDEMKm4kBmN+F29qdA3cQssZkZBRjUAbp +wIk+wZXJuukwoQHY9bwobr85rEf6UiEi0e3sxD4yN4GEU+rX9JVHgGUmbi0fWmu6 +YHMRfnRKOu8IMu50Ry+oPIwHpzSek6IfYKI1D+484UBJ1sMESQyo3V47rQIDAQAB +AoGAOuuDCQLq6D9RsFeljd7Ey1wBrVKHXTCNvv3kv1nQ2btilUil0bx9EiDVzm1Y +aP12NsOGWx0D0+jKvTnWapvLOw8e3F+XvkKCzhcyz/M2NlQh5bGlgtG1TBN/C/K6 +HuJk1GGDFei2dKPadkN18mHvq/2fFLtdJ+Z5Fczd6U3fXQkCQQD82sUY7uoN023q +smqxn60N3B+PN6DOaD+n2jOzrmkWvvY90X7WzxHRMWrV2a/Gov+MGOCebPNC9VLF +luxhU50LAkEA7qT5MSK3XfvmxcUfSMCjED2X4cf8VEBsYEHl/qQTxcXvo40dLinD +0za04iC6/NIUZaAhLzMsg/lByCkJZ09NJwJATR8Y4Kr2PnNPYjc67aRLLyAFjDQm +Wu5XBAY8oMBAk0x5ZI+CRVhxEcIl2MYFo+tRUFTCJfALHlAfB98ph+Ht0wJBANh7 +qV5MauEEES1ZC28Y6RNjfHMh0qGvK2EKhpQ/zXv8ec34xf7Jfk4M83uqS1XrUPt7 +jn7dwkUaCPWFXHVuN8MCQQCUkgXZRHjO+C9G9vKKhgiEWrDw/cx6+3o8sFELLqQn ++wgXov454z+ksILx9hxCFaDUDq1iqhVTK71njsIMZ1Gi +-----END RSA PRIVATE KEY----- diff --git a/test/provider/files/cert/bitmask.net.crt b/test/provider/files/cert/bitmask.net.crt deleted file mode 100644 index f3aaae4..0000000 --- a/test/provider/files/cert/bitmask.net.crt +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICZzCCAdCgAwIBAgIRAPF3nvtTiGL4Z/z8rrJ2OKAwDQYJKoZIhvcNAQELBQAw -SjEQMA4GA1UECgwHQml0bWFzazEcMBoGA1UECwwTaHR0cHM6Ly9iaXRtYXNrLm5l -dDEYMBYGA1UEAwwPQml0bWFzayBSb290IENBMB4XDTE2MDQwOTAwMDAwMFoXDTE3 -MDQwOTAwMDAwMFowKDEQMA4GA1UECgwHQml0bWFzazEUMBIGA1UEAwwLYml0bWFz -ay5uZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMcuc0zp/JMOkZZXmaH/ -/ABBtc3i79OD90LRk4AEXZ7X46Ougw92qeHvX8worEHgpiPxzlj2QETrH25ljuqK -e/nDpHwO/43couFFliq3VnLLBDJvYzL5byTd5V0bs/q4tl5CUYt1j6Xg4ses/Hv3 -cHyNqNQKfVJuyeWdZhtNizhHAgMBAAGjbzBtMB0GA1UdDgQWBBTB0njg6dZRnf/Z -dO7EBRUy2+fBpTALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCQYD -VR0TBAIwADAfBgNVHSMEGDAWgBQCuoulI/QMOR5z5nDOeXoOzkZtOjANBgkqhkiG -9w0BAQsFAAOBgQAQ9EWhZJqLKLwCTOG0AD5+KwpbAkhHgdO3BXcMJAqLhjezmd9c -cHQ/DZ/BSKmIm0eV6UsnxOBy9lZNIL1KqpazUyCgcCPDwDhd8Ihgk0x5ciNHgCFq -6rCQ3kQVPVJZ2S2gQLOKJz1a0muMBE5KmIEL0ZMgqpn97YHgrOMCIjoM9g== ------END CERTIFICATE----- diff --git a/test/provider/files/cert/bitmask.net.csr b/test/provider/files/cert/bitmask.net.csr deleted file mode 100644 index d106cb1..0000000 --- a/test/provider/files/cert/bitmask.net.csr +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBpjCCAQ8CAQAwKDEQMA4GA1UECgwHQml0bWFzazEUMBIGA1UEAwwLYml0bWFz -ay5uZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMcuc0zp/JMOkZZXmaH/ -/ABBtc3i79OD90LRk4AEXZ7X46Ougw92qeHvX8worEHgpiPxzlj2QETrH25ljuqK -e/nDpHwO/43couFFliq3VnLLBDJvYzL5byTd5V0bs/q4tl5CUYt1j6Xg4ses/Hv3 -cHyNqNQKfVJuyeWdZhtNizhHAgMBAAGgPjA8BgkqhkiG9w0BCQ4xLzAtMAkGA1Ud -EwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3 -DQEBCwUAA4GBAFnt0V7+qyPfQZQGF12DdCy0t3MRqFVQbcIegNPshKWP1GIruVMX -ltJmTB1oVqVQ8Pmj0lIAbCrudHBqblnUUt1tME1JmWgH9wQtDaP5jnATJ1DQGMl1 -bQJQdiSE3/VGSeHn3K/XY7Yk2kmWZ3mzf1AwCmpwrn4SxIPiGcYa+21U ------END CERTIFICATE REQUEST----- diff --git a/test/provider/files/cert/bitmask.net.key b/test/provider/files/cert/bitmask.net.key deleted file mode 100644 index 877f781..0000000 --- a/test/provider/files/cert/bitmask.net.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXwIBAAKBgQDHLnNM6fyTDpGWV5mh//wAQbXN4u/Tg/dC0ZOABF2e1+OjroMP -dqnh71/MKKxB4KYj8c5Y9kBE6x9uZY7qinv5w6R8Dv+N3KLhRZYqt1ZyywQyb2My -+W8k3eVdG7P6uLZeQlGLdY+l4OLHrPx793B8jajUCn1SbsnlnWYbTYs4RwIDAQAB -AoGBAKOKXh0+2aUdByi8EGbVOeI0EcRUmrm+1txEG6m26++qLzyL4wxlUCM0WiHV -G2qTu5Yzykt9FVQBAbOxK2EkB5mezLxGhnR24bPcpvDAqWy/dKBQ5t4hARKdgw4A -2iyhojno7aB/inP3ViTNvr/Kg77XyUgIq7fsLa8AsXJo0FAxAkEA5bye9XAYa29w -uK64rrtaflWcUqeejl9BQtrAKQmlRHC3uKxmWv260fn2OZzYwsNdD96y8YKeFS6g -65jj/eMPgwJBAN3znApBwUBDw4dX8ZLz2AC1P3ikQPGu+ySSf5+NJPUU3pgl6eL6 -pGaxplbDpFdvxgsfyxeSgNsFd/zmrD+v9O0CQQDjbTy3oIasJKAkU+NEJvjIxBuC -v6j5LFdAxakhdwkCnctiqFiTj0cYgyk7k4gKFrjT8xSWfUXdllF7qdlaByPdAkEA -t37+FKTERoM/lhepCxs6C2vNa8owPx+xVk0f4iLo2Q5F8Xf248bgQF7C7JyWtAse -qnfAil5+1ZSx3I5A/e5VCQJBALWoaVH/laZinIWgka9TngD0BtLPvYjoH7iLSpAK -STdh5IdwlcCKq/TzC+DpRYsEJM2wHEC+0nOLDp8xDwYPHfw= ------END RSA PRIVATE KEY----- diff --git a/test/provider/files/cert/commercial_ca.crt b/test/provider/files/cert/commercial_ca.crt index 468941e..765b61d 100644 --- a/test/provider/files/cert/commercial_ca.crt +++ b/test/provider/files/cert/commercial_ca.crt @@ -1,15 +1,15 @@  -----BEGIN CERTIFICATE----- -MIICbDCCAdWgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRAwDgYDVQQKDAdCaXRt -YXNrMRwwGgYDVQQLDBNodHRwczovL2JpdG1hc2submV0MRgwFgYDVQQDDA9CaXRt -YXNrIFJvb3QgQ0EwIBcNMTYwNDA5MDAwMDAwWhgPMjExNjA0MDkwMDAwMDBaMEox -EDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNVBAsME2h0dHBzOi8vYml0bWFzay5uZXQx -GDAWBgNVBAMMD0JpdG1hc2sgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw -gYkCgYEArDu+1XWnEHS9CsemL6wuFZ09vY59SpXcpkMEOYLl+H5HibLsjt7PkDCi -x4Bmf/0Mvlk5bft7VGHKtRbIe5/vIyA7IyIX76IHsX2iWASS4HaUE4ERtFTqE+2b -x5N0/r5mYJCIhRslZdcAvzVb6NbujsQHU7NSRMOjBofVk1oYn+8CAwEAAaNgMF4w -HQYDVR0OBBYEFAK6i6Uj9Aw5HnPmcM55eg7ORm06MA4GA1UdDwEB/wQEAwICBDAM -BgNVHRMEBTADAQH/MB8GA1UdIwQYMBaAFAK6i6Uj9Aw5HnPmcM55eg7ORm06MA0G -CSqGSIb3DQEBDQUAA4GBAD7cxb1nmhtfHfA4KnnK25dkHygMhqihj2xby3dLtAMO -BuataWvN4ssgrUs7XdZRdagI2W2jA7RyLX8hFo+F2A0CRzYNwHl+Ffa2GuZko6M9 -4Muo4aEs7/h20jsxVFLezTGwN7lcyA8FoueGkCUXMm8WAAL0Id1hk+3ek70ywewh +MIICbDCCAdWgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRAwDgYDVQQKDAdFeGFt +cGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3JnMRgwFgYDVQQDDA9FeGFt +cGxlIFJvb3QgQ0EwIBcNMTYwOTI4MDAwMDAwWhgPMjExNjA5MjgwMDAwMDBaMEox +EDAOBgNVBAoMB0V4YW1wbGUxHDAaBgNVBAsME2h0dHBzOi8vZXhhbXBsZS5vcmcx +GDAWBgNVBAMMD0V4YW1wbGUgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEA2qvO4cFgWRuMMgubaTP8L6ygeBPvHQrK0ZbM3MRJxtBUKfF0uT/+Y8CH +XtQ9Jz+uy4+0n/W/BvExOilY/A/S7cmdD/xdRl7IxSmpCpvQmuhtgV48wSvC9E1v +TB9pSZBJtfYiLqf6WXncSe/3AbjsF+z1id3Ye/4wcbY77MeSaRMCAwEAAaNgMF4w +HQYDVR0OBBYEFLWsA8r7sW8c87nGA4JQcSbYlZGBMA4GA1UdDwEB/wQEAwICBDAM +BgNVHRMEBTADAQH/MB8GA1UdIwQYMBaAFLWsA8r7sW8c87nGA4JQcSbYlZGBMA0G +CSqGSIb3DQEBDQUAA4GBAEeo1alEkXmugRJHjczC7od50zZxaoG/1fIfXqhTGPs7 +99FuXnlKKeFjQOUN1V0Ef5m3MOaTVk4Zx8zyZ9ybljriVS6Dwf2AdMUkOppYe4wp +soLvZ2y0bY//F279Xn/GvmHHLfA722mPJ/Z7csirWHaEhqp9prBXN5Fqin8mNCiF  -----END CERTIFICATE----- diff --git a/test/provider/files/cert/example.org.crt b/test/provider/files/cert/example.org.crt new file mode 100644 index 0000000..a863de4 --- /dev/null +++ b/test/provider/files/cert/example.org.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICZzCCAdCgAwIBAgIRAIm+g8LZXIiwbrNxIjkZUUgwDQYJKoZIhvcNAQELBQAw +SjEQMA4GA1UECgwHRXhhbXBsZTEcMBoGA1UECwwTaHR0cHM6Ly9leGFtcGxlLm9y +ZzEYMBYGA1UEAwwPRXhhbXBsZSBSb290IENBMB4XDTE2MDkyODAwMDAwMFoXDTE3 +MDkyODAwMDAwMFowKDEQMA4GA1UECgwHRXhhbXBsZTEUMBIGA1UEAwwLZXhhbXBs +ZS5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJ3cGk0Y1CCHqaqj8fJr +gAuINrofB/NXpVyLYCVhU4C+3xJEpOXSrOT0DqXHockChnaAusoBTrwN7jvqIBeU +6DmlC+kQqTTizF6c0Xna43ftjfuZAdIpehqA+7wQwKfilC+SNh+8U7V7VrDIrNfR +iluOWSA6jl+PMUA6atrzIWsLAgMBAAGjbzBtMB0GA1UdDgQWBBTNmHvqnul7KbX1 +uGrJs7Jh6VyEIzALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCQYD +VR0TBAIwADAfBgNVHSMEGDAWgBS1rAPK+7FvHPO5xgOCUHEm2JWRgTANBgkqhkiG +9w0BAQsFAAOBgQAT6TUL9rYqEK7E4wCRbzyjaUc+7OTBtnYNVKCY4+jQzQR5r+wo +3fLbsQ5qd1a0BXp44rRlto0oj5ihHAauG/v0BVXbi4vshfV4pdlEWxsbHvRqat0w +gxNlEB9goapeMGdLjPo7uQiEtZhWEHcpyRBukve1aIxDPIHrogPftR0yMA== +-----END CERTIFICATE----- diff --git a/test/provider/files/cert/example.org.csr b/test/provider/files/cert/example.org.csr new file mode 100644 index 0000000..5542c22 --- /dev/null +++ b/test/provider/files/cert/example.org.csr @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBpjCCAQ8CAQAwKDEQMA4GA1UECgwHRXhhbXBsZTEUMBIGA1UEAwwLZXhhbXBs +ZS5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJ3cGk0Y1CCHqaqj8fJr +gAuINrofB/NXpVyLYCVhU4C+3xJEpOXSrOT0DqXHockChnaAusoBTrwN7jvqIBeU +6DmlC+kQqTTizF6c0Xna43ftjfuZAdIpehqA+7wQwKfilC+SNh+8U7V7VrDIrNfR +iluOWSA6jl+PMUA6atrzIWsLAgMBAAGgPjA8BgkqhkiG9w0BCQ4xLzAtMAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3 +DQEBCwUAA4GBAIF7mpeTKfvghlxuS+7CfOO24BNYS/uZu16XBNgzlHR+1eSHb8nU +EGzWCT7C8I7vWQHYXTOX4fMDUeHMw/w7rchgWd/7DikPJR5PAwotQnAVefNAjWpb ++l4rW2pqIJHzGZGoipFmTA2ISbD4AtGhzQOn5u/uu5H3Lo8tIA4iip2/ +-----END CERTIFICATE REQUEST----- diff --git a/test/provider/files/cert/example.org.key b/test/provider/files/cert/example.org.key new file mode 100644 index 0000000..f6eedec --- /dev/null +++ b/test/provider/files/cert/example.org.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCd3BpNGNQgh6mqo/Hya4ALiDa6HwfzV6Vci2AlYVOAvt8SRKTl +0qzk9A6lx6HJAoZ2gLrKAU68De476iAXlOg5pQvpEKk04sxenNF52uN37Y37mQHS +KXoagPu8EMCn4pQvkjYfvFO1e1awyKzX0YpbjlkgOo5fjzFAOmra8yFrCwIDAQAB +AoGAODbrPs06rSLibpvXSwaxIGovYvQt9qAdiOkxId6Yx94wvean+hed7iJjHPIM +UPKPQ5/v5IO2sA0d60QijYM/dshqwNp/4eXNEceymGFzbqKvSi4xSdoEwDjTTHMl +YDLuAHDgn6s5AM5EvK8eOSb3mkR6kxOODUH6aidhdcDsRCECQQDPY4N7g7oCdwK/ +bkfAxheLh49gnFUi8EsQ3QgssPTN1vhs7zAWt+9ggenMybOgnKk3SY7f+rNErCjc +ZdINwYWTAkEAwtyTEboWOeArCxTJaT+1kZaon2GmDt5K7yu9+kly4r046bly7atD +GKRvttvgdDo59np6lIw/t5+dCmT7LiTvqQJAbW7fdI+f2akfBBCXQDvHNNNFbv9P +VW5izfU0WRDPPMbQs/rK71IDuHMVAgD1Di1chVYFVF8ftX762MHJw4R4jQJBAKU4 +gTq2ncHU4Ko0pdInwrv/ElqRYUuaD89bN2nQfSjjaC5En74FSI7MXiydomLqO9tR +Xj417JC1NWJq3M7zYoECQEN1tIObc0bQzQ+CqwW7M4xt5zzVL/qTvNnwgXkidE6p +WPigAjslZa9gJgJ7V4dYTCSie8baL3IdU824jSzZ10Q= +-----END RSA PRIVATE KEY----- diff --git a/test/provider/files/nodes/couch1/couch1.crt b/test/provider/files/nodes/couch1/couch1.crt new file mode 100644 index 0000000..74854e5 --- /dev/null +++ b/test/provider/files/nodes/couch1/couch1.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICoDCCAgmgAwIBAgIQKGOYsoZwiYJpbIxjvubpATANBgkqhkiG9w0BAQsFADBK +MRAwDgYDVQQKDAdFeGFtcGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3Jn +MRgwFgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0EwIBcNMTYwOTI4MDAwMDAwWhgPMjEx +NjA5MjgwMDAwMDBaMB0xGzAZBgNVBAMMEmNvdWNoMS5leGFtcGxlLm9yZzCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA55qmjlgU/9HCi/Ki9O2CNHbF38CCpVUd +/7dJaGVIxBgZI8a27TWNFHk3g+JPu+NCx9TT9bJYobnHe4UHnhhgjk1o6Z0Z9ele +nStVDqYwde3rG7uxwt6qmPrYVMYmujPDAZq3UF8ECl0t1nJtEKsaIu0AbCEqZDFC +sNhOnJflxn0CAwEAAaOBsTCBrjAdBgNVHQ4EFgQUjGqJowOEXygZJRnEUMTwlt1q +Rx0wNQYDVR0RBC4wLIIQY291Y2gxLmV4YW1wbGUuaYISY291Y2gxLmV4YW1wbGUu +b3JnhwQKBQUCMAsGA1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwCQYDVR0TBAIwADAfBgNVHSMEGDAWgBS1rAPK+7FvHPO5xgOCUHEm2JWR +gTANBgkqhkiG9w0BAQsFAAOBgQCNPzztMWdzDkTZHAxCv4ekZs+iiBR5R7hn92Xh +WJ7Gm9GtD+w8f6tYACdj7C+/+WGjuvl2xqN7qMv2FM1I/cQMuJumXXRjyJVKBYVb +VInoyy+0eFBAzLdx1CRY0zFytJfigBYD3Wq4HY3Dsm0W97xw8v7slYZ2fE0mEFqo +bXgDPg== +-----END CERTIFICATE----- diff --git a/test/provider/files/nodes/couch1/couch1.key b/test/provider/files/nodes/couch1/couch1.key new file mode 100644 index 0000000..47403ee --- /dev/null +++ b/test/provider/files/nodes/couch1/couch1.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDnmqaOWBT/0cKL8qL07YI0dsXfwIKlVR3/t0loZUjEGBkjxrbt +NY0UeTeD4k+740LH1NP1slihucd7hQeeGGCOTWjpnRn16V6dK1UOpjB17esbu7HC +3qqY+thUxia6M8MBmrdQXwQKXS3Wcm0Qqxoi7QBsISpkMUKw2E6cl+XGfQIDAQAB +AoGANNxZU3fLIzBPBP4WL2zeIPdS5mTb7LxmomzE9mzXlNojMsUyDyX/00JvZ0yK +Ako2fcGXtyZDkHYEj66nNHA/6QueOiXmehC12GuElwWJirnZfVlxGg57FGZ6da39 +hBH2Ip/qnh7cE6j8jPz52MFhb0x5qN9TSaD4V6OS33thNgECQQD1bKkF6OggRwI4 +htBM5IESL9uQtjbeCa6QhFhNQjp0ZwXwp+5mNOBcja4FUReLtcsYc97Z4BCBXEsY +U+xVdlTBAkEA8ZWI2KCJ8tpz3qCbWOkZHhBbrBZbXrSDkHHU08Alh0ERo3eB2STU +r2VrzB1722jZhrILQlvNOwICjiH/8NI0vQJAM0gXMVLvXf84aZNR5x9AEQrK+Dv6 +zv566VueD9as3DHCvfx5BgY6c1xvZlEBeIHuBBgCEsiM6lrcniK7GUh2gQJBANDf +YBUkIIFnnNz0cbwatcvHiusr3U3xtvqxYLjAHfJmMPDrx8nNzVHk16IAL/FRIxoR +YCi8pKILJ9hpzxcRN+UCQQDML4DU0l7oYN2KMycOFuNub21UGuT3z164Fpmr7kbc +cPz84rHUKyzGjKpDWL2DL1po/HT4qBLxsRA4n0A0U4Dt +-----END RSA PRIVATE KEY----- diff --git a/test/provider/files/nodes/couch2/couch2.crt b/test/provider/files/nodes/couch2/couch2.crt new file mode 100644 index 0000000..79e6d21 --- /dev/null +++ b/test/provider/files/nodes/couch2/couch2.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICoTCCAgqgAwIBAgIRAPmeg9lEoavLgBFgmRYVgUMwDQYJKoZIhvcNAQELBQAw +SjEQMA4GA1UECgwHRXhhbXBsZTEcMBoGA1UECwwTaHR0cHM6Ly9leGFtcGxlLm9y +ZzEYMBYGA1UEAwwPRXhhbXBsZSBSb290IENBMCAXDTE2MDkyODAwMDAwMFoYDzIx +MTYwOTI4MDAwMDAwWjAdMRswGQYDVQQDDBJjb3VjaDIuZXhhbXBsZS5vcmcwgZ8w +DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALfbx4JzrvYWinH3ZVg/tTMySiqNT5f2 +YHognrH/P9Ukys4nGA11gQbMpyQfXO/3dE592ReTnp2IVmJ2oVAKNkdQnbjk2Xx1 +3/6/AuaASdy68PZAqiWadw5MjAf0y6W0iDDqOQiXH+vEswK4HfP5rsfrsnKCh6U7 +drj+erU4JfVTAgMBAAGjgbEwga4wHQYDVR0OBBYEFLakLgOpq1j5EDHAHNFSKtjW +NA76MDUGA1UdEQQuMCyCEGNvdWNoMi5leGFtcGxlLmmCEmNvdWNoMi5leGFtcGxl +Lm9yZ4cESS1XCzALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG +AQUFBwMCMAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAUtawDyvuxbxzzucYDglBxJtiV +kYEwDQYJKoZIhvcNAQELBQADgYEAlnRbf94YpFnqwLdCk3VBWEeGtwj2kEEJJjlC +R1WouYwz9tChUB8H26judnPsTafDN3f3gx3yAqooFXPXz8P0Gm5+XFPWDiL+GtIu +UngRlT6qunepEQ2BVlNuZO9Vd9ov5bD+rEAULASXkiJQhdaPW1Z+QjuMT+1Yykf5 +5zHts90= +-----END CERTIFICATE----- diff --git a/test/provider/files/nodes/couch2/couch2.key b/test/provider/files/nodes/couch2/couch2.key new file mode 100644 index 0000000..fa0106f --- /dev/null +++ b/test/provider/files/nodes/couch2/couch2.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC328eCc672Fopx92VYP7UzMkoqjU+X9mB6IJ6x/z/VJMrOJxgN +dYEGzKckH1zv93ROfdkXk56diFZidqFQCjZHUJ245Nl8dd/+vwLmgEncuvD2QKol +mncOTIwH9MultIgw6jkIlx/rxLMCuB3z+a7H67JygoelO3a4/nq1OCX1UwIDAQAB +AoGAV9ciD5pTefE0/dQT0EDHwokBVCklYNXuLAsPprzrc1rbpfiZjjyYg3YdWK2/ +Skqwf5uyr4fwnRT5KJvC4Cmw2ju89qOQ1+WXprlM1o0Z4z5dj+LC8S4WlFZuHGB+ ++F5uKgEyO3zEvT5LF9V00IonsaXXpYeJlK0tXOA2ZXkmaXECQQDpCpQW+UZeNQaV +NBXIN7DXzdfsTfO7U4Sf7VB5hMlOwM75XgtXw7ekh0UHohsO2yzINj7QM7pJ5I0m +1FriJGHbAkEAyfjJBAFk4V/cmBcCzlKUV59w+GW+sgzHx4gnBbXu/JLIVaoAQZtS +kq6GHMMPiJK9KbRov9meaOI0wfsoRxw/6QJAMrIzbyABV+MvMGwpROoglYHZNDXt +DNZpZqUouZbSeEhnfkYgL5KLM8adlMCGJGA3yMJMPdzS7NpEfqr5rnJ9uwJBAJ1M +Tjn5X/kK8MHewgewVun7Oj+q9h6zR3CGAGY5MHyzUKUu9m4iKugkVjzWSiXCquJt +KFuqf+4NpqshEVh4jukCQEUVw/a4QvmVkQmsrde64fXm4EaELY+Ri48ibBLlc/qM +wfeyE8m2TEeXx6BPOBOFLeL6kYJkt3uDcXuQnmdsV5c= +-----END RSA PRIVATE KEY----- diff --git a/test/provider/files/nodes/ns1/ns1.crt b/test/provider/files/nodes/ns1/ns1.crt new file mode 100644 index 0000000..2e4b38a --- /dev/null +++ b/test/provider/files/nodes/ns1/ns1.crt @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIClzCCAgCgAwIBAgIQEqzDOUwybJEK/8K5jRJNqTANBgkqhkiG9w0BAQsFADBK +MRAwDgYDVQQKDAdFeGFtcGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3Jn +MRgwFgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0EwIBcNMTYwOTI4MDAwMDAwWhgPMjEx +NjA5MjgwMDAwMDBaMBoxGDAWBgNVBAMMD25zMS5leGFtcGxlLm9yZzCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEAxrvUSmtjXIvzxEAZlh/rJdqyyI706+DvNeha +BqtCHhT+iZ2+IdRXq2EhwoUWsTDBN0iw6wBk7qlvxGcpq782iCregwjQKJgMFHCs +UaTSnuQd9Apv7YyeopcXcD1d/Ee3wMyDUNH8rKksyi2gZfmn6HXsHjCQ8iEebwmD +yVAXg7cCAwEAAaOBqzCBqDAdBgNVHQ4EFgQUJ5qb5nFE1xTSCgvvnbWH6qO32cYw +LwYDVR0RBCgwJoINbnMxLmV4YW1wbGUuaYIPbnMxLmV4YW1wbGUub3JnhwQBAQEB +MAsGA1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwCQYD +VR0TBAIwADAfBgNVHSMEGDAWgBS1rAPK+7FvHPO5xgOCUHEm2JWRgTANBgkqhkiG +9w0BAQsFAAOBgQAdwsJ6DhUj5IfsK/esWeCOgCdqAhdW61jABKAv0Y6BH5XqItEG +fsI4INYro2CzgEKVMdLzuK1dEHB17j2gYFowvAJ34KtPlXf3Ne++1Qbc5oJDCnxi +l3PQo4rHQC8V7sKnB/cEiQ15SG16u3B+kMwj8lU+QShh5osMbEG/n+Qs5A== +-----END CERTIFICATE----- diff --git a/test/provider/files/nodes/ns1/ns1.key b/test/provider/files/nodes/ns1/ns1.key new file mode 100644 index 0000000..c85cf7e --- /dev/null +++ b/test/provider/files/nodes/ns1/ns1.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDGu9RKa2Nci/PEQBmWH+sl2rLIjvTr4O816FoGq0IeFP6Jnb4h +1FerYSHChRaxMME3SLDrAGTuqW/EZymrvzaIKt6DCNAomAwUcKxRpNKe5B30Cm/t +jJ6ilxdwPV38R7fAzINQ0fysqSzKLaBl+afodeweMJDyIR5vCYPJUBeDtwIDAQAB +AoGAXEdVMOUicxOtMiBNgS77Ak3FnGj9AxYkHRTx0IzvG4bGFmJ/qbeuqa5lfaxM +uCQaY7BGLii1tThJ5Jm+eLhF+iyXoISzsepzmHflcjNXdu0W44Gb+hQz96DagAhm +dwMHAhquu7AQZ8iTIXzwuSp8B5WcyruPapoX4H+6AgqVlAECQQDurdhbFYN1Qlef +EU9htGaazx+DjSvncmcgFu7gRqUuKX2aPpA1yMbHZ7jTAKeqNBUgks4EwecP3Fxc +RZrMIsIBAkEA1SfiFr1936b2CUnT4KlIwwEcWTGM30tPM7fJ8oJk13eW+pIpLc4X +bFLvauAH15CLjYHkXMBUFVnXdMyhz9TVtwJABMVY08lETW28DqPr8EoI2wNU3+5M +eF3jDdMnhzgiSR/vMMwbWdffkVDTcvRKZa6Q1YvZrmKp2blP51BE3du8AQJARmE3 +1nhUwm73V9PHoKtkefa47H5e3C+ahCIQDQGe2EIFWNC/xf8BXuP3Z1t3W2a/nUah +JzrdyHr0l/0lBGFq+wJBAL59z6MACU4iLsULD5euJNDMtefeK2CvEhKsXJ7UlZ+q +a5eOtsjwEwtV4hSbd5yNR3FpW5grxxytnfVj7bYU584= +-----END RSA PRIVATE KEY----- diff --git a/test/provider/files/nodes/ns2/ns2.crt b/test/provider/files/nodes/ns2/ns2.crt new file mode 100644 index 0000000..3003781 --- /dev/null +++ b/test/provider/files/nodes/ns2/ns2.crt @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICmDCCAgGgAwIBAgIRANJEIfGpsriEvDXwPskuNbQwDQYJKoZIhvcNAQELBQAw +SjEQMA4GA1UECgwHRXhhbXBsZTEcMBoGA1UECwwTaHR0cHM6Ly9leGFtcGxlLm9y +ZzEYMBYGA1UEAwwPRXhhbXBsZSBSb290IENBMCAXDTE2MDkyODAwMDAwMFoYDzIx +MTYwOTI4MDAwMDAwWjAaMRgwFgYDVQQDDA9uczIuZXhhbXBsZS5vcmcwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAJ8JECo3emqgpKCUESglWAfHljSCA0zMT8NN +zXvyeLTXwFLZvtDPBN6DcN1YBZN0tJHq222flkzHO+xyZs/qaDsTc3Y208FF7Fj2 +W8S/oP/bKvnOGI05jocmwR8Oso8KmzgjdrnEfGOVXFDnJH+oN2UeYxwph+ddJzUQ +JVVmc+xZAgMBAAGjgaswgagwHQYDVR0OBBYEFPwzZRN6d8nHri5XyzYSEcwnRtZ0 +MC8GA1UdEQQoMCaCDW5zMi5leGFtcGxlLmmCD25zMi5leGFtcGxlLm9yZ4cEAQEB +AjALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAkG +A1UdEwQCMAAwHwYDVR0jBBgwFoAUtawDyvuxbxzzucYDglBxJtiVkYEwDQYJKoZI +hvcNAQELBQADgYEAmMBg3ETAY8EfKrGOmghVjIKZLXDDdE8BDoJuIFvn0A1y52aq +YZqsv3R5oYJkrejwh/raE3opNOuTT4LdHE4W09cwwctz4TYOS2Xfy713qfp1QUVo +9q6fFwLbICpScECtk8e5c7JPpOi7utPDbX37gyE9VZ9varmBekQ//bflhSI= +-----END CERTIFICATE----- diff --git a/test/provider/files/nodes/ns2/ns2.key b/test/provider/files/nodes/ns2/ns2.key new file mode 100644 index 0000000..b74914c --- /dev/null +++ b/test/provider/files/nodes/ns2/ns2.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQCfCRAqN3pqoKSglBEoJVgHx5Y0ggNMzE/DTc178ni018BS2b7Q +zwTeg3DdWAWTdLSR6tttn5ZMxzvscmbP6mg7E3N2NtPBRexY9lvEv6D/2yr5zhiN +OY6HJsEfDrKPCps4I3a5xHxjlVxQ5yR/qDdlHmMcKYfnXSc1ECVVZnPsWQIDAQAB +AoGABHSQi146g7o0YntDb8h8CtvAjYAG76PZqDMJyqskToysyqVm/xqNnF46Tzkk +Dtl6JYxa0VtjLot2Vk1uK+z5NoMoN6J9pQkH6zVVAh5FnQdTWKCRSBLiC2FqSh3z +cbgn1ZwheeUo/Vc0zvJm4RGQ5gMGjBZEU89CHutzgkSxMzECQQDScrFtt+AWemCN +BlHSYJxcX6d7FS0ks2WVka2sXXj/1KolOfTV8NFbBhtagBxR7Orov9L6VtFfXrQK +tLBi71aTAkEAwXWA7BFZSGkDZEiym9wYEfvZ3Z9zlEghpHkhCW9Yd9/22hyyKLR+ +rgu69T3Wudnfukz19+sUYDumul1xHc444wJAFyif9d8CPfcBoQNNBcWz70Zne9f8 +u8kyKJ97aThwFFcm0inqk5CIuWeWowLuGuXjg/F4Gixrpf8Z+QOhVYHZGQJBALxO +1B71BCMnlNWYrcJoikV3EKpY+vfq/lRKU44Lg+Grb2z/YaudhXGEmYb9mnVtTgjZ +wNKBUGQbrD7bla+dfGECQQCDkDXPqK1UDxM0YIYG+gxW3BQr/q/3XUZs/2/X7PuU +aa0psnl5OcS3RkomavWKVXUpnwG3CSHBRQQ5xFNCPVNG +-----END RSA PRIVATE KEY----- diff --git a/test/provider/files/nodes/pcouch1/pcouch1.crt b/test/provider/files/nodes/pcouch1/pcouch1.crt new file mode 100644 index 0000000..5cbf7c3 --- /dev/null +++ b/test/provider/files/nodes/pcouch1/pcouch1.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICozCCAgygAwIBAgIQd+WMYQcsfEJ7tKGgTQPhmzANBgkqhkiG9w0BAQsFADBK +MRAwDgYDVQQKDAdFeGFtcGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3Jn +MRgwFgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0EwIBcNMTYwOTI4MDAwMDAwWhgPMjEx +NjA5MjgwMDAwMDBaMB4xHDAaBgNVBAMME3Bjb3VjaDEuZXhhbXBsZS5vcmcwgZ8w +DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMDRNMvBNsLhOchxLHX6S+kTMZSRBbUD +bVV4QnVzXNlhOld42QcWh0sETjpBISsJe9se3qrbwBfQKCzbguJYCnLOa8q8sJhk +AM+VgYoSESAFRlWeu0CDXGH3FaLVHso7OSNFliq39h+LiPPAcCkli55rHwaTqWY/ +wrN8nU8CA0ynAgMBAAGjgbMwgbAwHQYDVR0OBBYEFN9u3kcthGftJHfo89tT+a/9 +ZVNoMDcGA1UdEQQwMC6CEXBjb3VjaDEuZXhhbXBsZS5pghNwY291Y2gxLmV4YW1w +bGUub3JnhwQLAAACMAsGA1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI +KwYBBQUHAwIwCQYDVR0TBAIwADAfBgNVHSMEGDAWgBS1rAPK+7FvHPO5xgOCUHEm +2JWRgTANBgkqhkiG9w0BAQsFAAOBgQBYajPrmFVBzJXxwBe7DN5giQ9VCM71XMVj +OMN2fAnrHKgozgnRxn2ZtyxI3vvMMal/n2ZUax0ku0XdFXJouZUhF3PNtu5fpFrJ +fngJnUeMY4bHneqG3iR4We6trkVIn1/b9CA8qqXsChF33LGQptCGnGe7x4zalBeX +b7xeGhepdw== +-----END CERTIFICATE----- diff --git a/test/provider/files/nodes/pcouch1/pcouch1.key b/test/provider/files/nodes/pcouch1/pcouch1.key new file mode 100644 index 0000000..36e3d44 --- /dev/null +++ b/test/provider/files/nodes/pcouch1/pcouch1.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDA0TTLwTbC4TnIcSx1+kvpEzGUkQW1A21VeEJ1c1zZYTpXeNkH +FodLBE46QSErCXvbHt6q28AX0Cgs24LiWApyzmvKvLCYZADPlYGKEhEgBUZVnrtA +g1xh9xWi1R7KOzkjRZYqt/Yfi4jzwHApJYueax8Gk6lmP8KzfJ1PAgNMpwIDAQAB +AoGAf5ZvnxwdBltOhwoMZ4zWSkY/GpXT9vFrmZDYOSu7FsS1fEglJAGOSN9yfC24 +que9o0MMCHcc5yUAUJ54PxoO3rxFc9WRJFKT7jnPabGWjAwynFCEW/okM4gV6KBc +dw6jmQFLAAC2jRyUZhGP4zuDo9+P4zJ3D84J4mW8wh8MIsECQQD/SHehLFj/feLF +8kpXAF3bvNB+DK8iPDfbzgPoxhV3yX2/Jai7xhapiRLqekA66EVs4kwmJqlZdJAF +D8nZLHHXAkEAwVvUcQZzP5RJoogh7+LhbuiAC1HrY5qciIdJ+VbI+z3TSCYATeuw +IHVufX6u6jIyqYPqttbYfydgEmlhn4dBsQJBAPyOcDQXENFrdKBLLXrXVQQgz8/0 +sotXMhgWwE1ZM0H4KJykIEPtHNyLTRiG6+abhpvLYnTYCPEEXbt0PEjMLK8CQE4U +JtT9JcymtJVNI2ca1q1SdWIc0lCGPm9jMhvdT4skjAy2S6krYxO4V8WVQkyPuKV6 +/2yVlRbDb6f/pcwlcgECQQDx+bypDnhmlINXQIy6fktRDJsPrBNcRyrlrxcRNPMU +Qv/AcFVYpxhwf8Jg688RKcHhk00Ga1pkF6gKQooFTETR +-----END RSA PRIVATE KEY----- diff --git a/test/provider/files/nodes/pweb1/pweb1.crt b/test/provider/files/nodes/pweb1/pweb1.crt new file mode 100644 index 0000000..7ec04f5 --- /dev/null +++ b/test/provider/files/nodes/pweb1/pweb1.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC0TCCAjqgAwIBAgIRALfSNQhp6ztK+6EupzNn0CowDQYJKoZIhvcNAQELBQAw +SjEQMA4GA1UECgwHRXhhbXBsZTEcMBoGA1UECwwTaHR0cHM6Ly9leGFtcGxlLm9y +ZzEYMBYGA1UEAwwPRXhhbXBsZSBSb290IENBMCAXDTE2MDkyODAwMDAwMFoYDzIx +MTYwOTI4MDAwMDAwWjAcMRowGAYDVQQDDBFwd2ViMS5leGFtcGxlLm9yZzCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxfB90ytpi5oTo6vPq99HDg3Ci13wiegj +bBgqHMAo6jXoxT/3nq+1KE1ThqpNIvpuc6EgWCh5jfzK1hk8aFfWkXWLiPmGTelo +I84FKUa4zigQdJsaCU8aUj8oxT9eO0oXGR9Hv9Es8KVe5InFYBz54v/SwYbrunXS +vzXH1EpWIq0CAwEAAaOB4jCB3zAdBgNVHQ4EFgQUeAGn9QLkE31Y8tIC4H/XKLT/ +hDEwZgYDVR0RBF8wXYIPYXBpLmV4YW1wbGUub3JnggtleGFtcGxlLm9yZ4ITbmlj +a255bS5leGFtcGxlLm9yZ4IPcHdlYjEuZXhhbXBsZS5pghFwd2ViMS5leGFtcGxl +Lm9yZ4cECwAAATALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG +AQUFBwMCMAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAUtawDyvuxbxzzucYDglBxJtiV +kYEwDQYJKoZIhvcNAQELBQADgYEAs9F+A9JOtU+7UHhhmf2DVFFGb+n7iTOaUzDv +/1nn3OyD1hY3kMsJWcZUuAiIGB0ZBNzStalFADNXy8QDI9xRwC1bt+if5I3XK8Ag +563xBpkSXtVp3IY7YHmxJu3j/6R/HOa3xIAkmpEryJ+r8XZgOF+gim+HmDOjpBOI +dKfZcFk= +-----END CERTIFICATE----- diff --git a/test/provider/files/nodes/pweb1/pweb1.key b/test/provider/files/nodes/pweb1/pweb1.key new file mode 100644 index 0000000..356ac6f --- /dev/null +++ b/test/provider/files/nodes/pweb1/pweb1.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDF8H3TK2mLmhOjq8+r30cODcKLXfCJ6CNsGCocwCjqNejFP/ee +r7UoTVOGqk0i+m5zoSBYKHmN/MrWGTxoV9aRdYuI+YZN6WgjzgUpRrjOKBB0mxoJ +TxpSPyjFP147ShcZH0e/0SzwpV7kicVgHPni/9LBhuu6ddK/NcfUSlYirQIDAQAB +AoGADP18dHhb4+KHuW0UIvZzRlPW2aifmZ1XfceUM/DUfpJtJUzOZmann+57NdJF +X69JwmLnqYF2gL//W8+qLDrfhOzC5Qr3m4lFIvACmmh0Aj3u47k6W4pJryAp1B0f +Khiql1TZ006EzVRf+2h0pdVK2C1vGOEyhBMagytHXLFZCyECQQDicOqK25JL0n9J +t7+ZviZknLLAW15+P3I7oehZlUtN9CleBA0m/DrMX8oepKoPomK0tXs7pT6ZGZza ++8IxD88pAkEA38cfXPNRjm0YsXJZuIBblHt0tU0dajo0Ac2tKih0qtltnZoc8usj +p0ci9qRLg1Tp7Tu7DopSVtNOpphoeySb5QJBALGU5Bspvz1/QxvI4pXrrahRy01X +Wm+fyjJB8znt/zSPOrHkc3wTavlEVfpaIJRKQSZ+/Ln2CXV/xKdnsQ9Q2qECQQCk +KsHAgCTR1wlpjJlzuH73BEcPht5QgxiKRiiGqB1HBbHcECays3x5iL+Gr+tSEuZ2 +iv5k4WccmXK211K3HJldAkBrvf1NueFdH1OIAn7v1HFSXTzy8xPjUKLI6Ez6y2O5 +A6k4jqXnyCko2rJsae90xe5F5E2eW8W2h9+elBmGYTtJ +-----END RSA PRIVATE KEY----- diff --git a/test/provider/files/nodes/vpn1/vpn1.crt b/test/provider/files/nodes/vpn1/vpn1.crt new file mode 100644 index 0000000..41f5c13 --- /dev/null +++ b/test/provider/files/nodes/vpn1/vpn1.crt @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICmzCCAgSgAwIBAgIRANBnTjUGZeOrBzKeKeuhf+8wDQYJKoZIhvcNAQELBQAw +SjEQMA4GA1UECgwHRXhhbXBsZTEcMBoGA1UECwwTaHR0cHM6Ly9leGFtcGxlLm9y +ZzEYMBYGA1UEAwwPRXhhbXBsZSBSb290IENBMCAXDTE2MDkyODAwMDAwMFoYDzIx +MTYwOTI4MDAwMDAwWjAbMRkwFwYDVQQDDBB2cG4xLmV4YW1wbGUub3JnMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDADq6rtpObpScLStIREnPTxwOpqc71cPUa +OYy4C9gZIcqTxYcgAv8UF5DdV8dDBMLC2s2XdwcyGjDg2ElkVaKpqaGfPMKPnQ5u +ALtGQy+DFyQhfYRxUtlC3EATNLe3JJHTlRNI2VPzcVyOHpBBPa1PZuq/peq7HQ3V +hznHeTDZxQIDAQABo4GtMIGqMB0GA1UdDgQWBBSxyVFLKJYHJBbjl6hQePY6LNmS +nzAxBgNVHREEKjAogg52cG4xLmV4YW1wbGUuaYIQdnBuMS5leGFtcGxlLm9yZ4cE +CgUFAzALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAUtawDyvuxbxzzucYDglBxJtiVkYEwDQYJ +KoZIhvcNAQELBQADgYEApW4Vz9mqxw975Mw32S9FwrpkufC8+sGir4/xCP8Q1xg4 +pg8SvSaMdoPHKPkevHx8I3QY3H+l2XkZEtArgv8jjbpvPQkIvHVx9iUMRXWwxlTI +B5d1V4QCzow829JFyy8giRANK+dZF5pp4+G0+f0IJgQM52U+y6XKIL8DZvfarSQ= +-----END CERTIFICATE----- diff --git a/test/provider/files/nodes/vpn1/vpn1.key b/test/provider/files/nodes/vpn1/vpn1.key new file mode 100644 index 0000000..1ea72ad --- /dev/null +++ b/test/provider/files/nodes/vpn1/vpn1.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDADq6rtpObpScLStIREnPTxwOpqc71cPUaOYy4C9gZIcqTxYcg +Av8UF5DdV8dDBMLC2s2XdwcyGjDg2ElkVaKpqaGfPMKPnQ5uALtGQy+DFyQhfYRx +UtlC3EATNLe3JJHTlRNI2VPzcVyOHpBBPa1PZuq/peq7HQ3VhznHeTDZxQIDAQAB +AoGAF8x59os8RUg0y2BtIXJw6eg6WvbQz3c82BATkObe01ZtnNwYP24/n4TADb2H +0pUvcSfd3AwC10GJlwMWLRmzey07cbDSm77YSMoDNFuLNxxiSDJsR7kSeGjzTz92 +YEC59cNus8j5ExPCGnx4OVz264dpUyvbJKLbcX6hwO3cFc0CQQD9HvDe0kRVMug2 +2I1IUeF8QEt9B0lauq/mCapY1AWEJ2Y0rzg3nListOaHetTdNuLVsKc4UyGrF62D +MNK97UnXAkEAwj3ucfpdE7eZAiee4vwP4sg0C4HJEsAdhkhO/E6hKxRijTvVoWep +1Gkq/gwxO7qoAnaBzfLgBo840LUWKIXdwwJBAN2ykPQIpJMe8GbBSxVxqhZC1htf +G2+dHd1Uz9/XbDFwtMMmSQ3kdZfHJja5beGHZiwV+pCJt258YZwLUjnJsKcCQQCM +K17vlyklul7LJEZPLHBWSfzstNqiEkr8BSAiiKdbTBmWK7CNCh6O7tmcfLXmkVr+ +dABV20d41E++pH75/Sg7AkEA4yk7pPDe6A4IdWz5BEXOENG47qnQGbRgrtD8svee +J9yAujIm84up14Fv8WObuyHR7xVjhOhLBKo3cVbfnwY3Vg== +-----END RSA PRIVATE KEY----- diff --git a/test/provider/files/nodes/web1/web1.crt b/test/provider/files/nodes/web1/web1.crt new file mode 100644 index 0000000..915a84e --- /dev/null +++ b/test/provider/files/nodes/web1/web1.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIICzjCCAjegAwIBAgIRAMoUUP0EOpqOEeOkAm6kbN0wDQYJKoZIhvcNAQELBQAw +SjEQMA4GA1UECgwHRXhhbXBsZTEcMBoGA1UECwwTaHR0cHM6Ly9leGFtcGxlLm9y +ZzEYMBYGA1UEAwwPRXhhbXBsZSBSb290IENBMCAXDTE2MDkyODAwMDAwMFoYDzIx +MTYwOTI4MDAwMDAwWjAbMRkwFwYDVQQDDBB3ZWIxLmV4YW1wbGUub3JnMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDS0Q+RQuBrFcVjaG44p6JBGYBtTeS1hpID +1yosGMftjVXnWYF8zi8XoNZr2Cp4g/BHb4OyC43C3f4sPx6qIU/Qt7fVfwKdV+A9 +c9PcvUE/RLhZMlzTu5UwBOWNOndQ2clkap/dyhfiRt0aAExh9IzfWyQJSDUiH9Ys +5jBE+5jMowIDAQABo4HgMIHdMB0GA1UdDgQWBBSSToilj03s0BIzdNb4p5WhAFpa +LDBkBgNVHREEXTBbgg9hcGkuZXhhbXBsZS5vcmeCC2V4YW1wbGUub3JnghNuaWNr +bnltLmV4YW1wbGUub3Jngg53ZWIxLmV4YW1wbGUuaYIQd2ViMS5leGFtcGxlLm9y +Z4cEBgYHBzALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF +BwMCMAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAUtawDyvuxbxzzucYDglBxJtiVkYEw +DQYJKoZIhvcNAQELBQADgYEAdcbYb1C0+thmBXyN7xcoSGvbHoIVbXvBYKi14hxT +6P/ZnI0zAQVWHhOliXXqTOGRCc5GWFUp6MufZtWd/yHhkxf1cCjSfnvqVAv7rtx5 +crECppsXCVFHyuLXvNfAS0y+FuqmK2pBZUdVXv1bXSYNN5ZcMwFacI0UGoOwN/65 +LOc= +-----END CERTIFICATE----- diff --git a/test/provider/files/nodes/web1/web1.key b/test/provider/files/nodes/web1/web1.key new file mode 100644 index 0000000..ecb2485 --- /dev/null +++ b/test/provider/files/nodes/web1/web1.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDS0Q+RQuBrFcVjaG44p6JBGYBtTeS1hpID1yosGMftjVXnWYF8 +zi8XoNZr2Cp4g/BHb4OyC43C3f4sPx6qIU/Qt7fVfwKdV+A9c9PcvUE/RLhZMlzT +u5UwBOWNOndQ2clkap/dyhfiRt0aAExh9IzfWyQJSDUiH9Ys5jBE+5jMowIDAQAB +AoGBAKEljXDMXh99FNVYDmjgOvboN3NWB2164EJvRp1OlATR9MhTctekA/tbxovJ +QS2+LP1uEI0Yp9Q9PP01gospy4erJTWdDmzKotrA3DSjw6Gr5EW1rVes93eM+Uqg +u70ETfZVwfp7+iB7OjUWZHrylt6ISPs2rbW8QSHPr9L4NUsBAkEA/2w/R+ph4XtN +D5j4PWtr6pRzcdno3jkWF3xrx6YM+tnN0qkuXoIEGsGWeGPPiGh12Ys2zwQwmTbA +CoBFPHeMKwJBANNLArQCBjA+UcCWMZoe5NoRp/hXBjPekmxiYyJ+XnGQCOEZWf0E +rMKUHNVe9TzfubhW/Cydiwag6CcQjCymbWkCQQDwhO09+i67llEle+VeaNZRKgNf +1VPcVqM/8HDJqsqUOR8A3UEFy6azz1GzAkH98GfxN4+f9xEQZacG/Gy2GNjLAkBj +Duash8po8b6YIJIOpG88QUzTY9E3niBdid7aPA6BBTr0dVM4COoJqzC9Y/BrYqQK +ZVWCgTW9nNBaCCr/f+MJAkEAxPVu3x0OL3WILkKhR37zAaFRoqWH4JZPM9LkKaYZ +Br7RdR3kBFzDAbxe7InXJ5/ZtWh4wFsqPtccHLuT4JcllA== +-----END RSA PRIVATE KEY----- diff --git a/test/provider/provider.json b/test/provider/provider.json index d0f8abf..f7c1df0 100644 --- a/test/provider/provider.json +++ b/test/provider/provider.json @@ -2,12 +2,12 @@  // General service provider configuration.  //  { -  "domain": "bitmask.net", +  "domain": "example.org",    "name": { -    "en": "Bitmask" +    "en": "Example"    },    "description": { -    "en": "A demonstration service provider using the LEAP platform" +    "en": "CI test"    },    "languages": ["en"],    "default_language": "en", diff --git a/test/provider/secrets.json b/test/provider/secrets.json deleted file mode 100644 index ffadc24..0000000 --- a/test/provider/secrets.json +++ /dev/null @@ -1,54 +0,0 @@ -{ -  "default": { -    "api_monitor_auth_token": "UrmuBDZkA9XTsfaq4kpjbtshHY5daUxX", -    "couch_admin_password": "TDMmtYBmm4r5dI4VXPXnxXsKkLfFPEPR", -    "couch_admin_password_salt": "8b2db5d295e54bdef430aae96b955845", -    "couch_leap_mx_password": "YXhAyvm57XgwhIZNYqxF3g8ykzhkg4SF", -    "couch_leap_mx_password_salt": "ef432b612887112fd227de859ab78521", -    "couch_nickserver_password": "sjNIQ98ymFwaAHyIX4XJKraNmwdHgBw9", -    "couch_nickserver_password_salt": "7b932afd1c2ffc42763d340e4e8b2bcd", -    "couch_replication_password": "UZne4MrH5HzNAamMeYReHjW7LJLabDZJ", -    "couch_replication_password_salt": "341d5e378e3a1bffaa709dcca9bcd465", -    "couch_soledad_password": "wVLLKJCLzmbkPNfzhLbPy3gjWhhBMRhF", -    "couch_soledad_password_salt": "e40a4751078ffa0f364a77a486d0dc4c", -    "couch_webapp_password": "LRQUHweyjIFnELw4sQT8pveEUqKhIxLU", -    "couch_webapp_password_salt": "fbb4fa950d30e524b10775c6aa712564", -    "nagios_test_password": "4XpCbaFbcAAcfPqAqMtXMdMpUWengLEk", -    "webapp_secret_token": "BzWmcgK4Xf7xgmkdYHZK2qKBM2YT2ffM" -  }, -  "local": { -    "api_monitor_auth_token": "BUKNpTd9CPWcebeIXcSrmUmcXZZw3HEz", -    "couch_admin_password": "mw2yxDQWw2HzTn5cIkBVnJhZJ5VXVEgZ", -    "couch_admin_password_salt": "bbacf42821cee0af5a2fd638d014f939", -    "couch_leap_mx_password": "Ray9PHuEUKscNQsIenpsfgbM2u2WBzPq", -    "couch_leap_mx_password_salt": "d0dc07939c3f45a57954343f0e5fa13a", -    "couch_nickserver_password": "pbXQcHXQ5cR9xwk9xsAwMCQ8mfLpvMmE", -    "couch_nickserver_password_salt": "70cbc22a8603732bb6161f6e978d4abe", -    "couch_replication_password": "aDgQI87unwHqkJWPxchayQpf7taUPTYe", -    "couch_replication_password_salt": "6faaec5dc8c0ac5db9da91e01fc379a8", -    "couch_soledad_password": "uEN8sfF3xXbhHg2WjpCVQyUy7LrkfTnA", -    "couch_soledad_password_salt": "0db6d77f631df372bacc63dddea89e55", -    "couch_webapp_password": "RT7D7KTjzuVdXXs5HDYTIMpdDFfJKeZu", -    "couch_webapp_password_salt": "d8a7fb6c2f258137a4946ccb931d4e53", -    "nagios_test_password": "FfbLyjPIQUBDvnHtVNCwHZsZ9UYfZdqa", -    "scramblesuit_password_vpn1": "GJ2TSRLYKJLVAU2JKNNEIYSDKBKEGZ2R", -    "scramblesuit_port_vpn1": 31531 -  }, -  "production": { -    "api_monitor_auth_token": "TFkfYQHp5AMJmSY27YrPngg7sk5DtvBB", -    "couch_admin_password": "Hqu7IhKmFHVpHU9pgTHffQYzh7ZWHc5B", -    "couch_admin_password_salt": "8e7865b9e5263d06e1f74aea3dd44dd2", -    "couch_leap_mx_password": "AMrrWcKnFbbhaBj4MxxgTFeHnNnHjQay", -    "couch_leap_mx_password_salt": "2960d63958d067654be8c8d44131cd94", -    "couch_nickserver_password": "WPUfpbEHu4d5FHTWgrefgrYHaKCsQKYX", -    "couch_nickserver_password_salt": "983b745e70c31d811c876ca2c44d2ed0", -    "couch_replication_password": "ImeBu2DIA3gRbrHcqHgzsFBYHkwbeJQS", -    "couch_replication_password_salt": "54c09b42eb697972a4d7faabc9b4f2a6", -    "couch_soledad_password": "fNbUdYdErwnfFCKZUHLBaLmYfnxIjEbW", -    "couch_soledad_password_salt": "81cab24a5881de53ac79b4797b467d9f", -    "couch_webapp_password": "8tFtJ84rYa59ECjrMbVUQVCjp4YhhK7F", -    "couch_webapp_password_salt": "559eeeaa6ccd25169c9358c6c90eb24b", -    "nagios_test_password": "8cuLRjYICKFPe4YaKwk22EytRsjQKP9X", -    "webapp_secret_token": "4UQKXV94xqtFVkNSCqrphdNFJaPkQBx8" -  } -} diff --git a/test/provider/users/duck/duck_ssh.pub b/test/provider/users/duck/duck_ssh.pub deleted file mode 100644 index 591f614..0000000 --- a/test/provider/users/duck/duck_ssh.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDezTqhg/zFkGOQia0QRVRaDUmgdc73CEXadwVgYN41PITesjQinyT4hMOO8BJZVV70W1dWWCtT2j3JTFWLvhpgbjlYdiG676i9UpARvHTdt1FTAmlWfEfKvhDTqPByFyUooYfXBbpcZtqw+5ChP/lIjfWmfUVS3phTm5LzMetWTXY//dmuF+sHU9ZAWvrkYVI+IuJvb3mxv+CEbpS5s9yTS56qPP2czETbANoXsbBa29Ag+x22X/OiEUZ/mAfEuqBGh2uKH+9I/HhjorXSflYcwVhgA5P6QAhZEKU+B/PprIX/dF0HZLayJ6Y+0E7uUzNKxHupHmPI03VbxRO74K9t duck@home diff --git a/test/test_helper.rb b/test/test_helper.rb index f7ec6d9..cd856a3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,6 +7,7 @@ require 'gli'  require 'fileutils'  DEBUG = true +TEST  = true  module LeapCli::Commands    extend GLI::App @@ -19,7 +20,7 @@ class Minitest::Test    def initialize(*args)      super(*args) -    LeapCli::Bootstrap::setup([], test_provider_path) +    LeapCli::Bootstrap::setup([], provider_path)      LeapCli::Bootstrap::load_libraries(LeapCli::Commands)    end @@ -43,33 +44,56 @@ class Minitest::Test    end    def leap_bin(*args) -    `cd #{test_provider_path} && #{ruby_path} #{base_path}/bin/leap --no-color #{args.join ' '}` +    cmd = "cd #{provider_path} && PLATFORM_DIR=#{platform_path} #{base_path}/bin/leap --debug --yes --no-color #{args.join ' '}" +    `#{cmd}`    end -  def test_provider_path +  def leap_bin!(*args) +    output = leap_bin(*args) +    exit_code = $? +    assert_equal 0, exit_code, +      "The command `leap #{args.join(' ')}` should have exited 0 " + +      "(was #{exit_code}).\n" + +      "Output was: #{output}" +    output +  end + +  def provider_path      "#{base_path}/test/provider"    end +  # +  # for tests, we assume that the leap_platform code is +  # in a sister directory to leap_cli. +  # +  def platform_path +    ENV['PLATFORM_DIR'] || "#{base_path}/../leap_platform" +  end +    def cleanup_files(*args) -    Dir.chdir(test_provider_path) do +    Dir.chdir(provider_path) do        args.each do |file|          FileUtils.rm_r(file) if File.exist?(file)        end      end    end +  # +  # we no longer support ruby 1.8, but this might be useful in the future +  #    def with_multiple_rubies(&block) -    if ENV["RUBY"] -      ENV["RUBY"].split(',').each do |ruby| -        self.ruby_path = `which #{ruby}`.strip -        next unless ruby_path.chars.any? -        yield -      end -    else -      self.ruby_path = `which ruby`.strip -      yield -    end -    self.ruby_path = "" +    yield +  #  if ENV["RUBY"] +  #    ENV["RUBY"].split(',').each do |ruby| +  #      self.ruby_path = `which #{ruby}`.strip +  #      next unless ruby_path.chars.any? +  #      yield +  #    end +  #  else +  #    self.ruby_path = `which ruby`.strip +  #    yield +  #  end +  #  self.ruby_path = ""    end  end diff --git a/test/unit/command_line_test.rb b/test/unit/command_line_test.rb index 0b57ed0..393bcf2 100644 --- a/test/unit/command_line_test.rb +++ b/test/unit/command_line_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require_relative 'test_helper'  class CommandLineTest < Minitest::Test @@ -13,7 +13,7 @@ class CommandLineTest < Minitest::Test      with_multiple_rubies do        output = leap_bin('list')        assert_equal 0, $?, "list should exit 0" -      assert output =~ /ns1   dns/m +      assert output =~ /ns1  dns/m      end    end @@ -21,7 +21,8 @@ class CommandLineTest < Minitest::Test      cleanup_files('nodes/banana.json', 'files/nodes/banana')      output = leap_bin("node add banana tags:production "+        "services:openvpn ip_address:1.1.1.1 openvpn.gateway_address:2.2.2.2") -    assert_match /created nodes\/banana\.json/, output +    assert_match(/created nodes\/banana\.json/, output) +    cleanup_files('nodes/banana.json', 'files/nodes/banana')    end  end diff --git a/test/unit/config_object_list_test.rb b/test/unit/config_object_list_test.rb index 9b6e09f..042a742 100644 --- a/test/unit/config_object_list_test.rb +++ b/test/unit/config_object_list_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require_relative 'test_helper'  class ConfigObjectListTest < Minitest::Test @@ -9,7 +9,6 @@ class ConfigObjectListTest < Minitest::Test    end    def test_complex_node_search -    domain = provider.domain      nodes = manager.nodes['location.country_code' => 'US']      assert nodes.size != manager.nodes.size, 'should not return all nodes'      assert_equal 2, nodes.size, 'should be some nodes' diff --git a/test/unit/config_object_test.rb b/test/unit/config_object_test.rb index 54b45d1..88e11e6 100644 --- a/test/unit/config_object_test.rb +++ b/test/unit/config_object_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require_relative 'test_helper'  class ConfigObjectTest < Minitest::Test diff --git a/test/unit/quick_start_test.rb b/test/unit/quick_start_test.rb new file mode 100644 index 0000000..d26f9c8 --- /dev/null +++ b/test/unit/quick_start_test.rb @@ -0,0 +1,127 @@ +require_relative 'test_helper' + +# +# Runs all the commands in https://leap.se/quick-start +# + +Minitest.after_run { +  FileUtils.rm_r(QuickStartTest::TMP_PROVIDER) +} + +class QuickStartTest < Minitest::Test + +  # very reasonable to have ordered tests in this case, actually +  i_suck_and_my_tests_are_order_dependent! + +  TMP_PROVIDER = Dir.mktmpdir("test_leap_provider_") + +  # +  # use minimal bit sizes for our test. +  # +  PROVIDER_JSON = <<HERE +{ +  "domain": "example.org", +  "name": { +    "en": "Example" +  }, +  "description": { +    "en": "Example" +  }, +  "languages": ["en"], +  "default_language": "en", +  "enrollment_policy": "open", +  "contacts": { +    "default": "root@localhost" +  }, +  "ca": { +    "bit_size": 1024, +    "client_certificates": { +      "bit_size": 1024, +      "digest": "SHA1", +      "life_span": "100 years" +    }, +    "life_span": "100 years", +    "server_certificates": { +      "bit_size": 1024, +      "digest": "SHA1", +      "life_span": "100 years" +    } +  } +} +HERE + +  def provider_path +    TMP_PROVIDER +  end + +  def test_01_new +    output = leap_bin! "new --contacts me@example.org --domain example.org --name Example --platform='#{platform_path}' ." +    assert_file "Leapfile" +    assert_file "provider.json" +    assert_dir "nodes" +    File.write(File.join(provider_path, 'provider.json'), PROVIDER_JSON) +  end + +  def test_02_ca +    leap_bin! "cert ca" +    assert_dir "files/ca" +    assert_file "files/ca/ca.crt" +    assert_file "files/ca/ca.key" +  end + +  def test_03_csr +    leap_bin! "cert csr" +    assert_file "files/cert/example.org.csr" +    assert_file "files/cert/example.org.crt" +    assert_file "files/cert/example.org.key" +  end + +  def test_04_nodes +    leap_bin! "node add wildebeest ip_address:1.1.1.2 services:webapp,couchdb" +    leap_bin! "node add hippo ip_address:1.1.1.3 services:static" +    assert_file "nodes/wildebeest.json" +    assert_dir "files/nodes/wildebeest" +    assert_file "files/nodes/wildebeest/wildebeest.crt" +    assert_file "files/nodes/wildebeest/wildebeest.key" +  end + +  def test_05_compile +    user_dir = File.join(provider_path, 'users', 'dummy') +    user_key = File.join(user_dir, 'dummy_ssh.pub') +    FileUtils.mkdir_p(user_dir) +    File.write(user_key, 'ssh-rsa dummydummydummy') + +    leap_bin! "compile" +    assert_file "hiera/wildebeest.yaml" +    assert_file "hiera/hippo.yaml" +  end + +  def test_06_rename +    leap_bin! "node mv hippo hippopotamus" +    assert_file "nodes/hippopotamus.json" +    assert_dir "files/nodes/hippopotamus" +    assert_file "files/nodes/hippopotamus/hippopotamus.key" +  end + +  def test_07_rm +    leap_bin! "node rm hippopotamus" +    assert_file_missing "nodes/hippopotamus.json" +    assert_file_missing "files/nodes/hippopotamus/hippopotamus.key" +  end + +  def assert_file(path) +    assert File.exist?(File.join(provider_path, path)), "The file `#{path}` should exist in #{provider_path}. Actual: \n#{provider_files}" +  end + +  def assert_file_missing(path) +    assert !File.exist?(File.join(provider_path, path)), "The file `#{path}` should NOT exist in #{provider_path}." +  end + +  def assert_dir(path) +    assert Dir.exist?(File.join(provider_path, path)), "The directory `#{path}` should exist in #{provider_path}. Actual: \n#{provider_files}" +  end + +  def provider_files +    `cd #{provider_path} && find .` +  end +end diff --git a/test/unit/test_helper.rb b/test/unit/test_helper.rb index 25a36de..057e4b7 100644 --- a/test/unit/test_helper.rb +++ b/test/unit/test_helper.rb @@ -1 +1 @@ -require File.expand_path('../../test_helper', __FILE__) +require_relative '../test_helper' diff --git a/vendor/acme-client/Gemfile b/vendor/acme-client/Gemfile new file mode 100644 index 0000000..e0b10df --- /dev/null +++ b/vendor/acme-client/Gemfile @@ -0,0 +1,12 @@ +source 'https://rubygems.org' +gemspec + +group :development, :test do +  gem 'pry' +  gem 'rubocop', '0.36.0' +  gem 'ruby-prof', require: false + +  if Gem::Version.new(RUBY_VERSION) <= Gem::Version.new('2.2.2') +    gem 'activesupport', '~> 4.2.6' +  end +end diff --git a/vendor/acme-client/LICENSE.txt b/vendor/acme-client/LICENSE.txt new file mode 100644 index 0000000..73b96b4 --- /dev/null +++ b/vendor/acme-client/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Charles Barbier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/acme-client/README.md b/vendor/acme-client/README.md new file mode 100644 index 0000000..2047885 --- /dev/null +++ b/vendor/acme-client/README.md @@ -0,0 +1,168 @@ +# Acme::Client +[](https://travis-ci.org/unixcharles/acme-client) + +`acme-client` is a client implementation of the [ACME](https://letsencrypt.github.io/acme-spec) protocol in Ruby. + +You can find the ACME reference implementations of the [server](https://github.com/letsencrypt/boulder) in Go and the [client](https://github.com/letsencrypt/letsencrypt) in Python. + +ACME is part of the [Letsencrypt](https://letsencrypt.org/) project, which goal is to provide free SSL/TLS certificates with  automation of the acquiring and renewal process. + +## Installation + +Via Rubygems: + +	$ gem install acme-client + +Or add it to a Gemfile: + +```ruby +gem 'acme-client' +``` + +## Usage + +### Register client + +In order to authenticate our client, we have to create an account for it. + +```ruby +# We're going to need a private key. +require 'openssl' +private_key = OpenSSL::PKey::RSA.new(4096) + +# We need an ACME server to talk to, see github.com/letsencrypt/boulder +# WARNING: This endpoint is the production endpoint, which is rate limited and will produce valid certificates. +# You should probably use the staging endpoint for all your experimentation: +# endpoint = 'https://acme-staging.api.letsencrypt.org/' +endpoint = 'https://acme-v01.api.letsencrypt.org/' + +# Initialize the client +require 'acme-client' +client = Acme::Client.new(private_key: private_key, endpoint: endpoint, connection_options: { request: { open_timeout: 5, timeout: 5 } }) + +# If the private key is not known to the server, we need to register it for the first time. +registration = client.register(contact: 'mailto:contact@example.com') + +# You may need to agree to the terms of service (that's up the to the server to require it or not but boulder does by default) +registration.agree_terms +``` + +### Authorize for domain + +Before you are able to obtain certificates for your domain, you have to prove that you are in control of it. + +```ruby +authorization = client.authorize(domain: 'example.org') + +# If authorization.status returns 'valid' here you can already get a certificate +# and _must not_ try to solve another challenge. +authorization.status # => 'pending' + +# You can can store the authorization's URI to fully recover it and +# any associated challenges via Acme::Client#fetch_authorization. +authorization.uri # => '...' + +# This example is using the http-01 challenge type. Other challenges are dns-01 or tls-sni-01. +challenge = authorization.http01 + +# The http-01 method will require you to respond to a HTTP request. + +# You can retrieve the challenge token +challenge.token # => "some_token" + +# You can retrieve the expected path for the file. +challenge.filename # => ".well-known/acme-challenge/:some_token" + +# You can generate the body of the expected response. +challenge.file_content # => 'string token and JWK thumbprint' + +# You are not required to send a Content-Type. This method will return the right Content-Type should you decide to include one. +challenge.content_type + +# Save the file. We'll create a public directory to serve it from, and inside it we'll create the challenge file. +FileUtils.mkdir_p( File.join( 'public', File.dirname( challenge.filename ) ) ) + +# We'll write the content of the file +File.write( File.join( 'public', challenge.filename), challenge.file_content ) + +# Optionally save the challenge for use at another time (eg: by a background job processor) +File.write('challenge', challenge.to_h.to_json) + +# The challenge file can be served with a Ruby webserver. +# You can run a webserver in another console for that purpose. You may need to forward ports on your router. +# +# $ ruby -run -e httpd public -p 8080 --bind-address 0.0.0.0 + +# Load a saved challenge. This is only required if you need to reuse a saved challenge as outlined above. +challenge = client.challenge_from_hash(JSON.parse(File.read('challenge'))) + +# Once you are ready to serve the confirmation request you can proceed. +challenge.request_verification # => true +challenge.authorization.verify_status # => 'pending' + +# Wait a bit for the server to make the request, or just blink. It should be fast. +sleep(1) + +# Rely on authorization.verify_status more than on challenge.verify_status, +# if the former is 'valid' you can already issue a certificate and the status of +# the challenge is not relevant and in fact may never change from pending. +challenge.authorization.verify_status # => 'valid' +challenge.error # => nil + +# If authorization.verify_status is 'invalid', you can get at the error +# message only through the failed challenge. +authorization.verify_status # => 'invalid' +authorization.http01.error # => {"type" => "...", "detail" => "..."} +``` + +### Obtain a certificate + +Now that your account is authorized for the domain, you should be able to obtain a certificate for it. + +```ruby +# We're going to need a certificate signing request. If not explicitly +# specified, the first name listed becomes the common name. +csr = Acme::Client::CertificateRequest.new(names: %w[example.org www.example.org]) + +# We can now request a certificate. You can pass anything that returns +# a valid DER encoded CSR when calling to_der on it. For example an +# OpenSSL::X509::Request should work too. +certificate = client.new_certificate(csr) # => #<Acme::Client::Certificate ....> + +# Save the certificate and the private key to files +File.write("privkey.pem", certificate.request.private_key.to_pem) +File.write("cert.pem", certificate.to_pem) +File.write("chain.pem", certificate.chain_to_pem) +File.write("fullchain.pem", certificate.fullchain_to_pem) + +# Start a webserver, using your shiny new certificate +# ruby -r openssl -r webrick -r 'webrick/https' -e "s = WEBrick::HTTPServer.new( +#   :Port => 8443, +#   :DocumentRoot => Dir.pwd, +#   :SSLEnable => true, +#   :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.read('privkey.pem') ), +#   :SSLCertificate => OpenSSL::X509::Certificate.new( File.read('cert.pem') )); trap('INT') { s.shutdown }; s.start" +``` + +# Not implemented + +- Recovery methods are not implemented. + +# Requirements + +Ruby >= 2.1 + +## Development + +All the tests use VCR to mock the interaction with the server but if you +need to record new interation against the server simply clone boulder and +run it normally with `./start.py`. + +## Pull request? + +Yes. + +## License + +[MIT License](http://opensource.org/licenses/MIT) + diff --git a/vendor/acme-client/acme-client.gemspec b/vendor/acme-client/acme-client.gemspec new file mode 100644 index 0000000..b62d60c --- /dev/null +++ b/vendor/acme-client/acme-client.gemspec @@ -0,0 +1,27 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'acme/client/version' + +Gem::Specification.new do |spec| +  spec.name          = 'acme-client' +  spec.version       = Acme::Client::VERSION +  spec.authors       = ['Charles Barbier'] +  spec.email         = ['unixcharles@gmail.com'] +  spec.summary       = 'Client for the ACME protocol.' +  spec.homepage      = 'http://github.com/unixcharles/acme-client' +  spec.license       = 'MIT' + +  spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } +  spec.require_paths = ['lib'] + +  spec.required_ruby_version = '>= 2.1.0' + +  spec.add_development_dependency 'bundler', '~> 1.6', '>= 1.6.9' +  spec.add_development_dependency 'rake', '~> 10.0' +  spec.add_development_dependency 'rspec', '~> 3.3', '>= 3.3.0' +  spec.add_development_dependency 'vcr', '~> 2.9', '>= 2.9.3' +  spec.add_development_dependency 'webmock', '~> 1.21', '>= 1.21.0' + +  spec.add_runtime_dependency 'faraday', '~> 0.9', '>= 0.9.1' +end diff --git a/vendor/acme-client/lib/acme-client.rb b/vendor/acme-client/lib/acme-client.rb new file mode 100644 index 0000000..7cc7a0a --- /dev/null +++ b/vendor/acme-client/lib/acme-client.rb @@ -0,0 +1 @@ +require 'acme/client' diff --git a/vendor/acme-client/lib/acme/client.rb b/vendor/acme-client/lib/acme/client.rb new file mode 100644 index 0000000..801479e --- /dev/null +++ b/vendor/acme-client/lib/acme/client.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require 'faraday' +require 'json' +require 'openssl' +require 'digest' +require 'forwardable' +require 'base64' +require 'time' + +module Acme; end +class Acme::Client; end + +require 'acme/client/version' +require 'acme/client/certificate' +require 'acme/client/certificate_request' +require 'acme/client/self_sign_certificate' +require 'acme/client/crypto' +require 'acme/client/resources' +require 'acme/client/faraday_middleware' +require 'acme/client/error' + +class Acme::Client +  DEFAULT_ENDPOINT = 'http://127.0.0.1:4000'.freeze +  DIRECTORY_DEFAULT = { +    'new-authz' => '/acme/new-authz', +    'new-cert' => '/acme/new-cert', +    'new-reg' => '/acme/new-reg', +    'revoke-cert' => '/acme/revoke-cert' +  }.freeze + +  def initialize(private_key:, endpoint: DEFAULT_ENDPOINT, directory_uri: nil, connection_options: {}) +    @endpoint, @private_key, @directory_uri, @connection_options = endpoint, private_key, directory_uri, connection_options +    @nonces ||= [] +    load_directory! +  end + +  attr_reader :private_key, :nonces, :operation_endpoints + +  def register(contact:) +    payload = { +      resource: 'new-reg', contact: Array(contact) +    } + +    response = connection.post(@operation_endpoints.fetch('new-reg'), payload) +    ::Acme::Client::Resources::Registration.new(self, response) +  end + +  def authorize(domain:) +    payload = { +      resource: 'new-authz', +      identifier: { +        type: 'dns', +        value: domain +      } +    } + +    response = connection.post(@operation_endpoints.fetch('new-authz'), payload) +    ::Acme::Client::Resources::Authorization.new(self, response.headers['Location'], response) +  end + +  def fetch_authorization(uri) +    response = connection.get(uri) +    ::Acme::Client::Resources::Authorization.new(self, uri, response) +  end + +  def new_certificate(csr) +    payload = { +      resource: 'new-cert', +      csr: Base64.urlsafe_encode64(csr.to_der) +    } + +    response = connection.post(@operation_endpoints.fetch('new-cert'), payload) +    ::Acme::Client::Certificate.new(OpenSSL::X509::Certificate.new(response.body), response.headers['location'], fetch_chain(response), csr) +  end + +  def revoke_certificate(certificate) +    payload = { resource: 'revoke-cert', certificate: Base64.urlsafe_encode64(certificate.to_der) } +    endpoint = @operation_endpoints.fetch('revoke-cert') +    response = connection.post(endpoint, payload) +    response.success? +  end + +  def self.revoke_certificate(certificate, *arguments) +    client = new(*arguments) +    client.revoke_certificate(certificate) +  end + +  def connection +    @connection ||= Faraday.new(@endpoint, **@connection_options) do |configuration| +      configuration.use Acme::Client::FaradayMiddleware, client: self +      configuration.adapter Faraday.default_adapter +    end +  end + +  private + +  def fetch_chain(response, limit = 10) +    links = response.headers['link'] +    if limit.zero? || links.nil? || links['up'].nil? +      [] +    else +      issuer = connection.get(links['up']) +      [OpenSSL::X509::Certificate.new(issuer.body), *fetch_chain(issuer, limit - 1)] +    end +  end + +  def load_directory! +    @operation_endpoints = if @directory_uri +      response = connection.get(@directory_uri) +      body = response.body +      { +        'new-reg' => body.fetch('new-reg'), +        'new-authz' => body.fetch('new-authz'), +        'new-cert' => body.fetch('new-cert'), +        'revoke-cert' => body.fetch('revoke-cert'), +      } +    else +      DIRECTORY_DEFAULT +    end +  end +end diff --git a/vendor/acme-client/lib/acme/client/certificate.rb b/vendor/acme-client/lib/acme/client/certificate.rb new file mode 100644 index 0000000..6c68cc5 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/certificate.rb @@ -0,0 +1,30 @@ +class Acme::Client::Certificate +  extend Forwardable + +  attr_reader :x509, :x509_chain, :request, :private_key, :url + +  def_delegators :x509, :to_pem, :to_der + +  def initialize(certificate, url, chain, request) +    @x509 = certificate +    @url = url +    @x509_chain = chain +    @request = request +  end + +  def chain_to_pem +    x509_chain.map(&:to_pem).join +  end + +  def x509_fullchain +    [x509, *x509_chain] +  end + +  def fullchain_to_pem +    x509_fullchain.map(&:to_pem).join +  end + +  def common_name +    x509.subject.to_a.find { |name, _, _| name == 'CN' }[1] +  end +end diff --git a/vendor/acme-client/lib/acme/client/certificate_request.rb b/vendor/acme-client/lib/acme/client/certificate_request.rb new file mode 100644 index 0000000..8eae0c6 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/certificate_request.rb @@ -0,0 +1,111 @@ +class Acme::Client::CertificateRequest +  extend Forwardable + +  DEFAULT_KEY_LENGTH = 2048 +  DEFAULT_DIGEST = OpenSSL::Digest::SHA256 +  SUBJECT_KEYS = { +    common_name:         'CN', +    country_name:        'C', +    organization_name:   'O', +    organizational_unit: 'OU', +    state_or_province:   'ST', +    locality_name:       'L' +  }.freeze + +  SUBJECT_TYPES = { +    'CN' => OpenSSL::ASN1::UTF8STRING, +    'C'  => OpenSSL::ASN1::UTF8STRING, +    'O'  => OpenSSL::ASN1::UTF8STRING, +    'OU' => OpenSSL::ASN1::UTF8STRING, +    'ST' => OpenSSL::ASN1::UTF8STRING, +    'L'  => OpenSSL::ASN1::UTF8STRING +  }.freeze + +  attr_reader :private_key, :common_name, :names, :subject + +  def_delegators :csr, :to_pem, :to_der + +  def initialize(common_name: nil, names: [], private_key: generate_private_key, subject: {}, digest: DEFAULT_DIGEST.new) +    @digest = digest +    @private_key = private_key +    @subject = normalize_subject(subject) +    @common_name = common_name || @subject[SUBJECT_KEYS[:common_name]] || @subject[:common_name] +    @names = names.to_a.dup +    normalize_names +    @subject[SUBJECT_KEYS[:common_name]] ||= @common_name +    validate_subject +  end + +  def csr +    @csr ||= generate +  end + +  private + +  def generate_private_key +    OpenSSL::PKey::RSA.new(DEFAULT_KEY_LENGTH) +  end + +  def normalize_subject(subject) +    @subject = subject.each_with_object({}) do |(key, value), hash| +      hash[SUBJECT_KEYS.fetch(key, key)] = value.to_s +    end +  end + +  def normalize_names +    if @common_name +      @names.unshift(@common_name) unless @names.include?(@common_name) +    else +      raise ArgumentError, 'No common name and no list of names given' if @names.empty? +      @common_name = @names.first +    end +  end + +  def validate_subject +    validate_subject_attributes +    validate_subject_common_name +  end + +  def validate_subject_attributes +    extra_keys = @subject.keys - SUBJECT_KEYS.keys - SUBJECT_KEYS.values +    return if extra_keys.empty? +    raise ArgumentError, "Unexpected subject attributes given: #{extra_keys.inspect}" +  end + +  def validate_subject_common_name +    return if @common_name == @subject[SUBJECT_KEYS[:common_name]] +    raise ArgumentError, 'Conflicting common name given in arguments and subject' +  end + +  def generate +    OpenSSL::X509::Request.new.tap do |csr| +      csr.public_key = @private_key.public_key +      csr.subject = generate_subject +      csr.version = 2 +      add_extension(csr) +      csr.sign @private_key, @digest +    end +  end + +  def generate_subject +    OpenSSL::X509::Name.new( +      @subject.map {|name, value| +        [name, value, SUBJECT_TYPES[name]] +      } +    ) +  end + +  def add_extension(csr) +    return if @names.size <= 1 + +    extension = OpenSSL::X509::ExtensionFactory.new.create_extension( +      'subjectAltName', @names.map { |name| "DNS:#{name}" }.join(', '), false +    ) +    csr.add_attribute( +      OpenSSL::X509::Attribute.new( +        'extReq', +        OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([extension])]) +      ) +    ) +  end +end diff --git a/vendor/acme-client/lib/acme/client/crypto.rb b/vendor/acme-client/lib/acme/client/crypto.rb new file mode 100644 index 0000000..dfa5cdc --- /dev/null +++ b/vendor/acme-client/lib/acme/client/crypto.rb @@ -0,0 +1,98 @@ +class Acme::Client::Crypto +  attr_reader :private_key + +  def initialize(private_key) +    @private_key = private_key +  end + +  def generate_signed_jws(header:, payload:) +    header = { typ: 'JWT', alg: jws_alg, jwk: jwk }.merge(header) + +    encoded_header = urlsafe_base64(header.to_json) +    encoded_payload = urlsafe_base64(payload.to_json) +    signature_data = "#{encoded_header}.#{encoded_payload}" + +    signature = private_key.sign digest, signature_data +    encoded_signature = urlsafe_base64(signature) + +    { +      protected: encoded_header, +      payload: encoded_payload, +      signature: encoded_signature +    }.to_json +  end + +  def thumbprint +    urlsafe_base64 digest.digest(jwk.to_json) +  end + +  def digest +    OpenSSL::Digest::SHA256.new +  end + +  def urlsafe_base64(data) +    Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '') +  end + +  private + +  def jws_alg +    { 'RSA' => 'RS256', 'EC' => 'ES256' }.fetch(jwk[:kty]) +  end + +  def jwk +    @jwk ||= case private_key +             when OpenSSL::PKey::RSA +               rsa_jwk +             when OpenSSL::PKey::EC +               ec_jwk +             else +               raise ArgumentError, "Can't handle #{private_key} as private key, only OpenSSL::PKey::RSA and OpenSSL::PKey::EC" +    end +  end + +  def rsa_jwk +    { +      e: urlsafe_base64(public_key.e.to_s(2)), +      kty: 'RSA', +      n: urlsafe_base64(public_key.n.to_s(2)) +    } +  end + +  def ec_jwk +    { +      crv: curve_name, +      kty: 'EC', +      x: urlsafe_base64(coordinates[:x].to_s(2)), +      y: urlsafe_base64(coordinates[:y].to_s(2)) +    } +  end + +  def curve_name +    { +      'prime256v1' => 'P-256', +      'secp384r1' => 'P-384', +      'secp521r1' => 'P-521' +    }.fetch(private_key.group.curve_name) { raise ArgumentError, 'Unknown EC curve' } +  end + +  # rubocop:disable Metrics/AbcSize +  def coordinates +    @coordinates ||= begin +      hex = public_key.to_bn.to_s(16) +      data_len = hex.length - 2 +      hex_x = hex[2, data_len / 2] +      hex_y = hex[2 + data_len / 2, data_len / 2] + +      { +        x: OpenSSL::BN.new([hex_x].pack('H*'), 2), +        y: OpenSSL::BN.new([hex_y].pack('H*'), 2) +      } +    end +  end +  # rubocop:enable Metrics/AbcSize + +  def public_key +    @public_key ||= private_key.public_key +  end +end diff --git a/vendor/acme-client/lib/acme/client/error.rb b/vendor/acme-client/lib/acme/client/error.rb new file mode 100644 index 0000000..2b35623 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/error.rb @@ -0,0 +1,16 @@ +class Acme::Client::Error < StandardError +  class NotFound < Acme::Client::Error; end +  class BadCSR < Acme::Client::Error; end +  class BadNonce < Acme::Client::Error; end +  class Connection < Acme::Client::Error; end +  class Dnssec < Acme::Client::Error; end +  class Malformed < Acme::Client::Error; end +  class ServerInternal < Acme::Client::Error; end +  class Acme::Tls < Acme::Client::Error; end +  class Unauthorized < Acme::Client::Error; end +  class UnknownHost < Acme::Client::Error; end +  class Timeout < Acme::Client::Error; end +  class RateLimited < Acme::Client::Error; end +  class RejectedIdentifier < Acme::Client::Error; end +  class UnsupportedIdentifier < Acme::Client::Error; end +end diff --git a/vendor/acme-client/lib/acme/client/faraday_middleware.rb b/vendor/acme-client/lib/acme/client/faraday_middleware.rb new file mode 100644 index 0000000..21e29c9 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/faraday_middleware.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +class Acme::Client::FaradayMiddleware < Faraday::Middleware +  attr_reader :env, :response, :client + +  repo_url = 'https://github.com/unixcharles/acme-client' +  USER_AGENT = "Acme::Client v#{Acme::Client::VERSION} (#{repo_url})".freeze + +  def initialize(app, client:) +    super(app) +    @client = client +  end + +  def call(env) +    @env = env +    @env[:request_headers]['User-Agent'] = USER_AGENT +    @env.body = crypto.generate_signed_jws(header: { nonce: pop_nonce }, payload: env.body) +    @app.call(env).on_complete { |response_env| on_complete(response_env) } +  rescue Faraday::TimeoutError +    raise Acme::Client::Error::Timeout +  end + +  def on_complete(env) +    @env = env + +    raise_on_not_found! +    store_nonce +    env.body = decode_body +    env.response_headers['Link'] = decode_link_headers + +    return if env.success? + +    raise_on_error! +  end + +  private + +  def raise_on_not_found! +    raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404 +  end + +  def raise_on_error! +    raise error_class, error_message +  end + +  def error_message +    if env.body.is_a? Hash +      env.body['detail'] +    else +      "Error message: #{env.body}" +    end +  end + +  def error_class +    if error_name && !error_name.empty? && Acme::Client::Error.const_defined?(error_name) +      Object.const_get("Acme::Client::Error::#{error_name}") +    else +      Acme::Client::Error +    end +  end + +  def error_name +    @error_name ||= begin +      return unless env.body.is_a?(Hash) +      return unless env.body.key?('type') + +      env.body['type'].gsub('urn:acme:error:', '').split(/[_-]/).map(&:capitalize).join +    end +  end + +  def decode_body +    content_type = env.response_headers['Content-Type'] + +    if content_type == 'application/json' || content_type == 'application/problem+json' +      JSON.load(env.body) +    else +      env.body +    end +  end + +  LINK_MATCH = /<(.*?)>;rel="([\w-]+)"/ + +  def decode_link_headers +    return unless env.response_headers.key?('Link') +    link_header = env.response_headers['Link'] + +    links = link_header.split(', ').map { |entry| +      _, link, name = *entry.match(LINK_MATCH) +      [name, link] +    } + +    Hash[*links.flatten] +  end + +  def store_nonce +    nonces << env.response_headers['replay-nonce'] +  end + +  def pop_nonce +    if nonces.empty? +      get_nonce +    else +      nonces.pop +    end +  end + +  def get_nonce +    response = Faraday.head(env.url, nil, 'User-Agent' => USER_AGENT) +    response.headers['replay-nonce'] +  end + +  def nonces +    client.nonces +  end + +  def private_key +    client.private_key +  end + +  def crypto +    @crypto ||= Acme::Client::Crypto.new(private_key) +  end +end diff --git a/vendor/acme-client/lib/acme/client/resources.rb b/vendor/acme-client/lib/acme/client/resources.rb new file mode 100644 index 0000000..ad55688 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/resources.rb @@ -0,0 +1,5 @@ +module Acme::Client::Resources; end + +require 'acme/client/resources/registration' +require 'acme/client/resources/challenges' +require 'acme/client/resources/authorization' diff --git a/vendor/acme-client/lib/acme/client/resources/authorization.rb b/vendor/acme-client/lib/acme/client/resources/authorization.rb new file mode 100644 index 0000000..9ca2e76 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/resources/authorization.rb @@ -0,0 +1,44 @@ +class Acme::Client::Resources::Authorization +  HTTP01 = Acme::Client::Resources::Challenges::HTTP01 +  DNS01 = Acme::Client::Resources::Challenges::DNS01 +  TLSSNI01 = Acme::Client::Resources::Challenges::TLSSNI01 + +  attr_reader :client, :uri, :domain, :status, :expires, :http01, :dns01, :tls_sni01 + +  def initialize(client, uri, response) +    @client = client +    @uri = uri +    assign_attributes(response.body) +  end + +  def verify_status +    response = @client.connection.get(@uri) + +    assign_attributes(response.body) +    status +  end + +  private + +  def assign_attributes(body) +    @expires = Time.iso8601(body['expires']) if body.key? 'expires' +    @domain = body['identifier']['value'] +    @status = body['status'] +    assign_challenges(body['challenges']) +  end + +  def assign_challenges(challenges) +    challenges.each do |attributes| +      challenge = case attributes.fetch('type') +                  when 'http-01' +                    @http01 ||= HTTP01.new(self) +                  when 'dns-01' +                    @dns01 ||= DNS01.new(self) +                  when 'tls-sni-01' +                    @tls_sni01 ||= TLSSNI01.new(self) +      end + +      challenge.assign_attributes(attributes) if challenge +    end +  end +end diff --git a/vendor/acme-client/lib/acme/client/resources/challenges.rb b/vendor/acme-client/lib/acme/client/resources/challenges.rb new file mode 100644 index 0000000..ec92d47 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/resources/challenges.rb @@ -0,0 +1,6 @@ +module Acme::Client::Resources::Challenges; end + +require 'acme/client/resources/challenges/base' +require 'acme/client/resources/challenges/http01' +require 'acme/client/resources/challenges/dns01' +require 'acme/client/resources/challenges/tls_sni01' diff --git a/vendor/acme-client/lib/acme/client/resources/challenges/base.rb b/vendor/acme-client/lib/acme/client/resources/challenges/base.rb new file mode 100644 index 0000000..c78c74e --- /dev/null +++ b/vendor/acme-client/lib/acme/client/resources/challenges/base.rb @@ -0,0 +1,43 @@ +class Acme::Client::Resources::Challenges::Base +  attr_reader :authorization, :status, :uri, :token, :error + +  def initialize(authorization) +    @authorization = authorization +  end + +  def client +    authorization.client +  end + +  def verify_status +    authorization.verify_status + +    status +  end + +  def request_verification +    response = client.connection.post(@uri, resource: 'challenge', type: challenge_type, keyAuthorization: authorization_key) +    response.success? +  end + +  def assign_attributes(attributes) +    @status = attributes.fetch('status', 'pending') +    @uri = attributes.fetch('uri') +    @token = attributes.fetch('token') +    @error = attributes['error'] +  end + +  private + +  def challenge_type +    self.class::CHALLENGE_TYPE +  end + +  def authorization_key +    "#{token}.#{crypto.thumbprint}" +  end + +  def crypto +    @crypto ||= Acme::Client::Crypto.new(client.private_key) +  end +end diff --git a/vendor/acme-client/lib/acme/client/resources/challenges/dns01.rb b/vendor/acme-client/lib/acme/client/resources/challenges/dns01.rb new file mode 100644 index 0000000..543f438 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/resources/challenges/dns01.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Acme::Client::Resources::Challenges::DNS01 < Acme::Client::Resources::Challenges::Base +  CHALLENGE_TYPE = 'dns-01'.freeze +  RECORD_NAME = '_acme-challenge'.freeze +  RECORD_TYPE = 'TXT'.freeze + +  def record_name +    RECORD_NAME +  end + +  def record_type +    RECORD_TYPE +  end + +  def record_content +    crypto.urlsafe_base64(crypto.digest.digest(authorization_key)) +  end +end diff --git a/vendor/acme-client/lib/acme/client/resources/challenges/http01.rb b/vendor/acme-client/lib/acme/client/resources/challenges/http01.rb new file mode 100644 index 0000000..4966091 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/resources/challenges/http01.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Acme::Client::Resources::Challenges::HTTP01 < Acme::Client::Resources::Challenges::Base +  CHALLENGE_TYPE = 'http-01'.freeze +  CONTENT_TYPE = 'text/plain'.freeze + +  def content_type +    CONTENT_TYPE +  end + +  def file_content +    authorization_key +  end + +  def filename +    ".well-known/acme-challenge/#{token}" +  end +end diff --git a/vendor/acme-client/lib/acme/client/resources/challenges/tls_sni01.rb b/vendor/acme-client/lib/acme/client/resources/challenges/tls_sni01.rb new file mode 100644 index 0000000..8f455f5 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/resources/challenges/tls_sni01.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class Acme::Client::Resources::Challenges::TLSSNI01 < Acme::Client::Resources::Challenges::Base +  CHALLENGE_TYPE = 'tls-sni-01'.freeze + +  def hostname +    digest = crypto.digest.hexdigest(authorization_key) +    "#{digest[0..31]}.#{digest[32..64]}.acme.invalid" +  end + +  def certificate +    self_sign_certificate.certificate +  end + +  def private_key +    self_sign_certificate.private_key +  end + +  private + +  def self_sign_certificate +    @self_sign_certificate ||= Acme::Client::SelfSignCertificate.new(subject_alt_names: [hostname]) +  end +end diff --git a/vendor/acme-client/lib/acme/client/resources/registration.rb b/vendor/acme-client/lib/acme/client/resources/registration.rb new file mode 100644 index 0000000..b7a4c11 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/resources/registration.rb @@ -0,0 +1,37 @@ +class Acme::Client::Resources::Registration +  attr_reader :id, :key, :contact, :uri, :next_uri, :recover_uri, :term_of_service_uri + +  def initialize(client, response) +    @client = client +    @uri = response.headers['location'] +    assign_links(response.headers['Link']) +    assign_attributes(response.body) +  end + +  def get_terms +    return unless @term_of_service_uri + +    @client.connection.get(@term_of_service_uri).body +  end + +  def agree_terms +    return true unless @term_of_service_uri + +    response = @client.connection.post(@uri, resource: 'reg', agreement: @term_of_service_uri) +    response.success? +  end + +  private + +  def assign_links(links) +    @next_uri = links['next'] +    @recover_uri = links['recover'] +    @term_of_service_uri = links['terms-of-service'] +  end + +  def assign_attributes(body) +    @id = body['id'] +    @key = body['key'] +    @contact = body['contact'] +  end +end diff --git a/vendor/acme-client/lib/acme/client/self_sign_certificate.rb b/vendor/acme-client/lib/acme/client/self_sign_certificate.rb new file mode 100644 index 0000000..2e7d98c --- /dev/null +++ b/vendor/acme-client/lib/acme/client/self_sign_certificate.rb @@ -0,0 +1,60 @@ +class Acme::Client::SelfSignCertificate +  attr_reader :private_key, :subject_alt_names, :not_before, :not_after + +  extend Forwardable +  def_delegators :certificate, :to_pem, :to_der + +  def initialize(subject_alt_names:, not_before: default_not_before, not_after: default_not_after, private_key: generate_private_key) +    @private_key = private_key +    @subject_alt_names = subject_alt_names +    @not_before = not_before +    @not_after = not_after +  end + +  def certificate +    @certificate ||= begin +      certificate = generate_certificate + +      extension_factory = generate_extension_factory(certificate) +      subject_alt_name_entry = subject_alt_names.map { |d| "DNS: #{d}" }.join(',') +      subject_alt_name_extension = extension_factory.create_extension('subjectAltName', subject_alt_name_entry) +      certificate.add_extension(subject_alt_name_extension) + +      certificate.sign(private_key, digest) +    end +  end + +  private + +  def generate_private_key +    OpenSSL::PKey::RSA.new(2048) +  end + +  def default_not_before +    Time.now - 3600 +  end + +  def default_not_after +    Time.now + 30 * 24 * 3600 +  end + +  def digest +    OpenSSL::Digest::SHA256.new +  end + +  def generate_certificate +    certificate = OpenSSL::X509::Certificate.new +    certificate.not_before = not_before +    certificate.not_after = not_after +    certificate.public_key = private_key.public_key +    certificate.version = 2 +    certificate +  end + +  def generate_extension_factory(certificate) +    extension_factory = OpenSSL::X509::ExtensionFactory.new +    extension_factory.subject_certificate = certificate +    extension_factory.issuer_certificate = certificate +    extension_factory +  end +end diff --git a/vendor/acme-client/lib/acme/client/version.rb b/vendor/acme-client/lib/acme/client/version.rb new file mode 100644 index 0000000..c989c12 --- /dev/null +++ b/vendor/acme-client/lib/acme/client/version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Acme +  class Client +    VERSION = '0.4.1'.freeze +  end +end diff --git a/vendor/base32/LICENSE b/vendor/base32/LICENSE new file mode 100644 index 0000000..cdc04d9 --- /dev/null +++ b/vendor/base32/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2007-2011 Samuel Tesla + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/base32/base32.gemspec b/vendor/base32/base32.gemspec new file mode 100644 index 0000000..4e84cea --- /dev/null +++ b/vendor/base32/base32.gemspec @@ -0,0 +1,10 @@ +$:.push File.expand_path('../lib', __FILE__) + +Gem::Specification.new do |s| +  s.name    = 'base32' +  s.version = '0.3.2' +  s.authors = ['Samuel Tesla'] +  s.email   = 'samuel.tesla@gmail.com' +  s.summary = 'Ruby extension for base32 encoding and decoding' +  s.require_paths = ['lib'] +end diff --git a/vendor/base32/lib/base32.rb b/vendor/base32/lib/base32.rb new file mode 100644 index 0000000..4df2b1a --- /dev/null +++ b/vendor/base32/lib/base32.rb @@ -0,0 +1,67 @@ +require 'openssl' + +# Module for encoding and decoding in Base32 per RFC 3548 +module Base32 +  TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.freeze + +  class Chunk +    def initialize(bytes) +      @bytes = bytes +    end + +    def decode +      bytes = @bytes.take_while {|c| c != 61} # strip padding +      n = (bytes.length * 5.0 / 8.0).floor +      p = bytes.length < 8 ? 5 - (n * 8) % 5 : 0 +      c = bytes.inject(0) {|m,o| (m << 5) + Base32.table.index(o.chr)} >> p +      (0..n-1).to_a.reverse.collect {|i| ((c >> i * 8) & 0xff).chr} +    end + +    def encode +      n = (@bytes.length * 8.0 / 5.0).ceil +      p = n < 8 ? 5 - (@bytes.length * 8) % 5 : 0 +      c = @bytes.inject(0) {|m,o| (m << 8) + o} << p +      [(0..n-1).to_a.reverse.collect {|i| Base32.table[(c >> i * 5) & 0x1f].chr}, +       ("=" * (8-n))] +    end +  end + +  def self.chunks(str, size) +    result = [] +    bytes = str.bytes +    while bytes.any? do +      result << Chunk.new(bytes.take(size)) +      bytes = bytes.drop(size) +    end +    result +  end + +  def self.encode(str) +    chunks(str, 5).collect(&:encode).flatten.join +  end + +  def self.decode(str) +    chunks(str, 8).collect(&:decode).flatten.join +  end + +  def self.random_base32(length=16, padding=true) +    random = '' +    OpenSSL::Random.random_bytes(length).each_byte do |b| +      random << self.table[b % 32] +    end +    padding ? random.ljust((length / 8.0).ceil * 8, '=') : random +  end + +  def self.table=(table) +    raise ArgumentError, "Table must have 32 unique characters" unless self.table_valid?(table) +    @table = table +  end + +  def self.table +    @table || TABLE +  end + +  def self.table_valid?(table) +    table.bytes.to_a.size == 32 && table.bytes.to_a.uniq.size == 32 +  end +end diff --git a/vendor/certificate_authority/certificate_authority.gemspec b/vendor/certificate_authority/certificate_authority.gemspec index b7e8676..71ffb4a 100644 --- a/vendor/certificate_authority/certificate_authority.gemspec +++ b/vendor/certificate_authority/certificate_authority.gemspec @@ -2,15 +2,17 @@  # DO NOT EDIT THIS FILE DIRECTLY  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'  # -*- encoding: utf-8 -*- +# stub: certificate_authority 0.2.0 ruby lib  Gem::Specification.new do |s|    s.name = "certificate_authority"    s.version = "0.2.0"    s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= +  s.require_paths = ["lib"]    s.authors = ["Chris Chandler"] -  s.date = "2012-09-16" -  s.email = "chris@flatterline.com" +  s.date = "2016-06-21" +  s.email = "squanderingtime@gmail.com"    s.extra_rdoc_files = [      "README.rdoc"    ] @@ -24,6 +26,7 @@ Gem::Specification.new do |s|      "lib/certificate_authority.rb",      "lib/certificate_authority/certificate.rb",      "lib/certificate_authority/certificate_revocation_list.rb", +    "lib/certificate_authority/core_extensions.rb",      "lib/certificate_authority/distinguished_name.rb",      "lib/certificate_authority/extensions.rb",      "lib/certificate_authority/key_material.rb", @@ -33,6 +36,7 @@ Gem::Specification.new do |s|      "lib/certificate_authority/serial_number.rb",      "lib/certificate_authority/signing_entity.rb",      "lib/certificate_authority/signing_request.rb", +    "lib/certificate_authority/validations.rb",      "lib/tasks/certificate_authority.rake",      "spec/samples/certs/DigiCertHighAssuranceEVCA-1.pem",      "spec/samples/certs/apple_wwdr_issued_cert.pem", @@ -63,27 +67,20 @@ Gem::Specification.new do |s|    ]    s.homepage = "https://github.com/cchandler/certificate_authority"    s.licenses = ["MIT"] -  s.require_paths = ["lib"] -  s.rubygems_version = "1.8.15" +  s.rubygems_version = "2.2.2"    s.summary = "Ruby gem for managing the core functions outlined in RFC-3280 for PKI"    if s.respond_to? :specification_version then -    s.specification_version = 3 +    s.specification_version = 4      if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then -      s.add_runtime_dependency(%q<activemodel>, [">= 3.0.6"]) -      s.add_runtime_dependency(%q<activesupport>, [">= 3.0.6"])        s.add_development_dependency(%q<rspec>, [">= 0"])        s.add_development_dependency(%q<jeweler>, [">= 1.5.2"])      else -      s.add_dependency(%q<activemodel>, [">= 3.0.6"]) -      s.add_dependency(%q<activesupport>, [">= 3.0.6"])        s.add_dependency(%q<rspec>, [">= 0"])        s.add_dependency(%q<jeweler>, [">= 1.5.2"])      end    else -    s.add_dependency(%q<activemodel>, [">= 3.0.6"]) -    s.add_dependency(%q<activesupport>, [">= 3.0.6"])      s.add_dependency(%q<rspec>, [">= 0"])      s.add_dependency(%q<jeweler>, [">= 1.5.2"])    end diff --git a/vendor/certificate_authority/lib/certificate_authority.rb b/vendor/certificate_authority/lib/certificate_authority.rb index a697c1b..c52e4b6 100644 --- a/vendor/certificate_authority/lib/certificate_authority.rb +++ b/vendor/certificate_authority/lib/certificate_authority.rb @@ -2,11 +2,12 @@ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) ||  #Exterior requirements  require 'openssl' -require 'active_model'  #Internal modules +require 'certificate_authority/core_extensions'  require 'certificate_authority/signing_entity'  require 'certificate_authority/revocable' +require 'certificate_authority/validations'  require 'certificate_authority/distinguished_name'  require 'certificate_authority/serial_number'  require 'certificate_authority/key_material' diff --git a/vendor/certificate_authority/lib/certificate_authority/certificate.rb b/vendor/certificate_authority/lib/certificate_authority/certificate.rb index 496d91e..cdf432c 100644 --- a/vendor/certificate_authority/lib/certificate_authority/certificate.rb +++ b/vendor/certificate_authority/lib/certificate_authority/certificate.rb @@ -1,6 +1,6 @@  module CertificateAuthority    class Certificate -    include ActiveModel::Validations +    include Validations      include Revocable      attr_accessor :distinguished_name @@ -15,7 +15,7 @@ module CertificateAuthority      attr_accessor :parent -    validate do |certificate| +    def validate        errors.add :base, "Distinguished name must be valid" unless distinguished_name.valid?        errors.add :base, "Key material must be valid" unless key_material.valid?        errors.add :base, "Serial number must be valid" unless serial_number.valid? @@ -32,8 +32,8 @@ module CertificateAuthority        self.distinguished_name = DistinguishedName.new        self.serial_number = SerialNumber.new        self.key_material = MemoryKeyMaterial.new -      self.not_before = Time.now -      self.not_after = Time.now + 60 * 60 * 24 * 365 # One year +      self.not_before = Date.today.utc +      self.not_after = Date.today.advance(:years => 1).utc        self.parent = self        self.extensions = load_extensions() diff --git a/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb b/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb index c84d588..cb3aaf7 100644 --- a/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +++ b/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb @@ -1,20 +1,22 @@  module CertificateAuthority    class CertificateRevocationList -    include ActiveModel::Validations +    include Validations      attr_accessor :certificates      attr_accessor :parent      attr_accessor :crl_body      attr_accessor :next_update +    attr_accessor :last_update_skew_seconds -    validate do |crl| -      errors.add :next_update, "Next update must be a positive value" if crl.next_update < 0 -      errors.add :parent, "A parent entity must be set" if crl.parent.nil? +    def validate +      errors.add :next_update, "Next update must be a positive value" if self.next_update < 0 +      errors.add :parent, "A parent entity must be set" if self.parent.nil?      end      def initialize        self.certificates = []        self.next_update = 60 * 60 * 4 # 4 hour default +      self.last_update_skew_seconds = 0      end      def <<(revocable) @@ -54,7 +56,7 @@ module CertificateAuthority        end        crl.version = 1 -      crl.last_update = Time.now +      crl.last_update = Time.now - self.last_update_skew_seconds        crl.next_update = Time.now + self.next_update        signing_cert = OpenSSL::X509::Certificate.new(self.parent.to_pem) diff --git a/vendor/certificate_authority/lib/certificate_authority/core_extensions.rb b/vendor/certificate_authority/lib/certificate_authority/core_extensions.rb new file mode 100644 index 0000000..0508f9a --- /dev/null +++ b/vendor/certificate_authority/lib/certificate_authority/core_extensions.rb @@ -0,0 +1,46 @@ +# +# ActiveSupport has these modifications. Now that we don't use ActiveSupport, +# these are added here as a kindness. +# + +require 'date' + +unless nil.respond_to?(:blank?) +  class NilClass +    def blank? +      true +    end +  end +end + +unless String.respond_to?(:blank?) +  class String +    def blank? +      self.empty? +    end +  end +end + +class Date + +  def today +    t = Time.now.utc +    Date.new(t.year, t.month, t.day) +  end + +  def utc +    self.to_datetime.to_time.utc +  end + +  unless Date.respond_to?(:advance) +    def advance(options) +      options = options.dup +      d = self +      d = d >> options.delete(:years) * 12 if options[:years] +      d = d >> options.delete(:months)     if options[:months] +      d = d +  options.delete(:weeks) * 7  if options[:weeks] +      d = d +  options.delete(:days)       if options[:days] +      d +    end +  end +end diff --git a/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb b/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb index 32d9c1e..3b83582 100644 --- a/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +++ b/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb @@ -1,8 +1,12 @@  module CertificateAuthority    class DistinguishedName -    include ActiveModel::Validations +    include Validations -    validates_presence_of :common_name +    def validate +      if self.common_name.nil? || self.common_name.empty? +        errors.add :common_name, 'cannot be blank' +      end +    end      attr_accessor :common_name      alias :cn :common_name diff --git a/vendor/certificate_authority/lib/certificate_authority/extensions.rb b/vendor/certificate_authority/lib/certificate_authority/extensions.rb index 7bc4fab..2b9478b 100644 --- a/vendor/certificate_authority/lib/certificate_authority/extensions.rb +++ b/vendor/certificate_authority/lib/certificate_authority/extensions.rb @@ -31,13 +31,20 @@ module CertificateAuthority        OPENSSL_IDENTIFIER = "basicConstraints"        include ExtensionAPI -      include ActiveModel::Validations +      include Validations        attr_accessor :critical        attr_accessor :ca        attr_accessor :path_len -      validates :critical, :inclusion => [true,false] -      validates :ca, :inclusion => [true,false] + +      def validate +        unless [true, false].include? self.critical +          errors.add :critical, 'must be true or false' +        end +        unless [true, false].include? self.ca +          errors.add :ca, 'must be true or false' +        end +      end        def initialize          @critical = false diff --git a/vendor/certificate_authority/lib/certificate_authority/key_material.rb b/vendor/certificate_authority/lib/certificate_authority/key_material.rb index 1fd4dd9..ae3a530 100644 --- a/vendor/certificate_authority/lib/certificate_authority/key_material.rb +++ b/vendor/certificate_authority/lib/certificate_authority/key_material.rb @@ -38,7 +38,7 @@ module CertificateAuthority    class MemoryKeyMaterial      include KeyMaterial -    include ActiveModel::Validations +    include Validations      attr_accessor :keypair      attr_accessor :private_key @@ -47,11 +47,13 @@ module CertificateAuthority      def initialize      end -    validates_each :private_key do |record, attr, value| -        record.errors.add :private_key, "cannot be blank" if record.private_key.nil? -    end -    validates_each :public_key do |record, attr, value| -      record.errors.add :public_key, "cannot be blank" if record.public_key.nil? +    def validate +      if private_key.nil? +        errors.add :private_key, "cannot be blank" +      end +      if public_key.nil? +        errors.add :public_key, "cannot be blank" +      end      end      def is_in_hardware? @@ -80,10 +82,10 @@ module CertificateAuthority    class SigningRequestKeyMaterial      include KeyMaterial -    include ActiveModel::Validations +    include Validations -    validates_each :public_key do |record, attr, value| -      record.errors.add :public_key, "cannot be blank" if record.public_key.nil? +    def validate +      errors.add :public_key, "cannot be blank" if public_key.nil?      end      attr_accessor :public_key diff --git a/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb b/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb index e101f98..0f2661c 100644 --- a/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb +++ b/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb @@ -68,7 +68,7 @@ module CertificateAuthority    ## DEPRECATED    class OCSPHandler -    include ActiveModel::Validations +    include Validations      attr_accessor :ocsp_request      attr_accessor :certificate_ids @@ -78,10 +78,10 @@ module CertificateAuthority      attr_accessor :ocsp_response_body -    validate do |crl| +    def validate        errors.add :parent, "A parent entity must be set" if parent.nil? +      all_certificates_available      end -    validate :all_certificates_available      def initialize        self.certificates = {} diff --git a/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb b/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb index d4ebc47..8a83f0e 100644 --- a/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb +++ b/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb @@ -1,8 +1,6 @@  module CertificateAuthority    class Pkcs11KeyMaterial      include KeyMaterial -    include ActiveModel::Validations -    include ActiveModel::Serialization      attr_accessor :engine      attr_accessor :token_id diff --git a/vendor/certificate_authority/lib/certificate_authority/serial_number.rb b/vendor/certificate_authority/lib/certificate_authority/serial_number.rb index b9a43cc..99f3002 100644 --- a/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +++ b/vendor/certificate_authority/lib/certificate_authority/serial_number.rb @@ -2,12 +2,18 @@ require 'securerandom'  module CertificateAuthority    class SerialNumber -    include ActiveModel::Validations +    include Validations      include Revocable      attr_accessor :number -    validates :number, :presence => true, :numericality => {:greater_than => 0} +    def validate +      if self.number.nil? +        errors.add :number, "must not be empty" +      elsif self.number.to_i <= 0 +        errors.add :number, "must be greater than zero" +      end +    end      def initialize        self.number = SecureRandom.random_number(2**128-1) diff --git a/vendor/certificate_authority/lib/certificate_authority/validations.rb b/vendor/certificate_authority/lib/certificate_authority/validations.rb new file mode 100644 index 0000000..a429c96 --- /dev/null +++ b/vendor/certificate_authority/lib/certificate_authority/validations.rb @@ -0,0 +1,31 @@ +# +# This is a super simple replacement for ActiveSupport::Validations +# + +module CertificateAuthority +  class Errors < Array +    def add(symbol, msg) +      self.push([symbol, msg]) +    end +    def full_messages +      self.map {|i| i[0].to_s + ": " + i[1]}.join("\n") +    end +  end + +  module Validations +    def valid? +      @errors = Errors.new +      validate +      errors.empty? +    end + +    # must be overridden +    def validate +      raise NotImplementedError +    end + +    def errors +      @errors ||= Errors.new +    end +  end +end diff --git a/vendor/rsync_command/README.md b/vendor/rsync_command/README.md index 4b53a5c..5e44845 100644 --- a/vendor/rsync_command/README.md +++ b/vendor/rsync_command/README.md @@ -11,13 +11,15 @@ Installation  Usage  ------------------------------------ -    rsync   = RsyncCommand.new(:logger => logger, :ssh => {:auth_methods => 'publickey'}, :flags => '-a') -    source  = '/source/path' +    rsync   = RsyncCommand.new(:ssh => {:auth_methods => 'publickey'}, :flags => '-a')      servers = ['red', 'green', 'blue'] -    rsync.asynchronously(servers) do |server| -      dest = {:user => 'root', :host => server, :path => '/dest/path'} -      rsync.exec(source, dest) +    rsync.asynchronously(servers) do |sync, server| +      sync.user = 'root' +      sync.host = server +      sync.source = '/from' +      sync.dest = '/to' +      sync.exec      end      if rsync.failed? diff --git a/vendor/rsync_command/lib/rsync_command.rb b/vendor/rsync_command/lib/rsync_command.rb index 39e5945..bdcafe0 100644 --- a/vendor/rsync_command/lib/rsync_command.rb +++ b/vendor/rsync_command/lib/rsync_command.rb @@ -4,6 +4,44 @@ require "rsync_command/thread_pool"  require 'monitor' +class RsyncRunner +  attr_accessor :logger +  attr_accessor :source, :dest, :flags, :includes, :excludes +  attr_accessor :user, :host +  attr_accessor :chdir, :ssh +  def initialize(rsync_command) +    @logger = nil +    @source = "" +    @dest   = "" +    @flags  = "" +    @includes = [] +    @excludes = [] +    @rsync_command = rsync_command +  end +  def log(*args) +    @logger.log(*args) +  end +  def valid? +    !@source.empty? || !@dest.empty? +  end +  def to_hash +    fields = [:flags, :includes, :excludes, :logger, :ssh, :chdir] +    fields.inject({}){|hsh, i| +      hsh[i] = self.send(i); hsh +    } +  end +  def exec +    return unless valid? +    dest = { +      :user => self.user, +      :host => self.host, +      :path => self.dest +    } +    src = self.source +    @rsync_command.exec_rsync(src, dest, self.to_hash) +  end +end +  class RsyncCommand    attr_accessor :failures, :logger @@ -21,15 +59,23 @@ class RsyncCommand    def asynchronously(array, &block)      pool = ThreadPool.new      array.each do |item| -      pool.schedule(item, &block) +      pool.schedule(RsyncRunner.new(self), item, &block)      end      pool.shutdown    end    # +  # returns true if last exec returned a failure +  # +  def failed? +    @failures && @failures.any? +  end + +  #    # runs rsync, recording failures    # -  def exec(src, dest, options={}) +  def exec_rsync(src, dest, options={}) +    logger = options[:logger] || @logger      @failures.synchronize do        @failures.clear      end @@ -37,7 +83,7 @@ class RsyncCommand      if options[:chdir]        rsync_cmd = "cd '#{options[:chdir]}'; #{rsync_cmd}"      end -    @logger.debug rsync_cmd if @logger +    logger.debug rsync_cmd if logger      ok = system(rsync_cmd)      unless ok        @failures.synchronize do @@ -47,13 +93,6 @@ class RsyncCommand    end    # -  # returns true if last exec returned a failure -  # -  def failed? -    @failures && @failures.any? -  end - -  #    # build rsync command    #    def command(src, dest, options={}) @@ -70,8 +109,6 @@ class RsyncCommand      "rsync #{flags.compact.join(' ')} #{src} #{dest}"    end -  private -    #    # Creates an rsync location if the +address+ is a hash with keys :user, :host, and :path    # (each component is optional). If +address+ is a string, we just pass it through.  | 
