Class: DatagroutConduit::Identity
- Inherits:
-
Object
- Object
- DatagroutConduit::Identity
- Defined in:
- lib/datagrout_conduit/identity.rb
Overview
mTLS client identity for Conduit connections.
Holds the client certificate and private key presented during every TLS handshake. The server verifies the caller’s identity without any application-layer token.
Auto-discovery order (try_discover)
-
override_dir(if provided) -
CONDUIT_MTLS_CERT/CONDUIT_MTLS_KEYenv vars (PEM strings) -
CONDUIT_IDENTITY_DIRenv var → directory with identity.pem + identity_key.pem -
~/.conduit/identity.pem +
identity_key.pem -
.conduit/relative to cwd
Instance Attribute Summary collapse
-
#ca_pem ⇒ Object
readonly
Returns the value of attribute ca_pem.
-
#cert_pem ⇒ Object
readonly
Returns the value of attribute cert_pem.
-
#expires_at ⇒ Object
readonly
Returns the value of attribute expires_at.
-
#key_pem ⇒ Object
readonly
Returns the value of attribute key_pem.
Class Method Summary collapse
-
.from_env ⇒ Object
Build from environment variables.
-
.from_paths(cert_path, key_path, ca_path: nil) ⇒ Object
Build by reading PEM files from disk.
-
.from_pem(cert_pem, key_pem, ca_pem: nil) ⇒ Object
Build from PEM strings already in memory.
-
.try_discover(override_dir: nil) ⇒ Object
Walk the auto-discovery chain and return the first identity found, or nil if nothing is available.
Instance Method Summary collapse
-
#configure_ssl(ssl) ⇒ Object
Configure Faraday SSL options with this identity’s mTLS credentials.
-
#initialize(cert_pem:, key_pem:, ca_pem: nil, expires_at: nil) ⇒ Identity
constructor
A new instance of Identity.
-
#needs_rotation?(threshold_days: 30) ⇒ Boolean
Returns true if the certificate expires within
threshold_days. -
#openssl_ca ⇒ Object
Return an OpenSSL::X509::Certificate for the CA, if present.
-
#openssl_cert ⇒ Object
Return an OpenSSL::X509::Certificate for use with Faraday SSL config.
-
#openssl_key ⇒ Object
Return an OpenSSL::PKey for use with Faraday SSL config.
- #with_expiry(expires_at) ⇒ Object
Constructor Details
#initialize(cert_pem:, key_pem:, ca_pem: nil, expires_at: nil) ⇒ Identity
Returns a new instance of Identity.
22 23 24 25 26 27 28 29 |
# File 'lib/datagrout_conduit/identity.rb', line 22 def initialize(cert_pem:, key_pem:, ca_pem: nil, expires_at: nil) validate_cert!(cert_pem) validate_key!(key_pem) @cert_pem = cert_pem @key_pem = key_pem @ca_pem = ca_pem @expires_at = expires_at end |
Instance Attribute Details
#ca_pem ⇒ Object (readonly)
Returns the value of attribute ca_pem.
20 21 22 |
# File 'lib/datagrout_conduit/identity.rb', line 20 def ca_pem @ca_pem end |
#cert_pem ⇒ Object (readonly)
Returns the value of attribute cert_pem.
20 21 22 |
# File 'lib/datagrout_conduit/identity.rb', line 20 def cert_pem @cert_pem end |
#expires_at ⇒ Object (readonly)
Returns the value of attribute expires_at.
20 21 22 |
# File 'lib/datagrout_conduit/identity.rb', line 20 def expires_at @expires_at end |
#key_pem ⇒ Object (readonly)
Returns the value of attribute key_pem.
20 21 22 |
# File 'lib/datagrout_conduit/identity.rb', line 20 def key_pem @key_pem end |
Class Method Details
.from_env ⇒ Object
Build from environment variables.
Variables:
-
CONDUIT_MTLS_CERT — PEM string for the client certificate
-
CONDUIT_MTLS_KEY — PEM string for the private key
-
CONDUIT_MTLS_CA — PEM string for the CA (optional)
Returns nil if CONDUIT_MTLS_CERT is not set.
54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/datagrout_conduit/identity.rb', line 54 def self.from_env cert = ENV["CONDUIT_MTLS_CERT"] return nil if cert.nil? || cert.empty? key = ENV["CONDUIT_MTLS_KEY"] raise ConfigError, "CONDUIT_MTLS_CERT is set but CONDUIT_MTLS_KEY is missing" if key.nil? || key.empty? ca = ENV["CONDUIT_MTLS_CA"] ca = nil if ca && ca.empty? new(cert_pem: cert, key_pem: key, ca_pem: ca) end |
.from_paths(cert_path, key_path, ca_path: nil) ⇒ Object
Build by reading PEM files from disk.
37 38 39 40 41 42 43 44 |
# File 'lib/datagrout_conduit/identity.rb', line 37 def self.from_paths(cert_path, key_path, ca_path: nil) cert_pem = File.read(cert_path) key_pem = File.read(key_path) ca_pem = ca_path ? File.read(ca_path) : nil new(cert_pem: cert_pem, key_pem: key_pem, ca_pem: ca_pem) rescue Errno::ENOENT => e raise ConfigError, "Cannot read identity file: #{e.}" end |
.from_pem(cert_pem, key_pem, ca_pem: nil) ⇒ Object
Build from PEM strings already in memory.
32 33 34 |
# File 'lib/datagrout_conduit/identity.rb', line 32 def self.from_pem(cert_pem, key_pem, ca_pem: nil) new(cert_pem: cert_pem, key_pem: key_pem, ca_pem: ca_pem) end |
.try_discover(override_dir: nil) ⇒ Object
Walk the auto-discovery chain and return the first identity found, or nil if nothing is available.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/datagrout_conduit/identity.rb', line 69 def self.try_discover(override_dir: nil) # 1. Override directory if override_dir id = try_load_from_dir(override_dir) return id if id end # 2. Environment variables (individual cert/key PEMs) id = from_env return id if id # 3. CONDUIT_IDENTITY_DIR env var identity_dir = ENV["CONDUIT_IDENTITY_DIR"] if identity_dir && !identity_dir.empty? id = try_load_from_dir(identity_dir) return id if id end # 4. ~/.conduit/ home = ENV["HOME"] || ENV["USERPROFILE"] if home id = try_load_from_dir(File.join(home, ".conduit")) return id if id end # 5. .conduit/ relative to cwd id = try_load_from_dir(File.join(Dir.pwd, ".conduit")) return id if id nil rescue ConfigError nil end |
Instance Method Details
#configure_ssl(ssl) ⇒ Object
Configure Faraday SSL options with this identity’s mTLS credentials.
132 133 134 135 136 137 138 139 140 |
# File 'lib/datagrout_conduit/identity.rb', line 132 def configure_ssl(ssl) ssl.client_cert = openssl_cert ssl.client_key = openssl_key if @ca_pem store = OpenSSL::X509::Store.new store.add_cert(openssl_ca) ssl.cert_store = store end end |
#needs_rotation?(threshold_days: 30) ⇒ Boolean
Returns true if the certificate expires within threshold_days. Returns false when no expiry is known.
109 110 111 112 113 114 |
# File 'lib/datagrout_conduit/identity.rb', line 109 def needs_rotation?(threshold_days: 30) return false if @expires_at.nil? deadline = Time.now + (threshold_days * 86_400) deadline > @expires_at end |
#openssl_ca ⇒ Object
Return an OpenSSL::X509::Certificate for the CA, if present.
127 128 129 |
# File 'lib/datagrout_conduit/identity.rb', line 127 def openssl_ca @ca_pem ? OpenSSL::X509::Certificate.new(@ca_pem) : nil end |
#openssl_cert ⇒ Object
Return an OpenSSL::X509::Certificate for use with Faraday SSL config.
117 118 119 |
# File 'lib/datagrout_conduit/identity.rb', line 117 def openssl_cert OpenSSL::X509::Certificate.new(@cert_pem) end |
#openssl_key ⇒ Object
Return an OpenSSL::PKey for use with Faraday SSL config.
122 123 124 |
# File 'lib/datagrout_conduit/identity.rb', line 122 def openssl_key OpenSSL::PKey.read(@key_pem) end |
#with_expiry(expires_at) ⇒ Object
103 104 105 |
# File 'lib/datagrout_conduit/identity.rb', line 103 def with_expiry(expires_at) dup.tap { |i| i.instance_variable_set(:@expires_at, expires_at) } end |