Class: OpenC3::Secrets
Direct Known Subclasses
Constant Summary collapse
- DEFAULT_SECRET_FILE_DIR =
Default base directory that FILE type secret paths must reside under. FILE type secrets are written by the operator (or mounted by Kubernetes) and then read back by the microservice, so the path is a destination we control, not an arbitrary file on the host.
'/tmp'
Class Method Summary collapse
- .getClient ⇒ Object
-
.path_contained?(base, path) ⇒ Boolean
True if path is inside base, or is base itself.
-
.secret_file_dir ⇒ Object
Base directory that FILE type secret paths must reside under.
-
.validate_file_path(path) ⇒ String
Validates a FILE type secret path.
Instance Method Summary collapse
- #delete(key, secret_store: nil, scope:) ⇒ Object
- #get(key, secret_store: nil, scope:) ⇒ Object
-
#initialize ⇒ Secrets
constructor
A new instance of Secrets.
- #keys(secret_store: nil, scope:) ⇒ Object
- #set(key, value, secret_store: nil, scope:) ⇒ Object
- #setup(secrets) ⇒ Object
Constructor Details
#initialize ⇒ Secrets
Returns a new instance of Secrets.
24 25 26 |
# File 'lib/openc3/utilities/secrets.rb', line 24 def initialize @local_secrets = {} end |
Class Method Details
.getClient ⇒ Object
67 68 69 70 71 72 |
# File 'lib/openc3/utilities/secrets.rb', line 67 def self.getClient raise 'OPENC3_SECRET_BACKEND environment variable is required' unless ENV['OPENC3_SECRET_BACKEND'] secrets_class = ENV.fetch('OPENC3_SECRET_BACKEND').capitalize + 'Secrets' klass = OpenC3.require_class('openc3/utilities/' + secrets_class.class_name_to_filename) klass.new end |
.path_contained?(base, path) ⇒ Boolean
Returns true if path is inside base, or is base itself.
63 64 65 |
# File 'lib/openc3/utilities/secrets.rb', line 63 def self.path_contained?(base, path) path == base or path.start_with?(base.end_with?(File::SEPARATOR) ? base : base + File::SEPARATOR) end |
.secret_file_dir ⇒ Object
Base directory that FILE type secret paths must reside under
29 30 31 32 33 |
# File 'lib/openc3/utilities/secrets.rb', line 29 def self.secret_file_dir dir = ENV.fetch('OPENC3_SECRET_FILE_DIR', nil) dir = DEFAULT_SECRET_FILE_DIR if dir.nil? or dir.empty? dir end |
.validate_file_path(path) ⇒ String
Validates a FILE type secret path. The path must expand to a location strictly inside Secrets.secret_file_dir. This prevents a plugin configuration such as 'SECRET FILE KEY /root/.ssh/id_rsa' or 'SECRET FILE KEY /tmp/../etc/shadow' from reading or overwriting arbitrary files as the OpenC3 process user.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/openc3/utilities/secrets.rb', line 43 def self.validate_file_path(path) raise ArgumentError, "Secret file path must be a String but is a #{path.class}" unless path.is_a?(String) raise ArgumentError, "Secret file path must not be blank" if path.strip.empty? raise ArgumentError, "Secret file path must not contain a null byte" if path.include?("\x00") # expand_path collapses '..' and '.', expands '~', and makes a relative path # absolute, so the comparison below can't be walked out of. This is # deliberately a purely lexical check: it does not touch the filesystem, so # a path validates identically at plugin install time (cmd-tlm-api) and at # write time (openc3-operator), which are separate containers. Symlinks are # rejected by the operator when it writes the secret. base = File.(secret_file_dir) absolute = File.(path) unless absolute.start_with?(base + File::SEPARATOR) raise ArgumentError, "Secret file path '#{path}' must be under '#{base}'" end absolute end |
Instance Method Details
#delete(key, secret_store: nil, scope:) ⇒ Object
86 87 88 |
# File 'lib/openc3/utilities/secrets.rb', line 86 def delete(key, secret_store: nil, scope:) raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end |
#get(key, secret_store: nil, scope:) ⇒ Object
78 79 80 |
# File 'lib/openc3/utilities/secrets.rb', line 78 def get(key, secret_store: nil, scope:) return @local_secrets[key] end |
#keys(secret_store: nil, scope:) ⇒ Object
74 75 76 |
# File 'lib/openc3/utilities/secrets.rb', line 74 def keys(secret_store: nil, scope:) raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end |
#set(key, value, secret_store: nil, scope:) ⇒ Object
82 83 84 |
# File 'lib/openc3/utilities/secrets.rb', line 82 def set(key, value, secret_store: nil, scope:) raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end |
#setup(secrets) ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/openc3/utilities/secrets.rb', line 90 def setup(secrets) secrets.each do |secret| if secret.length < 3 raise ArgumentError, "Secret must have at least 3 items (type, key, data), got #{secret.length}" end type, key, data, _secret_store = secret case type when 'ENV' @local_secrets[key] = ENV.fetch(data, nil) when 'FILE' @local_secrets[key] = File.read(Secrets.validate_file_path(data)) else raise "Unknown secret type: #{type}" end end end |