diff --git a/.gitignore b/.gitignore index 8308f81..98636ea 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ # Ignore vim swp files .*.sw? +.sw? # ignore generated Gemfile.lock /Gemfile.lock diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bdc68af..f877cc2 100644 --- a/app/controllers/application_controller.rb +++ b/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: diff --git a/app/controllers/certificates_controller.rb b/app/controllers/certificates_controller.rb deleted file mode 100644 index 220d4c7..0000000 --- a/app/controllers/certificates_controller.rb +++ /dev/null @@ -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: diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 97b0de5..b636b19 100644 --- a/app/controllers/dashboard_controller.rb +++ b/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 - 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 # vim: set et ts=2 sw=2: diff --git a/app/controllers/lxd_hosts_controller.rb b/app/controllers/lxd_hosts_controller.rb index eed8dfa..8973692 100644 --- a/app/controllers/lxd_hosts_controller.rb +++ b/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 @@ -62,13 +74,16 @@ class LxdHostsController < ApplicationController end 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 +# vim: set et ts=2 sw=2: diff --git a/app/models/certificate.rb b/app/models/certificate.rb index 5e7b2d0..d4fdaac 100644 --- a/app/models/certificate.rb +++ b/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: diff --git a/app/models/lxd/api.rb b/app/models/lxd/api.rb index 60ca59d..a6c77b5 100644 --- a/app/models/lxd/api.rb +++ b/app/models/lxd/api.rb @@ -1,45 +1,51 @@ module Lxd::API - def self.get host, certificate + def self.get host, certificate 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 @con = con end def call req - handle_response(Lxd::API.call @con, req) + handle_response(Lxd::API.call @con, req) 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 -# vim: set ts=2 sw=2: +# vim: set et ts=2 sw=2: diff --git a/app/models/lxd/api/v1_0.rb b/app/models/lxd/api/v1_0.rb index 0458a9a..1a1e934 100644 --- a/app/models/lxd/api/v1_0.rb +++ b/app/models/lxd/api/v1_0.rb @@ -1,54 +1,55 @@ 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 -# vim: set ts=2 sw=2: +# vim: set et ts=2 sw=2: diff --git a/app/models/lxd/certificate.rb b/app/models/lxd/certificate.rb index da6b852..ce53c83 100644 --- a/app/models/lxd/certificate.rb +++ b/app/models/lxd/certificate.rb @@ -1,22 +1,26 @@ 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 -# vim: set ts=2 sw=2: +# vim: set et ts=2 sw=2: diff --git a/app/models/lxd_host.rb b/app/models/lxd_host.rb index da7d170..b1458fd 100644 --- a/app/models/lxd_host.rb +++ b/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: diff --git a/app/views/certificates/_form.html.erb b/app/views/certificates/_form.html.erb deleted file mode 100644 index 10b22d8..0000000 --- a/app/views/certificates/_form.html.erb +++ /dev/null @@ -1,29 +0,0 @@ -<%= form_for(@certificate) do |f| %> - <% if @certificate.errors.any? %> -
<%= notice %>
- -| Key | -Cert | -Active | -- | ||
|---|---|---|---|---|---|
| <%= certificate.key_fpr.scan(/../).join(':') %> | -<%= certificate.cert_fpr.scan(/../).join(':') %> | -<%= certificate.active %> | -<%= link_to 'Show', certificate %> | -<%= link_to 'Edit', edit_certificate_path(certificate) %> | -<%= link_to 'Destroy', certificate, method: :delete, data: { confirm: 'Are you sure?' } %> | -
<%= notice %>
- -- Key: - <%= @certificate.key %> -
- -- Cert: - <%= @certificate.cert %> -
- -- Active: - <%= @certificate.active %> -
- -<%= link_to 'Edit', edit_certificate_path(@certificate) %> | -<%= link_to 'Back', certificates_path %> diff --git a/app/views/certificates/show.json.jbuilder b/app/views/certificates/show.json.jbuilder deleted file mode 100644 index bcae0cf..0000000 --- a/app/views/certificates/show.json.jbuilder +++ /dev/null @@ -1 +0,0 @@ -json.extract! @certificate, :id, :key, :cert, :active, :created_at, :updated_at diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb index 1899a62..e212918 100644 --- a/app/views/dashboard/index.html.erb +++ b/app/views/dashboard/index.html.erb @@ -1,11 +1,9 @@<%= @lxd_host.class %>
-<%= @cert.class %>
-<%= @lxd_config.class %>
-<%= @lxd_config.api_extensions.inspect %>
-<%= @lxd_config.api_status %>
-<%= @lxd_config.api_version %>
-<%= @lxd_config.auth %>
-<%= @lxd_config.config.inspect %>
-<%= @lxd_config.environment.inspect %>
-<%= @lxd_config.public %>
+<%= @cert.cert_fpr %>
+Serial: <%= @cert.cert.serial %>
+<% @lxd_hosts.each do |host| -%> +<%= host.config.inspect %>
+<% end -%> +<% @certificates.each do |cert| -%> +<%= cert.fingerprint %>
+<% end -%> diff --git a/app/views/lxd_hosts/auth.html.erb b/app/views/lxd_hosts/auth.html.erb new file mode 100644 index 0000000..b486098 --- /dev/null +++ b/app/views/lxd_hosts/auth.html.erb @@ -0,0 +1,13 @@ +...<%= @data.inspect %>
+ +<%= form_for :lxd_hosts, url: { action: "add_key" }, method: 'put' do |f| %> +