Class: Legion::Crypt::Spiffe::WorkloadApiClient

Inherits:
Object
  • Object
show all
Includes:
Logging::Helper
Defined in:
lib/legion/crypt/spiffe/workload_api_client.rb

Overview

Minimal SPIFFE Workload API client.

The SPIFFE Workload API is served over a Unix domain socket by a local SPIRE agent. The wire protocol is gRPC/HTTP2, but we avoid pulling in a full gRPC stack by implementing just enough of the HTTP/2 framing to send a single unary RPC call and parse a single response.

For environments that cannot make a real SPIRE call (CI, lite mode, no socket present) the client returns a self-signed fallback SVID so that callers never have to special-case the nil case.

Constant Summary collapse

GRPC_CONTENT_TYPE =

gRPC content-type and method path for the Workload API FetchX509SVID RPC.

'application/grpc'
FETCH_X509_METHOD =
'/spiffe.workload.SpiffeWorkloadAPI/FetchX509SVID'
FETCH_JWT_METHOD =
'/spiffe.workload.SpiffeWorkloadAPI/FetchJWTSVID'
HTTP2_PREFACE =

Handshake + settings frames required to open an HTTP/2 connection.

"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
HTTP2_SETTINGS_FRAME =
"\x00\x00\x00\x04\x00\x00\x00\x00\x00".b
CONNECT_TIMEOUT =
5
READ_TIMEOUT =
10

Constants included from Logging::Helper

Logging::Helper::CompatLogger

Instance Method Summary collapse

Methods included from Logging::Helper

#handle_exception, #log

Constructor Details

#initialize(socket_path: nil, trust_domain: nil, allow_x509_fallback: nil) ⇒ WorkloadApiClient

Returns a new instance of WorkloadApiClient.



35
36
37
38
39
# File 'lib/legion/crypt/spiffe/workload_api_client.rb', line 35

def initialize(socket_path: nil, trust_domain: nil, allow_x509_fallback: nil)
  @socket_path         = socket_path  || Legion::Crypt::Spiffe.socket_path
  @trust_domain        = trust_domain || Legion::Crypt::Spiffe.trust_domain
  @allow_x509_fallback = allow_x509_fallback.nil? ? Legion::Crypt::Spiffe.allow_x509_fallback? : allow_x509_fallback
end

Instance Method Details

#available?Boolean

Returns true when the SPIRE agent socket exists and is reachable.

Returns:

  • (Boolean)


84
85
86
87
88
89
90
91
92
93
94
# File 'lib/legion/crypt/spiffe/workload_api_client.rb', line 84

def available?
  return false unless ::File.exist?(@socket_path)

  sock = UNIXSocket.new(@socket_path)
  sock.close
  true
rescue StandardError => e
  handle_exception(e, level: :debug, operation: 'crypt.spiffe.workload_api_client.available',
                   socket_path: @socket_path)
  false
end

#fetch_jwt_svid(audience:) ⇒ Object

Fetch a JWT SVID from the SPIRE Workload API for the given audience.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/legion/crypt/spiffe/workload_api_client.rb', line 66

def fetch_jwt_svid(audience:)
  log.info("[SPIFFE] Fetching JWT SVID from Workload API audience=#{audience}")
  payload  = encode_jwt_request(audience)
  raw      = call_workload_api(FETCH_JWT_METHOD, payload)
  parse_jwt_svid_response(raw, audience)
rescue WorkloadApiError, IOError, Errno::ENOENT, Errno::ECONNREFUSED, Errno::EPIPE => e
  handle_exception(e, level: :warn, operation: 'crypt.spiffe.workload_api_client.fetch_jwt_svid',
                   socket_path: @socket_path, audience: audience)
  log.warn("[SPIFFE] JWT SVID fetch failed (#{e.message})")
  raise SvidError, "Failed to fetch JWT SVID for audience '#{audience}': #{e.message}"
rescue StandardError => e
  handle_exception(e, level: :error, operation: 'crypt.spiffe.workload_api_client.fetch_jwt_svid',
                   socket_path: @socket_path, audience: audience)
  log.error("[SPIFFE] JWT SVID fetch failed: #{e.message}")
  raise SvidError, "Failed to fetch JWT SVID for audience '#{audience}': #{e.message}"
end

#fetch_x509_svidObject

Fetch an X.509 SVID from the SPIRE Workload API. Returns a populated X509Svid struct. Falls back to a self-signed certificate when the Workload API is unavailable.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/legion/crypt/spiffe/workload_api_client.rb', line 44

def fetch_x509_svid
  log.info("[SPIFFE] Fetching X.509 SVID from Workload API socket=#{@socket_path}")
  raw = call_workload_api(FETCH_X509_METHOD, '')
  parse_x509_svid_response(raw)
rescue WorkloadApiError, IOError, Errno::ENOENT, Errno::ECONNREFUSED, Errno::EPIPE => e
  handle_exception(e, level: :warn, operation: 'crypt.spiffe.workload_api_client.fetch_x509_svid',
                   socket_path: @socket_path, fallback: @allow_x509_fallback)
  unless @allow_x509_fallback
    log.error("[SPIFFE] Workload API unavailable (#{e.message}); X.509 fallback disabled")
    raise SvidError, "Failed to fetch X.509 SVID: #{e.message}"
  end

  log.warn("[SPIFFE] Workload API unavailable (#{e.message}); using self-signed fallback")
  self_signed_fallback
rescue StandardError => e
  handle_exception(e, level: :error, operation: 'crypt.spiffe.workload_api_client.fetch_x509_svid',
                   socket_path: @socket_path)
  log.error("[SPIFFE] X.509 SVID fetch failed: #{e.message}")
  raise
end