Commit 680d8a992f68f40215ed277b86784bdc7eea52a3

Authored by Georg Hopp
1 parent c496fc34

optimze cert management

... ... @@ -18,6 +18,7 @@
18 18
19 19 # Ignore vim swp files
20 20 .*.sw?
  21 +.sw?
21 22
22 23 # ignore generated Gemfile.lock
23 24 /Gemfile.lock
... ...
... ... @@ -4,6 +4,33 @@ class ApplicationController < ActionController::Base
4 4 protect_from_forgery with: :exception
5 5
6 6 def check_cert
  7 + @cert = Certificate.find_by active: true
  8 + unless @cert
  9 + @cert = Certificate.create
  10 + @cert.save
  11 + end
  12 +
  13 + # update cert on all hosts if close to end.
  14 + # This will never fail as lxd is very lax with its certificates.
  15 + # It accepts certificates even behind the not_after date.
  16 + # As a result a password is only required when a new host is added
  17 + # or we remove the current cert completely.
  18 + if (@cert.cert.not_after - 1.day + 300) < Time.now
  19 + @new_cert = @cert.update
  20 + LxdHost.all.each { |host|
  21 + host.cert = @cert
  22 + # add new certificate
  23 + cert = Lxd::Certificate.new(
  24 + api: host.api,
  25 + certificate: @new_cert.cert.to_pem.split("\n")[1...-1].join)
  26 + cert.add
  27 + # delete old certificate / we don't want this to be used
  28 + # any more.
  29 + Lxd::Certificate.new(
  30 + api: host.api, fingerprint: @cert.cert_fpr).delete
  31 + }
  32 + @cert = @new_cert
  33 + end
7 34 end
8 35 end
9 36 # vim: set et ts=2 sw=2:
... ...
1   -require 'openssl'
2   -
3   -class CertificatesController < ApplicationController
4   - before_action :set_certificate, only: [:show, :edit, :update, :destroy]
5   -
6   - # GET /certificates
7   - # GET /certificates.json
8   - def index
9   - @certificates = Certificate.all
10   - end
11   -
12   - # GET /certificates/1
13   - # GET /certificates/1.json
14   - def show
15   - end
16   -
17   - # GET /certificates/new
18   - def new
19   - @certificate = Certificate.new
20   - end
21   -
22   - # GET /certificates/1/edit
23   - def edit
24   - end
25   -
26   - # POST /certificates
27   - # POST /certificates.json
28   - def create
29   - @certificate = Certificate.new(certificate_params)
30   -
31   - key = OpenSSL::PKey::RSA.new 4096
32   - name = OpenSSL::X509::Name.parse 'CN=lex-deeit/DC=weird-web-workers/DC=org'
33   -
34   - cert = OpenSSL::X509::Certificate.new
35   - cert.version = 2
36   - cert.serial = 0
37   - cert.not_before = Time.now
38   - cert.not_after = Time.now + 3600
39   -
40   - cert.public_key = key.public_key
41   - cert.subject = name
42   - cert.sign key, OpenSSL::Digest::SHA256.new
43   -
44   - @certificate.key = key.to_pem
45   - @certificate.cert = cert.to_pem
46   -
47   - respond_to do |format|
48   - if @certificate.save
49   - format.html { redirect_to @certificate, notice: 'Certificate was successfully created.' }
50   - format.json { render :show, status: :created, location: @certificate }
51   - else
52   - format.html { render :new }
53   - format.json { render json: @certificate.errors, status: :unprocessable_entity }
54   - end
55   - end
56   - end
57   -
58   - # PATCH/PUT /certificates/1
59   - # PATCH/PUT /certificates/1.json
60   - def update
61   - respond_to do |format|
62   - if @certificate.update(certificate_params)
63   - format.html { redirect_to @certificate, notice: 'Certificate was successfully updated.' }
64   - format.json { render :show, status: :ok, location: @certificate }
65   - else
66   - format.html { render :edit }
67   - format.json { render json: @certificate.errors, status: :unprocessable_entity }
68   - end
69   - end
70   - end
71   -
72   - # DELETE /certificates/1
73   - # DELETE /certificates/1.json
74   - def destroy
75   - @certificate.destroy
76   - respond_to do |format|
77   - format.html { redirect_to certificates_url, notice: 'Certificate was successfully destroyed.' }
78   - format.json { head :no_content }
79   - end
80   - end
81   -
82   - private
83   - # Use callbacks to share common setup or constraints between actions.
84   - def set_certificate
85   - @certificate = Certificate.find(params[:id])
86   - end
87   -
88   - # Never trust parameters from the scary internet, only allow the white list through.
89   - def certificate_params
90   - params.require(:certificate).permit(:key, :cert, :active)
91   - end
92   -end
93   -
94   -# vim: set et ts=2 sw=2:
1 1 class DashboardController < ApplicationController
2 2 def index
3   - @lxd_host = LxdHost.find(1)
4   - @cert = Certificate.find(1)
5   - @api = Lxd::API.get @lxd_host, @cert
6   - @lxd_config = Lxd::Config.get @api
  3 + check_cert
  4 + @lxd_hosts = LxdHost.all
