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. 22
      app/controllers/dashboard_controller.rb
  5. 33
      app/controllers/lxd_hosts_controller.rb
  6. 35
      app/models/certificate.rb
  7. 70
      app/models/lxd/api.rb
  8. 105
      app/models/lxd/api/v1_0.rb
  9. 36
      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. 7
      db/schema.rb

1
.gitignore

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

27
app/controllers/application_controller.rb

@ -4,6 +4,33 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception protect_from_forgery with: :exception
def check_cert 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
end end
# vim: set et ts=2 sw=2: # 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:

22
app/controllers/dashboard_controller.rb

@ -1,16 +1,18 @@
class DashboardController < ApplicationController class DashboardController < ApplicationController
def index 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
end
@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
end end
# vim: set et ts=2 sw=2: # vim: set et ts=2 sw=2:

33
app/controllers/lxd_hosts_controller.rb

@ -1,5 +1,6 @@
class LxdHostsController < ApplicationController 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
# GET /lxd_hosts.json # GET /lxd_hosts.json
@ -21,6 +22,17 @@ class LxdHostsController < ApplicationController
def edit def edit
end 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
# POST /lxd_hosts.json # POST /lxd_hosts.json
def create def create
@ -62,13 +74,16 @@ class LxdHostsController < ApplicationController
end end
private private
# Use callbacks to share common setup or constraints between actions.
def set_lxd_host
@lxd_host = LxdHost.find(params[:id])
end
# 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.
def lxd_host_params
params.require(:lxd_host).permit(:name, :uri, :password, :password_confirmation)
end
# Never trust parameters from the scary internet, only allow the white list through.
def lxd_host_params
params.require(:lxd_host).permit(:name, :uri, :password, :password_confirmation)
end
end end
# vim: set et ts=2 sw=2:

35
app/models/certificate.rb

@ -2,12 +2,38 @@ require "openssl"
require 'digest/md5' require 'digest/md5'
class Certificate < ActiveRecord::Base 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 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 end
def cert 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 end
def key_fpr def key_fpr
@ -17,10 +43,5 @@ class Certificate < ActiveRecord::Base
def cert_fpr def cert_fpr
Digest::SHA256.hexdigest(cert.to_der).upcase Digest::SHA256.hexdigest(cert.to_der).upcase
end end
private
def _key
end
end end
# vim: set et ts=2 sw=2: # vim: set et ts=2 sw=2:

70
app/models/lxd/api.rb

@ -1,45 +1,51 @@
module Lxd::API module Lxd::API
def self.get host, certificate
def self.get host, certificate
uri = URI.parse host.uri uri = URI.parse host.uri
con = Net::HTTP.new uri.host, uri.port
con.use_ssl = true
con.cert = OpenSSL::X509::Certificate.new certificate.cert
con.key = OpenSSL::PKey::RSA.new certificate.key
con.verify_mode = OpenSSL::SSL::VERIFY_NONE
resp = self.call con, Net::HTTP::Get.new('/')
raise "unsupported api version" unless resp['metadata'].include? '/1.0'
Lxd::API::V1_0.new con
end
def self.call con, req
resp = con.request req
raise "request failure: " + resp.code unless resp.code != 200
JSON.parse resp.body
end
con = Net::HTTP.new uri.host, uri.port
con.use_ssl = true
con.cert = OpenSSL::X509::Certificate.new certificate.cert
con.key = OpenSSL::PKey::RSA.new certificate.key
con.verify_mode = OpenSSL::SSL::VERIFY_NONE
resp = self.call con, Net::HTTP::Get.new('/')
raise "unsupported api version" unless resp['metadata'].include? '/1.0'
Lxd::API::V1_0.new con
end
def self.call con, req
resp = con.request req
raise "request failure: " + resp.code unless resp.code != 200
JSON.parse resp.body
end
def initialize con def initialize con
@con = con @con = con
end end
def call req def call req
handle_response(Lxd::API.call @con, req)
handle_response(Lxd::API.call @con, req)
end end
def get uri
call Net::HTTP::Get.new uri
end
def get uri
call Net::HTTP::Get.new uri
end
def put uri, data={}
request = Net::HTTP::Put.new uri
request.body = data.to_json
call request
end
def put uri, data={}
request = Net::HTTP::Put.new uri
request.body = data.to_json
call request
end
def post uri, data={}
request = Net::HTTP::Post.new uri
request.body = data.to_json
call request
end
def post uri, data={}
request = Net::HTTP::Post.new uri
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 end
# vim: set ts=2 sw=2:
# vim: set et ts=2 sw=2:

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

