Skip to content
This repository was archived by the owner on Dec 18, 2022. It is now read-only.

Commit ab6a29b

Browse files
authored
Merge pull request #1 from smalex86/dev
2020-02-17
2 parents 199e48a + 1376257 commit ab6a29b

23 files changed

+318
-49
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@
3434
/yarn-error.log
3535
yarn-debug.log*
3636
.yarn-integrity
37+
38+
# docker
39+
/docker/postgresql/db/

Gemfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@ gem 'bootsnap', '>= 1.4.2', require: false
3131
# grape
3232
gem 'grape'
3333
gem 'grape-entity'
34+
gem 'grape_on_rails_routes'
3435

3536
# sidekiq
3637
gem 'sidekiq'
3738
gem 'sidekiq-scheduler'
3839

40+
# postgres
41+
gem 'pg'
42+
3943
group :development, :test do
4044
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
4145
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
@@ -50,7 +54,7 @@ group :development do
5054
gem 'spring-watcher-listen', '~> 2.0.0'
5155

5256
gem 'solargraph'
53-
57+
5458
# rebocop
5559
gem 'rubocop'
5660
gem 'rubocop-performance'

Gemfile.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ GEM
119119
grape-entity (0.7.1)
120120
activesupport (>= 4.0)
121121
multi_json (>= 1.3.2)
122+
grape_on_rails_routes (0.3.2)
123+
rails (>= 3.1.1)
122124
i18n (1.8.2)
123125
concurrent-ruby (~> 1.0)
124126
jaro_winkler (1.5.4)
@@ -153,6 +155,7 @@ GEM
153155
parallel (1.19.1)
154156
parser (2.7.0.2)
155157
ast (~> 2.4.0)
158+
pg (1.2.2)
156159
public_suffix (4.0.3)
157160
puma (4.3.1)
158161
nio4r (~> 2.0)
@@ -309,8 +312,10 @@ DEPENDENCIES
309312
capybara (>= 2.15)
310313
grape
311314
grape-entity
315+
grape_on_rails_routes
312316
jbuilder (~> 2.7)
313317
listen (>= 3.0.5, < 3.2)
318+
pg
314319
puma (~> 4.1)
315320
rails (~> 6.0.2, >= 6.0.2.1)
316321
rubocop

Procfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
web: bundle exec rails server -p $PORT
2+
worker: bundle exec sidekiq -c 5

README.md

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,62 @@
1-
# README
2-
3-
This README would normally document whatever steps are necessary to get the
4-
application up and running.
5-
6-
Things you may want to cover:
7-
8-
* Ruby version
9-
10-
* System dependencies
11-
12-
* Configuration
13-
14-
* Database creation
15-
16-
* Database initialization
17-
18-
* How to run the test suite
19-
20-
* Services (job queues, cache servers, search engines, etc.)
21-
22-
* Deployment instructions
23-
24-
* ...
1+
# Мониторинг сертификатов SSL
2+
3+
## Описание функционала
4+
5+
Приложение реализует следующий функционал:
6+
* В базу добавляются домены, для которых нужно отслеживать
7+
проблемы с SSL сертификатами
8+
* Нельзя дважды добавить один и тот же домен
9+
* У домена есть два статуса - "Всё хорошо" и "Всё плохо"
10+
* По результатам проверки у домена меняется статус
11+
* Текст ошибки сохраняется в той же модели
12+
* Проверки происходят в фоне, раз в 20 минут
13+
* Подключен веб-интерфейс sidekiq и закрыт Basic-авторизацией
14+
* Для проверки используется openssl, проверка происходит внутри приложения без
15+
использования сторонних API и сервисов
16+
* Интерфейс не реализовыван, только API
17+
18+
## API
19+
20+
Реализованы следующие методы:
21+
* `GET /status` - выводит все домены с их текущим состоянием
22+
* `POST /domain` - добавляет новый домен в список, при добавлении домена выполняется его проверка
23+
24+
## Запуск приложения
25+
26+
### 1. Установка гемов
27+
```ruby
28+
bundle install
29+
```
30+
31+
## 2. Запуск служб
32+
33+
Если в системе не установлены postgresql и redis, то запустить их через
34+
docker-compose:
35+
36+
```bash
37+
cd docker
38+
./services-up.sh
39+
```
40+
41+
Для остановки контейнеров docker использовать следующий скрипт:
42+
```bash
43+
cd docker
44+
./services-down.sh
45+
```
46+
47+
## 3. Создание базы данных и ее структуры
48+
49+
```bash
50+
rails db:create
51+
rails db:migrate
52+
```
53+
54+
## 4. Запуск приложения
55+
```bash
56+
rails s
57+
```
58+
59+
## 5. Запуск sidekiq
60+
```bash
61+
bundle exec sidekiq
62+
```

app/api/ssl_monitor/base.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module SslMonitor
2+
class Base < Grape::API
3+
mount SslMonitor::Certificates
4+
end
5+
end

app/api/ssl_monitor/certificates.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module SslMonitor
2+
class Certificates < Grape::API
3+
format :json
4+
5+
desc 'Добавление нового домена' do
6+
params SslMonitor::Entities::Certificate.documentation
7+
end
8+
post '/domain' do
9+
c = Certificate.create!(domain: params[:domain])
10+
c.check
11+
present c, with: SslMonitor::Entities::Certificate
12+
rescue StandardError => e
13+
{ result: 'bad', error: e.message }
14+
end
15+
16+
desc 'Список доменов с их статусами проверки сертификатов'
17+
get '/status' do
18+
present(
19+
Certificate.all.order(:domain),
20+
with: SslMonitor::Entities::Certificate
21+
)
22+
end
23+
end
24+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module SslMonitor
2+
module Entities
3+
class Certificate < Grape::Entity
4+
expose :domain, documentation: { type: 'string', desc: 'Домен для проверки' }
5+
expose :formatted_status, as: :status
6+
expose :error
7+
expose :updated_at
8+
9+
private
10+
11+
def formatted_status
12+
object.status.to_i.zero? ? 'Всё хорошо' : 'Всё плохо'
13+
end
14+
end
15+
end
16+
end