7 5
8   - if @lxd_config.auth == 'untrusted'
9   - # Here the controller has to ask for the password
10   - cert = Lxd::Certificate.new api: @api
11   - cert.save 'xxxxxxxxxx'
12   - @lxd_config = Lxd::Config.get @api
13   - end
  6 + @lxd_hosts.map { |host|
  7 + host.cert = @cert
  8 + if host.config.auth == 'untrusted'
  9 + session[:return_to] = request.env["REQUEST_URI"]
  10 + redirect_to controller: 'lxd_hosts', action: 'auth', id: host.id
  11 + return
  12 + end
  13 + }
  14 +
  15 + @certificates = Lxd::Certificate.all @lxd_hosts.first.api
14 16 end
15 17 end
16 18 # vim: set et ts=2 sw=2:
... ...
1 1 class LxdHostsController < ApplicationController
2   - before_action :set_lxd_host, only: [:show, :edit, :update, :destroy]
  2 + before_action :set_lxd_host,
  3 + only: [:auth, :add_key, :show, :edit, :update, :destroy]
3 4
4 5 # GET /lxd_hosts
5 6 # GET /lxd_hosts.json
... ... @@ -21,6 +22,17 @@ class LxdHostsController < ApplicationController
21 22 def edit
22 23 end
23 24
  25 + # GET /lxd_hosts/1/auth
  26 + def auth
  27 + end
  28 +
  29 + # PATCH/PUT /lxd_hosts/1/add_key
  30 + def add_key
  31 + cert = Lxd::Certificate.new api: @lxd_host.api
  32 + cert.add params[:lxd_hosts][:password]
  33 + redirect_to session.delete(:return_to)
  34 + end
  35 +
24 36 # POST /lxd_hosts
25 37 # POST /lxd_hosts.json
26 38 def create
... ... @@ -62,13 +74,16 @@ class LxdHostsController < ApplicationController
62 74 end
63 75
64 76 private
65   - # Use callbacks to share common setup or constraints between actions.
66   - def set_lxd_host
67   - @lxd_host = LxdHost.find(params[:id])
68   - end
  77 + # Use callbacks to share common setup or constraints between actions.
  78 + def set_lxd_host
  79 + check_cert
  80 + @lxd_host = LxdHost.find(params[:id])
  81 + @lxd_host.cert = @cert
  82 + end
69 83
70   - # Never trust parameters from the scary internet, only allow the white list through.
71   - def lxd_host_params
72   - params.require(:lxd_host).permit(:name, :uri, :password, :password_confirmation)
73   - end
  84 + # Never trust parameters from the scary internet, only allow the white list through.
  85 + def lxd_host_params
  86 + params.require(:lxd_host).permit(:name, :uri, :password, :password_confirmation)
  87 + end
74 88 end
  89 +# vim: set et ts=2 sw=2:
