Skip to content

Commit 2347a9c

Browse files
authored
feat: Added configuration and basic logging to hyperproof sync. (#9)
1 parent d1d9ff2 commit 2347a9c

File tree

13 files changed

+202
-42
lines changed

13 files changed

+202
-42
lines changed

.github/workflows/ruby.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
working-directory: ${{ matrix.dir }}
6161
- name: RuboCop Linter
6262
working-directory: ${{ matrix.dir }}
63-
run: bundle exec rubocop
63+
run: bundle exec rubocop --format github
6464

6565
spec:
6666
name: Run ruby tests
@@ -81,4 +81,4 @@ jobs:
8181
working-directory: ${{ matrix.dir }}
8282
- name: Run tests
8383
working-directory: ${{ matrix.dir }}
84-
run: bundle exec rspec
84+
run: bundle exec rake spec

hyperproof/Gemfile.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ PATH
66
aws-sdk-configservice (~> 1.128)
77
aws-sdk-rds (~> 1.276)
88
aws-sdk-resourceexplorer2 (~> 1.36)
9+
configsl (~> 1.0)
910
csv (~> 3.3)
1011
faraday (~> 2.13)
1112
faraday-multipart (~> 1.1)
@@ -72,11 +73,14 @@ GEM
7273
benchmark (0.4.0)
7374
bigdecimal (3.1.9)
7475
concurrent-ruby (1.3.4)
76+
configsl (1.0.2)
77+
facets (~> 3.1)
7578
connection_pool (2.5.3)
7679
csv (3.3.4)
7780
diff-lcs (1.6.2)
7881
docile (1.4.1)
7982
drb (2.2.1)
83+
facets (3.1.0)
8084
faraday (2.13.1)
8185
faraday-net_http (>= 2.0, < 3.5)
8286
json

hyperproof/cfa-security-controls-hyperproof.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
2727
s.add_dependency 'aws-sdk-configservice', '~> 1.128'
2828
s.add_dependency 'aws-sdk-rds', '~> 1.276'
2929
s.add_dependency 'aws-sdk-resourceexplorer2', '~> 1.36'
30+
s.add_dependency 'configsl', '~> 1.0'
3031
s.add_dependency 'csv', '~> 3.3'
3132
s.add_dependency 'faraday', '~> 2.13'
3233
s.add_dependency 'faraday-multipart', '~> 1.1'
Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,63 @@
11
# frozen_string_literal: true
22

3+
require_relative 'hyperproof/config'
34
require_relative 'hyperproof/entities/label'
45
require_relative 'hyperproof/entities/proof'
56
require_relative 'hyperproof/proofs'
67

78
module CfaSecurityControls
89
# Top level module for our gem.
910
module Hyperproof
11+
@mutex = Mutex.new
12+
13+
# Set or load the system configuration.
14+
#
15+
# If no configuration is explicitly set, it will be loaded from the
16+
# environment.
17+
#
18+
# @param config [Config] Configuration to set for the system.
19+
def self.config(config = nil)
20+
@mutex.synchronize do
21+
if config
22+
@config = config
23+
else
24+
@config ||= Config.from_environment
25+
end
26+
end
27+
end
28+
1029
# Collect and upload all proofs to Hyperproof.
1130
def self.run
1231
Dir.mktmpdir do |dir|
32+
config.logger.info("Writing proofs to #{dir}")
1333
writer = Writer.new(dir)
1434
Proofs.proofs.map do |klass|
1535
proof = klass.new
16-
filename = proof.write(writer)
17-
18-
label = Entities::Label.new(proof.label)
19-
label.create unless label.exists?
20-
Entities::Proof.new(File.basename(filename), label:).create(filename)
36+
filename = collect_proof(proof, writer)
37+
Entities::Proof.new(File.basename(filename), label: proof_label(proof))
38+
.create(filename)
2139
end
2240
end
2341
end
42+
43+
# Collect evidence for a specific proof.
44+
#
45+
# @param proof [Proofs::Proof] The proof to collect evidence for.
46+
# @param writer [Writer] The writer to use for formatting the proof.
47+
# @return [String] The filename where the proof was written.
48+
private_class_method def self.collect_proof(proof, writer)
49+
config.logger.debug("Collecting proof for #{proof.name} (#{proof.label})")
50+
proof.write(writer)
51+
end
52+
53+
# Get the label for a proof.
54+
#
55+
# @param proof [Proofs::Proof] The proof to get the label for.
56+
# @return [Entities::Label] The label entity for the proof.
57+
private_class_method def self.proof_label(proof)
58+
label = Entities::Label.new(proof.label)
59+
label.create unless label.exists?
60+
label
61+
end
2462
end
2563
end

hyperproof/lib/cfa_security_controls/hyperproof/clients/aptible.rb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ def databases
2121

2222
private
2323

24+
# Configuration for the system.
25+
#
26+
# @return [Config]
27+
def config
28+
@config ||= CfaSecurityControls::Hyperproof.config
29+
end
30+
2431
# Retrieve a token to authenticate with Aptible.
2532
#
2633
# This method uses a chain to find the first valid set of credentials:
@@ -62,11 +69,10 @@ def sso_token
6269
# @return [Aptible::Auth::Token, Boolean] The token for the Aptible API,
6370
# or false if the credentials aren't present.
6471
def basic_auth_token
65-
email = ENV.fetch('APTIBLE_USERNAME', nil)
66-
password = ENV.fetch('APTIBLE_PASSWORD', nil)
67-
return false unless email && password
72+
return false unless config.aptible_username && config.aptible_password
6873

69-
::Aptible::Auth::Token.create(email:, password:,
74+
::Aptible::Auth::Token.create(email: config.aptible_username,
75+
password: config.aptible_password,
7076
headers: { 'Authorization' => nil })
7177
rescue OAuth2::Error
7278
false

hyperproof/lib/cfa_security_controls/hyperproof/clients/hyperproof.rb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ def proofs(params = {})
9898

9999
private
100100

101+
# Configuration for the system.
102+
#
103+
# @return [Config]
104+
def config
105+
@config ||= CfaSecurityControls::Hyperproof.config
106+
end
107+
101108
# Establish a connection to the Hyperproof API.
102109
#
103110
# @return [Faraday::Connection] The Faraday connection object.
@@ -117,15 +124,11 @@ def conn
117124
#
118125
# @return [String] The authentication token.
119126
def auth_token
120-
unless ENV.key?('HYPERPROOF_CLIENT_ID') && ENV.key?('HYPERPROOF_CLIENT_SECRET')
121-
raise Unauthorized, 'Missing Hyperproof credentials'
122-
end
123-
124127
response = Faraday.post(
125128
'https://accounts.hyperproof.app/oauth/token',
126129
{
127-
client_id: ENV.fetch('HYPERPROOF_CLIENT_ID'),
128-
client_secret: ENV.fetch('HYPERPROOF_CLIENT_SECRET'),
130+
client_id: config.hyperproof_client_id,
131+
client_secret: config.hyperproof_client_secret,
129132
grant_type: 'client_credentials'
130133
}.to_json,
131134
{
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
3+
require 'configsl'
4+
5+
module CfaSecurityControls
6+
module Hyperproof
7+
# Configuration for the Hyperproof integration.
8+
class Config < ConfigSL::Config
9+
option :log_level, type: Symbol, default: :info,
10+
values: %i[debug info warn error]
11+
12+
option :aptible_username, type: String
13+
option :aptible_password, type: String
14+
option :hyperproof_client_id, type: String, required: true
15+
option :hyperproof_client_secret, type: String, required: true
16+
17+
def initialize(params = {})
18+
super
19+
validate!
20+
end
21+
22+
def logger
23+
@logger ||= Logger.new($stdout, level: log_level)
24+
end
25+
end
26+
end
27+
end

hyperproof/spec/spec_helper.rb

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,37 @@
1313
end
1414
end
1515

16-
# Include the gem.
16+
# Include the gem and test helpers.
1717
require_relative '../lib/cfa-security-controls-hyperproof'
18+
require_relative 'support/helpers'
19+
20+
RSpec.configure do |config|
21+
# Keep the original $stderr and $stdout so that we can suppress output during
22+
# tests.
23+
original_stderr = $stderr
24+
original_stdout = $stdout
25+
26+
config.include Helpers::Config
27+
28+
config.before do
29+
# Clear the configuration before each test.
30+
stub_const('ENV', default_config_env)
31+
clear_config
32+
allow(File).to receive(:exist?).and_call_original
33+
end
34+
35+
config.before(:all) do
36+
# Suppress logger output.
37+
$stderr = File.new(File::NULL, 'w')
38+
$stdout = File.new(File::NULL, 'w')
39+
end
40+
41+
config.after(:all) do
42+
# Restore the original $stderr and $stdout after all tests.
43+
$stderr = original_stderr
44+
$stdout = original_stdout
45+
end
46+
end
1847

1948
# Include supporting resources.
2049
require_relative 'support/examples'

hyperproof/spec/support/helpers.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'helpers/config'
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# frozen_string_literal: true
2+
3+
module Helpers
4+
# Test helpers for system configuration.
5+
module Config
6+
# Clear the current configuration.
7+
def clear_config
8+
CfaSecurityControls::Hyperproof.instance_variable_set(:@config, nil)
9+
end
10+
11+
# Default configuration environment variables.
12+
#
13+
# This is useful for tests that need to override the environment, but need
14+
# the configuration to be valid.
15+
#
16+
# @return [Hash] A hash of environment variables.
17+
def default_config_env
18+
{
19+
'APTIBLE_PASSWORD' => 'aptible_password',
20+
'APTIBLE_USERNAME' => 'aptible_username',
21+
'HYPERPROOF_CLIENT_ID' => 'hyperproof_client_id',
22+
'HYPERPROOF_CLIENT_SECRET' => 'hyperproof_client_secret'
23+
}
24+
end
25+
26+
# Force the configuration to be valid without actually validating it.
27+
#
28+
# This is useful for tests that need to run without a valid configuration
29+
# but still want to avoid validation errors.
30+
#
31+
# rubocop:disable RSpec/AnyInstance
32+
def force_valid_config
33+
allow_any_instance_of(CfaSecurityControls::Hyperproof::Config).to \
34+
receive(:validate!).and_return(true)
35+
end
36+
# rubocop:enable RSpec/AnyInstance
37+
38+
# Set the configuration for the system.
39+
#
40+
# @param options [Hash] Options to set in the configuration.
41+
# @return [CfaSecurityControls::Hyperproof::Config] The configuration object.
42+
def set_config(options = {})
43+
config = CfaSecurityControls::Hyperproof::Config.new(options)
44+
CfaSecurityControls::Hyperproof.config(config)
45+
config
46+
end
47+
end
48+
end

0 commit comments

Comments
 (0)