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 |
# File 'lib/datagrout_conduit/identity.rb', line 69 def self.try_discover(override_dir: nil) # When an explicit directory is given, scope search to that dir only. if override_dir return try_load_from_dir(override_dir) end # 1. Environment variables (individual cert/key PEMs) id = from_env return id if id # 2. 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 # 3. ~/.conduit/ home = ENV["HOME"] || ENV["USERPROFILE"] if home id = try_load_from_dir(File.join(home, ".conduit")) return id if id end # 4. .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.
131 132 133 134 135 136 137 138 139 |
# File 'lib/datagrout_conduit/identity.rb', line 131 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.
108 109 110 111 112 113 |
# File 'lib/datagrout_conduit/identity.rb', line 108 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.
126 127 128 |
# File 'lib/datagrout_conduit/identity.rb', line 126 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.
116 117 118 |
# File 'lib/datagrout_conduit/identity.rb', line 116 def openssl_cert OpenSSL::X509::Certificate.new(@cert_pem) end |
#openssl_key ⇒ Object
Return an OpenSSL::PKey for use with Faraday SSL config.
121 122 123 |
# File 'lib/datagrout_conduit/identity.rb', line 121 def openssl_key OpenSSL::PKey.read(@key_pem) end |
#with_expiry(expires_at) ⇒ Object
102 103 104 |
# File 'lib/datagrout_conduit/identity.rb', line 102 def with_expiry(expires_at) dup.tap { |i| i.instance_variable_set(:@expires_at, expires_at) } end |