... ...
... ... @@ -2,12 +2,38 @@ require "openssl"
2 2 require 'digest/md5'
3 3
4 4 class Certificate < ActiveRecord::Base
  5 + def self.create(old=nil)
  6 + key = if old then old.key else OpenSSL::PKey::RSA.new 4096 end
  7 + cert = OpenSSL::X509::Certificate.new
  8 + cert.version = if old then old.cert.version else 2 end
  9 + cert.serial = if old then old.cert.serial+1 else 0 end
  10 + cert.not_before = Time.now
  11 + #cert.not_after = Time.now + 1.year
  12 + cert.not_after = Time.now + 1.day
  13 + cert.public_key = key.public_key
  14 + cert.subject =
  15 + OpenSSL::X509::Name.parse(
  16 + 'CN=lex-deeit/' + Rails.configuration.x.certificate['x509_base'])
  17 + cert.sign key, OpenSSL::Digest::SHA256.new
  18 + Certificate.new key: key.to_pem, cert: cert.to_pem, active: true
  19 + end
  20 +
  21 + def update
  22 + self.active = false
  23 + self.save
  24 + cert = Certificate.create(self)
  25 + cert.save
  26 + cert
  27 + end
  28 +
5 29 def key
6   - OpenSSL::PKey::EC.new read_attribute(:key) if read_attribute(:key)
  30 + OpenSSL::PKey::RSA.new read_attribute(
  31 + :key) if read_attribute(:key)
7 32 end
8 33
9 34 def cert
10   - OpenSSL::X509::Certificate.new read_attribute(:cert) if read_attribute(:cert)
  35 + OpenSSL::X509::Certificate.new read_attribute(
  36 + :cert) if read_attribute(:cert)
11 37 end
12 38
13 39 def key_fpr
... ... @@ -17,10 +43,5 @@ class Certificate < ActiveRecord::Base
17 43 def cert_fpr
18 44 Digest::SHA256.hexdigest(cert.to_der).upcase
19 45 end
20   -
21   - private
22   -
23   - def _key
24   - end
25 46 end
26 47 # vim: set et ts=2 sw=2:
... ...
1 1 module Lxd::API
2   - def self.get host, certificate
  2 + def self.get host, certificate
3 3 uri = URI.parse host.uri
4   - con = Net::HTTP.new uri.host, uri.port
5   - con.use_ssl = true
6   - con.cert = OpenSSL::X509::Certificate.new certificate.cert
7   - con.key = OpenSSL::PKey::RSA.new certificate.key
8   - con.verify_mode = OpenSSL::SSL::VERIFY_NONE
9   -
10   - resp = self.call con, Net::HTTP::Get.new('/')
11   - raise "unsupported api version" unless resp['metadata'].include? '/1.0'
12   - Lxd::API::V1_0.new con
13   - end
14   -
15   - def self.call con, req
16   - resp = con.request req
17   - raise "request failure: " + resp.code unless resp.code != 200
18   - JSON.parse resp.body
19   - end
  4 + con = Net::HTTP.new uri.host, uri.port
  5 + con.use_ssl = true
  6 + con.cert = OpenSSL::X509::Certificate.new certificate.cert
  7 + con.key = OpenSSL::PKey::RSA.new certificate.key
  8 + con.verify_mode = OpenSSL::SSL::VERIFY_NONE
  9 +
  10 + resp = self.call con, Net::HTTP::Get.new('/')
  11 + raise "unsupported api version" unless resp['metadata'].include? '/1.0'
  12 + Lxd::API::V1_0.new con
  13 + end
  14 +
  15 + def self.call con, req
  16 + resp = con.request req
  17 + raise "request failure: " + resp.code unless resp.code != 200
  18 + JSON.parse resp.body
  19 + end
20 20
21 21 def initialize con
22 22 @con = con
23 23 end
24 24
25 25 def call req
26   - handle_response(Lxd::API.call @con, req)
  26 + handle_response(Lxd::API.call @con, req)
27 27 end
28 28
29   - def get uri
30   - call Net::HTTP::Get.new uri
31   - end
  29 + def get uri
  30 + call Net::HTTP::Get.new uri
  31 + end
32 32
33   - def put uri, data={}
34   - request = Net::HTTP::Put.new uri
35   - request.body = data.to_json
36   - call request
37   - end
  33 + def put uri, data={}
  34 + request = Net::HTTP::Put.new uri
  35 + request.body = data.to_json
  36 + call request
  37 + end
  38 +
  39 + def post uri, data={}
  40 + request = Net::HTTP::Post.new uri
  41 + request.body = data.to_json
  42 + call request
  43 + end
