Compare commits
3 Commits
489d77c65b
...
253b07e20a
Author | SHA1 | Date |
---|---|---|
|
253b07e20a | 8 months ago |
|
f64dd9a640 | 8 months ago |
|
0686d864dd | 8 months ago |
@ -0,0 +1,3 @@ |
||||
--require spec_helper |
||||
--format progress |
||||
--format html --out tmp/rspec.html |
@ -0,0 +1,2 @@ |
||||
sidekiq: bundle exec sidekiq -q default |
||||
sidekiq_1_0: BUNDLE_GEMFILE=Gemfile-2.3 bin/sidekiq 1.0 -q tls_1_0 |
@ -0,0 +1,2 @@ |
||||
class StatsController < ApplicationController |
||||
end |
@ -1 +1,2 @@ |
||||
import 'css/application' |
||||
import 'bootstrap/js/dist/tab' |
||||
|
@ -1,36 +1,67 @@ |
||||
class Analysis < ApplicationRecord |
||||
enum service: %i[https smtp xmpp tls ssh].collect { |e| [e, e.to_s] }.to_h |
||||
validates :service, presence: true |
||||
validates :host, presence: true |
||||
|
||||
def self.[](service, host, args) |
||||
key = self.key service, host, args |
||||
self.find_by key |
||||
end |
||||
|
||||
def self.pending!(service, host, args) |
||||
key = self.key service, host, args |
||||
analysis = self.find_or_create_by! key |
||||
analysis.pending! |
||||
end |
||||
|
||||
def pending! |
||||
self.update! pending: true |
||||
self |
||||
end |
||||
|
||||
def self.post!(service, host, args, result) |
||||
analysis = self[service, host, args] |
||||
analysis.post! result |
||||
end |
||||
|
||||
def post!(result) |
||||
self.update! pending: false, result: result |
||||
end |
||||
|
||||
private |
||||
|
||||
def self.key(service, host, args) |
||||
{ service: service, host: host, args: args } |
||||
end |
||||
enum service: %i[https smtp xmpp tls ssh].collect { |e| [e, e.to_s] }.to_h |
||||
validates :service, presence: true |
||||
validates :host, presence: true |
||||
|
||||
def self.[](service, host, args) |
||||
key = self.key service, host, args |
||||
self.find_by key |
||||
end |
||||
|
||||
def self.pending!(service, host, args) |
||||
key = self.key service, host, args |
||||
analysis = self.find_or_create_by! key |
||||
analysis.pending! |
||||
end |
||||
|
||||
def pending! |
||||
self.update! pending: true |
||||
self |
||||
end |
||||
|
||||
def self.post!(service, host, args, result) |
||||
analysis = self[service, host, args] |
||||
analysis.post! result |
||||
end |
||||
|
||||
RESOLVER = ::Resolv::DNS.new |
||||
DNS_TXT_FORMAT = /^cryptcheck=(.*)/ |
||||
DEBUG = 'debug' |
||||
DEFAULT_REFRESH_DELAY = Rails.configuration.refresh_delay |
||||
|
||||
def find_refresh_delay |
||||
host = self.host |
||||
loop do |
||||
break unless host && PublicSuffix.valid?(host) |
||||
RESOLVER.getresources(host, ::Resolv::DNS::Resource::IN::TXT).each do |txt| |
||||
txt.strings.each do |value| |
||||
if match = DNS_TXT_FORMAT.match(value) |
||||
match = match[1] |
||||
return if match == DEBUG |
||||
begin |
||||
delay = ::ActiveSupport::Duration.parse match |
||||
delay = DEFAULT_REFRESH_DELAY if delay > DEFAULT_REFRESH_DELAY |
||||
return delay |
||||
rescue ActiveSupport::Duration::ISO8601Parser::ParsingError |
||||
end |
||||
end |
||||
end |
||||
end |
||||
_, host = host.split '.', 2 |
||||
end |
||||
DEFAULT_REFRESH_DELAY |
||||
rescue |
||||
DEFAULT_REFRESH_DELAY |
||||
end |
||||
|
||||
def post!(result) |
||||
refresh_at = self.find_refresh_delay&.since |
||||
self.update! pending: false, refresh_at: refresh_at, result: result |
||||
end |
||||
|
||||
private |
||||
|
||||
def self.key(service, host, args) |
||||
{ service: service, host: host, args: args } |
||||
end |
||||
end |
||||
|
@ -0,0 +1,167 @@ |
||||
<% colors = { |
||||
"A+" => 'great', |
||||
"good" => 'great', |
||||
"A" => 'great', |
||||
"B+" => 'best', |
||||
"B" => 'best', |
||||
"C+" => 'good', |
||||
"C" => 'good', |
||||
"D" => 'effort', |
||||
"E" => 'warning', |
||||
"F" => 'error', |
||||
"G" => 'critical', |
||||
"bad" => 'critical', |
||||
"ssl" => 'critical', |
||||
"tls" => 'error', |
||||
"tls1_2" => 'effort', |
||||
"tls1_2_only" => 'great' |
||||
} %> |
||||
|
||||
<ul class="nav nav-pills nav-fill" id="pills-tab" role="Navigation stats list"> |
||||
<% %i[https smtp tls xmpp].each do |service| %> |
||||
<li class="nav-item" role="presentation"> |
||||
<button class="nav-link<%= " active btn-dark" if service.to_s == "https" %>" |
||||
id="nav-<%= service %>-pill" data-bs-toggle="pill" |
||||
data-bs-target="#nav-<%= service %>" type="button" role="Button to show <%= service %> stats" aria-controls="nav-<%= service %>" aria-selected="true"> |
||||
<%= service.to_s.upcase %> |
||||
</button> |
||||
</li> |
||||
<% end %> |
||||
</ul> |
||||
|
||||
<div class="tab-content p-2 mb-4" id="nav-pillsContent"> |
||||
<% %i[https smtp tls xmpp].each do |service| %> |
||||
<div class="tab-pane fade show <%= "active" if service.to_s == "https" %>" |
||||
id="nav-<%= service %>" role="<%= service.to_s %>> stats" |
||||
aria-labelledby="nav-<%= service %>-pill"> |
||||
<h2>Grades for service <%= service.to_s.upcase %></h2> |
||||
|
||||
<% |
||||
grades = Stat["grades_for_#{service}"] |
||||
total = grades.data.collect { _2 }.sum |
||||
%> |
||||
|
||||
<p>Over <%= total %> URL tested with a grade.</p> |
||||
|
||||
<table class="table table-bordered table-striped"> |
||||
<thead class="bg-dark text-light"> |
||||
<tr> |
||||
<th>Grade</th> |
||||
<th>Count</th> |
||||
<th></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<% sorted_grades = grades.data.keys.sort &CryptCheck::Grade.method(:compare) |
||||
sorted_grades.each do |grade| |
||||
unless %w(T V).include?(grade) |
||||
count = grades.data[grade].to_i |
||||
percent = (count.to_f / total.to_f) * 100.0 |
||||
color = CryptCheck::Grade::GRADE_STATUS.fetch grade.to_sym %> |
||||
<tr> |
||||
<td class="col-4"><%= grade.capitalize %></td> |
||||
<td class="col-4"><%= count %> |
||||
(<%= percent.round %>%) |
||||
</td> |
||||
<td class="col-4"> |
||||
<div class="progress bg-light"> |
||||
<div class="progress-bar progress-<%= color %> border-dark" |
||||
style="width: <%= percent.round %>%" role="progressbar" |
||||
aria-valuenow="<%= percent.round %>" aria-valuemin="0" |
||||
aria-valuemax="100"></div> |
||||
</div> |
||||
</td> |
||||
</tr> |
||||
<% end |
||||
end %> |
||||
</tbody> |
||||
</table> |
||||
|
||||
<h2>Ciphers for service <%= service.to_s.upcase %></h2> |
||||
|
||||
<% |
||||
name = "ciphers_for_#{service}" |
||||
ciphers = Stat[name] |
||||
total = ciphers.data.collect { _2 }.sum |
||||
%> |
||||
<p>Over <%= total %> URL tested with a cipher.</p> |
||||
|
||||
<table class="table table-bordered table-striped"> |
||||
<thead class="bg-dark text-light"> |
||||
<tr> |
||||
<%= content_tag :th, t(:'.ciphers.title') %> |
||||
<th>Count</th> |
||||
<th></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<% |
||||
ciphers = ciphers.data |
||||
%w[good bad].each do |grade| |
||||
count = ciphers.fetch grade |
||||
percent = (count.to_f / total.to_f) * 100.0 |
||||
color = colors[grade] |
||||
%> |
||||
<tr> |
||||
<td class="col-4"><%= t ".ciphers.#{grade}" %></td> |
||||
<td class="col-4"><%= count %> (<%= percent.round %> |
||||
%) |
||||
</td> |
||||
<td class="col-4"> |
||||
<div class="progress bg-light"> |
||||
<div class="progress-bar progress-<%= color %>" |
||||
style="width: <%= percent.round %>%" role="progressbar" |
||||
aria-valuenow="<%= percent.round %>" aria-valuemin="0" |
||||
aria-valuemax="100"></div> |
||||
</div> |
||||
</td> |
||||
</tr> |
||||
<% end %> |
||||
</tbody> |
||||
</table> |
||||
|
||||
<h2>TLS for service <%= service.to_s.upcase %></h2> |
||||
|
||||
<% |
||||
name = "tls_for_#{service}" |
||||
tls = Stat[name] |
||||
total = tls.data.collect { _2 }.sum |
||||
%> |
||||
<p>Over <%= total %> URL tested with TLS.</p> |
||||
|
||||
<table class="table table-bordered table-striped"> |
||||
<thead class="bg-dark text-light"> |
||||
<tr> |
||||
<%= content_tag :th, t(:'.tls.title') %> |
||||
<th>Count</th> |
||||
<th></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<% |
||||
tls = tls.data |
||||
%w[tls1_2_only tls1_2 tls ssl].each do |grade| |
||||
count = tls.fetch grade |
||||
percent = (count.to_f / total.to_f) * 100.0 |
||||
color = colors[grade] |
||||
%> |
||||
<tr> |
||||
<td class="col-4"><%= t ".tls.#{grade}" %></td> |
||||
<td class="col-4"><%= count %> (<%= percent.round %> |
||||
%) |
||||
</td> |
||||
<td class="col-4"> |
||||
<div class="progress bg-light"> |
||||
<div class="progress-bar progress-<%= color %>" |
||||
style="width: <%= percent.round %>%" role="progressbar" |
||||
aria-valuenow="<%= percent.round %>" aria-valuemin="0" |
||||
aria-valuemax="100"></div> |
||||
</div> |
||||
</td> |
||||
</tr> |
||||
<% end %> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
<% end %> |
||||
</div> |
@ -0,0 +1,29 @@ |
||||
#!/usr/bin/env ruby |
||||
# frozen_string_literal: true |
||||
|
||||
# |
||||
# This file was generated by Bundler. |
||||
# |
||||
# The application 'rspec' is installed as part of a gem, and |
||||
# this file is here to facilitate running it. |
||||
# |
||||
|
||||
require "pathname" |
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", |
||||
Pathname.new(__FILE__).realpath) |
||||
|
||||
bundle_binstub = File.expand_path("../bundle", __FILE__) |
||||
|
||||
if File.file?(bundle_binstub) |
||||
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ |
||||
load(bundle_binstub) |
||||
else |
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. |
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") |
||||
end |
||||
end |
||||
|
||||
require "rubygems" |
||||
require "bundler/setup" |
||||
|
||||
load Gem.bin_path("rspec-core", "rspec") |
@ -0,0 +1,16 @@ |
||||
class AddRefreshToAnalysis < ActiveRecord::Migration[7.0] |
||||
def up |
||||
add_column :analyses, :refresh_at, :timestamp |
||||
|
||||
delay = Rails.configuration.refresh_delay |
||||
progress = ProgressBar.create total: Analysis.count, format: '%t (%c/%C) %W %e' |
||||
Analysis.all.each do |analysis| |
||||
analysis.update_column :refresh_at, analysis.updated_at + delay |
||||
progress.increment |
||||
end |
||||
end |
||||
|
||||
def down |
||||
remove_column :analyses, :refresh_at |
||||
end |
||||
end |
@ -0,0 +1,55 @@ |
||||
require 'rails_helper' |
||||
|
||||
describe Analysis do |
||||
describe '::find_refresh_delay' do |
||||
it 'must return host value' do |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([::Resolv::DNS::Resource::IN::TXT.new('cryptcheck=PT1M')]) |
||||
allow(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_raise(Exception, :must_not_be_called) |
||||
host = Analysis.new host: 'www.example.org' |
||||
expect(host.find_refresh_delay).to eq 1.minute |
||||
end |
||||
|
||||
it 'must recurse domain value' do |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([]) |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([::Resolv::DNS::Resource::IN::TXT.new('cryptcheck=PT1M')]) |
||||
allow(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_raise(Exception, :must_not_be_called) |
||||
host = Analysis.new host: 'www.example.org' |
||||
expect(host.find_refresh_delay).to eq 1.minute |
||||
end |
||||
|
||||
it 'must not test TLD' do |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([]) |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([]) |
||||
allow(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_raise(Exception, :must_not_be_called) |
||||
host = Analysis.new host: 'www.example.org' |
||||
expect(host.find_refresh_delay).to eq 1.hour |
||||
end |
||||
|
||||
it 'must support debug mode' do |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([::Resolv::DNS::Resource::IN::TXT.new('cryptcheck=debug')]) |
||||
allow(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_raise(Exception, :must_not_be_called) |
||||
host = Analysis.new host: 'www.example.org' |
||||
expect(host.find_refresh_delay).to be_nil |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue