Class: Spiffe::Workload::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/spiffe/workload/client.rb

Overview

Client for SPIFFE Workload API Connects to SPIRE Agent via Unix domain socket and fetches SVIDs

Constant Summary collapse

DEFAULT_SOCKET_PATH =
'/run/spire/sockets/agent.sock'
DEFAULT_TIMEOUT =

seconds

5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(socket_path: nil, timeout: DEFAULT_TIMEOUT) ⇒ Client

Returns a new instance of Client.

Parameters:

  • socket_path (String) (defaults to: nil)

    Path to SPIRE Agent Unix socket

  • timeout (Integer) (defaults to: DEFAULT_TIMEOUT)

    Connection timeout in seconds



22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/spiffe/workload/client.rb', line 22

def initialize(socket_path: nil, timeout: DEFAULT_TIMEOUT)
  @socket_path = socket_path || ENV['SPIFFE_ENDPOINT_SOCKET']&.sub('unix://', '') || DEFAULT_SOCKET_PATH
  @timeout = timeout
  @mutex = Mutex.new
  @x509_svid_cache = nil
  @x509_bundles_cache = nil
  @jwt_bundles_cache = nil
  @rotation_thread = nil
  @shutdown = false

  validate_socket!
end

Instance Attribute Details

#socket_pathObject (readonly)

Returns the value of attribute socket_path.



18
19
20
# File 'lib/spiffe/workload/client.rb', line 18

def socket_path
  @socket_path
end

Instance Method Details

#current_x509_svidX509SVIDWrapper?

Get the current X.509 SVID without blocking

Returns:



49
50
51
# File 'lib/spiffe/workload/client.rb', line 49

def current_x509_svid
  @mutex.synchronize { @x509_svid_cache }
end

#jwt_bundlesHash<String, String>

Fetch JWT bundles

Returns:

  • (Hash<String, String>)

    Map of trust domain to JWKS



99
100
101
102
103
104
105
# File 'lib/spiffe/workload/client.rb', line 99

def jwt_bundles
  ensure_jwt_bundles_stream_started
  
  @mutex.synchronize do
    @jwt_bundles_cache || {}
  end
end

#jwt_svid(audience:, spiffe_id: nil) ⇒ JWTSVIDWrapper

Fetch JWT SVID for the given audience

Parameters:

  • audience (String, Array<String>)

    Target audience(s)

  • spiffe_id (String, nil) (defaults to: nil)

    Optional specific SPIFFE ID to request

Returns:

Raises:



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/spiffe/workload/client.rb', line 68

def jwt_svid(audience:, spiffe_id: nil)
  audiences = audience.is_a?(Array) ? audience : [audience]
  
  request = Spiffe::Workload::JWTSVIDRequest.new(
    audience: audiences,
    spiffe_id: spiffe_id || ''
  )

  # SPIRE requires this security header for workload API calls
   = { 'workload.spiffe.io' => 'true' }
  response = stub.fetch_jwtsvid(request, metadata: )
  
  raise Spiffe::Error, 'No JWT SVID returned' if response.svids.empty?
  
  JWTSVIDWrapper.from_proto(response.svids.first)
rescue GRPC::BadStatus => e
  raise Spiffe::Error, "Failed to fetch JWT SVID: #{e.message}"
end

#on_x509_svid_update {|X509SVID| ... } ⇒ Object

Register a callback to be called when X.509 SVID is updated

Yields:



123
124
125
126
127
128
# File 'lib/spiffe/workload/client.rb', line 123

def on_x509_svid_update(&block)
  @mutex.synchronize do
    @x509_update_callbacks ||= []
    @x509_update_callbacks << block
  end
end

#shutdownObject

Shutdown the client and stop all background threads



131
132
133
134
135
136
137
# File 'lib/spiffe/workload/client.rb', line 131

def shutdown
  @shutdown = true
  @rotation_thread&.kill
  @x509_stream_thread&.kill
  @x509_bundles_stream_thread&.kill
  @jwt_bundles_stream_thread&.kill
end

#tls_contextOpenSSL::SSL::SSLContext

Create an OpenSSL context configured with the current SVID

Returns:

  • (OpenSSL::SSL::SSLContext)


109
110
111
112
113
114
115
116
117
118
119
# File 'lib/spiffe/workload/client.rb', line 109

def tls_context
  svid = x509_svid
  
  context = OpenSSL::SSL::SSLContext.new
  context.cert = svid.leaf_certificate
  context.key = svid.private_key
  context.extra_chain_cert = svid.cert_chain[1..-1] if svid.cert_chain.length > 1
  context.cert_store = svid.trust_bundle
  context.verify_mode = OpenSSL::SSL::VERIFY_PEER
  context
end

#x509_bundlesHash<String, OpenSSL::X509::Store>

Fetch X.509 trust bundles

Returns:

  • (Hash<String, OpenSSL::X509::Store>)

    Map of trust domain to bundle



89
90
91
92
93
94
95
# File 'lib/spiffe/workload/client.rb', line 89

def x509_bundles
  ensure_x509_bundles_stream_started
  
  @mutex.synchronize do
    @x509_bundles_cache || {}
  end
end

#x509_svidX509SVIDWrapper

Fetch X.509 SVID (blocking call, returns first response)

Returns:

Raises:



38
39
40
41
42
43
44
45
# File 'lib/spiffe/workload/client.rb', line 38

def x509_svid
  ensure_x509_svid_stream_started
  
  @mutex.synchronize do
    raise Spiffe::Error, 'No X.509 SVID available' unless @x509_svid_cache
    @x509_svid_cache
  end
end

#x509_svidsArray<X509SVIDWrapper>

Fetch all X.509 SVIDs

Returns:



55
56
57
58
59
60
61
# File 'lib/spiffe/workload/client.rb', line 55

def x509_svids
  ensure_x509_svid_stream_started
  
  @mutex.synchronize do
    @x509_svids_cache || []
  end
end