aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--DEVNOTES99
-rw-r--r--README.md30
-rw-r--r--leap_cli.gemspec2
-rw-r--r--lib/leap_cli.rb2
-rw-r--r--lib/leap_cli/commands/compile.rb2
-rw-r--r--lib/leap_cli/commands/init.rb2
-rw-r--r--lib/leap_cli/commands/pre.rb4
-rw-r--r--lib/leap_cli/commands/user.rb106
-rw-r--r--lib/leap_cli/commands/util.rb125
-rw-r--r--lib/leap_cli/config/manager.rb18
-rw-r--r--lib/leap_cli/log.rb19
-rw-r--r--lib/leap_cli/path.rb10
-rw-r--r--lib/leap_cli/util.rb171
13 files changed, 551 insertions, 39 deletions
diff --git a/DEVNOTES b/DEVNOTES
index 967f2a6..713d8c2 100644
--- a/DEVNOTES
+++ b/DEVNOTES
@@ -44,8 +44,9 @@ useful liberaries
notes to myself
user interaction
+ gli -- http://davetron5000.github.com/gli/rdoc/classes/GLI/DSL.html
readline
- highline
+ highline https://github.com/JEG2/highline/tree/master/examples
terminal-tables
rainbow
http://stackoverflow.com/questions/9577718/what-ruby-libraries-should-i-use-for-building-a-console-based-application
@@ -58,13 +59,11 @@ help
ronn -- write man pages in markdown
push examples
+
https://github.com/net-ssh/net-ssh
https://github.com/seattlerb/rake-remote_task
http://docs.seattlerb.org/rake-remote_task/
https://github.com/seattlerb/rake-remote_task/blob/master/lib/rake/remote_task.rb
- https://github.com/davidwinter/sooty
- push puppet with rake/remote_task
- https://github.com/davidwinter/sooty/blob/master/lib/sooty.rb
calling rsync from ruby
https://github.com/RichGuk/rrsync/blob/master/rrsync.rb
http://rubyforge.org/projects/six-rsync/
@@ -74,3 +73,95 @@ push examples
https://github.com/delano/rye
https://github.com/adamwiggins/rush
+ssh keygen
+ https://github.com/duritong/puppet-sshd/blob/master/lib/puppet/parser/functions/ssh_keygen.rb
+
+invoke puppet
+ https://github.com/davidwinter/sooty/blob/master/lib/sooty.rb
+
+
+ssh
+================================
+
+fingerprints
+--------------------
+
+ssh-keygen -lf <keyfile> tells you the fingerprint of an encryption key
+
+ ls -1 /etc/ssh/*key*
+ /etc/ssh/ssh_host_dsa_key
+ /etc/ssh/ssh_host_dsa_key.pub
+ /etc/ssh/ssh_host_rsa_key
+ /etc/ssh/ssh_host_rsa_key.pub
+
+fetch the public host ida of a bunch of nodes:
+ ssh-keyscan -t rsa <host list>
+
+ssh certificate authority
+----------------------------------
+
+maybe wait off on this: "The certificate cert format seems to have changed between 5.5 and 6.0"
+
+search for "ssh-keygen -s"
+
+http://blog.habets.pp.se/2011/07/OpenSSH-certificates
+http://en.community.dell.com/techcenter/b/techcenter/archive/2011/09/08/setting-up-certificate-authority-keys-with-openssh-version-5-4.aspx
+http://serverfault.com/questions/264515/how-to-revoke-an-ssh-certificate-not-ssh-identity-file
+
+ruby
+---------------
+
+ruby net::ssh
+
+ def generate_key_fingerprint(key)
+ blob = Net::SSH::Buffer.from(:key, key).to_s
+ fingerprint = OpenSSL::Digest::MD5.hexdigest(blob).scan(/../).join(":")
+
+ [blob, fingerprint]
+ rescue ::Exception => e
+ [nil, "(could not generate fingerprint: #{e.message})"]
+ end
+
+ def exchange_keys
+ result = send_kexinit
+ verify_server_key(result[:server_key])
+ session_id = verify_signature(result)
+ confirm_newkeys
+
+ return { :session_id => session_id,
+ :server_key => result[:server_key],
+ :shared_secret => result[:shared_secret],
+ :hashing_algorithm => digester }
+ end
+
+DNS
+======================================
+
+problem: we want to be able to refer to the nodes by hostname (in a variety of programs) without requiring an external dns server.
+
+idea:
+
+ simple lightweight ruby dns server -- https://github.com/ioquatix/rubydns
+ another ruby dns server (eventmachine) -- https://github.com/nricciar/em-dns-server
+
+ modify /etc/resolveconf/resolve.conf.d/tail with
+ nameserver locahost
+ maybe like this:
+ resolveconf -a eth0.leap 'nameserver localhost'
+
+ the problem is that there is probably already a resolving nameserver living at localhost.
+ linux doesn't appear to have a way to let you specify the port number for dns lookups (unlike bsd). boo
+
+ a few other possibilies:
+ * alter /etc/hosts
+ * alter dnsmasq to use additional /etc/hosts files (simple switch for this). dnsmasq is running on my desktop, although there is no /etc/dnsmasq.
+ * write a libnss_ruby or something that would let you use a custom db for /etc/nsswitch.conf
+ see http://uw714doc.sco.com/en/SEC_admin/nssover.html
+
+ssh solution:
+
+ ssh -l root -o "HostName=10.9.8.7" -o "HostKeyAlias=server_a" server_a
+..
+
+
+
diff --git a/README.md b/README.md
index fccd6d1..3995533 100644
--- a/README.md
+++ b/README.md
@@ -77,9 +77,35 @@ Options in the configuration files might be nested. For example:
}
}
-When compiled into hiera and made available in puppet, this becomes a Hash object with flattened keys:
+If the value string is prefixed with an '=' character, the value is evaluated as ruby. For example
+
+ {
+ "domain": {
+ "public": "domain.org"
+ }
+ "api_domain": "= 'api.' + domain.public"
+ }
+
+In this case, "api_domain" will be set to "api.domain.org".
+
+The following methods are available to the evaluated ruby:
+
+* nodes -- A list of all nodes. This list can be filtered.
+
+* global.services -- A list of all services.
+
+* global.tags -- A list of all tags.
+
+* file(file_path) -- Inserts the full contents of the file. If the file is an erb
+ template, it is rendered. The file is searched for by first checking platform
+ and then provider/files,
+
+* variable -- Any variable inherited by a particular node is available
+ by just referencing it using either hash notation or object notation
+ (i.e. self['domain']['public'] or domain.public). Circular
+ references are not allowed, but otherwise it is ok to nest
+ evaluated values in other evaluated values.
- {"openvpn.ip_address" => "1.1.1.1"}
Node Configuration
=================================
diff --git a/leap_cli.gemspec b/leap_cli.gemspec
index 6a495f0..cf785ba 100644
--- a/leap_cli.gemspec
+++ b/leap_cli.gemspec
@@ -43,4 +43,6 @@ spec = Gem::Specification.new do |s|
s.add_runtime_dependency('json_pure')
s.add_runtime_dependency('terminal-table')
s.add_runtime_dependency('highline')
+ s.add_runtime_dependency('gpgme')
+
end
diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb
index 5d35c1e..0b3a59f 100644
--- a/lib/leap_cli.rb
+++ b/lib/leap_cli.rb
@@ -11,12 +11,12 @@ require 'core_ext/nil'
require 'leap_cli/init'
require 'leap_cli/path'
+require 'leap_cli/util'
require 'leap_cli/log'
require 'leap_cli/config/object'
require 'leap_cli/config/object_list'
require 'leap_cli/config/manager'
-
#
# make 1.8 act like ruby 1.9
#
diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb
index 8764e52..3e9d42d 100644
--- a/lib/leap_cli/commands/compile.rb
+++ b/lib/leap_cli/commands/compile.rb
@@ -5,7 +5,7 @@ module LeapCli
command :compile do |c|
c.action do |global_options,options,args|
manager.load(Path.provider)
- Path.ensure_dir(Path.hiera)
+ ensure_dir(Path.hiera)
manager.export(Path.hiera)
end
end
diff --git a/lib/leap_cli/commands/init.rb b/lib/leap_cli/commands/init.rb
index 75cc876..de43a45 100644
--- a/lib/leap_cli/commands/init.rb
+++ b/lib/leap_cli/commands/init.rb
@@ -7,7 +7,7 @@ module LeapCli
c.action do |global_options,options,args|
directory = args.first
unless directory && directory.any?
- help_now! "Directory name is required."
+ help! "Directory name is required."
end
directory = File.expand_path(directory)
if File.exists?(directory)
diff --git a/lib/leap_cli/commands/pre.rb b/lib/leap_cli/commands/pre.rb
index 2281bf6..ada6a6a 100644
--- a/lib/leap_cli/commands/pre.rb
+++ b/lib/leap_cli/commands/pre.rb
@@ -7,7 +7,7 @@ module LeapCli
desc 'Verbosity level 0..2'
arg_name 'level'
- default_value '0'
+ default_value '1'
flag [:v, :verbose]
desc 'Specify the root directory'
@@ -30,7 +30,7 @@ module LeapCli
if Path.ok?
true
else
- fail!("Could not find the root directory. Change current working directory or try --root")
+ bail!("Could not find the root directory. Change current working directory or try --root")
end
end
diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb
new file mode 100644
index 0000000..af59074
--- /dev/null
+++ b/lib/leap_cli/commands/user.rb
@@ -0,0 +1,106 @@
+require 'gpgme'
+
+#
+# notes:
+#
+# file ~/.gnupg/00440025.asc
+# /home/elijah/.gnupg/00440025.asc: PGP public key block
+#
+# file ~/.ssh/id_rsa.pub
+# /home/elijah/.ssh/id_rsa.pub: OpenSSH RSA public key
+#
+
+module LeapCli
+ module Commands
+
+ desc 'adds a new trusted sysadmin'
+ arg_name '<username>', :optional => false, :multiple => false
+ command :'add-user' do |c|
+
+ c.switch 'self', :desc => 'lets you choose among your public keys', :negatable => false
+ c.flag 'ssh-pub-key', :desc => 'SSH public key file for this new user'
+ c.flag 'pgp-pub-key', :desc => 'OpenPGP public key file for this new user'
+
+ c.action do |global_options,options,args|
+ username = args.first
+ if !username.any? && !options[:self]
+ help! "Either 'username' or --self is required."
+ end
+
+ ssh_pub_key = nil
+ pgp_pub_key = nil
+
+ if options['ssh-pub-key']
+ ssh_pub_key = read_file!(options['ssh-pub-key'])
+ end
+ if options['pgp-pub-key']
+ pgp_pub_key = read_file!(options['pgp-pub-key'])
+ end
+
+ if options[:self]
+ username ||= `whoami`.strip
+ ssh_pub_key ||= pick_ssh_key
+ pgp_pub_key ||= pick_pgp_key
+ end
+
+ assert!(ssh_pub_key, 'Sorry, could not find SSH public key.')
+ assert!(pgp_pub_key, 'Sorry, could not find OpenPGP public key.')
+
+ if ssh_pub_key
+ write_file!(:user_ssh, username, ssh_pub_key)
+ end
+ if pgp_pub_key
+ write_file!(:user_pgp, username, pgp_pub_key)
+ end
+ end
+ end
+
+ #
+ # let the the user choose among the ssh public keys that we encounter, or just pick the key if there is only one.
+ #
+ def pick_ssh_key
+ assert_bin! 'ssh-add'
+ ssh_fingerprints = `ssh-add -l`.split("\n").compact
+ assert! ssh_fingerprints.any?, 'Sorry, could not find any SSH public key for you. Have you run ssh-keygen?'
+
+ if ssh_fingerprints.length > 1
+ key_index = numbered_choice_menu('Choose your SSH public key', ssh_fingerprints) do |key, i|
+ say("#{i+1}. #{key}")
+ end
+ else
+ key_index = 0
+ end
+
+ ssh_keys = `ssh-add -L`.split("\n").compact
+ return ssh_keys[key_index]
+ end
+
+ #
+ # let the the user choose among the gpg public keys that we encounter, or just pick the key if there is only one.
+ #
+ def pick_pgp_key
+ secret_keys = GPGME::Key.find(:secret)
+
+ assert_bin! 'gpg'
+ assert! secret_keys.any?, 'Sorry, could not find any OpenPGP keys for you.'
+
+ if secret_keys.length > 1
+ key_index = numbered_choice_menu('Choose your OpenPGP public key', secret_keys) do |key, i|
+ key_info = key.to_s.split("\n")[0..1].map{|line| line.sub(/^\s*(sec|uid)\s*/,'')}.join(' -- ')
+ say("#{i+1}. #{key_info}")
+ end
+ else
+ key_index = 0
+ end
+
+ key_id = secret_keys[key_index].sha
+
+ # can't use this, it includes signatures:
+ #puts GPGME::Key.export(key_id, :armor => true, :export_options => :export_minimal)
+
+ # export with signatures removed:
+ return `gpg --armor --export-options export-minimal --export #{key_id}`.strip
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/commands/util.rb b/lib/leap_cli/commands/util.rb
new file mode 100644
index 0000000..ad4f01c
--- /dev/null
+++ b/lib/leap_cli/commands/util.rb
@@ -0,0 +1,125 @@
+module LeapCli
+ module Commands
+ extend self
+ extend LeapCli::Util
+# #
+# # 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/
+ bail!
+ else
+ i = index.to_i - 1
+ if i < 0 || i >= items.length
+ bail!
+ else
+ return i
+ end
+ end
+ end
+ end
+
+# #
+# # read a file, exit if the file doesn't exist.
+# #
+# def read_file!(file_path)
+# if !File.exists?(file_path)
+# bail!("File '%s' does not exist." % file_path)
+# else
+# File.readfile(file_path)
+# end
+# end
+
+# ##
+# ## LOGGING
+# ##
+
+# def log0(message=nil, &block)
+# if message
+# puts message
+# elsif block
+# puts yield(block)
+# end
+# end
+
+# def log1(message=nil, &block)
+# if LeapCli.log_level > 0
+# if message
+# puts message
+# elsif block
+# puts yield(block)
+# end
+# end
+# end
+
+# def log2(message=nil, &block)
+# if LeapCli.log_level > 1
+# if message
+# puts message
+# elsif block
+# puts yield(block)
+# end
+# end
+# end
+
+# def progress(message)
+# log1(" * " + message)
+# end
+
+# ##
+# ## QUITTING
+# ##
+
+# #
+# # quit and print help
+# #
+# def help!(message=nil)
+# ENV['GLI_DEBUG'] = "false"
+# help_now!(message)
+# #say("ERROR: " + message)
+# end
+
+# #
+# # quit with a message that we are bailing out.
+# #
+# def bail!(message="")
+# say(message)
+# say("Bailing out.")
+# raise SystemExit.new
+# #ENV['GLI_DEBUG'] = "false"
+# #exit_now!(message)
+# end
+
+# #
+# # quit with no message
+# #
+# def quit!(message='')
+# say(message)
+# raise SystemExit.new
+# end
+
+# #
+# # bails out with message if assertion is false.
+# #
+# def assert!(boolean, message)
+# bail!(message) unless boolean
+# end
+
+# #
+# # assert that the command is available
+# #
+# def assert_bin!(cmd_name)
+# assert! `which #{cmd_name}`.strip.any?, "Sorry, bailing out, the command '%s' is not installed." % cmd_name
+# end
+
+ end
+end
diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb
index 55575cf..b35251a 100644
--- a/lib/leap_cli/config/manager.rb
+++ b/lib/leap_cli/config/manager.rb
@@ -3,6 +3,7 @@ require 'yaml'
module LeapCli
module Config
+
#
# A class to manage all the objects in all the configuration files.
#
@@ -32,15 +33,16 @@ module LeapCli
# save compiled hiera .yaml files
#
def export(dir)
- Dir.glob(dir + '/*.yaml').each do |f|
- File.unlink(f)
- end
+ existing_files = Dir.glob(dir + '/*.yaml')
+ updated_files = []
@nodes.each do |name, node|
# not sure if people will approve of this change:
- # File.open("#{dir}/#{name}.#{node.domain_internal}.yaml", 'w') do |f|
- File.open("#{dir}/#{name}.yaml", 'w') do |f|
- f.write node.to_yaml
- end
+ filepath = "#{dir}/#{name}.yaml"
+ updated_files << filepath
+ Util::write_file!(filepath, node.to_yaml)
+ end
+ (existing_files - updated_files).each do |filepath|
+ Util::remove_file!(filepath)
end
end
@@ -99,7 +101,7 @@ module LeapCli
end
def load_json(filename, config_type)
- log2 { filename.sub(/^#{Regexp.escape(Path.root)}/,'') }
+ #log2 { filename.sub(/^#{Regexp.escape(Path.root)}/,'') }
#
# read file, strip out comments
diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb
index fe8e5ac..ac35eae 100644
--- a/lib/leap_cli/log.rb
+++ b/lib/leap_cli/log.rb
@@ -1,15 +1,19 @@
module LeapCli
+ extend self
- def self.log_level
+ def log_level
@log_level
end
- def self.log_level=(value)
+ def log_level=(value)
@log_level = value
end
-
end
+##
+## LOGGING
+##
+
def log0(message=nil, &block)
if message
puts message
@@ -38,12 +42,7 @@ def log2(message=nil, &block)
end
end
-def help!(message=nil)
- ENV['GLI_DEBUG'] = "false"
- help_now!(message)
+def progress(message)
+ log1(" * " + message)
end
-def fail!(message=nil)
- ENV['GLI_DEBUG'] = "false"
- exit_now!(message)
-end \ No newline at end of file
diff --git a/lib/leap_cli/path.rb b/lib/leap_cli/path.rb
index 5dc8fe8..f3cbad9 100644
--- a/lib/leap_cli/path.rb
+++ b/lib/leap_cli/path.rb
@@ -36,16 +36,6 @@ module LeapCli
raise "No such directory '#{@root}'" unless File.directory?(@root)
end
- def self.ensure_dir(dir)
- unless File.directory?(dir)
- if File.exists?(dir)
- raise 'Unable to create directory "%s", file already exists.' % dir
- else
- FileUtils.mkdir_p(dir)
- end
- end
- end
-
def self.find_file(name, filename)
path = [Path.files, filename].join('/')
return path if File.exists?(path)
diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb
new file mode 100644
index 0000000..67fca8d
--- /dev/null
+++ b/lib/leap_cli/util.rb
@@ -0,0 +1,171 @@
+require 'md5'
+
+module LeapCli
+ module Util
+ extend self
+
+ ##
+ ## QUITTING
+ ##
+
+ #
+ # quit and print help
+ #
+ def help!(message=nil)
+ ENV['GLI_DEBUG'] = "false"
+ help_now!(message)
+ #say("ERROR: " + message)
+ end
+
+ #
+ # quit with a message that we are bailing out.
+ #
+ def bail!(message="")
+ say(message)
+ say("Bailing out.")
+ raise SystemExit.new
+ #ENV['GLI_DEBUG'] = "false"
+ #exit_now!(message)
+ end
+
+ #
+ # quit with no message
+ #
+ def quit!(message='')
+ say(message)
+ raise SystemExit.new
+ end
+
+ #
+ # bails out with message if assertion is false.
+ #
+ def assert!(boolean, message)
+ bail!(message) unless boolean
+ end
+
+ #
+ # assert that the command is available
+ #
+ def assert_bin!(cmd_name)
+ assert! `which #{cmd_name}`.strip.any?, "Sorry, bailing out, the command '%s' is not installed." % cmd_name
+ end
+
+ ##
+ ## FILES AND DIRECTORIES
+ ##
+
+ def relative_path(path)
+ path.sub(/^#{Regexp.escape(Path.provider)}\//,'')
+ end
+
+ def progress_created(path)
+ progress 'created %s' % relative_path(path)
+ end
+
+ def progress_updated(path)
+ progress 'updated %s' % relative_path(path)
+ end
+
+ def progress_nochange(path)
+ progress 'no change %s' % relative_path(path)
+ end
+
+ def progress_removed(path)
+ progress 'removed %s' % relative_path(path)
+ end
+
+ #
+ # creates a directory if it doesn't already exist
+ #
+ def ensure_dir(dir)
+ unless File.directory?(dir)
+ if File.exists?(dir)
+ bail! 'Unable to create directory "%s", file already exists.' % dir
+ else
+ FileUtils.mkdir_p(dir)
+ unless dir =~ /\/$/
+ dir = dir + '/'
+ end
+ progress_created dir
+ end
+ end
+ end
+
+ NAMED_PATHS = {
+ :user_ssh => 'users/#{arg}/#{arg}_ssh.pub',
+ :user_pgp => 'users/#{arg}/#{arg}_pgp.pub'
+ }
+
+ #
+ # read a file, exit if the file doesn't exist.
+ #
+ def read_file!(file_path)
+ if !File.exists?(file_path)
+ bail!("File '%s' does not exist." % file_path)
+ else
+ File.readfile(file_path)
+ end
+ end
+
+ def write_file!(*args)
+ if args.first.is_a? Symbol
+ write_named_file!(*args)
+ else
+ write_to_path!(*args)
+ end
+ end
+
+ def remove_file!(file_path)
+ if File.exists?(file_path)
+ File.unlink(file_path)
+ progress_removed(file_path)
+ end
+ end
+
+ #
+ # saves a named file
+ #
+ def write_named_file!(name, arg, contents)
+ assert!(NAMED_PATHS[name], "Error, I don't know the path for #{arg}")
+
+ filename = eval('"' + NAMED_PATHS[name] + '"')
+ fullpath = Path.provider + '/' + filename
+
+ write_to_path!(fullpath, contents)
+ end
+
+ def write_to_path!(filepath, contents)
+ ensure_dir File.dirname(filepath)
+ existed = File.exists?(filepath)
+ if existed
+ if file_content_is?(filepath, contents)
+ progress_nochange filepath
+ return
+ end
+ end
+
+ File.open(filepath, 'w') do |f|
+ f.write contents
+ end
+
+ if existed
+ progress_updated filepath
+ else
+ progress_created filepath
+ end
+ end
+
+ private
+
+ def file_content_is?(filepath, contents)
+ output = `md5sum '#{filepath}'`.strip
+ if $?.to_i == 0
+ return output.split(" ").first == MD5.md5(contents).to_s
+ else
+ return false
+ end
+ end
+
+ end
+end
+