capistrano_recia 0.0.2
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/README.rdoc +64 -0
- data/RELEASE.rdoc +9 -0
- data/lib/capistrano_recia.rb +9 -0
- data/lib/capistrano_recia/filter.rb +91 -0
- data/lib/capistrano_recia/parents.rb +77 -0
- data/lib/capistrano_recia/password.rb +7 -0
- data/lib/capistrano_recia/password/manager.rb +143 -0
- metadata +120 -0
data/README.rdoc
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
= RECIA Capistrano
|
2
|
+
|
3
|
+
This gem is used by the recia to implements specific behavior in capistrano. It furnish the following services :
|
4
|
+
|
5
|
+
- parents : call of hosts' parents to execute action on them
|
6
|
+
- filters : filter hosts to execute by its roles
|
7
|
+
- password manager : help to generate password and secure them.
|
8
|
+
|
9
|
+
== Parents
|
10
|
+
|
11
|
+
The function is to execute command on the parent host of current hosts. To do this, we need the gem "nagios_mklivestatus".
|
12
|
+
|
13
|
+
To call the function from a task, we need to call a specific method :
|
14
|
+
|
15
|
+
run_parent(cmd)
|
16
|
+
|
17
|
+
This will select only the servers' parents containing the guest (select parents which are servers).
|
18
|
+
There are some notable informations.
|
19
|
+
|
20
|
+
The method works as the standard run, except that the ENV['HOSTS'] are remplaced by their parents.
|
21
|
+
The command is replaced by its Capistrano::Command::Script version and the original hosts are ordered by their parents and given to the script.
|
22
|
+
If some variables are encountered, a loop is created to go through all guest of each host (parents).
|
23
|
+
|
24
|
+
Those are for functionality purpose if you need to execute command with guest as variables you should use this :
|
25
|
+
|
26
|
+
$PARENT:GUEST$ : variable that will be replaced by the name of the guest (we'll create a loop in script)
|
27
|
+
$PARENT:LOOP:START$ : variable that will be replaced by the start of the loop (if not defined and $PARENT:GUEST$ exists, its automatically placed at the start of the command)
|
28
|
+
$PARENT:LOOP:END$ : variable that will be replaced by the end of the loop (if not defined and $PARENT:GUEST$ exists, its automatically placed at the end of the command)
|
29
|
+
|
30
|
+
== Filters
|
31
|
+
|
32
|
+
This module is made to filter hosts with the roles in task options. It furnish 2 methods for task to run :
|
33
|
+
|
34
|
+
run_filter_roles(cmd) # the host must match one of the roles (connection SSH)
|
35
|
+
run_telnet_filter_roles(cmd) # the host must match one of the roles (connection Telnet)
|
36
|
+
|
37
|
+
If hosts does not correspond to the filter, they are removed from the list (with informations display) and the command is executed on the remaining hosts.
|
38
|
+
|
39
|
+
== Password manager
|
40
|
+
|
41
|
+
This is a class which help to manage password security inside capistrano. It's help to generate password (with save in a crypted file), and check the corruption of this password, plus it can securize all the password through a security automaton
|
42
|
+
|
43
|
+
To use the manager:
|
44
|
+
|
45
|
+
require 'capistrano_recia'
|
46
|
+
manager = Capistrano::Password::Manager.new(<file_path>)
|
47
|
+
# generate pass
|
48
|
+
new_pass = manager.generate
|
49
|
+
# generate and save
|
50
|
+
new_pass = manager.generate("key")
|
51
|
+
|
52
|
+
# test corruption
|
53
|
+
is_corrupt = manager.is_corrupted? "key"
|
54
|
+
|
55
|
+
# corrupt password (giving it to a non authorized person
|
56
|
+
pass = manager.corrupt "key"
|
57
|
+
|
58
|
+
# live the password uncorrupted when asking for it
|
59
|
+
pass = manager.live_uncorrupt "key"
|
60
|
+
|
61
|
+
# securize all corrupted password
|
62
|
+
# securized_pass is a hash of key => password
|
63
|
+
securized_pass = manager.securize
|
64
|
+
|
data/RELEASE.rdoc
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'capistrano'
|
2
|
+
require 'capistrano_telnet'
|
3
|
+
require 'capistrano_supports'
|
4
|
+
require 'nagios_mklivestatus'
|
5
|
+
|
6
|
+
load File.join(File.dirname(__FILE__), File.basename(__FILE__, ".rb"), "parents.rb")
|
7
|
+
load File.join(File.dirname(__FILE__), File.basename(__FILE__, ".rb"), "filter.rb")
|
8
|
+
load File.join(File.dirname(__FILE__), File.basename(__FILE__, ".rb"), "password.rb")
|
9
|
+
|
@@ -0,0 +1,91 @@
|
|
1
|
+
##
|
2
|
+
# override the actions invocation class of capistrano
|
3
|
+
# to add the run_parent method and give access to it inside task definition
|
4
|
+
##
|
5
|
+
Capistrano::Configuration::Actions::Invocation.class_eval do
|
6
|
+
|
7
|
+
##
|
8
|
+
# Check if host contains task
|
9
|
+
##
|
10
|
+
def host_as_task_role(current_host)
|
11
|
+
# Active From debug Only !
|
12
|
+
# puts "DEBUG :: #{current_host}"
|
13
|
+
|
14
|
+
roles_ok_from_host = nil
|
15
|
+
|
16
|
+
current_task.options[:roles].each do |task_role|
|
17
|
+
|
18
|
+
# Active From debug Only !
|
19
|
+
# puts "DEBUG :: #{task_role.to_s} ::"
|
20
|
+
roles[:"#{task_role.to_s}"].servers.each do |server|
|
21
|
+
# Active From debug Only !
|
22
|
+
# puts "#{server}"
|
23
|
+
if server.host.to_s == current_host.to_s
|
24
|
+
roles_ok_from_host = 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
if roles_ok_from_host != nil && roles_ok_from_host != 0
|
29
|
+
return 1
|
30
|
+
else
|
31
|
+
return 0
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Use the role task option to filter the server with this role
|
37
|
+
##
|
38
|
+
def run_filter_roles(cmd, options={}, &block)
|
39
|
+
|
40
|
+
block ||= self.class.default_io_proc
|
41
|
+
|
42
|
+
hosts_server = find_servers_for_task(current_task)
|
43
|
+
hosts = Array.new
|
44
|
+
|
45
|
+
## calcul des rôles de la tâche
|
46
|
+
if current_task.options[:roles].kind_of? Array
|
47
|
+
current_task.options[:roles].each do |task_role|
|
48
|
+
task_roles = "#{task_roles} #{task_role}"
|
49
|
+
end
|
50
|
+
else
|
51
|
+
task_roles = "#{current_task.options[:roles]}"
|
52
|
+
current_task.options[:roles] = [current_task.options[:roles]]
|
53
|
+
end
|
54
|
+
|
55
|
+
# redistribution des machines par hôte
|
56
|
+
hosts_server.each do |server|
|
57
|
+
if host_as_task_role(server).to_i == 0
|
58
|
+
logger.info "Ce Serveur (#{server}) n'a pas un des role de la tache :#{task_roles}"
|
59
|
+
else
|
60
|
+
hosts.push(server.to_s)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if hosts != nil and not hosts.empty?
|
65
|
+
|
66
|
+
# save remaining hosts as the callee
|
67
|
+
ENV['HOSTS'] = hosts.join(',')
|
68
|
+
|
69
|
+
# execute action.
|
70
|
+
tree = Capistrano::Command::Tree.new(self) { |t| t.else(cmd, &block) }
|
71
|
+
run_tree(tree, options)
|
72
|
+
else
|
73
|
+
logger.important "Aucun serveurs ne possede un role : #{task_roles}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Use the role task option to filter the server with this role and run a telnet method
|
79
|
+
##
|
80
|
+
def run_telnet_filter_roles(cmd, options={}, &block)
|
81
|
+
|
82
|
+
set(:telnet, "true")
|
83
|
+
options[:shell] = false
|
84
|
+
|
85
|
+
run_filter_roles(cmd, options, &block)
|
86
|
+
|
87
|
+
unset(:telnet)
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
##
|
2
|
+
# Include Nagios Query Helper to Capistrano::Configuration
|
3
|
+
# in order to access helper from methods.
|
4
|
+
##
|
5
|
+
Capistrano::Configuration.class_eval do
|
6
|
+
include Nagios::MkLiveStatus::QueryHelper
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# override the actions invocation class of capistrano
|
11
|
+
# to add the run_parent method and give access to it inside task definition
|
12
|
+
##
|
13
|
+
Capistrano::Configuration::Actions::Invocation.class_eval do
|
14
|
+
|
15
|
+
##
|
16
|
+
# Calculate the parent server of those in parameters and execute the command on them.
|
17
|
+
# We can loop through the child by using:
|
18
|
+
# - $PARENT:GUEST$ : required if we loop through guests, its replaced by the child name of the host.
|
19
|
+
# - $PARENT:LOOP:START$ : (opt.) start of the loop if not defined its automatically positionned at the beginning of the command
|
20
|
+
# - $PARENT:LOOP:END$ : (opt.) end of the loop if not defined its automatically positionned at the end of the command
|
21
|
+
def run_parent(cmd, options={}, &block)
|
22
|
+
|
23
|
+
block ||= self.class.default_io_proc
|
24
|
+
|
25
|
+
guest_servers = find_servers_for_task(current_task)
|
26
|
+
hosts = Hash.new
|
27
|
+
|
28
|
+
# redistribution des machines par hôte
|
29
|
+
guest_servers.each do |server|
|
30
|
+
guest = server.to_s
|
31
|
+
mklive = Nagios::MkLiveStatus::Request.new(fetch(:nagios_path, nil))
|
32
|
+
query = Nagios::MkLiveStatus::Query.new()
|
33
|
+
query.get "hosts"
|
34
|
+
query.addColumn "parents"
|
35
|
+
query.addFilter nagmk_filter("host_name", "=", guest)
|
36
|
+
|
37
|
+
result = mklive.query(query)
|
38
|
+
|
39
|
+
parent = result.split("\n")[0] if result != nil
|
40
|
+
|
41
|
+
if parent == nil or parent.empty? or parent.match(/^rca/)
|
42
|
+
logger.important "Ce serveur (#{guest}) n'est pas une machine virtuelle : Aucun serveur hote trouve pour la machine"
|
43
|
+
else
|
44
|
+
if not hosts.key?(parent)
|
45
|
+
hosts[parent] = Array.new
|
46
|
+
end
|
47
|
+
hosts[parent].push(guest)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
# save parent hosts as the callee
|
53
|
+
ENV['HOSTS'] = hosts.keys.join(',')
|
54
|
+
|
55
|
+
# create a command script.
|
56
|
+
cmd = Capistrano::Command::Script.new("#{cmd.gsub(/\\/, '\\').gsub(/\"/,'\"')}") if cmd.kind_of? String
|
57
|
+
cmd['host'] = hosts
|
58
|
+
|
59
|
+
## replace vars if they are encountered
|
60
|
+
loop_start = "<% prop('host', Hash.new)['$CAPISTRANO:HOST$'].each do |guest| %>"
|
61
|
+
loop_end = "<% end %>"
|
62
|
+
loop_guest = "<%= guest %>"
|
63
|
+
|
64
|
+
if cmd.include? "$PARENT:GUEST$"
|
65
|
+
cmd.contents.insert(0, "$PARENT:LOOP:START$\n") if not cmd.include? "$PARENT:LOOP:START"
|
66
|
+
cmd.contents.insert(-1, "\n$PARENT:LOOP:END$") if not cmd.include? "$PARENT:LOOP:END"
|
67
|
+
end
|
68
|
+
|
69
|
+
cmd.gsub(/\$PARENT:LOOP:START\$/, loop_start)
|
70
|
+
cmd.gsub(/\$PARENT:LOOP:END\$/, loop_end)
|
71
|
+
cmd.gsub(/\$PARENT:GUEST\$/, loop_guest)
|
72
|
+
|
73
|
+
# execute action.
|
74
|
+
tree = Capistrano::Command::Tree.new(self) { |t| t.else(cmd, &block) }
|
75
|
+
run_tree(tree, options)
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
class Capistrano::Password::Manager
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
require 'encrypted_strings'
|
6
|
+
require 'keepass/password'
|
7
|
+
|
8
|
+
|
9
|
+
# constructor must determine the path to the save file.
|
10
|
+
# if we need to change the keepass regexp, the second parameter is used for it (default [S]20)
|
11
|
+
def initialize(file_path, keepass = "[S]{20}")
|
12
|
+
@file_path = file_path
|
13
|
+
@salt = "recia_password"
|
14
|
+
@keepass_regex = keepass
|
15
|
+
end
|
16
|
+
|
17
|
+
# to use only in a secure context (batch, admins who should know the pass)
|
18
|
+
def live_uncorrupt(save_key)
|
19
|
+
pass = nil
|
20
|
+
# load yaml file
|
21
|
+
yaml = load_file
|
22
|
+
# if yaml file contains informations
|
23
|
+
if yaml and yaml.has_key? save_key
|
24
|
+
# get the password
|
25
|
+
pass = yaml[save_key]["password"]
|
26
|
+
end
|
27
|
+
|
28
|
+
# decrypt the password
|
29
|
+
if pass
|
30
|
+
pass = pass.decrypt(:symmetric, :password => @salt)
|
31
|
+
end
|
32
|
+
|
33
|
+
# return password
|
34
|
+
return pass
|
35
|
+
end
|
36
|
+
|
37
|
+
# permet de savoir si le mot de passe est corrompu
|
38
|
+
def is_corrupted?(save_key)
|
39
|
+
# load yaml file
|
40
|
+
yaml = load_file
|
41
|
+
if yaml and yaml.has_key? save_key
|
42
|
+
# check if password is corrupted
|
43
|
+
corrupt = yaml[save_key]["corrupt"]
|
44
|
+
if corrupt.to_s == "true"
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
return false
|
50
|
+
end
|
51
|
+
|
52
|
+
# permet de récupérer les mots de passe corrompus
|
53
|
+
def corrupted()
|
54
|
+
list = Array.new
|
55
|
+
|
56
|
+
yaml = load_file
|
57
|
+
|
58
|
+
yaml.keys.each do |key|
|
59
|
+
if is_corrupted? key
|
60
|
+
list.push key
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
list
|
65
|
+
end
|
66
|
+
|
67
|
+
# permet de récupérer un mot de passe si sa clé de sauvegarde est enregistré, sinon retourne nil
|
68
|
+
def corrupt(save_key)
|
69
|
+
# get passwd
|
70
|
+
pass = live_uncorrupt save_key
|
71
|
+
|
72
|
+
# load yaml file
|
73
|
+
yaml = load_file
|
74
|
+
|
75
|
+
# if yaml file contains informations
|
76
|
+
if yaml and pass
|
77
|
+
# set password to corrupted
|
78
|
+
yaml[save_key]["corrupt"] = true
|
79
|
+
|
80
|
+
# save the information of corruption to the file
|
81
|
+
File.open(@file_path, "w") do |io|
|
82
|
+
io.write(yaml.to_yaml)
|
83
|
+
io.close
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# return password
|
88
|
+
return pass
|
89
|
+
end
|
90
|
+
|
91
|
+
# permet de générer un mot de passe.
|
92
|
+
# le premier paramètre indique si nous devons sauvegarder ou non le mot de passe (nil ou "" aucune sauvegarde)
|
93
|
+
# le deuxième paramètre est celui des options de keepass.
|
94
|
+
def generate(save_key="")
|
95
|
+
|
96
|
+
# generate a password without lookalikes (l, I, !, ...)
|
97
|
+
pass = KeePass::Password.generate(@keepass_regex, :remove_lookalikes => true)
|
98
|
+
|
99
|
+
# crypt the password
|
100
|
+
enc_pass = pass.encrypt(:symmetric, :password => @salt)
|
101
|
+
enc_pass.send :remove_instance_variable, :@cipher
|
102
|
+
|
103
|
+
# if a save key is passed
|
104
|
+
if save_key and not save_key.empty?
|
105
|
+
# load yaml file
|
106
|
+
yaml = load_file
|
107
|
+
yaml[save_key] = Hash.new if not yaml.has_key? save_key
|
108
|
+
# add password to file
|
109
|
+
yaml[save_key]["password"] = enc_pass
|
110
|
+
yaml[save_key]["corrupt"] = false
|
111
|
+
# save file
|
112
|
+
File.open(@file_path, "w") do |io|
|
113
|
+
io.write(yaml.to_yaml)
|
114
|
+
io.close
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
return pass, enc_pass
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
# regenerate all corrupted password. and return all the password which are regenerated by keys
|
123
|
+
def securize()
|
124
|
+
|
125
|
+
securized = Hash.new
|
126
|
+
|
127
|
+
# for each corrupted passwd
|
128
|
+
corrupted.each do |key|
|
129
|
+
securized[key] = Hash.new
|
130
|
+
securized[key]['old'] = corrupt(key)
|
131
|
+
securized[key]['new'], crypted = generate(key)
|
132
|
+
end
|
133
|
+
|
134
|
+
securized
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
def load_file()
|
139
|
+
yaml = Hash.new
|
140
|
+
yaml = YAML::load_file(@file_path) if File.exists? @file_path
|
141
|
+
yaml
|
142
|
+
end
|
143
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capistrano_recia
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Esco-lan Team
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: capistrano
|
16
|
+
requirement: &10927620 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.11.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *10927620
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: capistrano_telnet
|
27
|
+
requirement: &10927080 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.0.1
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *10927080
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: capistrano_supports
|
38
|
+
requirement: &10926320 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.0.1
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *10926320
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: nagios_mklivestatus
|
49
|
+
requirement: &10925540 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.0.11
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *10925540
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: keepass-password-generator
|
60
|
+
requirement: &10924860 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 0.1.1
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *10924860
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: encrypted_strings
|
71
|
+
requirement: &10923940 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 0.3.3
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *10923940
|
80
|
+
description: RECIA Capistrano Support
|
81
|
+
email: team@esco-lan.org
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files:
|
85
|
+
- README.rdoc
|
86
|
+
- RELEASE.rdoc
|
87
|
+
files:
|
88
|
+
- lib/capistrano_recia/filter.rb
|
89
|
+
- lib/capistrano_recia/password.rb
|
90
|
+
- lib/capistrano_recia/password/manager.rb
|
91
|
+
- lib/capistrano_recia/parents.rb
|
92
|
+
- lib/capistrano_recia.rb
|
93
|
+
- README.rdoc
|
94
|
+
- RELEASE.rdoc
|
95
|
+
homepage: https://github.com/RECIA/capistrano_recia
|
96
|
+
licenses: []
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 1.8.8
|
116
|
+
signing_key:
|
117
|
+
specification_version: 3
|
118
|
+
summary: Provides additionnal methods to capistrano like run_parent, run_filter_role_contained
|
119
|
+
and run_filter_role_uncontained
|
120
|
+
test_files: []
|