app/jobs/monitor_job.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class MonitorJob < ApplicationJob
2+
def perform
3+
Certificate.check
4+
end
5+
end

app/models/certificate.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
class Certificate < ApplicationRecord
2+
ONE_DAY = 86_400
3+
4+
require 'net/http'
5+
require 'openssl'
6+
7+
# Коррекция домена перед создание записи
8+
before_create do
9+
self.domain = URI.parse(domain).host if domain.present?
10+
self.error = '' if error.nil?
11+
self.status = 0 if status.nil?
12+
end
13+
14+
# Выполнить проверку сертификата домена
15+
def check
16+
set_ok
17+
begin
18+
check_expired(cert.not_after)
19+
rescue Errno::ECONNREFUSED => e
20+
self.error = "Ошибка соединения: #{e.message}"
21+
rescue OpenSSL::SSL::SSLError => e
22+
ssl_error(e.message)
23+
rescue StandardError => e
24+
self.error = "Ошибка: #{e.message}"
25+
end
26+
self.status = 1 if error.present?
27+
save!
28+
puts "Domain: #{domain}, status: #{status}, error: #{error}"
29+
end
30+
31+
# Получить сертификат
32+
# @return [OpenSSL::X509::Certificate]
33+
def cert
34+
uri = URI::HTTPS.build(host: domain)
35+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true)
36+
response.peer_cert
37+
end
38+
39+
# Начальные значения
40+
def set_ok
41+
self.error = ''
42+
self.status = 0
43+
touch
44+
end
45+
46+
# Текст ошибки SSL
47+
# @param [String] error
48+
# @return [String]
49+
def ssl_error(msg)
50+
pos = msg.index('error: ')
51+
self.error = "Ошибка SSL: #{msg[pos + 7, msg.length - 1]}" unless pos.nil?
52+
end
53+
54+
# Проверить когда истекает действие сертификата
55+
# @param [Time] not_after
56+
def check_expired(not_after)
57+
if Time.now + 14 * ONE_DAY > not_after
58+
self.error = 'Действие сертификата истекает менее чем через 2 недели'
59+
return
60+
end
61+
if Time.now + 7 * ONE_DAY > not_after
62+
self.error = 'Действие сертификата истекает менее чем через 1 неделю'
63+
end
64+
end
65+
66+
# Проверить все
67+
def self.check
68+
Certificate.all.each(&:check)
69+
end
70+
end

config/database.yml

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
1-
# SQLite. Versions 3.8.0 and up are supported.
2-
# gem install sqlite3
3-
#
4-
# Ensure the SQLite 3 gem is defined in your Gemfile
5-
# gem 'sqlite3'
6-
#
7-
default: &default
8-
adapter: sqlite3
9-
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10-
timeout: 5000
11-
121
development:
13-
<<: *default
14-
database: db/development.sqlite3
15-
16-
# Warning: The database defined as "test" will be erased and
17-
# re-generated from your development database when you run "rake".
18-
# Do not set this db to the same as development or production.
19-
test:
20-
<<: *default
21-
database: db/test.sqlite3
2+
adapter: postgresql
3+
encoding: utf8
4+
username: postgres
5+
password: 12345678
6+
database: sslmonitor
7+
host: localhost
8+
pool: 30
9+
timeout: 5000
10+
port: 5432
2211

2312
production:
24-
<<: *default
25-
database: db/production.sqlite3
13+
adapter: postgresql
14+
encoding: utf8
15+
username: postgres
16+
password: 12345678
17+
database: sslmonitor
18+
host: localhost
19+
pool: 30
20+
timeout: 5000
21+
port: 5432

config/routes.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
Rails.application.routes.draw do
2-
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
2+
3+
require 'sidekiq/web'
4+
require 'sidekiq-scheduler/web'
5+
6+
mount SslMonitor::Base => '/'
7+
8+
Sidekiq::Web.use Rack::Auth::Basic do |username, password|
9+
username == 'admin' && password == '12345678'
10+
end
11+
mount Sidekiq::Web => '/sidekiq'
312
end

config/sidekiq.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
:verbose: false
2+
:concurrency: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
3+
:timeout: 30
4+
:queues:
5+
- [low_priority, 1]
6+
- [high_priority, 2] # high priority
7+
:schedule:
8+
MonitorJob:
9+
cron: '*/2 * * * *'
10+
queue: scheduler
11+
enabled: true
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class CreateCertificates < ActiveRecord::Migration[6.0]
2+
def change
3+
create_table(
4+
:certificates,
5+
comment: 'Таблица сертификатов'
6+
) do |t|
7+
t.string :domain, comment: 'Домен, для которого выполняется проверка серта'
8+
t.integer :status, comment: 'Код статуса проверки сертификата'
9+
t.string :error, comment: 'Текст ошибки'
10+
t.datetime :checked_at, comment: 'Дата последней проверки'
11+
t.timestamps
12+
end
13+
end
14+
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class AddUniqueDomainIndexIntoCertificates < ActiveRecord::Migration[6.0]
2+
def up
3+
add_index :certificates, :domain, unique: true
4+
end
5+
def down
6+
remove_index :certificates, name: 'index_certificates_on_domain'
7+
end
8+
end

0 commit comments

Comments
 (0)