Browse Source

optimze cert management

master
Georg Hopp 10 years ago
parent
commit
680d8a992f
  1. 1
      .gitignore
  2. 27
      app/controllers/application_controller.rb
  3. 94
      app/controllers/certificates_controller.rb
  4. 20
      app/controllers/dashboard_controller.rb
  5. 17
      app/controllers/lxd_hosts_controller.rb
  6. 35
      app/models/certificate.rb
  7. 8
      app/models/lxd/api.rb
  8. 19
      app/models/lxd/api/v1_0.rb
  9. 10
      app/models/lxd/certificate.rb
  10. 13
      app/models/lxd_host.rb
  11. 29
      app/views/certificates/_form.html.erb
  12. 6
      app/views/certificates/edit.html.erb
  13. 31
      app/views/certificates/index.html.erb
  14. 4
      app/views/certificates/index.json.jbuilder
  15. 5
      app/views/certificates/new.html.erb
  16. 19
      app/views/certificates/show.html.erb
  17. 1
      app/views/certificates/show.json.jbuilder
  18. 18
      app/views/dashboard/index.html.erb
  19. 13
      app/views/lxd_hosts/auth.html.erb
  20. 13
      config/certificate.yml
  21. 3
      config/environments/development.rb
  22. 3
      config/environments/production.rb
  23. 3
      config/environments/test.rb
  24. 3
      config/routes.rb
  25. 6
      db/migrate/20160425195446_delete_password_from_lxd_hosts.rb
  26. 3
      db/schema.rb

1
.gitignore

@ -18,6 +18,7 @@
# Ignore vim swp files
.*.sw?
.sw?
# ignore generated Gemfile.lock
/Gemfile.lock

27
app/controllers/application_controller.rb

@ -4,6 +4,33 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
def check_cert
@cert = Certificate.find_by active: true
unless @cert
@cert = Certificate.create
@cert.save
end
# update cert on all hosts if close to end.
# This will never fail as lxd is very lax with its certificates.
# It accepts certificates even behind the not_after date.
# As a result a password is only required when a new host is added
# or we remove the current cert completely.
if (@cert.cert.not_after - 1.day + 300) < Time.now
@new_cert = @cert.update
LxdHost.all.each { |host|
host.cert = @cert
# add new certificate
cert = Lxd::Certificate.new(
api: host.api,
certificate: @new_cert.cert.to_pem.split("\n")[1...-1].join)
cert.add
# delete old certificate / we don't want this to be used
# any more.
Lxd::Certificate.new(
api: host.api, fingerprint: @cert.cert_fpr).delete
}
@cert = @new_cert
end
end
end
# vim: set et ts=2 sw=2:

94
app/controllers/certificates_controller.rb