38 44
39   - def post uri, data={}
40   - request = Net::HTTP::Post.new uri
41   - request.body = data.to_json
42   - call request
43   - end
  45 + def delete uri, data={}
  46 + request = Net::HTTP::Delete.new uri
  47 + request.body = data.to_json
  48 + call request
  49 + end
44 50 end
45   -# vim: set ts=2 sw=2:
  51 +# vim: set et ts=2 sw=2:
... ...
1 1 class Lxd::API::V1_0
2   - include Lxd::API
3   -
4   - def config
5   - get '/1.0'
6   - end
7   -
8   - def config= config={}
9   - put '/1.0', config: config
10   - end
11   -
12   - def certificates
13   - get '/1.0/certificates'.map { |uri|
14   - {
15   - :uri => uri,
16   - :cert => get(uri)
17   - }
18   - }
19   - end
20   -
21   - def add_certificate cert={}
22   - # TODO validate hash
23   - post '/1.0/certificates', cert
24   - end
25   -
26   - def handle_response resp
27   - """
28   - 100 Operation created
29   - 101 Started
30   - 102 Stopped
31   - 103 Running
32   - 104 Cancelling
33   - 105 Pending
34   - 106 Starting
35   - 107 Stopping
36   - 108 Aborting
37   - 109 Freezing
38   - 110 Frozen
39   - 111 Thawed
40   - 200 Success
41   - 400 Failure
42   - 401 Cancelled
43   -
44   -
45   - 100 to 199: resource state (started, stopped, ready, ...)
46   - 200 to 399: positive action result
47   - 400 to 599: negative action result
48   - 600 to 999: future use
49   - """
50   - raise "api error" if [400..500].include? resp['error_code']
51   - resp['metadata']
52   - end
  2 + include Lxd::API
  3 +
  4 + def config
  5 + get '/1.0'
  6 + end
  7 +
  8 + def config= config={}
  9 + put '/1.0', config: config
  10 + end
  11 +
  12 + def certificates
  13 + get('/1.0/certificates').map { |uri|
  14 + { :uri => uri }.merge get(uri)
  15 + }
  16 + end
  17 +
  18 + def add_certificate data={}
  19 + # TODO validate hash
  20 + post '/1.0/certificates', data
  21 + end
  22 +
  23 + def delete_certificate fingerprint
  24 + delete '/1.0/certificates/' + fingerprint
  25 + end
  26 +
  27 + def handle_response resp
  28 + """
  29 + 100 Operation created
  30 + 101 Started
  31 + 102 Stopped
  32 + 103 Running
  33 + 104 Cancelling
  34 + 105 Pending
  35 + 106 Starting
  36 + 107 Stopping
  37 + 108 Aborting
  38 + 109 Freezing
  39 + 110 Frozen
  40 + 111 Thawed
  41 + 200 Success
  42 + 400 Failure
  43 + 401 Cancelled
  44 +
  45 +
  46 + 100 to 199: resource state (started, stopped, ready, ...)
  47 + 200 to 399: positive action result
  48 + 400 to 599: negative action result
  49 + 600 to 999: future use
  50 + """
  51 + raise "api error: (" + resp['error_code'].to_s + ") " + resp['error'] if resp['error_code'] and resp['error_code'] != 403
  52 + resp['metadata']
  53 + end
53 54 end
54   -# vim: set ts=2 sw=2:
  55 +# vim: set et ts=2 sw=2:
... ...
1 1 class Lxd::Certificate
2   - include ActiveModel::Model
  2 + include ActiveModel::Model
3 3
4   - attr_accessor :api, :type, :certificate, :fingerprint
  4 + attr_accessor :api, :uri, :type, :certificate, :fingerprint
5 5
6   - def self.all api
7   - api.certificates.map { |cert|
8   - Lxd::Certificate.new({api: api}.merge cert)
9   - }
10   - end
  6 + def self.all api
  7 + api.certificates.map { |cert|
  8 + Lxd::Certificate.new({api: api}.merge cert)
  9 + }
  10 + end
11 11
12   - def add password=nil, name='lex-deeit'
13   - data = Hash.new
14   - data[:type] = @type if @type else 'client'
15   - data[:name] = name
16   - data[:password] = password if password
17   - data[:certificate] = @certificate if @certificate
  12 + def add password=nil, name='lex-deeit'
  13 + data = Hash.new
  14 + data[:type] = if @type then @type else 'client' end
  15 + data[:name] = name
  16 + data[:password] = password if password
  17 + data[:certificate] = @certificate if @certificate
