puppet 0.9.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (182) hide show
  1. data/CHANGELOG +0 -0
  2. data/COPYING +340 -0
  3. data/LICENSE +17 -0
  4. data/README +24 -0
  5. data/Rakefile +294 -0
  6. data/TODO +4 -0
  7. data/bin/cf2puppet +186 -0
  8. data/bin/puppet +176 -0
  9. data/bin/puppetca +213 -0
  10. data/bin/puppetd +246 -0
  11. data/bin/puppetdoc +184 -0
  12. data/bin/puppetmasterd +258 -0
  13. data/examples/code/allatonce +13 -0
  14. data/examples/code/assignments +11 -0
  15. data/examples/code/classing +35 -0
  16. data/examples/code/components +73 -0
  17. data/examples/code/execs +16 -0
  18. data/examples/code/failers/badclassnoparam +10 -0
  19. data/examples/code/failers/badclassparam +10 -0
  20. data/examples/code/failers/badcompnoparam +9 -0
  21. data/examples/code/failers/badcompparam +9 -0
  22. data/examples/code/failers/badtypeparam +3 -0
  23. data/examples/code/file.bl +11 -0
  24. data/examples/code/filedefaults +10 -0
  25. data/examples/code/fileparsing +116 -0
  26. data/examples/code/filerecursion +15 -0
  27. data/examples/code/functions +3 -0
  28. data/examples/code/groups +7 -0
  29. data/examples/code/head +30 -0
  30. data/examples/code/importing +8 -0
  31. data/examples/code/nodes +20 -0
  32. data/examples/code/one +8 -0
  33. data/examples/code/relationships +34 -0
  34. data/examples/code/selectors +28 -0
  35. data/examples/code/simpletests +11 -0
  36. data/examples/code/snippets/argumentdefaults +14 -0
  37. data/examples/code/snippets/casestatement +39 -0
  38. data/examples/code/snippets/classheirarchy.pp +15 -0
  39. data/examples/code/snippets/classincludes.pp +17 -0
  40. data/examples/code/snippets/classpathtest +11 -0
  41. data/examples/code/snippets/dirchmod +19 -0
  42. data/examples/code/snippets/failmissingexecpath.pp +13 -0
  43. data/examples/code/snippets/falsevalues.pp +3 -0
  44. data/examples/code/snippets/filecreate +11 -0
  45. data/examples/code/snippets/implicititeration +15 -0
  46. data/examples/code/snippets/multipleinstances +7 -0
  47. data/examples/code/snippets/namevartest +9 -0
  48. data/examples/code/snippets/scopetest +13 -0
  49. data/examples/code/snippets/selectorvalues.pp +22 -0
  50. data/examples/code/snippets/simpledefaults +5 -0
  51. data/examples/code/snippets/simpleselector +38 -0
  52. data/examples/code/svncommit +13 -0
  53. data/examples/root/bin/sleeper +69 -0
  54. data/examples/root/etc/configfile +0 -0
  55. data/examples/root/etc/debian-passwd +29 -0
  56. data/examples/root/etc/debian-syslog.conf +71 -0
  57. data/examples/root/etc/init.d/sleeper +65 -0
  58. data/examples/root/etc/otherfile +0 -0
  59. data/examples/root/etc/puppet/fileserver.conf +3 -0
  60. data/examples/root/etc/puppet/puppetmasterd.conf +10 -0
  61. data/ext/module:puppet +195 -0
  62. data/install.rb +270 -0
  63. data/lib/puppet.rb +249 -0
  64. data/lib/puppet/base64.rb +19 -0
  65. data/lib/puppet/client.rb +519 -0
  66. data/lib/puppet/config.rb +49 -0
  67. data/lib/puppet/daemon.rb +208 -0
  68. data/lib/puppet/element.rb +71 -0
  69. data/lib/puppet/event.rb +259 -0
  70. data/lib/puppet/log.rb +321 -0
  71. data/lib/puppet/metric.rb +250 -0
  72. data/lib/puppet/parsedfile.rb +38 -0
  73. data/lib/puppet/parser/ast.rb +1560 -0
  74. data/lib/puppet/parser/interpreter.rb +150 -0
  75. data/lib/puppet/parser/lexer.rb +226 -0
  76. data/lib/puppet/parser/parser.rb +1354 -0
  77. data/lib/puppet/parser/scope.rb +755 -0
  78. data/lib/puppet/server.rb +170 -0
  79. data/lib/puppet/server/authstore.rb +227 -0
  80. data/lib/puppet/server/ca.rb +140 -0
  81. data/lib/puppet/server/filebucket.rb +147 -0
  82. data/lib/puppet/server/fileserver.rb +477 -0
  83. data/lib/puppet/server/logger.rb +43 -0
  84. data/lib/puppet/server/master.rb +103 -0
  85. data/lib/puppet/server/servlet.rb +247 -0
  86. data/lib/puppet/sslcertificates.rb +737 -0
  87. data/lib/puppet/statechange.rb +150 -0
  88. data/lib/puppet/storage.rb +95 -0
  89. data/lib/puppet/transaction.rb +179 -0
  90. data/lib/puppet/transportable.rb +151 -0
  91. data/lib/puppet/type.rb +1354 -0
  92. data/lib/puppet/type/component.rb +141 -0
  93. data/lib/puppet/type/cron.rb +543 -0
  94. data/lib/puppet/type/exec.rb +316 -0
  95. data/lib/puppet/type/group.rb +152 -0
  96. data/lib/puppet/type/nameservice.rb +3 -0
  97. data/lib/puppet/type/nameservice/netinfo.rb +173 -0
  98. data/lib/puppet/type/nameservice/objectadd.rb +146 -0
  99. data/lib/puppet/type/nameservice/posix.rb +200 -0
  100. data/lib/puppet/type/package.rb +420 -0
  101. data/lib/puppet/type/package/apt.rb +70 -0
  102. data/lib/puppet/type/package/dpkg.rb +108 -0
  103. data/lib/puppet/type/package/rpm.rb +81 -0
  104. data/lib/puppet/type/package/sun.rb +117 -0
  105. data/lib/puppet/type/package/yum.rb +58 -0
  106. data/lib/puppet/type/pfile.rb +569 -0
  107. data/lib/puppet/type/pfile/checksum.rb +219 -0
  108. data/lib/puppet/type/pfile/create.rb +108 -0
  109. data/lib/puppet/type/pfile/group.rb +129 -0
  110. data/lib/puppet/type/pfile/mode.rb +131 -0
  111. data/lib/puppet/type/pfile/source.rb +264 -0
  112. data/lib/puppet/type/pfile/type.rb +31 -0
  113. data/lib/puppet/type/pfile/uid.rb +166 -0
  114. data/lib/puppet/type/pfilebucket.rb +80 -0
  115. data/lib/puppet/type/pprocess.rb +97 -0
  116. data/lib/puppet/type/service.rb +347 -0
  117. data/lib/puppet/type/service/base.rb +17 -0
  118. data/lib/puppet/type/service/debian.rb +50 -0
  119. data/lib/puppet/type/service/init.rb +145 -0
  120. data/lib/puppet/type/service/smf.rb +29 -0
  121. data/lib/puppet/type/state.rb +182 -0
  122. data/lib/puppet/type/symlink.rb +183 -0
  123. data/lib/puppet/type/tidy.rb +183 -0
  124. data/lib/puppet/type/typegen.rb +149 -0
  125. data/lib/puppet/type/typegen/filerecord.rb +243 -0
  126. data/lib/puppet/type/typegen/filetype.rb +316 -0
  127. data/lib/puppet/type/user.rb +290 -0
  128. data/lib/puppet/util.rb +138 -0
  129. data/test/certmgr/certmgr.rb +265 -0
  130. data/test/client/client.rb +203 -0
  131. data/test/executables/puppetbin.rb +53 -0
  132. data/test/executables/puppetca.rb +79 -0
  133. data/test/executables/puppetd.rb +71 -0
  134. data/test/executables/puppetmasterd.rb +153 -0
  135. data/test/executables/puppetmodule.rb +60 -0
  136. data/test/language/ast.rb +412 -0
  137. data/test/language/interpreter.rb +71 -0
  138. data/test/language/scope.rb +412 -0
  139. data/test/language/snippets.rb +445 -0
  140. data/test/other/events.rb +111 -0
  141. data/test/other/log.rb +195 -0
  142. data/test/other/metrics.rb +92 -0
  143. data/test/other/overrides.rb +115 -0
  144. data/test/other/parsedfile.rb +31 -0
  145. data/test/other/relationships.rb +113 -0
  146. data/test/other/state.rb +106 -0
  147. data/test/other/storage.rb +39 -0
  148. data/test/other/transactions.rb +235 -0
  149. data/test/parser/lexer.rb +120 -0
  150. data/test/parser/parser.rb +180 -0
  151. data/test/puppet/conffiles.rb +104 -0
  152. data/test/puppet/defaults.rb +100 -0
  153. data/test/puppet/error.rb +23 -0
  154. data/test/puppet/utiltest.rb +120 -0
  155. data/test/puppettest.rb +774 -0
  156. data/test/server/authstore.rb +209 -0
  157. data/test/server/bucket.rb +227 -0
  158. data/test/server/ca.rb +201 -0
  159. data/test/server/fileserver.rb +710 -0
  160. data/test/server/logger.rb +175 -0
  161. data/test/server/master.rb +150 -0
  162. data/test/server/server.rb +130 -0
  163. data/test/tagging/tagging.rb +80 -0
  164. data/test/test +51 -0
  165. data/test/types/basic.rb +119 -0
  166. data/test/types/component.rb +272 -0
  167. data/test/types/cron.rb +261 -0
  168. data/test/types/exec.rb +273 -0
  169. data/test/types/file.rb +616 -0
  170. data/test/types/filebucket.rb +167 -0
  171. data/test/types/fileignoresource.rb +287 -0
  172. data/test/types/filesources.rb +587 -0
  173. data/test/types/filetype.rb +162 -0
  174. data/test/types/group.rb +271 -0
  175. data/test/types/package.rb +205 -0
  176. data/test/types/query.rb +101 -0
  177. data/test/types/service.rb +100 -0
  178. data/test/types/symlink.rb +93 -0
  179. data/test/types/tidy.rb +124 -0
  180. data/test/types/type.rb +135 -0
  181. data/test/types/user.rb +371 -0
  182. metadata +243 -0