@ -1,54 +1,55 @@
class Lxd::API::V1_0 class Lxd::API::V1_0
include Lxd::API
def config
get '/1.0'
end
def config= config={}
put '/1.0', config: config
end
def certificates
get '/1.0/certificates'.map { |uri|
{
:uri => uri,
:cert => get(uri)
}
}
end
def add_certificate cert={}
# TODO validate hash
post '/1.0/certificates', cert
end
def handle_response resp
"""
100 Operation created
101 Started
102 Stopped
103 Running
104 Cancelling
105 Pending
106 Starting
107 Stopping
108 Aborting
109 Freezing
110 Frozen
111 Thawed
200 Success
400 Failure
401 Cancelled
100 to 199: resource state (started, stopped, ready, ...)
200 to 399: positive action result
400 to 599: negative action result
600 to 999: future use
"""
raise "api error" if [400..500].include? resp['error_code']
resp['metadata']
end
include Lxd::API
def config
get '/1.0'
end
def config= config={}
put '/1.0', config: config
end
def certificates
get('/1.0/certificates').map { |uri|
{ :uri => uri }.merge get(uri)
}
end
def add_certificate data={}
# TODO validate hash
post '/1.0/certificates', data
end
def delete_certificate fingerprint
delete '/1.0/certificates/' + fingerprint
end
def handle_response resp
"""
100 Operation created
101 Started
102 Stopped
103 Running
104 Cancelling
105 Pending
106 Starting
107 Stopping
108 Aborting
109 Freezing
110 Frozen
111 Thawed
200 Success
400 Failure
401 Cancelled
100 to 199: resource state (started, stopped, ready, ...)
200 to 399: positive action result
400 to 599: negative action result
600 to 999: future use
"""
raise "api error: (" + resp['error_code'].to_s + ") " + resp['error'] if resp['error_code'] and resp['error_code'] != 403
resp['metadata']
end
end end
# vim: set ts=2 sw=2:
# vim: set et ts=2 sw=2:

36
app/models/lxd/certificate.rb

@ -1,22 +1,26 @@
class Lxd::Certificate class Lxd::Certificate
include ActiveModel::Model
include ActiveModel::Model
attr_accessor :api, :type, :certificate, :fingerprint
attr_accessor :api, :uri, :type, :certificate, :fingerprint
def self.all api
api.certificates.map { |cert|
Lxd::Certificate.new({api: api}.merge cert)
}
end
def self.all api
api.certificates.map { |cert|
Lxd::Certificate.new({api: api}.merge cert)
}
end
def add password=nil, name='lex-deeit'
data = Hash.new
data[:type] = @type if @type else 'client'
data[:name] = name
data[:password] = password if password
data[:certificate] = @certificate if @certificate
def add password=nil, name='lex-deeit'
data = Hash.new
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
@api.add_certificate data
end
def delete
@api.delete_certificate @fingerprint
end
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 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 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> <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 # Raises error for missing translations
# config.action_view.raise_on_missing_translations = true # config.action_view.raise_on_missing_translations = true
# Load certificates config
config.x.certificate = config_for(:certificate)
end end

3
config/environments/production.rb

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

3
config/environments/test.rb

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

3
config/routes.rb

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

7
db/schema.rb

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