leap_cli 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/leap +81 -0
- data/lib/core_ext/boolean.rb +14 -0
- data/lib/core_ext/hash.rb +35 -0
- data/lib/core_ext/json.rb +42 -0
- data/lib/core_ext/nil.rb +5 -0
- data/lib/core_ext/string.rb +14 -0
- data/lib/leap/platform.rb +52 -0
- data/lib/leap_cli/commands/ca.rb +430 -0
- data/lib/leap_cli/commands/clean.rb +16 -0
- data/lib/leap_cli/commands/compile.rb +134 -0
- data/lib/leap_cli/commands/deploy.rb +172 -0
- data/lib/leap_cli/commands/facts.rb +93 -0
- data/lib/leap_cli/commands/inspect.rb +140 -0
- data/lib/leap_cli/commands/list.rb +122 -0
- data/lib/leap_cli/commands/new.rb +126 -0
- data/lib/leap_cli/commands/node.rb +272 -0
- data/lib/leap_cli/commands/pre.rb +99 -0
- data/lib/leap_cli/commands/shell.rb +67 -0
- data/lib/leap_cli/commands/test.rb +55 -0
- data/lib/leap_cli/commands/user.rb +140 -0
- data/lib/leap_cli/commands/util.rb +50 -0
- data/lib/leap_cli/commands/vagrant.rb +201 -0
- data/lib/leap_cli/config/macros.rb +369 -0
- data/lib/leap_cli/config/manager.rb +369 -0
- data/lib/leap_cli/config/node.rb +37 -0
- data/lib/leap_cli/config/object.rb +336 -0
- data/lib/leap_cli/config/object_list.rb +174 -0
- data/lib/leap_cli/config/secrets.rb +43 -0
- data/lib/leap_cli/config/tag.rb +18 -0
- data/lib/leap_cli/constants.rb +7 -0
- data/lib/leap_cli/leapfile.rb +97 -0
- data/lib/leap_cli/load_paths.rb +15 -0
- data/lib/leap_cli/log.rb +166 -0
- data/lib/leap_cli/logger.rb +216 -0
- data/lib/leap_cli/markdown_document_listener.rb +134 -0
- data/lib/leap_cli/path.rb +84 -0
- data/lib/leap_cli/remote/leap_plugin.rb +204 -0
- data/lib/leap_cli/remote/puppet_plugin.rb +66 -0
- data/lib/leap_cli/remote/rsync_plugin.rb +35 -0
- data/lib/leap_cli/remote/tasks.rb +36 -0
- data/lib/leap_cli/requirements.rb +19 -0
- data/lib/leap_cli/ssh_key.rb +130 -0
- data/lib/leap_cli/util/remote_command.rb +110 -0
- data/lib/leap_cli/util/secret.rb +54 -0
- data/lib/leap_cli/util/x509.rb +32 -0
- data/lib/leap_cli/util.rb +431 -0
- data/lib/leap_cli/version.rb +9 -0
- data/lib/leap_cli.rb +46 -0
- data/lib/lib_ext/capistrano_connections.rb +16 -0
- data/lib/lib_ext/gli.rb +52 -0
- data/lib/lib_ext/markdown_document_listener.rb +122 -0
- data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +200 -0
- data/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +77 -0
- data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +97 -0
- data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +266 -0
- data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +148 -0
- data/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb +144 -0
- data/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb +65 -0
- data/vendor/certificate_authority/lib/certificate_authority/revocable.rb +14 -0
- data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +10 -0
- data/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb +16 -0
- data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +56 -0
- data/vendor/certificate_authority/lib/certificate_authority.rb +21 -0
- data/vendor/rsync_command/lib/rsync_command/ssh_options.rb +159 -0
- data/vendor/rsync_command/lib/rsync_command/thread_pool.rb +36 -0
- data/vendor/rsync_command/lib/rsync_command/version.rb +3 -0
- data/vendor/rsync_command/lib/rsync_command.rb +96 -0
- data/vendor/rsync_command/test/rsync_test.rb +74 -0
- data/vendor/rsync_command/test/ssh_options_test.rb +61 -0
- data/vendor/vagrant_ssh_keys/vagrant.key +27 -0
- data/vendor/vagrant_ssh_keys/vagrant.pub +1 -0
- metadata +345 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'gpgme'
|
2
|
+
|
3
|
+
#
|
4
|
+
# perhaps we want to verify that the key files are actually the key files we expect.
|
5
|
+
# we could use 'file' for this:
|
6
|
+
#
|
7
|
+
# > file ~/.gnupg/00440025.asc
|
8
|
+
# ~/.gnupg/00440025.asc: PGP public key block
|
9
|
+
#
|
10
|
+
# > file ~/.ssh/id_rsa.pub
|
11
|
+
# ~/.ssh/id_rsa.pub: OpenSSH RSA public key
|
12
|
+
#
|
13
|
+
|
14
|
+
module LeapCli
|
15
|
+
module Commands
|
16
|
+
|
17
|
+
desc 'Adds a new trusted sysadmin by adding public keys to the "users" directory.'
|
18
|
+
arg_name 'USERNAME' #, :optional => false, :multiple => false
|
19
|
+
command :'add-user' do |c|
|
20
|
+
|
21
|
+
c.switch 'self', :desc => 'Add yourself as a trusted sysadin by choosing among the public keys available for the current user.', :negatable => false
|
22
|
+
c.flag 'ssh-pub-key', :desc => 'SSH public key file for this new user'
|
23
|
+
c.flag 'pgp-pub-key', :desc => 'OpenPGP public key file for this new user'
|
24
|
+
|
25
|
+
c.action do |global_options,options,args|
|
26
|
+
username = args.first
|
27
|
+
if !username.any? && !options[:self]
|
28
|
+
help! "Either 'username' or --self is required."
|
29
|
+
end
|
30
|
+
|
31
|
+
ssh_pub_key = nil
|
32
|
+
pgp_pub_key = nil
|
33
|
+
|
34
|
+
if options['ssh-pub-key']
|
35
|
+
ssh_pub_key = read_file!(options['ssh-pub-key'])
|
36
|
+
end
|
37
|
+
if options['pgp-pub-key']
|
38
|
+
pgp_pub_key = read_file!(options['pgp-pub-key'])
|
39
|
+
end
|
40
|
+
|
41
|
+
if options[:self]
|
42
|
+
username ||= `whoami`.strip
|
43
|
+
ssh_pub_key ||= pick_ssh_key.to_s
|
44
|
+
pgp_pub_key ||= pick_pgp_key
|
45
|
+
end
|
46
|
+
|
47
|
+
assert!(ssh_pub_key, 'Sorry, could not find SSH public key.')
|
48
|
+
|
49
|
+
if ssh_pub_key
|
50
|
+
write_file!([:user_ssh, username], ssh_pub_key)
|
51
|
+
end
|
52
|
+
if pgp_pub_key
|
53
|
+
write_file!([:user_pgp, username], pgp_pub_key)
|
54
|
+
end
|
55
|
+
|
56
|
+
update_authorized_keys
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# let the the user choose among the ssh public keys that we encounter, or just pick the key if there is only one.
|
62
|
+
#
|
63
|
+
def pick_ssh_key
|
64
|
+
ssh_keys = []
|
65
|
+
Dir.glob("#{ENV['HOME']}/.ssh/*.pub").each do |keyfile|
|
66
|
+
ssh_keys << SshKey.load(keyfile)
|
67
|
+
end
|
68
|
+
|
69
|
+
if `which ssh-add`.strip.any?
|
70
|
+
`ssh-add -L 2> /dev/null`.split("\n").compact.each do |line|
|
71
|
+
key = SshKey.load(line)
|
72
|
+
key.comment = 'ssh-agent'
|
73
|
+
ssh_keys << key unless ssh_keys.include?(key)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
ssh_keys.compact!
|
77
|
+
|
78
|
+
assert! ssh_keys.any?, 'Sorry, could not find any SSH public key for you. Have you run ssh-keygen?'
|
79
|
+
|
80
|
+
if ssh_keys.length > 1
|
81
|
+
key_index = numbered_choice_menu('Choose your SSH public key', ssh_keys.collect(&:summary)) do |line, i|
|
82
|
+
say("#{i+1}. #{line}")
|
83
|
+
end
|
84
|
+
else
|
85
|
+
key_index = 0
|
86
|
+
end
|
87
|
+
|
88
|
+
return ssh_keys[key_index]
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# let the the user choose among the gpg public keys that we encounter, or just pick the key if there is only one.
|
93
|
+
#
|
94
|
+
def pick_pgp_key
|
95
|
+
secret_keys = GPGME::Key.find(:secret)
|
96
|
+
if secret_keys.empty?
|
97
|
+
log "Skipping OpenPGP setup because I could not find any OpenPGP keys for you"
|
98
|
+
return nil
|
99
|
+
end
|
100
|
+
|
101
|
+
assert_bin! 'gpg'
|
102
|
+
|
103
|
+
if secret_keys.length > 1
|
104
|
+
key_index = numbered_choice_menu('Choose your OpenPGP public key', secret_keys) do |key, i|
|
105
|
+
key_info = key.to_s.split("\n")[0..1].map{|line| line.sub(/^\s*(sec|uid)\s*/,'')}.join(' -- ')
|
106
|
+
say("#{i+1}. #{key_info}")
|
107
|
+
end
|
108
|
+
else
|
109
|
+
key_index = 0
|
110
|
+
end
|
111
|
+
|
112
|
+
key_id = secret_keys[key_index].sha
|
113
|
+
|
114
|
+
# can't use this, it includes signatures:
|
115
|
+
#puts GPGME::Key.export(key_id, :armor => true, :export_options => :export_minimal)
|
116
|
+
|
117
|
+
# export with signatures removed:
|
118
|
+
return `gpg --armor --export-options export-minimal --export #{key_id}`.strip
|
119
|
+
end
|
120
|
+
|
121
|
+
def update_authorized_keys
|
122
|
+
buffer = StringIO.new
|
123
|
+
keys = Dir.glob(path([:user_ssh, '*']))
|
124
|
+
if keys.empty?
|
125
|
+
bail! "You must have at least one public SSH user key configured in order to proceed. See `leap help add-user`."
|
126
|
+
end
|
127
|
+
keys.sort.each do |keyfile|
|
128
|
+
ssh_type, ssh_key = File.read(keyfile).strip.split(" ")
|
129
|
+
buffer << ssh_type
|
130
|
+
buffer << " "
|
131
|
+
buffer << ssh_key
|
132
|
+
buffer << " "
|
133
|
+
buffer << Path.relative_path(keyfile)
|
134
|
+
buffer << "\n"
|
135
|
+
end
|
136
|
+
write_file!(:authorized_keys, buffer.string)
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module LeapCli; module Commands
|
2
|
+
|
3
|
+
extend self
|
4
|
+
extend LeapCli::Util
|
5
|
+
extend LeapCli::Util::RemoteCommand
|
6
|
+
|
7
|
+
def path(name)
|
8
|
+
Path.named_path(name)
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# keeps prompting the user for a numbered choice, until they pick a good one or bail out.
|
13
|
+
#
|
14
|
+
# block is yielded and is responsible for rendering the choices.
|
15
|
+
#
|
16
|
+
def numbered_choice_menu(msg, items, &block)
|
17
|
+
while true
|
18
|
+
say("\n" + msg + ':')
|
19
|
+
items.each_with_index &block
|
20
|
+
say("q. quit")
|
21
|
+
index = ask("number 1-#{items.length}> ")
|
22
|
+
if index.empty?
|
23
|
+
next
|
24
|
+
elsif index =~ /q/
|
25
|
+
bail!
|
26
|
+
else
|
27
|
+
i = index.to_i - 1
|
28
|
+
if i < 0 || i >= items.length
|
29
|
+
bail!
|
30
|
+
else
|
31
|
+
return i
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def parse_node_list(nodes)
|
39
|
+
if nodes.is_a? Config::Object
|
40
|
+
Config::ObjectList.new(nodes)
|
41
|
+
elsif nodes.is_a? Config::ObjectList
|
42
|
+
nodes
|
43
|
+
elsif nodes.is_a? String
|
44
|
+
manager.filter!(nodes)
|
45
|
+
else
|
46
|
+
bail! "argument error"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end; end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module LeapCli; module Commands
|
5
|
+
|
6
|
+
desc "Manage local virtual machines."
|
7
|
+
long_desc "This command provides a convient way to manage Vagrant-based virtual machines. If FILTER argument is missing, the command runs on all local virtual machines. The Vagrantfile is automatically generated in 'test/Vagrantfile'. If you want to run vagrant commands manually, cd to 'test'."
|
8
|
+
command :local do |local|
|
9
|
+
local.desc 'Starts up the virtual machine(s)'
|
10
|
+
local.arg_name 'FILTER', :optional => true #, :multiple => false
|
11
|
+
local.command :start do |start|
|
12
|
+
start.action do |global_options,options,args|
|
13
|
+
vagrant_command(["up", "sandbox on"], args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
local.desc 'Shuts down the virtual machine(s)'
|
18
|
+
local.arg_name 'FILTER', :optional => true #, :multiple => false
|
19
|
+
local.command :stop do |stop|
|
20
|
+
stop.action do |global_options,options,args|
|
21
|
+
if global_options[:yes]
|
22
|
+
vagrant_command("halt --force", args)
|
23
|
+
else
|
24
|
+
vagrant_command("halt", args)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
local.desc 'Destroys the virtual machine(s), reclaiming the disk space'
|
30
|
+
local.arg_name 'FILTER', :optional => true #, :multiple => false
|
31
|
+
local.command :destroy do |destroy|
|
32
|
+
destroy.action do |global_options,options,args|
|
33
|
+
if global_options[:yes]
|
34
|
+
vagrant_command("destroy --force", args)
|
35
|
+
else
|
36
|
+
vagrant_command("destroy", args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
local.desc 'Print the status of local virtual machine(s)'
|
42
|
+
local.arg_name 'FILTER', :optional => true #, :multiple => false
|
43
|
+
local.command :status do |status|
|
44
|
+
status.action do |global_options,options,args|
|
45
|
+
vagrant_command("status", args)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
local.desc 'Saves the current state of the virtual machine as a new snapshot'
|
50
|
+
local.arg_name 'FILTER', :optional => true #, :multiple => false
|
51
|
+
local.command :save do |status|
|
52
|
+
status.action do |global_options,options,args|
|
53
|
+
vagrant_command("sandbox commit", args)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
local.desc 'Resets virtual machine(s) to the last saved snapshot'
|
58
|
+
local.arg_name 'FILTER', :optional => true #, :multiple => false
|
59
|
+
local.command :reset do |reset|
|
60
|
+
reset.action do |global_options,options,args|
|
61
|
+
vagrant_command("sandbox rollback", args)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
public
|
67
|
+
|
68
|
+
#
|
69
|
+
# returns the path to a vagrant ssh key file.
|
70
|
+
#
|
71
|
+
# if the vagrant.key file is owned by root or ourselves, then
|
72
|
+
# we need to make sure that it owned by us and not world readable.
|
73
|
+
#
|
74
|
+
def vagrant_ssh_key_file
|
75
|
+
file_path = File.expand_path('../../../vendor/vagrant_ssh_keys/vagrant.key', File.dirname(__FILE__))
|
76
|
+
Util.assert_files_exist! file_path
|
77
|
+
uid = File.new(file_path).stat.uid
|
78
|
+
if uid == 0 || uid == Process.euid
|
79
|
+
FileUtils.install file_path, '/tmp/vagrant.key', :mode => 0600
|
80
|
+
file_path = '/tmp/vagrant.key'
|
81
|
+
end
|
82
|
+
return file_path
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def vagrant_command(cmds, args)
|
88
|
+
vagrant_setup
|
89
|
+
cmds = cmds.to_a
|
90
|
+
if args.empty?
|
91
|
+
nodes = [""]
|
92
|
+
else
|
93
|
+
nodes = manager.filter(args)[:environment => "local"].field(:name)
|
94
|
+
end
|
95
|
+
if nodes.any?
|
96
|
+
vagrant_dir = File.dirname(Path.named_path(:vagrantfile))
|
97
|
+
exec = ["cd #{vagrant_dir}"]
|
98
|
+
cmds.each do |cmd|
|
99
|
+
nodes.each do |node|
|
100
|
+
exec << "vagrant #{cmd} #{node}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
execute exec.join('; ')
|
104
|
+
else
|
105
|
+
bail! "No nodes found. This command only works on nodes with ip_address in the network #{LeapCli.leapfile.vagrant_network}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def vagrant_setup
|
112
|
+
assert_bin! 'vagrant', 'Vagrant is required for running local virtual machines. Run "sudo apt-get install vagrant".'
|
113
|
+
|
114
|
+
version = vagrant_version
|
115
|
+
case version
|
116
|
+
when 0..1
|
117
|
+
gem_path = assert_run!('vagrant gem which sahara')
|
118
|
+
if gem_path.nil? || gem_path.empty? || gem_path =~ /^ERROR/
|
119
|
+
log :installing, "vagrant plugin 'sahara'"
|
120
|
+
assert_run! 'vagrant gem install sahara -v 0.0.13'
|
121
|
+
# (sahara versions above 0.0.13 require vagrant > 1.0)
|
122
|
+
end
|
123
|
+
when 2
|
124
|
+
unless assert_run!('vagrant plugin list | grep sahara | cat').chars.any?
|
125
|
+
log :installing, "vagrant plugin 'sahara'"
|
126
|
+
assert_run! 'vagrant plugin install sahara'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
create_vagrant_file
|
130
|
+
end
|
131
|
+
|
132
|
+
def vagrant_version
|
133
|
+
minor_version = `vagrant --version | rev | cut -d'.' -f 2`.to_i
|
134
|
+
version = case minor_version
|
135
|
+
when 1..9 then 2
|
136
|
+
when 0 then 1
|
137
|
+
else 0
|
138
|
+
end
|
139
|
+
return version
|
140
|
+
end
|
141
|
+
|
142
|
+
def execute(cmd)
|
143
|
+
log 2, :run, cmd
|
144
|
+
exec cmd
|
145
|
+
end
|
146
|
+
|
147
|
+
def create_vagrant_file
|
148
|
+
lines = []
|
149
|
+
netmask = IPAddr.new('255.255.255.255').mask(LeapCli.leapfile.vagrant_network.split('/').last).to_s
|
150
|
+
|
151
|
+
version = vagrant_version
|
152
|
+
case version
|
153
|
+
when 0..1
|
154
|
+
lines << %[Vagrant::Config.run do |config|]
|
155
|
+
manager.each_node do |node|
|
156
|
+
if node.vagrant?
|
157
|
+
lines << %[ config.vm.define :#{node.name} do |config|]
|
158
|
+
lines << %[ config.vm.box = "leap-wheezy"]
|
159
|
+
lines << %[ config.vm.box_url = "http://download.leap.se/leap-debian.box"]
|
160
|
+
lines << %[ config.vm.network :hostonly, "#{node.ip_address}", :netmask => "#{netmask}"]
|
161
|
+
lines << %[ config.vm.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]]
|
162
|
+
lines << %[ config.vm.customize ["modifyvm", :id, "--name", "#{node.name}"]]
|
163
|
+
lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line
|
164
|
+
lines << %[ end]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
when 2
|
168
|
+
lines << %[Vagrant.configure("2") do |config|]
|
169
|
+
manager.each_node do |node|
|
170
|
+
if node.vagrant?
|
171
|
+
lines << %[ config.vm.define :#{node.name} do |config|]
|
172
|
+
lines << %[ config.vm.box = "leap-wheezy"]
|
173
|
+
lines << %[ config.vm.box_url = "http://download.leap.se/leap-debian.box"]
|
174
|
+
lines << %[ config.vm.network :private_network, ip: "#{node.ip_address}"]
|
175
|
+
lines << %[ config.vm.provider "virtualbox" do |v|]
|
176
|
+
lines << %[ v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]]
|
177
|
+
lines << %[ v.name = "#{node.name}"]
|
178
|
+
lines << %[ end]
|
179
|
+
lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line
|
180
|
+
lines << %[ end]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
lines << %[end]
|
186
|
+
lines << ""
|
187
|
+
write_file! :vagrantfile, lines.join("\n")
|
188
|
+
end
|
189
|
+
|
190
|
+
def pick_next_vagrant_ip_address
|
191
|
+
taken_ips = manager.nodes[:environment => "local"].field(:ip_address)
|
192
|
+
if taken_ips.any?
|
193
|
+
highest_ip = taken_ips.map{|ip| IPAddr.new(ip)}.max
|
194
|
+
new_ip = highest_ip.succ
|
195
|
+
else
|
196
|
+
new_ip = IPAddr.new(LeapCli.leapfile.vagrant_network).succ.succ
|
197
|
+
end
|
198
|
+
return new_ip.to_s
|
199
|
+
end
|
200
|
+
|
201
|
+
end; end
|