ssl_poler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 72c6050edf8b45ecd96024cb4e23cdef79a2a40dd8049b948e930216a9fc3c2e
4
+ data.tar.gz: b821b1b3a5b196659604e1c7f37d47892f054c2314b668bc9ae697a7c46a3fb0
5
+ SHA512:
6
+ metadata.gz: fbf98f664330dfd12c41fc5ad7a193802da66594bc9f47d7c848d4296212b4141f8e1d2b6e736d4f7fcc600d4e29d93b661eff40f29de84dd2578860ef5f6608
7
+ data.tar.gz: d46b5fab78315ddf3816fcc95e2575386984ca157f1eddff2d9af5e348314e517be6ac41acd9bbd65be2c5938c35e497b5a68f471881beffcfc70c4e540a8ce9
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2025-10-13
4
+
5
+ - Initial release
6
+ - SSL certificate checking functionality
7
+ - YAML configuration support
8
+ - CLI interface
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # SslPoler
2
+
3
+ A Ruby gem for checking SSL certificate status from multiple URLs defined in a YAML configuration file. Monitor certificate expiration dates, issuers, and other important certificate details.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'ssl_poler'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```bash
22
+ $ gem install ssl_poler
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### 1. Create a YAML configuration file
28
+
29
+ Create a file (e.g., `config.yml`) with your URLs:
30
+
31
+ ```yaml
32
+ urls:
33
+ - https://example.com
34
+ - https://google.com
35
+ - name: GitHub
36
+ url: https://github.com
37
+ - name: Ruby Gems
38
+ url: https://rubygems.org
39
+ ```
40
+
41
+ URLs can be specified as simple strings or as objects with `name` and `url` keys.
42
+
43
+ ### 2. Run the CLI
44
+
45
+ ```bash
46
+ $ ssl_poler --config config.yml
47
+ ```
48
+
49
+ ### CLI Options
50
+
51
+ ```
52
+ Usage: ssl_poler [options]
53
+ -c, --config PATH Path to YAML config file (required)
54
+ -f, --format FORMAT Output format (text, json)
55
+ -w, --warning-days DAYS Days before expiration to warn (default: 30)
56
+ -h, --help Show this help message
57
+ -v, --version Show version
58
+ ```
59
+
60
+ ### Examples
61
+
62
+ **Basic check:**
63
+ ```bash
64
+ $ ssl_poler --config config.yml
65
+ ```
66
+
67
+ **JSON output:**
68
+ ```bash
69
+ $ ssl_poler --config config.yml --format json
70
+ ```
71
+
72
+ **Custom warning threshold (warn if expiring within 60 days):**
73
+ ```bash
74
+ $ ssl_poler --config config.yml --warning-days 60
75
+ ```
76
+
77
+ ### Sample Output
78
+
79
+ ```
80
+ ================================================================================
81
+ SSL Certificate Check Results
82
+ ================================================================================
83
+
84
+ example.com (https://example.com)
85
+ --------------------------------------------------------------------------------
86
+ Status: OK
87
+ Subject: CN=example.com
88
+ Issuer: CN=DigiCert TLS RSA SHA256 2020 CA1, O=DigiCert Inc, C=US
89
+ Valid From: 2024-01-15 00:00:00 UTC
90
+ Valid To: 2025-02-15 23:59:59 UTC
91
+ Expires In: 125 days
92
+ Serial: 12345678901234567890
93
+ Algorithm: sha256WithRSAEncryption
94
+ Key: OpenSSL::PKey::RSA (2048 bits)
95
+
96
+ ================================================================================
97
+ Summary:
98
+ Total: 4
99
+ OK: 3
100
+ Expiring Soon: 1
101
+ Expired: 0
102
+ Errors: 0
103
+ ================================================================================
104
+ ```
105
+
106
+ ## Programmatic Usage
107
+
108
+ You can also use the gem programmatically in your Ruby code:
109
+
110
+ ```ruby
111
+ require 'ssl_poler'
112
+
113
+ # Check a single URL
114
+ checker = SslPoler::CertificateChecker.new('https://example.com')
115
+ if checker.check
116
+ info = checker.certificate_info
117
+ puts "Expires in #{info[:expires_in_days]} days"
118
+ puts "Valid: #{info[:valid]}"
119
+ puts "Expired: #{info[:expired]}"
120
+ else
121
+ puts "Error: #{checker.error}"
122
+ end
123
+
124
+ # Load and check multiple URLs from config
125
+ config = SslPoler::ConfigLoader.new('config.yml')
126
+ config.load
127
+
128
+ config.urls.each do |entry|
129
+ checker = SslPoler::CertificateChecker.new(entry[:url])
130
+ checker.check
131
+ info = checker.certificate_info
132
+ puts "#{entry[:name]}: #{info[:expires_in_days]} days remaining"
133
+ end
134
+ ```
135
+
136
+ ## Certificate Information
137
+
138
+ The gem provides the following information for each certificate:
139
+
140
+ - **Subject**: The entity to which the certificate is issued
141
+ - **Issuer**: The entity that issued the certificate
142
+ - **Serial Number**: Unique identifier for the certificate
143
+ - **Valid From**: Certificate start date
144
+ - **Valid To**: Certificate expiration date
145
+ - **Expires In**: Number of days until expiration
146
+ - **Expired**: Boolean indicating if certificate has expired
147
+ - **Expires Soon**: Boolean indicating if certificate expires within warning threshold
148
+ - **Valid**: Boolean indicating if certificate is currently valid
149
+ - **Version**: X.509 version
150
+ - **Signature Algorithm**: Algorithm used to sign the certificate
151
+ - **Public Key Algorithm**: Type of public key (RSA, EC, etc.)
152
+ - **Key Size**: Size of the public key in bits
153
+
154
+ ## Exit Codes
155
+
156
+ The CLI uses the following exit codes:
157
+
158
+ - `0`: All certificates are valid and not expiring soon
159
+ - `1`: One or more certificates have errors, are expired, or expiring soon
160
+
161
+ This makes it easy to integrate with monitoring systems and CI/CD pipelines.
162
+
163
+ ## Development
164
+
165
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
166
+
167
+ To install this gem onto your local machine, run `bundle exec rake install`.
168
+
169
+ ## Contributing
170
+
171
+ Bug reports and pull requests are welcome on GitHub at https://github.com/yourusername/ssl_poler.
172
+
173
+ ## License
174
+
175
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/exe/ssl_poler ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "ssl_poler"
4
+
5
+ SslPoler::CLI.new(ARGV).run
@@ -0,0 +1,94 @@
1
+ require "openssl"
2
+ require "net/http"
3
+ require "uri"
4
+
5
+ module SslPoler
6
+ class CertificateChecker
7
+ attr_reader :url, :certificate, :error
8
+
9
+ def initialize(url)
10
+ @url = url
11
+ @certificate = nil
12
+ @error = nil
13
+ end
14
+
15
+ def check
16
+ uri = parse_url(url)
17
+
18
+ http = Net::HTTP.new(uri.host, uri.port)
19
+ http.use_ssl = true
20
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # We just want to inspect the cert, not validate the connection
21
+
22
+ http.start do |connection|
23
+ @certificate = connection.peer_cert
24
+ end
25
+
26
+ true
27
+ rescue => e
28
+ @error = e.message
29
+ false
30
+ end
31
+
32
+ def certificate_info
33
+ return nil unless certificate
34
+
35
+ {
36
+ subject: certificate.subject.to_s,
37
+ issuer: certificate.issuer.to_s,
38
+ serial_number: certificate.serial.to_s,
39
+ not_before: certificate.not_before,
40
+ not_after: certificate.not_after,
41
+ expires_in_days: days_until_expiration,
42
+ expired: expired?,
43
+ expires_soon: expires_soon?,
44
+ valid: valid?,
45
+ version: certificate.version,
46
+ signature_algorithm: certificate.signature_algorithm,
47
+ public_key_algorithm: certificate.public_key.class.name,
48
+ key_size: key_size
49
+ }
50
+ end
51
+
52
+ def valid?
53
+ return false unless certificate
54
+ !expired? && certificate.not_before <= Time.now
55
+ end
56
+
57
+ def expired?
58
+ return false unless certificate
59
+ certificate.not_after < Time.now
60
+ end
61
+
62
+ def expires_soon?(days = 30)
63
+ return false unless certificate
64
+ days_until_expiration <= days && !expired?
65
+ end
66
+
67
+ def days_until_expiration
68
+ return nil unless certificate
69
+ ((certificate.not_after - Time.now) / 86400).round
70
+ end
71
+
72
+ private
73
+
74
+ def parse_url(url)
75
+ uri = URI.parse(url)
76
+ uri.scheme = "https" if uri.scheme.nil?
77
+ uri.port = 443 if uri.port.nil?
78
+ uri
79
+ rescue URI::InvalidURIError => e
80
+ raise Error, "Invalid URL: #{url} - #{e.message}"
81
+ end
82
+
83
+ def key_size
84
+ case certificate.public_key
85
+ when OpenSSL::PKey::RSA
86
+ certificate.public_key.n.num_bits
87
+ when OpenSSL::PKey::EC
88
+ certificate.public_key.group.degree
89
+ else
90
+ nil
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,161 @@
1
+ require "optparse"
2
+
3
+ module SslPoler
4
+ class CLI
5
+ def initialize(args)
6
+ @args = args
7
+ @config_path = nil
8
+ @options = {
9
+ format: :text,
10
+ warning_days: 30
11
+ }
12
+ end
13
+
14
+ def run
15
+ parse_options
16
+
17
+ if @config_path.nil?
18
+ puts "Error: Config file is required"
19
+ puts @option_parser
20
+ exit 1
21
+ end
22
+
23
+ config_loader = ConfigLoader.new(@config_path)
24
+ config_loader.load
25
+
26
+ results = check_certificates(config_loader.urls)
27
+ display_results(results)
28
+
29
+ exit_code = results.any? { |r| r[:error] || r[:expired] || r[:expires_soon] } ? 1 : 0
30
+ exit exit_code
31
+ rescue Error => e
32
+ puts "Error: #{e.message}"
33
+ exit 1
34
+ end
35
+
36
+ private
37
+
38
+ def parse_options
39
+ @option_parser = OptionParser.new do |opts|
40
+ opts.banner = "Usage: ssl_poler [options]"
41
+
42
+ opts.on("-c", "--config PATH", "Path to YAML config file (required)") do |path|
43
+ @config_path = path
44
+ end
45
+
46
+ opts.on("-f", "--format FORMAT", [:text, :json], "Output format (text, json)") do |format|
47
+ @options[:format] = format
48
+ end
49
+
50
+ opts.on("-w", "--warning-days DAYS", Integer, "Days before expiration to warn (default: 30)") do |days|
51
+ @options[:warning_days] = days
52
+ end
53
+
54
+ opts.on("-h", "--help", "Show this help message") do
55
+ puts opts
56
+ exit 0
57
+ end
58
+
59
+ opts.on("-v", "--version", "Show version") do
60
+ puts "ssl_poler version #{VERSION}"
61
+ exit 0
62
+ end
63
+ end
64
+
65
+ @option_parser.parse!(@args)
66
+ end
67
+
68
+ def check_certificates(urls)
69
+ results = []
70
+
71
+ urls.each do |entry|
72
+ puts "Checking #{entry[:name]}..." if @options[:format] == :text
73
+
74
+ checker = CertificateChecker.new(entry[:url])
75
+ success = checker.check
76
+
77
+ if success
78
+ info = checker.certificate_info
79
+ info[:name] = entry[:name]
80
+ info[:url] = entry[:url]
81
+ info[:expires_soon] = info[:expires_in_days] && info[:expires_in_days] <= @options[:warning_days]
82
+ results << info
83
+ else
84
+ results << {
85
+ name: entry[:name],
86
+ url: entry[:url],
87
+ error: checker.error
88
+ }
89
+ end
90
+ end
91
+
92
+ results
93
+ end
94
+
95
+ def display_results(results)
96
+ case @options[:format]
97
+ when :json
98
+ require "json"
99
+ puts JSON.pretty_generate(results)
100
+ else
101
+ display_text_results(results)
102
+ end
103
+ end
104
+
105
+ def display_text_results(results)
106
+ puts "\n" + "=" * 80
107
+ puts "SSL Certificate Check Results"
108
+ puts "=" * 80
109
+
110
+ results.each do |result|
111
+ puts "\n#{result[:name]} (#{result[:url]})"
112
+ puts "-" * 80
113
+
114
+ if result[:error]
115
+ puts " Status: ERROR"
116
+ puts " Error: #{result[:error]}"
117
+ next
118
+ end
119
+
120
+ puts " Status: #{status_text(result)}"
121
+ puts " Subject: #{result[:subject]}"
122
+ puts " Issuer: #{result[:issuer]}"
123
+ puts " Valid From: #{result[:not_before]}"
124
+ puts " Valid To: #{result[:not_after]}"
125
+ puts " Expires In: #{result[:expires_in_days]} days"
126
+ puts " Serial: #{result[:serial_number]}"
127
+ puts " Algorithm: #{result[:signature_algorithm]}"
128
+ puts " Key: #{result[:public_key_algorithm]} (#{result[:key_size]} bits)"
129
+ end
130
+
131
+ puts "\n" + "=" * 80
132
+ display_summary(results)
133
+ end
134
+
135
+ def status_text(result)
136
+ if result[:expired]
137
+ "EXPIRED"
138
+ elsif result[:expires_soon]
139
+ "WARNING (expires in #{result[:expires_in_days]} days)"
140
+ else
141
+ "OK"
142
+ end
143
+ end
144
+
145
+ def display_summary(results)
146
+ total = results.size
147
+ errors = results.count { |r| r[:error] }
148
+ expired = results.count { |r| r[:expired] }
149
+ expiring_soon = results.count { |r| r[:expires_soon] && !r[:expired] }
150
+ ok = total - errors - expired - expiring_soon
151
+
152
+ puts "Summary:"
153
+ puts " Total: #{total}"
154
+ puts " OK: #{ok}"
155
+ puts " Expiring Soon: #{expiring_soon}"
156
+ puts " Expired: #{expired}"
157
+ puts " Errors: #{errors}"
158
+ puts "=" * 80
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,53 @@
1
+ require "yaml"
2
+
3
+ module SslPoler
4
+ class ConfigLoader
5
+ attr_reader :config, :urls
6
+
7
+ def initialize(config_path)
8
+ @config_path = config_path
9
+ @config = nil
10
+ @urls = []
11
+ end
12
+
13
+ def load
14
+ raise Error, "Config file not found: #{@config_path}" unless File.exist?(@config_path)
15
+
16
+ @config = YAML.load_file(@config_path)
17
+ validate_config!
18
+ extract_urls
19
+ self
20
+ rescue Psych::SyntaxError => e
21
+ raise Error, "Invalid YAML syntax in config file: #{e.message}"
22
+ end
23
+
24
+ private
25
+
26
+ def validate_config!
27
+ raise Error, "Config file is empty" if @config.nil? || @config.empty?
28
+ raise Error, "Config must contain 'urls' key" unless @config.key?("urls")
29
+ raise Error, "URLs must be an array" unless @config["urls"].is_a?(Array)
30
+ raise Error, "URLs array cannot be empty" if @config["urls"].empty?
31
+ end
32
+
33
+ def extract_urls
34
+ @urls = @config["urls"].map do |entry|
35
+ case entry
36
+ when String
37
+ { url: entry, name: entry }
38
+ when Hash
39
+ {
40
+ url: entry["url"] || entry[:url],
41
+ name: entry["name"] || entry[:name] || entry["url"] || entry[:url]
42
+ }
43
+ else
44
+ raise Error, "Invalid URL entry: #{entry.inspect}"
45
+ end
46
+ end
47
+
48
+ @urls.each do |entry|
49
+ raise Error, "URL cannot be nil or empty" if entry[:url].nil? || entry[:url].empty?
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module SslPoler
2
+ VERSION = "0.1.0"
3
+ end
data/lib/ssl_poler.rb ADDED
@@ -0,0 +1,8 @@
1
+ require_relative "ssl_poler/version"
2
+ require_relative "ssl_poler/certificate_checker"
3
+ require_relative "ssl_poler/config_loader"
4
+ require_relative "ssl_poler/cli"
5
+
6
+ module SslPoler
7
+ class Error < StandardError; end
8
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ssl_poler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michail Pantelelis
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '13.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '13.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ description: A Ruby gem that reads URLs from a YAML configuration and checks their
41
+ SSL certificate status, including expiration dates, issuers, and more.
42
+ email:
43
+ - mpantel@aegean.gr
44
+ executables:
45
+ - ssl_poler
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - CHANGELOG.md
50
+ - LICENSE.txt
51
+ - README.md
52
+ - exe/ssl_poler
53
+ - lib/ssl_poler.rb
54
+ - lib/ssl_poler/certificate_checker.rb
55
+ - lib/ssl_poler/cli.rb
56
+ - lib/ssl_poler/config_loader.rb
57
+ - lib/ssl_poler/version.rb
58
+ homepage: https://github.com/mpantel/ssl_poler
59
+ licenses:
60
+ - MIT
61
+ metadata:
62
+ source_code_uri: https://github.com/mpantel/ssl_poler
63
+ changelog_uri: https://github.com/mpantel/ssl_poler/blob/main/CHANGELOG.md
64
+ bug_tracker_uri: https://github.com/mpantel/ssl_poler/issues
65
+ documentation_uri: https://github.com/mpantel/ssl_poler/blob/main/README.md
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 3.2.0
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.7.1
81
+ specification_version: 4
82
+ summary: Check SSL certificate status for multiple URLs
83
+ test_files: []