Module: Legion::Crypt::ClusterSecret

Includes:
Logging::Helper
Included in:
Cipher
Defined in:
lib/legion/crypt/cluster_secret.rb

Constant Summary

Constants included from Logging::Helper

Logging::Helper::CompatLogger

Instance Method Summary collapse

Methods included from Logging::Helper

#handle_exception, #log

Instance Method Details

#cluster_secret_timeoutObject



116
117
118
# File 'lib/legion/crypt/cluster_secret.rb', line 116

def cluster_secret_timeout
  Legion::Settings[:crypt][:cluster_secret_timeout] || 5
end

#cluster_secret_vault_pathObject



146
147
148
# File 'lib/legion/crypt/cluster_secret.rb', line 146

def cluster_secret_vault_path
  'crypt'
end

#csObject



128
129
130
131
132
133
# File 'lib/legion/crypt/cluster_secret.rb', line 128

def cs
  @cs ||= Digest::SHA256.digest(find_cluster_secret)
rescue StandardError => e
  handle_exception(e, level: :error, operation: 'crypt.cluster_secret.cs')
  nil
end

#find_cluster_secretObject



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/legion/crypt/cluster_secret.rb', line 11

def find_cluster_secret
  %i[from_settings from_vault from_transport generate_secure_random].each do |method|
    result = send(method)
    next if result.nil?

    unless validate_hex(result)
      log.warn("Legion::Crypt.#{method} gave a value but it isn't a valid hex")
      next
    end

    set_cluster_secret(result, method != :from_vault)
    return result
  end
  return unless only_member?

  key = generate_secure_random
  set_cluster_secret(key)
  log.info 'Cluster secret generated locally because this node is the only member'
  key
end

#force_cluster_secretObject



78
79
80
# File 'lib/legion/crypt/cluster_secret.rb', line 78

def force_cluster_secret
  Legion::Settings[:crypt].fetch(:force_cluster_secret, false)
end

#from_settingsObject Also known as: cluster_secret



47
48
49
# File 'lib/legion/crypt/cluster_secret.rb', line 47

def from_settings
  Legion::Settings[:crypt][:cluster_secret]
end

#from_transportObject



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/legion/crypt/cluster_secret.rb', line 52

def from_transport
  return nil unless Legion::Settings[:transport][:connected]

  require 'legion/transport/messages/request_cluster_secret'
  log.info 'Requesting cluster secret via public key'
  log.warn 'cluster_secret already set but we are requesting a new value' unless from_settings.nil?
  start = Time.now
  Legion::Transport::Messages::RequestClusterSecret.new.publish
  sleep_time = 0.001
  until !Legion::Settings[:crypt][:cluster_secret].nil? || (Time.now - start) > cluster_secret_timeout
    sleep(sleep_time)
    sleep_time *= 2 unless sleep_time > 0.5
  end

  unless from_settings.nil?
    log.info "Received cluster secret in #{((Time.new - start) * 1000.0).round}ms"
    return from_settings
  end

  log.error 'Cluster secret is still unknown!'
  nil
rescue StandardError => e
  handle_exception(e, level: :error, operation: 'crypt.cluster_secret.from_transport')
  nil
end

#from_vaultObject



32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/legion/crypt/cluster_secret.rb', line 32

def from_vault
  return nil unless Legion::Crypt.respond_to?(:get) && Legion::Crypt.respond_to?(:exist?)
  return nil unless Legion::Settings[:crypt][:vault][:read_cluster_secret]
  return nil unless Legion::Settings[:crypt][:vault][:connected]
  return nil unless Legion::Crypt.exist?(cluster_secret_vault_path)

  data = Legion::Crypt.get(cluster_secret_vault_path)
  return nil unless data.is_a?(Hash)

  data[:cluster_secret] || data['cluster_secret']
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'crypt.cluster_secret.from_vault')
  nil
end

#generate_secure_random(length = secret_length) ⇒ Object



124
125
126
# File 'lib/legion/crypt/cluster_secret.rb', line 124

def generate_secure_random(length = secret_length)
  SecureRandom.hex(length)
end

#only_member?Boolean

Returns:

  • (Boolean)


89
90
91
92
93
94
# File 'lib/legion/crypt/cluster_secret.rb', line 89

def only_member?
  Legion::Transport::Queue.new('node.crypt', passive: true).consumer_count.zero?
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'crypt.cluster_secret.only_member?')
  nil
end

#push_cs_to_vaultObject



106
107
108
109
110
111
112
113
114
# File 'lib/legion/crypt/cluster_secret.rb', line 106

def push_cs_to_vault
  return false unless Legion::Settings[:crypt][:vault][:connected] && Legion::Settings[:crypt][:cluster_secret]

  log.info 'Pushing Cluster Secret to Vault'
  Legion::Crypt.write(cluster_secret_vault_path, cluster_secret: Legion::Settings[:crypt][:cluster_secret])
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'crypt.cluster_secret.push_cs_to_vault')
  false
end

#secret_lengthObject



120
121
122
# File 'lib/legion/crypt/cluster_secret.rb', line 120

def secret_length
  Legion::Settings[:crypt][:cluster_length] || Legion::Settings[:crypt][:cluster_lenth] || 32
end

#set_cluster_secret(value, push_to_vault = true) ⇒ Object

rubocop:disable Style/OptionalBooleanParameter

Raises:

  • (TypeError)


96
97
98
99
100
101
102
103
104
# File 'lib/legion/crypt/cluster_secret.rb', line 96

def set_cluster_secret(value, push_to_vault = true) # rubocop:disable Style/OptionalBooleanParameter
  raise TypeError unless validate_hex(value)

  Legion::Settings[:crypt][:cluster_secret] = value
  @cs = nil
  Legion::Settings[:crypt][:cs_encrypt_ready] = true
  push_cs_to_vault if push_to_vault && settings_push_vault
  log.info "Cluster secret loaded into settings push_to_vault=#{push_to_vault}"
end

#settings_push_vaultObject



82
83
84
85
86
87
# File 'lib/legion/crypt/cluster_secret.rb', line 82

def settings_push_vault
  vault_settings = Legion::Settings[:crypt][:vault]
  return vault_settings[:push_cluster_secret] unless vault_settings[:push_cluster_secret].nil?

  vault_settings.fetch(:push_cs_to_vault, false)
end

#validate_hex(value, length = secret_length) ⇒ Object



135
136
137
138
139
140
141
142
143
144
# File 'lib/legion/crypt/cluster_secret.rb', line 135

def validate_hex(value, length = secret_length)
  return false unless value.is_a?(String)
  return false if value.empty?
  return false unless value.match?(/\A\h+\z/)

  expected_length = length.to_i * 2
  return true if expected_length.zero?

  value.length == expected_length
end