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? %> -
-

<%= pluralize(@certificate.errors.count, "error") %> prohibited this certificate from being saved:

- - -
- <% end %> - -
- <%= f.label :key %>
- <%= f.text_area :key %> -
-
- <%= f.label :cert %>
- <%= f.text_area :cert %> -
-
- <%= f.label :active %>
- <%= f.check_box :active %> -
-
- <%= f.submit %> -
-<% end %> diff --git a/app/views/certificates/edit.html.erb b/app/views/certificates/edit.html.erb deleted file mode 100644 index 09bf928..0000000 --- a/app/views/certificates/edit.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -

Editing Certificate

- -<%= render 'form' %> - -<%= link_to 'Show', @certificate %> | -<%= link_to 'Back', certificates_path %> diff --git a/app/views/certificates/index.html.erb b/app/views/certificates/index.html.erb deleted file mode 100644 index c50ab12..0000000 --- a/app/views/certificates/index.html.erb +++ /dev/null @@ -1,31 +0,0 @@ -

<%= notice %>

- -

Listing Certificates

- - - - - - - - - - - - - <% @certificates.each do |certificate| %> - - - - - - - - - <% end %> - -
KeyCertActive
<%= 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?' } %>
- -
- -<%= link_to 'New Certificate', new_certificate_path %> diff --git a/app/views/certificates/index.json.jbuilder b/app/views/certificates/index.json.jbuilder deleted file mode 100644 index 59de5a6..0000000 --- a/app/views/certificates/index.json.jbuilder +++ /dev/null @@ -1,4 +0,0 @@ -json.array!(@certificates) do |certificate| - json.extract! certificate, :id, :key, :cert, :active - json.url certificate_url(certificate, format: :json) -end diff --git a/app/views/certificates/new.html.erb b/app/views/certificates/new.html.erb deleted file mode 100644 index d125662..0000000 --- a/app/views/certificates/new.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -

New Certificate

- -<%= render 'form' %> - -<%= link_to 'Back', certificates_path %> diff --git a/app/views/certificates/show.html.erb b/app/views/certificates/show.html.erb deleted file mode 100644 index 4750646..0000000 --- a/app/views/certificates/show.html.erb +++ /dev/null @@ -1,19 +0,0 @@ -

<%= 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 @@

Dashboard#index

-

<%= @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 @@ +

Authenticate Lxd Host: <%= @lxd_host.name %>

+ +

...<%= @data.inspect %>

+ +<%= form_for :lxd_hosts, url: { action: "add_key" }, method: 'put' do |f| %> +
+ <%= f.label :password %>
+ <%= f.password_field :password %> +
+
+ <%= f.submit %> +
+<% end %> diff --git a/config/certificate.yml b/config/certificate.yml new file mode 100644 index 0000000..2ae8dea --- /dev/null +++ b/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: diff --git a/config/environments/development.rb b/config/environments/development.rb index b55e214..bfb631b 100644 --- a/config/environments/development.rb +++ b/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 diff --git a/config/environments/production.rb b/config/environments/production.rb index 5c1b32e..176c9e2 100644 --- a/config/environments/production.rb +++ b/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 diff --git a/config/environments/test.rb b/config/environments/test.rb index 1c19f08..5c5c438 100644 --- a/config/environments/test.rb +++ b/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 diff --git a/config/routes.rb b/config/routes.rb index 4312c64..dee9a0f 100644 --- a/config/routes.rb +++ b/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. diff --git a/db/migrate/20160425195446_delete_password_from_lxd_hosts.rb b/db/migrate/20160425195446_delete_password_from_lxd_hosts.rb new file mode 100644 index 0000000..1b0d3b2 --- /dev/null +++ b/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: diff --git a/db/schema.rb b/db/schema.rb index 00d3265..36fd2a7 100644 --- a/db/schema.rb +++ b/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,9 +24,8 @@ 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 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end end