Class: CertificateDepot::Worker

Inherits:
Object
  • Object
show all
Defined in:
lib/certificate_depot/worker.rb

Overview

A worker runs in a separate process created by the server. It hangs around and polls the server socket for incoming connections. Once it finds one it tried to process it.

The worker has a lifeline to the server. The lifeline is a pipe used to signal the server. When the worker goes down the server can detect the severed lifeline. If the worker needs the server to stop sleeping it can write to the lifeline.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(server) ⇒ Worker

Creates a new worker instance. The first argument is a server instance.



14
15
16
17
# File 'lib/certificate_depot/worker.rb', line 14

def initialize(server)
  @server  = server
  @signals = []
end

Instance Attribute Details

#lifelineObject

Returns the value of attribute lifeline.



11
12
13
# File 'lib/certificate_depot/worker.rb', line 11

def lifeline
  @lifeline
end

#serverObject

Returns the value of attribute server.



11
12
13
# File 'lib/certificate_depot/worker.rb', line 11

def server
  @server
end

Class Method Details

.help(command) ⇒ Object

Returns help text for a certain command



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/certificate_depot/worker.rb', line 130

def self.help(command)
  case command
  when 'generate'
"GENERATE
  generate <distinguished name>
RETURNS
  A private key and certificate in PEM format.
EXAMPLE
  generate /UID=12/CN=Bob Owner,[email protected]
"
  when 'help'
"HELP
  help <command>
RETURNS
  A description of the command.
EXAMPLE
  help generate
"
  when 'shutdown'
"SHUTDOWN
  shutdown
RETURNS
  Kills the current worker handling the request.
EXAMPLE
  shutdown
"
  else
    "Unknown command #{command}"
  end
end

.parse_command(command) ⇒ Object

Parses a command issues by a client.



115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/certificate_depot/worker.rb', line 115

def self.parse_command(command)
  parts = command.split(' ')
  parts[0] = parts[0].intern if parts[0]
  
  case parts[0]
  when :generate
    parts[1] = OpenSSL::X509::Name.parse(parts[1].to_s) if parts[1]
  when :revoke
    parts[1] = parts[1].to_i if parts[1]
  end
  
  parts
end

Instance Method Details

#cleanupObject

Cleanup all worker resources



95
96
97
98
99
100
101
# File 'lib/certificate_depot/worker.rb', line 95

def cleanup
  server.log.info("Shutting down")
  begin
    lifeline.close
  rescue Errno::EPIPE
  end
end

#generate(socket, distinguished_name) ⇒ Object

Generates a new client certificate and writes it to the socket



20
21
22
23
24
25
26
27
28
# File 'lib/certificate_depot/worker.rb', line 20

def generate(socket, distinguished_name)
  attributes = {
    :type    => :client,
    :subject => distinguished_name
  }
  keypair, certificate = server.depot.generate_keypair_and_certificate(attributes)
  socket.write keypair.private_key.to_s
  socket.write certificate.certificate.to_s
end

#help(socket, command) ⇒ Object

Writes help to the socket about a topic or a list of commands



31
32
33
34
35
36
37
# File 'lib/certificate_depot/worker.rb', line 31

def help(socket, command)
  if command
    socket.write(self.class.help(command.downcase))
  else
    socket.write("generate help shutdown\n")
  end
end

#process_incoming_socket(socket, address) ⇒ Object

Processes an incoming request. Parse the command, run the command, and close the socket.



56
57
58
59
60
61
# File 'lib/certificate_depot/worker.rb', line 56

def process_incoming_socket(socket, address)
  input = socket.gets
  server.log.debug("Got input: #{input.strip}")
  run_command(socket, *self.class.parse_command(input))
  socket.close
end

#runObject

Starts the mainloop for the worker. The mainloop sleeps until one of the following three things happens: server gets a new request, activity on the lifeline to the server, or 2 seconds go by.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/certificate_depot/worker.rb', line 66

def run
  trap_signals
  loop do
    break if signals_want_shutdown
    begin
      # IO.select returns either a triplet of lists with IO objects that
      # need attention or nil on timeout of 2 seconds.
      if needy = IO.select([server.socket, lifeline], nil, [server.socket, lifeline], 2)
        server.log.debug("Detected activity on: #{needy.inspect}")
        # If the lifeline is active the server went down and we need to go
        # down as well.
        break if needy.flatten.any? { |io| !io.respond_to?(:accept_nonblock) }
        # Otherwise we handle incoming requests
        needy.flatten.each do |io|
          if io.respond_to?(:accept_nonblock)
            begin
              process_incoming_socket(*io.accept_nonblock)
            rescue Errno::EAGAIN, Errno::ECONNABORTED
            end
          end
        end
      end
    rescue EOFError, Errno::EAGAIN, Errno::EINTR, Errno::EBADF, IOError
    end
  end
  cleanup
end

#run_command(socket, *args) ⇒ Object

Runs a command and writes the result to the request socket.



40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/certificate_depot/worker.rb', line 40

def run_command(socket, *args)
  args    = args.dup
  command = args.shift
  
  case command
  when :generate
    generate(socket, args[0])
  when :help
    help(socket, args[0])
  when :shutdown
    exit 1
  end
end

#signals_want_shutdownObject

Returns true when the signals received by the process demand a shutdown



110
111
112
# File 'lib/certificate_depot/worker.rb', line 110

def signals_want_shutdown
  !@signals.empty?
end

#trap_signalsObject

Installs signal traps to listen for incoming signals to the process.



104
105
106
107
# File 'lib/certificate_depot/worker.rb', line 104

def trap_signals
  trap(:QUIT) { @signals << :QUIT }
  trap(:EXIT) { @signals << :EXIT }
end