18 18
19   - @api.add_certificate data
20   - end
  19 + @api.add_certificate data
  20 + end
  21 +
  22 + def delete
  23 + @api.delete_certificate @fingerprint
  24 + end
21 25 end
22   -# vim: set ts=2 sw=2:
  26 +# vim: set et ts=2 sw=2:
... ...
1 1 class LxdHost < ActiveRecord::Base
2   - has_secure_password
  2 + def cert=(cert)
  3 + @cert = cert
  4 + end
  5 +
  6 + def api
  7 + Lxd::API.get self, @cert
  8 + end
  9 +
  10 + def config
  11 + Lxd::Config.get api
  12 + end
3 13 end
  14 +# vim: ts=2 sw=2:
... ...
1   -<%= form_for(@certificate) do |f| %>
2   - <% if @certificate.errors.any? %>
3   - <div id="error_explanation">
4   - <h2><%= pluralize(@certificate.errors.count, "error") %> prohibited this certificate from being saved:</h2>
5   -
6   - <ul>
7   - <% @certificate.errors.full_messages.each do |message| %>
8   - <li><%= message %></li>
9   - <% end %>
10   - </ul>
11   - </div>
12   - <% end %>
13   -
14   - <div class="field">
15   - <%= f.label :key %><br>
16   - <%= f.text_area :key %>
17   - </div>
18   - <div class="field">
19   - <%= f.label :cert %><br>
20   - <%= f.text_area :cert %>
21   - </div>
22   - <div class="field">
23   - <%= f.label :active %><br>
24   - <%= f.check_box :active %>
25   - </div>
26   - <div class="actions">
27   - <%= f.submit %>
28   - </div>
29   -<% end %>
1   -<h1>Editing Certificate</h1>
2   -
3   -<%= render 'form' %>
4   -
5   -<%= link_to 'Show', @certificate %> |
6   -<%= link_to 'Back', certificates_path %>
1   -<p id="notice"><%= notice %></p>
2   -
3   -<h1>Listing Certificates</h1>
4   -
5   -<table>
6   - <thead>
7   - <tr>
8   - <th>Key</th>
9   - <th>Cert</th>
10   - <th>Active</th>
11   - <th colspan="3"></th>
12   - </tr>
13   - </thead>
14   -
15   - <tbody>
16   - <% @certificates.each do |certificate| %>
17   - <tr>
18   - <td><%= certificate.key_fpr.scan(/../).join(':') %></td>
19   - <td><%= certificate.cert_fpr.scan(/../).join(':') %></td>
20   - <td><%= certificate.active %></td>
21   - <td><%= link_to 'Show', certificate %></td>
22   - <td><%= link_to 'Edit', edit_certificate_path(certificate) %></td>
23   - <td><%= link_to 'Destroy', certificate, method: :delete, data: { confirm: 'Are you sure?' } %></td>
24   - </tr>
25   - <% end %>
26   - </tbody>
27   -</table>
28   -
29   -<br>
30   -
31   -<%= link_to 'New Certificate', new_certificate_path %>
1   -json.array!(@certificates) do |certificate|
2   - json.extract! certificate, :id, :key, :cert, :active
3   - json.url certificate_url(certificate, format: :json)
4   -end
1   -<h1>New Certificate</h1>
2   -
3   -<%= render 'form' %>
4   -
5   -<%= link_to 'Back', certificates_path %>
1   -<p id="notice"><%= notice %></p>
2   -
3   -<p>
4   - <strong>Key:</strong>
5   - <%= @certificate.key %>
6   -</p>
7   -
8   -<p>
9   - <strong>Cert:</strong>
10   - <%= @certificate.cert %>
11   -</p>
12   -
13   -<p>
14   - <strong>Active:</strong>
15   - <%= @certificate.active %>
16   -</p>
17   -
18   -<%= link_to 'Edit', edit_certificate_path(@certificate) %> |
19   -<%= link_to 'Back', certificates_path %>
1   -json.extract! @certificate, :id, :key, :cert, :active, :created_at, :updated_at
1 1 <h1>Dashboard#index</h1>
2   -<p><%= @lxd_host.class %></p>
3   -<p><%= @cert.class %></p>
4   -<p><%= @lxd_config.class %></p>
5   -<p><%= @lxd_config.api_extensions.inspect %></p>
6   -<p><%= @lxd_config.api_status %></p>
7   -<p><%= @lxd_config.api_version %></p>
8   -<p><%= @lxd_config.auth %></p>
9   -<p><%= @lxd_config.config.inspect %></p>
10   -<p><%= @lxd_config.environment.inspect %></p>
11   -<p><%= @lxd_config.public %></p>
  2 +<p><%= @cert.cert_fpr %></p>
  3 +<p>Serial: <%= @cert.cert.serial %></p>
  4 +<% @lxd_hosts.each do |host| -%>
  5 +<p><%= host.config.inspect %></p>
  6 +<% end -%>
  7 +<% @certificates.each do |cert| -%>
  8 +<p><%= cert.fingerprint %></p>
  9 +<% end -%>