@ -1,94 +0,0 @@
require 'openssl'
class CertificatesController < ApplicationController
before_action :set_certificate, only: [:show, :edit, :update, :destroy]
# GET /certificates
# GET /certificates.json
def index
@certificates = Certificate.all
end
# GET /certificates/1
# GET /certificates/1.json
def show
end
# GET /certificates/new
def new
@certificate = Certificate.new
end
# GET /certificates/1/edit
def edit
end
# POST /certificates
# POST /certificates.json
def create
@certificate = Certificate.new(certificate_params)
key = OpenSSL::PKey::RSA.new 4096
name = OpenSSL::X509::Name.parse 'CN=lex-deeit/DC=weird-web-workers/DC=org'
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 0
cert.not_before = Time.now
cert.not_after = Time.now + 3600
cert.public_key = key.public_key
cert.subject = name
cert.sign key, OpenSSL::Digest::SHA256.new
@certificate.key = key.to_pem
@certificate.cert = cert.to_pem
respond_to do |format|
if @certificate.save
format.html { redirect_to @certificate, notice: 'Certificate was successfully created.' }
format.json { render :show, status: :created, location: @certificate }
else
format.html { render :new }
format.json { render json: @certificate.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /certificates/1
# PATCH/PUT /certificates/1.json
def update
respond_to do |format|
if @certificate.update(certificate_params)
format.html { redirect_to @certificate, notice: 'Certificate was successfully updated.' }
format.json { render :show, status: :ok, location: @certificate }
else
format.html { render :edit }
format.json { render json: @certificate.errors, status: :unprocessable_entity }
end
end
end
# DELETE /certificates/1
# DELETE /certificates/1.json
def destroy
@certificate.destroy
respond_to do |format|
format.html { redirect_to certificates_url, notice: 'Certificate was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_certificate
@certificate = Certificate.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def certificate_params
params.require(:certificate).permit(:key, :cert, :active)
end
end
# vim: set et ts=2 sw=2:

20
app/controllers/dashboard_controller.rb

@ -1,16 +1,18 @@
class DashboardController < ApplicationController
def index
@lxd_host = LxdHost.find(1)
@cert = Certificate.find(1)
@api = Lxd::API.get @lxd_host, @cert
@lxd_config = Lxd::Config.get @api
check_cert
@lxd_hosts = LxdHost.all
if @lxd_config.auth == 'untrusted'
# Here the controller has to ask for the password
cert = Lxd::Certificate.new api: @api
cert.save 'xxxxxxxxxx'
@lxd_config = Lxd::Config.get @api
@lxd_hosts.map { |host|
host.cert = @cert
if host.config.auth == 'untrusted'
session[:return_to] = request.env["REQUEST_URI"]
redirect_to controller: 'lxd_hosts', action: 'auth', id: host.id
return
end
}
@certificates = Lxd::Certificate.all @lxd_hosts.first.api
end
end
# vim: set et ts=2 sw=2:

17
app/controllers/lxd_hosts_controller.rb

@ -1,5 +1,6 @@
class LxdHostsController < ApplicationController
before_action :set_lxd_host, only: [:show, :edit, :update, :destroy]
before_action :set_lxd_host,
only: [:auth, :add_key, :show, :edit, :update, :destroy]
# GET /lxd_hosts
# GET /lxd_hosts.json
@ -21,6 +22,17 @@ class LxdHostsController < ApplicationController
def edit
end
# GET /lxd_hosts/1/auth
def auth
end
# PATCH/PUT /lxd_hosts/1/add_key
def add_key
cert = Lxd::Certificate.new api: @lxd_host.api
cert.add params[:lxd_hosts][:password]
redirect_to session.delete(:return_to)
end
# POST /lxd_hosts
# POST /lxd_hosts.json
def create
@ -64,7 +76,9 @@ class LxdHostsController < ApplicationController
private
# Use callbacks to share common setup or constraints between actions.
def set_lxd_host
check_cert
@lxd_host = LxdHost.find(params[:id])
@lxd_host.cert = @cert
end
# Never trust parameters from the scary internet, only allow the white list through.
@ -72,3 +86,4 @@ class LxdHostsController < ApplicationController
params.require(:lxd_host).permit(:name, :uri, :password, :password_confirmation)
end
end
# vim: set et ts=2 sw=2:

35
app/models/certificate.rb

@ -2,12 +2,38 @@ require "openssl"
require 'digest/md5'
class Certificate < ActiveRecord::Base
def self.create(old=nil)
key = if old then old.key else OpenSSL::PKey::RSA.new 4096 end
cert = OpenSSL::X509::Certificate.new
cert.version = if old then old.cert.version else 2 end
cert.serial = if old then old.cert.serial+1 else 0 end
cert.not_before = Time.now
#cert.not_after = Time.now + 1.year
cert.not_after = Time.now + 1.day
cert.public_key = key.public_key
cert.subject =
OpenSSL::X509::Name.parse(
'CN=lex-deeit/' + Rails.configuration.x.certificate['x509_base'])
cert.sign key, OpenSSL::Digest::SHA256.new
Certificate.new key: key.to_pem, cert: cert.to_pem, active: true
end
def update
self.active = false
self.save
cert = Certificate.create(self)
cert.save
cert
end
def key
OpenSSL::PKey::EC.new read_attribute(:key) if read_attribute(:key)
OpenSSL::PKey::RSA.new read_attribute(
:key) if read_attribute(:key)
end
def cert
OpenSSL::X509::Certificate.new read_attribute(:cert) if read_attribute(:cert)
OpenSSL::X509::Certificate.new read_attribute(
:cert) if read_attribute(:cert)
end
def key_fpr
@ -17,10 +43,5 @@ class Certificate < ActiveRecord::Base
def cert_fpr
Digest::SHA256.hexdigest(cert.to_der).upcase
end
private
def _key
end
end
# vim: set et ts=2 sw=2:

8
app/models/lxd/api.rb

@ -41,5 +41,11 @@ module Lxd::API
request.body = data.to_json
call request
end
def delete uri, data={}
request = Net::HTTP::Delete.new uri
request.body = data.to_json
call request
end
end
# vim: set ts=2 sw=2:
# vim: set et ts=2 sw=2:

19
app/models/lxd/api/v1_0.rb

@ -10,17 +10,18 @@ class Lxd::API::V1_0
end
def certificates
get '/1.0/certificates'.map { |uri|
{
:uri => uri,
:cert => get(uri)
}
get('/1.0/certificates').map { |uri|
{ :uri => uri }.merge get(uri)
}
end
def add_certificate cert={}
def add_certificate data={}
# TODO validate hash
post '/1.0/certificates', cert
post '/1.0/certificates', data
end
def delete_certificate fingerprint
delete '/1.0/certificates/' + fingerprint
end
def handle_response resp
@ -47,8 +48,8 @@ class Lxd::API::V1_0
400 to 599: negative action result
600 to 999: future use
"""
raise "api error" if [400..500].include? resp['error_code']
raise "api error: (" + resp['error_code'].to_s + ") " + resp['error'] if resp['error_code'] and resp['error_code'] != 403
resp['metadata']
end
end
# vim: set ts=2 sw=2:
# vim: set et ts=2 sw=2:

10
app/models/lxd/certificate.rb

@ -1,7 +1,7 @@
class Lxd::Certificate
include ActiveModel::Model
attr_accessor :api, :type, :certificate, :fingerprint
attr_accessor :api, :uri, :type, :certificate, :fingerprint
def self.all api
api.certificates.map { |cert|
@ -11,12 +11,16 @@ class Lxd::Certificate
def add password=nil, name='lex-deeit'
data = Hash.new
data[:type] = @type if @type else 'client'
data[:type] = if @type then @type else 'client' end
data[:name] = name
data[:password] = password if password
data[:certificate] = @certificate if @certificate
@api.add_certificate data
end
def delete
@api.delete_certificate @fingerprint
end
end
# vim: set ts=2 sw=2:
# vim: set et ts=2 sw=2:

13
app/models/lxd_host.rb

@ -1,3 +1,14 @@
class LxdHost < ActiveRecord::Base
has_secure_password
def cert=(cert)
@cert = cert
end
def api
Lxd::API.get self, @cert
end
def config
Lxd::Config.get api
end
end
# vim: ts=2 sw=2:

29
app/views/certificates/_form.html.erb

@ -1,29 +0,0 @@
<%= form_for(@certificate) do |f| %>
<% if @certificate.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@certificate.errors.count, "error") %> prohibited this certificate from being saved:</h2>
<ul>
<% @certificate.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :key %><br>
<%= f.text_area :key %>
</div>
<div class="field">
<%= f.label :cert %><br>
<%= f.text_area :cert %>
</div>
<div class="field">
<%= f.label :active %><br>
<%= f.check_box :active %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

6
app/views/certificates/edit.html.erb

@ -1,6 +0,0 @@
<h1>Editing Certificate</h1>
<%= render 'form' %>
<%= link_to 'Show', @certificate %> |
<%= link_to 'Back', certificates_path %>

31
app/views/certificates/index.html.erb

@ -1,31 +0,0 @@
<p id="notice"><%= notice %></p>
<h1>Listing Certificates</h1>
<table>
<thead>
<tr>
<th>Key</th>
<th>Cert</th>
<th>Active</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @certificates.each do |certificate| %>
<tr>
<td><%= certificate.key_fpr.scan(/../).join(':') %></td>
<td><%= certificate.cert_fpr.scan(/../).join(':') %></td>
<td><%= certificate.active %></td>
<td><%= link_to 'Show', certificate %></td>
<td><%= link_to 'Edit', edit_certificate_path(certificate) %></td>
<td><%= link_to 'Destroy', certificate, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Certificate', new_certificate_path %>

4
app/views/certificates/index.json.jbuilder

@ -1,4 +0,0 @@
json.array!(@certificates) do |certificate|
json.extract! certificate, :id, :key, :cert, :active
json.url certificate_url(certificate, format: :json)
end

5
app/views/certificates/new.html.erb

@ -1,5 +0,0 @@
<h1>New Certificate</h1>
<%= render 'form' %>
<%= link_to 'Back', certificates_path %>

19
app/views/certificates/show.html.erb

@ -1,19 +0,0 @@
<p id="notice"><%= notice %></p>
<p>
<strong>Key:</strong>
<%= @certificate.key %>
</p>
<p>
<strong>Cert:</strong>
<%= @certificate.cert %>
</p>
<p>
<strong>Active:</strong>
<%= @certificate.active %>
</p>
<%= link_to 'Edit', edit_certificate_path(@certificate) %> |
<%= link_to 'Back', certificates_path %>

1
app/views/certificates/show.json.jbuilder

@ -1 +0,0 @@
json.extract! @certificate, :id, :key, :cert, :active, :created_at, :updated_at

18
app/views/dashboard/index.html.erb

@ -1,11 +1,9 @@
<h1>Dashboard#index</h1>
<p><%= @lxd_host.class %></p>
<p><%= @cert.class %></p>
<p><%= @lxd_config.class %></p>
<p><%= @lxd_config.api_extensions.inspect %></p>
<p><%= @lxd_config.api_status %></p>
<p><%= @lxd_config.api_version %></p>
<p><%= @lxd_config.auth %></p>
<p><%= @lxd_config.config.inspect %></p>
<p><%= @lxd_config.environment.inspect %></p>
<p><%= @lxd_config.public %></p>
<p><%= @cert.cert_fpr %></p>
<p>Serial: <%= @cert.cert.serial %></p>
<% @lxd_hosts.each do |host| -%>
<p><%= host.config.inspect %></p>
<% end -%>
<% @certificates.each do |cert| -%>
<p><%= cert.fingerprint %></p>
<% end -%>

13
app/views/lxd_hosts/auth.html.erb

@ -0,0 +1,13 @@
<h1>Authenticate Lxd Host: <%= @lxd_host.name %></h1>
<p>...<%= @data.inspect %></p>
<%= form_for :lxd_hosts, url: { action: "add_key" }, method: 'put' do |f| %>
<div class="field">
<%= f.label :password %><br>
<%= f.password_field :password %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

13
config/certificate.yml

@ -0,0 +1,13 @@
---
default: &default
x509_base: 'DC=weird-web-workers/DC=org'
development:
<<: *default
test:
<<: *default
production:
<<: *default
# vim: set et ts=2 sw=2:

3
config/environments/development.rb

@ -38,4 +38,7 @@ Rails.application.configure do
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
# Load certificates config
config.x.certificate = config_for(:certificate)
end

3
config/environments/production.rb

@ -76,4 +76,7 @@ Rails.application.configure do
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
# Load certificates config
config.x.certificate = config_for(:certificate)
end

3
config/environments/test.rb

@ -39,4 +39,7 @@ Rails.application.configure do
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
# Load certificates config
config.x.certificate = config_for(:certificate)
end

3
config/routes.rb

@ -1,6 +1,9 @@
Rails.application.routes.draw do
resources :lxd_hosts
resources :certificates
get 'lxd_hosts/:id/auth' => 'lxd_hosts#auth'
put 'lxd_hosts/:id/add_key' => 'lxd_hosts#add_key'
get 'dashboard/index'
# The priority is based upon order of creation: first created -> highest priority.

6
db/migrate/20160425195446_delete_password_from_lxd_hosts.rb

@ -0,0 +1,6 @@
class DeletePasswordFromLxdHosts < ActiveRecord::Migration
def change
remove_column :lxd_hosts, :password_digest
end
end
# vim: set ts=2 sw=2:

3
db/schema.rb

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160419112843) do
ActiveRecord::Schema.define(version: 20160425195446) do
create_table "certificates", force: :cascade do |t|
t.text "key"
@ -24,7 +24,6 @@ ActiveRecord::Schema.define(version: 20160419112843) do
create_table "lxd_hosts", force: :cascade do |t|
t.string "name"
t.string "uri"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

Loading…
Cancel
Save