@@ -0,0 +1,43 @@
1
+ module Puppet
2
+ class Server # :nodoc:
3
+ class LoggerError < RuntimeError; end
4
+
5
+ # Receive logs from remote hosts.
6
+ class Logger < Handler
7
+ @interface = XMLRPC::Service::Interface.new("puppetlogger") { |iface|
8
+ iface.add_method("void addlog(string)")
9
+ }
10
+
11
+ # accept a log message from a client, and route it accordingly
12
+ def addlog(message, client = nil, clientip = nil)
13
+ # if the client is set, then we're not local
14
+ if client
15
+ begin
16
+ message = Marshal::load(CGI.unescape(message))
17
+ #message = message
18
+ rescue => detail
19
+ raise XMLRPC::FaultException.new(
20
+ 1, "Could not unMarshal log message from %s" % client
21
+ )
22
+ end
23
+ end
24
+
25
+ # Mark it as remote, so it's not sent to syslog
26
+ message.remote = true
27
+
28
+ if client
29
+ if ! message.source or message.source == "Puppet"
30
+ message.source = client
31
+ end
32
+ end
33
+
34
+ Puppet::Log.newmessage(message)
35
+
36
+ # This is necessary or XMLRPC gets all pukey
37
+ return ""
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ # $Id: logger.rb 733 2005-10-28 21:28:59Z luke $
@@ -0,0 +1,103 @@
1
+ require 'openssl'
2
+ require 'puppet'
3
+ require 'puppet/parser/interpreter'
4
+ require 'puppet/sslcertificates'
5
+ require 'xmlrpc/server'
6
+
7
+ module Puppet
8
+ class Server
9
+ class MasterError < Puppet::Error; end
10
+ class Master < Handler
11
+ attr_accessor :ast, :local
12
+ attr_reader :ca
13
+
14
+ @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface|
15
+ iface.add_method("string getconfig(string)")
16
+ }
17
+
18
+ def initialize(hash = {})
19
+
20
+ # FIXME this should all be s/:File/:Manifest/g or something
21
+ # build our AST
22
+ @file = hash[:File] || Puppet[:manifest]
23
+ hash.delete(:File)
24
+
25
+ if hash[:Local]
26
+ @local = hash[:Local]
27
+ else
28
+ @local = false
29
+ end
30
+
31
+ if hash.include?(:CA) and hash[:CA]
32
+ @ca = Puppet::SSLCertificates::CA.new()
33
+ else
34
+ @ca = nil
35
+ end
36
+
37
+ Puppet.debug("Creating interpreter")
38
+
39
+ args = {:Manifest => @file}
40
+
41
+ if hash.include?(:UseNodes)
42
+ args[:UseNodes] = hash[:UseNodes]
43
+ elsif @local
44
+ args[:UseNodes] = false
45
+ end
46
+
47
+ # This is only used by the cfengine module
48
+ if hash.include?(:Classes)
49
+ args[:Classes] = hash[:Classes]
50
+ end
51
+
52
+ begin
53
+ @interpreter = Puppet::Parser::Interpreter.new(args)
54
+ rescue => detail
55
+ Puppet.err detail
56
+ raise
57
+ end
58
+ end
59
+
60
+ def getconfig(facts, client = nil, clientip = nil)
61
+ if @local
62
+ # we don't need to do anything, since we should already
63
+ # have raw objects
64
+ Puppet.debug "Our client is local"
65
+ else
66
+ Puppet.debug "Our client is remote"
67
+
68
+ # XXX this should definitely be done in the protocol, somehow
69
+ begin
70
+ facts = Marshal::load(CGI.unescape(facts))
71
+ rescue => detail
72
+ raise XMLRPC::FaultException.new(
73
+ 1, "Could not rebuild facts"
74
+ )
75
+ end
76
+ end
77
+
78
+ unless client
79
+ client = facts["hostname"]
80
+ clientip = facts["ipaddress"]
81
+ end
82
+ Puppet.debug("Running interpreter")
83
+ begin
84
+ retobjects = @interpreter.run(client, facts)
85
+ rescue Puppet::Error => detail
86
+ Puppet.err detail
87
+ raise XMLRPC::FaultException.new(
88
+ 1, detail.to_s
89
+ )
90
+ rescue => detail
91
+ Puppet.err detail.to_s
92
+ return ""
93
+ end
94
+
95
+ if @local
96
+ return retobjects
97
+ else
98
+ return CGI.escape(Marshal::dump(retobjects))
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,247 @@
1
+ require 'xmlrpc/server'
2
+
3
+ module Puppet
4
+ class Server
5
+ class ServletError < RuntimeError; end
6
+ class Servlet < XMLRPC::WEBrickServlet
7
+ ERR_UNAUTHORIZED = 30
8
+
9
+ attr_accessor :request
10
+
11
+ # this is just a duplicate of the normal method; it's here for
12
+ # debugging when i need it
13
+ def self.get_instance(server, *options)
14
+ self.new(server, *options)
15
+ end
16
+
17
+ # This is a hackish way to avoid an auth message every time we have a
18
+ # normal operation
19
+ def self.log(msg)
20
+ unless defined? @logs
21
+ @logs = {}
22
+ end
23
+ if @logs.include?(msg)
24
+ @logs[msg] += 1
25
+ else
26
+ Puppet.info msg
27
+ @logs[msg] = 1
28
+ end
29
+ end
30
+
31
+ def add_handler(interface, handler)
32
+ @loadedhandlers << interface.prefix
33
+ super
34
+ end
35
+
36
+ # Verify that our client has access. We allow untrusted access to
37
+ # puppetca methods but none others.
38
+ def authorize(request, method)
39
+ namespace = method.sub(/\..+/, '')
40
+ client = request.peeraddr[2]
41
+ ip = request.peeraddr[3]
42
+ if request.client_cert
43
+ Servlet.log "Allowing %s(%s) trusted access to %s" %
44
+ [client, ip, method]
45
+ return true
46
+ else
47
+ if method =~ /^puppetca\./
48
+ Puppet.notice "Allowing %s(%s) untrusted access to CA methods" %
49
+ [client, ip]
50
+ else
51
+ Puppet.err "Unauthenticated client %s(%s) cannot call %s" %
52
+ [client, ip, method]
53
+ return false
54
+ end
55
+ end
56
+ end
57
+
58
+ def available?(method)
59
+ namespace = method.sub(/\..+/, '')
60
+ client = request.peeraddr[2]
61
+ ip = request.peeraddr[3]
62
+ if @loadedhandlers.include?(namespace)
63
+ return true
64
+ else
65
+ Puppet.warning "Client %s(%s) requested unavailable functionality %s" %
66
+ [client, ip, namespace]
67
+ return false
68
+ end
69
+ end
70
+
71
+ def initialize(server, handlers)
72
+ #Puppet.info server.inspect
73
+
74
+ # the servlet base class does not consume any arguments
75
+ # and its BasicServer base class only accepts a 'class_delim'
76
+ # option which won't change in Puppet at all
77
+ # thus, we don't need to pass any args to our base class,
78
+ # and we can consume them all ourselves
79
+ super()
80
+
81
+ @loadedhandlers = []
82
+ handlers.each { |handler|
83
+ #Puppet.debug "adding handler for %s" % handler.class
84
+ self.add_handler(handler.class.interface, handler)
85
+ }
86
+
87
+ # Initialize these to nil, but they will get set to values
88
+ # by the 'service' method. These have to instance variables
89
+ # because I don't have a clear line from the service method to
90
+ # the service hook.
91
+ @request = nil
92
+ @client = nil
93
+ @clientip = nil
94
+
95
+ self.set_service_hook { |obj, *args|
96
+ #raise "crap!"
97
+ if @client and @clientip
98
+ args.push(@client, @clientip)
99
+ #obj.call(args, @request)
100
+ end
101
+ begin
102
+ obj.call(*args)
103
+ rescue XMLRPC::FaultException
104
+ raise
105
+ rescue Puppet::Server::AuthorizationError => detail
106
+ #Puppet.warning obj.inspect
107
+ #Puppet.warning args.inspect
108
+ Puppet.err "Permission denied: %s" % detail.to_s
109
+ raise XMLRPC::FaultException.new(
110
+ 1, detail.to_s
111
+ )
112
+ rescue Puppet::Error => detail
113
+ #Puppet.warning obj.inspect
114
+ #Puppet.warning args.inspect
115
+ Puppet.err detail.to_s
116
+ raise XMLRPC::FaultException.new(
117
+ 1, detail.to_s
118
+ )
119
+ rescue => detail
120
+ #Puppet.warning obj.inspect
121
+ #Puppet.warning args.inspect
122
+ puts detail.inspect
123
+ Puppet.err "Could not call: %s" % detail.to_s
124
+ raise XMLRPC::FaultException.new(1, detail.to_s)
125
+ end
126
+ }
127
+ end
128
+
129
+ # Handle the actual request. This does some basic collection of
130
+ # data, and then just calls the parent method.
131
+ def service(request, response)
132
+ @request = request
133
+
134
+ # The only way that @client can be nil is if the request is local.
135
+ if peer = request.peeraddr
136
+ @client = peer[2]
137
+ @clientip = peer[3]
138
+ else
139
+ raise XMLRPC::FaultException.new(
140
+ ERR_UNCAUGHT_EXCEPTION,
141
+ "Could not retrieve client information"
142
+ )
143
+ end
144
+
145
+ # If they have a certificate (which will almost always be true)
146
+ # then we get the hostname from the cert, instead of via IP
147
+ # info
148
+ if cert = request.client_cert
149
+ nameary = cert.subject.to_a.find { |ary|
150
+ ary[0] == "CN"
151
+ }
152
+
153
+ if nameary.nil?
154
+ Puppet.warning "Could not retrieve server name from cert"
155
+ else
156
+ unless @client == nameary[1]
157
+ Puppet.debug "Overriding %s with cert name %s" %
158
+ [@client, nameary[1]]
159
+ @client = nameary[1]
160
+ end
161
+ end
162
+ end
163
+ #if request.server_cert
164
+ # Puppet.info "server cert is %s" % @request.server_cert
165
+ #end
166
+ #p @request
167
+ begin
168
+ super
169
+ rescue => detail
170
+ Puppet.err "Could not service request: %s: %s" %
171
+ [detail.class, detail]
172
+ end
173
+ @client = nil
174
+ @clientip = nil
175
+ @request = nil
176
+ end
177
+
178
+ private
179
+
180
+ # this is pretty much just a copy of the original method but with more
181
+ # feedback
182
+ # here's where we have our authorization hooks
183
+ def dispatch(methodname, *args)
184
+
185
+ if defined? @request and @request
186
+ unless self.available?(methodname)
187
+ raise XMLRPC::FaultException.new(
188
+ ERR_UNAUTHORIZED,
189
+ "Functionality %s not available" %
190
+ methodname.sub(/\..+/, '')
191
+ )
192
+ end
193
+ unless self.authorize(@request, methodname)
194
+ raise XMLRPC::FaultException.new(
195
+ ERR_UNAUTHORIZED,
196
+ "Host %s not authorized to call %s" %
197
+ [@request.host, methodname]
198
+ )
199
+ end
200
+ else
201
+ raise Puppet::DevError, "Did not get request in dispatch"
202
+ end
203
+
204
+ #Puppet.warning "dispatch on %s called with %s" %
205
+ # [methodname, args.inspect]
206
+ for name, obj in @handler
207
+ if obj.kind_of? Proc
208
+ unless methodname == name
209
+ #Puppet.debug "obj is proc but %s != %s" %
210
+ # [methodname, name]
211
+ next
212
+ end
213
+ else
214
+ unless methodname =~ /^#{name}(.+)$/
215
+ #Puppet.debug "methodname did not match"
216
+ next
217
+ end
218
+ unless obj.respond_to? $1
219
+ #Puppet.debug "methodname does not respond to %s" % $1
220
+ next
221
+ end
222
+ obj = obj.method($1)
223
+ end
224
+
225
+ if check_arity(obj, args.size)
226
+ if @service_hook.nil?
227
+ return obj.call(*args)
228
+ else
229
+ return @service_hook.call(obj, *args)
230
+ end
231
+ else
232
+ Puppet.debug "arity is incorrect"
233
+ end
234
+ end
235
+
236
+ if @default_handler.nil?
237
+ raise XMLRPC::FaultException.new(
238
+ ERR_METHOD_MISSING,
239
+ "Method #{methodname} missing or wrong number of parameters!"
240
+ )
241
+ else
242
+ @default_handler.call(methodname, *args)
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,737 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ #--------------------
4
+ # the puppet client
5
+ #
6
+ # $Id: sslcertificates.rb 720 2005-10-21 06:16:43Z luke $
7
+
8
+
9
+ require 'puppet'
10
+ require 'openssl'
11
+
12
+ module Puppet
13
+ module SSLCertificates
14
+ def self.mkdir(dir)
15
+ # this is all a bunch of stupid hackery
16
+ unless FileTest.exists?(dir)
17
+ comp = Puppet::Type::Component.create(
18
+ :name => "certdir creation"
19
+ )
20
+ path = ['']
21
+
22
+ dir.split(File::SEPARATOR).each { |d|
23
+ path << d
24
+ if FileTest.exists?(File.join(path))
25
+ unless FileTest.directory?(File.join(path))
26
+ raise "%s exists but is not a directory" % File.join(path)
27
+ end
28
+ else
29
+ obj = Puppet::Type.type(:file).create(
30
+ :name => File.join(path),
31
+ :mode => "750",
32
+ :create => "directory"
33
+ )
34
+
35
+ comp.push obj
36
+ end
37
+ }
38
+ trans = comp.evaluate
39
+ trans.evaluate
40
+ end
41
+
42
+ Puppet::Type.allclear
43
+ end
44
+
45
+ #def self.mkcert(type, name, days, issuercert, issuername, serial, publickey)
46
+ def self.mkcert(hash)
47
+ [:type, :name, :days, :issuer, :serial, :publickey].each { |param|
48
+ unless hash.include?(param)
49
+ raise ArgumentError, "mkcert called without %s" % param
50
+ end
51
+ }
52
+
53
+ cert = OpenSSL::X509::Certificate.new
54
+ from = Time.now
55
+
56
+ cert.subject = hash[:name]
57
+ if hash[:issuer]
58
+ cert.issuer = hash[:issuer].subject
59
+ else
60
+ # we're a self-signed cert
61
+ cert.issuer = hash[:name]
62
+ end
63
+ cert.not_before = from
64
+ cert.not_after = from + (hash[:days] * 24 * 60 * 60)
65
+ cert.version = 2 # X509v3
66
+
67
+ cert.public_key = hash[:publickey]
68
+ cert.serial = hash[:serial]
69
+
70
+ basic_constraint = nil
71
+ key_usage = nil
72
+ ext_key_usage = nil
73
+
74
+ ef = OpenSSL::X509::ExtensionFactory.new
75
+
76
+ ef.subject_certificate = cert
77
+
78
+ if hash[:issuer]
79
+ ef.issuer_certificate = hash[:issuer]
80
+ else
81
+ ef.issuer_certificate = cert
82
+ end
83
+
84
+ ex = []
85
+ case hash[:type]
86
+ when :ca:
87
+ basic_constraint = "CA:TRUE"
88
+ key_usage = %w{cRLSign keyCertSign}
89
+ when :terminalsubca:
90
+ basic_constraint = "CA:TRUE,pathlen:0"
91
+ key_usage = %w{cRLSign keyCertSign}
92
+ when :server:
93
+ basic_constraint = "CA:FALSE"
94
+ key_usage = %w{digitalSignature keyEncipherment}
95
+ ext_key_usage = %w{serverAuth clientAuth}
96
+ when :ocsp:
97
+ basic_constraint = "CA:FALSE"
98
+ key_usage = %w{nonRepudiation digitalSignature}
99
+ ext_key_usage = %w{serverAuth OCSPSigning}
100
+ when :client:
101
+ basic_constraint = "CA:FALSE"
102
+ key_usage = %w{nonRepudiation digitalSignature keyEncipherment}
103
+ ext_key_usage = %w{clientAuth emailProtection}
104
+ ex << ef.create_extension("nsCertType", "client,email")
105
+ else
106
+ raise Puppet::Error, "unknown cert type '%s'" % hash[:type]
107
+ end
108
+
109
+ ex << ef.create_extension("nsComment",
110
+ "Puppet Ruby/OpenSSL Generated Certificate")
111
+ ex << ef.create_extension("basicConstraints", basic_constraint, true)
112
+ ex << ef.create_extension("subjectKeyIdentifier", "hash")
113
+
114
+ if key_usage
115
+ ex << ef.create_extension("keyUsage", key_usage.join(","))
116
+ end
117
+ if ext_key_usage
118
+ ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(","))
119
+ end
120
+
121
+ #if @ca_config[:cdp_location] then
122
+ # ex << ef.create_extension("crlDistributionPoints",
123
+ # @ca_config[:cdp_location])
124
+ #end
125
+
126
+ #if @ca_config[:ocsp_location] then
127
+ # ex << ef.create_extension("authorityInfoAccess",
128
+ # "OCSP;" << @ca_config[:ocsp_location])
129
+ #end
130
+ cert.extensions = ex
131
+
132
+ # for some reason this _must_ be the last extension added
133
+ if hash[:type] == :ca
134
+ ex << ef.create_extension("authorityKeyIdentifier",
135
+ "keyid:always,issuer:always")
136
+ end
137
+
138
+ return cert
139
+ end
140
+
141
+ def self.mkhash(dir, cert, certfile)
142
+ hash = "%x" % cert.issuer.hash
143
+ hashpath = nil
144
+ 10.times { |i|
145
+ path = File.join(dir, "%s.%s" % [hash, i])
146
+ if FileTest.exists?(path)
147
+ if FileTest.symlink?(path)
148
+ dest = File.readlink(path)
149
+ if dest == certfile
150
+ # the correct link already exists
151
+ hashpath = path
152
+ break
153
+ else
154
+ next
155
+ end
156
+ else
157
+ next
158
+ end
159
+ end
160
+
161
+ File.symlink(certfile, path)
162
+
163
+ hashpath = path
164
+ break
165
+ }
166
+
167
+ return hashpath
168
+ end
169
+
170
+
171
+
172
+ class CA
173
+ attr_accessor :keyfile, :file, :config, :dir, :cert
174
+
175
+ @@params = [
176
+ :certdir,
177
+ :publickeydir,
178
+ :privatekeydir,
179
+ :cadir,
180
+ :cakey,
181
+ :cacert,
182
+ :capass,
183
+ :capub,
184
+ :csrdir,
185
+ :signeddir,
186
+ :serial,
187
+ :privatedir,
188
+ :ca_crl_days,
189
+ :ca_days,
190
+ :ca_md,
191
+ :req_bits,
192
+ :keylength,
193
+ :autosign
194
+ ]
195
+
196
+ @@defaults = {
197
+ :certdir => [:ssldir, "certs"],
198
+ :publickeydir => [:ssldir, "public_keys"],
199
+ :privatekeydir => [:ssldir, "private_keys"],
200
+ :cadir => [:ssldir, "ca"],
201
+ :cacert => [:cadir, "ca_crt.pem"],
202
+ :cakey => [:cadir, "ca_key.pem"],
203
+ :capub => [:cadir, "ca_pub.pem"],
204
+ :csrdir => [:cadir, "requests"],
205
+ :signeddir => [:cadir, "signed"],
206
+ :capass => [:cadir, "ca.pass"],
207
+ :serial => [:cadir, "serial"],
208
+ :privatedir => [:ssldir, "private"],
209
+ :passfile => [:privatedir, "password"],
210
+ :autosign => [:puppetconf, "autosign.conf"],
211
+ :ca_crl_days => 365,
212
+ :ca_days => 1825,
213
+ :ca_md => "md5",
214
+ :req_bits => 2048,
215
+ :keylength => 1024,
216
+ }
217
+
218
+ @@params.each { |param|
219
+ Puppet.setdefault(param,@@defaults[param])
220
+ }
221
+
222
+ def certfile
223
+ @config[:cacert]
224
+ end
225
+
226
+ def host2csrfile(hostname)
227
+ File.join(Puppet[:csrdir], [hostname, "pem"].join("."))
228
+ end
229
+
230
+ # this stores signed certs in a directory unrelated to
231
+ # normal client certs
232
+ def host2certfile(hostname)
233
+ File.join(Puppet[:signeddir], [hostname, "pem"].join("."))
234
+ end
235
+
236
+ def thing2name(thing)
237
+ thing.subject.to_a.find { |ary|
238
+ ary[0] == "CN"
239
+ }[1]
240
+ end
241
+
242
+ def initialize(hash = {})
243
+ self.setconfig(hash)
244
+
245
+ self.getcert
246
+ unless FileTest.exists?(@config[:serial])
247
+ File.open(@config[:serial], "w") { |f|
248
+ f << "%04X" % 1
249
+ }
250
+ end
251
+
252
+ if Puppet[:capass] and ! FileTest.exists?(Puppet[:capass])
253
+ self.genpass
254
+ end
255
+ end
256
+
257
+ def genpass
258
+ pass = ""
259
+ 20.times { pass += (rand(74) + 48).chr }
260
+
261
+ unless @config[:capass]
262
+ raise "No passfile"
263
+ end
264
+ Puppet::SSLCertificates.mkdir(File.dirname(@config[:capass]))
265
+ File.open(@config[:capass], "w", 0600) { |f| f.print pass }
266
+ return pass
267
+ end
268
+
269
+ def getcert
270
+ if FileTest.exists?(@config[:cacert])
271
+ @cert = OpenSSL::X509::Certificate.new(
272
+ File.read(@config[:cacert])
273
+ )
274
+ else
275
+ self.mkrootcert
276
+ end
277
+ end
278
+
279
+ def getclientcsr(host)
280
+ csrfile = host2csrfile(host)
281
+ unless File.exists?(csrfile)
282
+ return nil
283
+ end
284
+
285
+ return OpenSSL::X509::Request.new(File.read(csrfile))
286
+ end
287
+
288
+ def getclientcert(host)
289
+ certfile = host2certfile(host)
290
+ unless File.exists?(certfile)
291
+ return [nil, nil]
292
+ end
293
+
294
+ return [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert]
295
+ end
296
+
297
+ def list
298
+ return Dir.entries(Puppet[:csrdir]).reject { |file|
299
+ file =~ /^\.+$/
300
+ }.collect { |file|
301
+ file.sub(/\.pem$/, '')
302
+ }
303
+ end
304
+
305
+ def mkrootcert
306
+ cert = Certificate.new(
307
+ :name => "CAcert",
308
+ :cert => @config[:cacert],
309
+ :encrypt => @config[:passfile],
310
+ :key => @config[:cakey],
311
+ :selfsign => true,
312
+ :length => 1825,
313
+ :type => :ca
314
+ )
315
+ @cert = cert.mkselfsigned
316
+ File.open(@config[:cacert], "w", 0660) { |f|
317
+ f.puts @cert.to_pem
318
+ }
319
+ @key = cert.key
320
+ return cert
321
+ end
322
+
323
+ def removeclientcsr(host)
324
+ csrfile = host2csrfile(host)
325
+ unless File.exists?(csrfile)
326
+ raise Puppet::Error, "No certificate request for %s" % host
327
+ end
328
+
329
+ File.unlink(csrfile)
330
+ end
331
+
332
+ def setconfig(hash)
333
+ @config = {}
334
+ @@params.each { |param|
335
+ if hash.include?(param)
336
+ begin
337
+ @config[param] = hash[param]
338
+ Puppet[param] = hash[param]
339
+ hash.delete(param)
340
+ rescue => detail
341
+ puts detail
342
+ exit
343
+ end
344
+ else
345
+ begin
346
+ @config[param] = Puppet[param]
347
+ rescue => detail
348
+ puts detail
349
+ exit
350
+ end
351
+ end
352
+ }
353
+
354
+ if hash.include?(:password)
355
+ @config[:password] = hash[:password]
356
+ hash.delete(:password)
357
+ end
358
+
359
+ if hash.length > 0
360
+ raise ArgumentError, "Unknown parameters %s" % hash.keys.join(",")
361
+ end
362
+
363
+ [:cadir, :csrdir, :signeddir].each { |dir|
364
+ unless @config[dir]
365
+ raise "%s is undefined" % dir
366
+ end
367
+ unless FileTest.exists?(@config[dir])
368
+ Puppet::SSLCertificates.mkdir(@config[dir])
369
+ end
370
+ }
371
+ end
372
+
373
+ def sign(csr)
374
+ unless csr.is_a?(OpenSSL::X509::Request)
375
+ raise Puppet::Error,
376
+ "CA#sign only accepts OpenSSL::X509::Request objects, not %s" %
377
+ csr.class
378
+ end
379
+
380
+ unless csr.verify(csr.public_key)
381
+ raise Puppet::Error, "CSR sign verification failed"
382
+ end
383
+
384
+ # i should probably check key length...
385
+
386
+ # read the ca cert in
387
+ cacert = OpenSSL::X509::Certificate.new(
388
+ File.read(@config[:cacert])
389
+ )
390
+
391
+ cakey = nil
392
+ if @config[:password]
393
+ cakey = OpenSSL::PKey::RSA.new(
394
+ File.read(@config[:cakey]), @config[:password]
395
+ )
396
+ else
397
+ cakey = OpenSSL::PKey::RSA.new(
398
+ File.read(@config[:cakey])
399
+ )
400
+ end
401
+
402
+ unless cacert.check_private_key(cakey)
403
+ raise Puppet::Error, "CA Certificate is invalid"
404
+ end
405
+
406
+ serial = File.read(@config[:serial]).chomp.hex
407
+ newcert = SSLCertificates.mkcert(
408
+ :type => :server,
409
+ :name => csr.subject,
410
+ :days => @config[:ca_days],
411
+ :issuer => cacert,
412
+ :serial => serial,
413
+ :publickey => csr.public_key
414
+ )
415
+
416
+ # increment the serial
417
+ File.open(@config[:serial], "w") { |f|
418
+ f << "%04X" % (serial + 1)
419
+ }
420
+
421
+ newcert.sign(cakey, OpenSSL::Digest::SHA1.new)
422
+
423
+ self.storeclientcert(newcert)
424
+
425
+ return [newcert, cacert]
426
+ end
427
+
428
+ def storeclientcsr(csr)
429
+ host = thing2name(csr)
430
+
431
+ csrfile = host2csrfile(host)
432
+ if File.exists?(csrfile)
433
+ raise Puppet::Error, "Certificate request for %s already exists" % host
434
+ end
435
+
436
+ File.open(csrfile, "w", 0660) { |f|
437
+ f.print csr.to_pem
438
+ }
439
+ end
440
+
441
+ def storeclientcert(cert)
442
+ host = thing2name(cert)
443
+
444
+ certfile = host2certfile(host)
445
+ if File.exists?(certfile)
446
+ Puppet.notice "Overwriting signed certificate %s for %s" %
447
+ [certfile, host]
448
+ end
449
+
450
+ File.open(certfile, "w", 0660) { |f|
451
+ f.print cert.to_pem
452
+ }
453
+ end
454
+
455
+ end
456
+
457
+ class Certificate
458
+ attr_accessor :certfile, :keyfile, :name, :dir, :hash, :type
459
+ attr_accessor :key, :cert, :csr, :cacert
460
+
461
+ @@params2names = {
462
+ :name => "CN",
463
+ :state => "ST",
464
+ :country => "C",
465
+ :email => "emailAddress",
466
+ :org => "O",
467
+ :city => "L",
468
+ :ou => "OU"
469
+ }
470
+
471
+ def certname
472
+ OpenSSL::X509::Name.new self.subject
473
+ end
474
+
475
+ def delete
476
+ [@certfile,@keyfile].each { |file|
477
+ if FileTest.exists?(file)
478
+ File.unlink(file)
479
+ end
480
+ }
481
+
482
+ if defined? @hash and @hash
483
+ if FileTest.symlink?(@hash)
484
+ File.unlink(@hash)
485
+ end
486
+ end
487
+ end
488
+
489
+ def exists?
490
+ return FileTest.exists?(@certfile)
491
+ end
492
+
493
+ def getkey
494
+ unless FileTest.exists?(@keyfile)
495
+ self.mkkey()
496
+ end
497
+ if @password
498
+ @key = OpenSSL::PKey::RSA.new(
499
+ File.read(@keyfile),
500
+ @password
501
+ )
502
+ else
503
+ @key = OpenSSL::PKey::RSA.new(
504
+ File.read(@keyfile)
505
+ )
506
+ end
507
+ end
508
+
509
+ def initialize(hash)
510
+ unless hash.include?(:name)
511
+ raise "You must specify the common name for the certificate"
512
+ end
513
+ @name = hash[:name]
514
+
515
+ # init a few variables
516
+ @cert = @key = @csr = nil
517
+
518
+ if hash.include?(:cert)
519
+ @certfile = hash[:cert]
520
+ @dir = File.dirname(@certfile)
521
+ else
522
+ @dir = hash[:dir] || Puppet[:certdir]
523
+ @certfile = File.join(@dir, @name)
524
+ end
525
+
526
+ @cacertfile ||= File.join(Puppet[:certdir], "ca.pem")
527
+
528
+ unless FileTest.directory?(@dir)
529
+ Puppet::SSLCertificates.mkdir(@dir)
530
+ end
531
+
532
+ unless @certfile =~ /\.pem$/
533
+ @certfile += ".pem"
534
+ end
535
+ @keyfile = hash[:key] || File.join(
536
+ Puppet[:privatekeydir], [@name,"pem"].join(".")
537
+ )
538
+ unless FileTest.directory?(@dir)
539
+ Puppet::SSLCertificates.mkdir(@dir)
540
+ end
541
+
542
+ [@keyfile].each { |file|
543
+ dir = File.dirname(file)
544
+
545
+ unless FileTest.directory?(dir)
546
+ Puppet::SSLCertificates.mkdir(dir)
547
+ end
548
+ }
549
+
550
+ @days = hash[:length] || 365
551
+ @selfsign = hash[:selfsign] || false
552
+ @encrypt = hash[:encrypt] || false
553
+ @replace = hash[:replace] || false
554
+ @issuer = hash[:issuer] || nil
555
+
556
+ if hash.include?(:type)
557
+ case hash[:type]
558
+ when :ca, :client, :server: @type = hash[:type]
559
+ else
560
+ raise "Invalid Cert type %s" % hash[:type]
561
+ end
562
+ else
563
+ @type = :client
564
+ end
565
+
566
+ @params = {:name => @name}
567
+ [:state, :country, :email, :org, :ou].each { |param|
568
+ if hash.include?(param)
569
+ @params[param] = hash[param]
570
+ end
571
+ }
572
+
573
+ if @encrypt
574
+ if @encrypt =~ /^\//
575
+ File.open(@encrypt) { |f|
576
+ @password = f.read.chomp
577
+ }
578
+ else
579
+ raise ":encrypt must be a path to a pass phrase file"
580
+ end
581
+ else
582
+ @password = nil
583
+ end
584
+
585
+ if hash.include?(:selfsign)
586
+ @selfsign = hash[:selfsign]
587
+ else
588
+ @selfsign = false
589
+ end
590
+ end
591
+
592
+ # this only works for servers, not for users
593
+ def mkcsr
594
+ unless defined? @key and @key
595
+ self.getkey
596
+ end
597
+
598
+ name = OpenSSL::X509::Name.new self.subject
599
+
600
+ @csr = OpenSSL::X509::Request.new
601
+ @csr.version = 0
602
+ @csr.subject = name
603
+ @csr.public_key = @key.public_key
604
+ @csr.sign(@key, OpenSSL::Digest::SHA1.new)
605
+
606
+ #File.open(@csrfile, "w") { |f|
607
+ # f << @csr.to_pem
608
+ #}
609
+
610
+ unless @csr.verify(@key.public_key)
611
+ raise Puppet::Error, "CSR sign verification failed"
612
+ end
613
+
614
+ return @csr
615
+ end
616
+
617
+ def mkkey
618
+ # @key is the file
619
+
620
+ @key = OpenSSL::PKey::RSA.new(1024)
621
+ # { |p,n|
622
+ # case p
623
+ # when 0; Puppet.info "key info: ." # BN_generate_prime
624
+ # when 1; Puppet.info "key info: +" # BN_generate_prime
625
+ # when 2; Puppet.info "key info: *" # searching good prime,
626
+ # # n = #of try,
627
+ # # but also data from BN_generate_prime
628
+ # when 3; Puppet.info "key info: \n" # found good prime, n==0 - p, n==1 - q,
629
+ # # but also data from BN_generate_prime
630
+ # else; Puppet.info "key info: *" # BN_generate_prime
631
+ # end
632
+ # }
633
+
634
+ if @password
635
+ #passwdproc = proc { @password }
636
+ keytext = @key.export(
637
+ OpenSSL::Cipher::DES.new(:EDE3, :CBC),
638
+ @password
639
+ )
640
+ File.open(@keyfile, "w", 0400) { |f|
641
+ f << keytext
642
+ }
643
+ else
644
+ File.open(@keyfile, "w", 0400) { |f|
645
+ f << @key.to_pem
646
+ }
647
+ end
648
+
649
+ #cmd = "#{ossl} genrsa -out #{@key} 1024"
650
+ end
651
+
652
+ def mkselfsigned
653
+ unless defined? @key and @key
654
+ self.getkey
655
+ end
656
+
657
+ if defined? @cert and @cert
658
+ raise Puppet::Error, "Cannot replace existing certificate"
659
+ end
660
+
661
+ args = {
662
+ :name => self.certname,
663
+ :days => @days,
664
+ :issuer => nil,
665
+ :serial => 0x0,
666
+ :publickey => @key.public_key
667
+ }
668
+ if @type
669
+ args[:type] = @type
670
+ else
671
+ args[:type] = :server
672
+ end
673
+ @cert = SSLCertificates.mkcert(args)
674
+
675
+ @cert.sign(@key, OpenSSL::Digest::SHA1.new) if @selfsign
676
+
677
+ return @cert
678
+ end
679
+
680
+ def subject(string = false)
681
+ subj = @@params2names.collect { |param, name|
682
+ if @params.include?(param)
683
+ [name, @params[param]]
684
+ end
685
+ }.reject { |ary| ary.nil? }
686
+
687
+ if string
688
+ return "/" + subj.collect { |ary|
689
+ "%s=%s" % ary
690
+ }.join("/") + "/"
691
+ else
692
+ return subj
693
+ end
694
+ end
695
+
696
+ # verify that we can track down the cert chain or whatever
697
+ def verify
698
+ "openssl verify -verbose -CAfile /home/luke/.puppet/ssl/certs/ca.pem -purpose sslserver culain.madstop.com.pem"
699
+ end
700
+
701
+ def write
702
+ files = {
703
+ @certfile => @cert,
704
+ @keyfile => @key,
705
+ }
706
+ if defined? @cacert
707
+ files[@cacertfile] = @cacert
708
+ end
709
+
710
+ files.each { |file,thing|
711
+ if defined? thing and thing
712
+ if FileTest.exists?(file)
713
+ next
714
+ end
715
+
716
+ text = nil
717
+
718
+ if thing.is_a?(OpenSSL::PKey::RSA) and @password
719
+ text = thing.export(
720
+ OpenSSL::Cipher::DES.new(:EDE3, :CBC),
721
+ @password
722
+ )
723
+ else
724
+ text = thing.to_pem
725
+ end
726
+
727
+ File.open(file, "w", 0660) { |f| f.print text }
728
+ end
729
+ }
730
+
731
+ if defined? @cacert
732
+ SSLCertificates.mkhash(Puppet[:certdir], @cacert, @cacertfile)
733
+ end
734
+ end
735
+ end
736
+ end
737
+ end