... ...
  1 +<h1>Authenticate Lxd Host: <%= @lxd_host.name %></h1>
  2 +
  3 +<p>...<%= @data.inspect %></p>
  4 +
  5 +<%= form_for :lxd_hosts, url: { action: "add_key" }, method: 'put' do |f| %>
  6 + <div class="field">
  7 + <%= f.label :password %><br>
  8 + <%= f.password_field :password %>
  9 + </div>
  10 + <div class="actions">
  11 + <%= f.submit %>
  12 + </div>
  13 +<% end %>
... ...
  1 +---
  2 +default: &default
  3 + x509_base: 'DC=weird-web-workers/DC=org'
  4 +
  5 +development:
  6 + <<: *default
  7 +
  8 +test:
  9 + <<: *default
  10 +
  11 +production:
  12 + <<: *default
  13 +# vim: set et ts=2 sw=2:
... ...
... ... @@ -38,4 +38,7 @@ Rails.application.configure do
38 38
39 39 # Raises error for missing translations
40 40 # config.action_view.raise_on_missing_translations = true
  41 +
  42 + # Load certificates config
  43 + config.x.certificate = config_for(:certificate)
41 44 end
... ...
... ... @@ -76,4 +76,7 @@ Rails.application.configure do
76 76
77 77 # Do not dump schema after migrations.
78 78 config.active_record.dump_schema_after_migration = false
  79 +
  80 + # Load certificates config
  81 + config.x.certificate = config_for(:certificate)
79 82 end
... ...
... ... @@ -39,4 +39,7 @@ Rails.application.configure do
39 39
40 40 # Raises error for missing translations
41 41 # config.action_view.raise_on_missing_translations = true
  42 +
  43 + # Load certificates config
  44 + config.x.certificate = config_for(:certificate)
42 45 end
... ...
1 1 Rails.application.routes.draw do
2 2 resources :lxd_hosts
3 3 resources :certificates
  4 +
  5 + get 'lxd_hosts/:id/auth' => 'lxd_hosts#auth'
  6 + put 'lxd_hosts/:id/add_key' => 'lxd_hosts#add_key'
4 7 get 'dashboard/index'
5 8
6 9 # The priority is based upon order of creation: first created -> highest priority.
... ...
  1 +class DeletePasswordFromLxdHosts < ActiveRecord::Migration
  2 + def change
  3 + remove_column :lxd_hosts, :password_digest
  4 + end
  5 +end
  6 +# vim: set ts=2 sw=2:
... ...
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended that you check this file into your version control system.
13 13
14   -ActiveRecord::Schema.define(version: 20160419112843) do
  14 +ActiveRecord::Schema.define(version: 20160425195446) do
15 15
16 16 create_table "certificates", force: :cascade do |t|
17 17 t.text "key"
... ... @@ -24,9 +24,8 @@ ActiveRecord::Schema.define(version: 20160419112843) do
24 24 create_table "lxd_hosts", force: :cascade do |t|
25 25 t.string "name"
26 26 t.string "uri"
27   - t.string "password_digest"
28   - t.datetime "created_at", null: false
29   - t.datetime "updated_at", null: false
  27 + t.datetime "created_at", null: false
  28 + t.datetime "updated_at", null: false
30 29 end
31 30
32 31 end
... ...
Please register or login to post a comment