SPIFFE Workload API - Ruby Client

CI Gem Version License

A Ruby client library for the SPIFFE Workload API, enabling Ruby applications to obtain and use SPIFFE identities from a SPIRE agent.

This project aims to be part of the SPIFFE ecosystem. See the SPIFFE GitHub for more information about the SPIFFE project.

What is this?

This gem allows Ruby applications (including Puppet) to:

  • Fetch X.509 SVIDs (certificates) from SPIRE for mTLS
  • Generate JWT SVIDs for API authentication
  • Access trust bundles for verification
  • Handle automatic credential rotation

It communicates directly with the SPIRE agent over Unix domain sockets, enabling proper process-based attestation (unlike subprocess-based CLI tools).

Installation

gem install spiffe-workload

For Puppet:

/opt/puppetlabs/puppet/bin/gem install spiffe-workload

Prerequisites

  1. SPIRE Agent running with workload API socket accessible
  2. Workload entry registered for your application
# Verify SPIRE agent is running
ls -la /run/spire/sockets/agent.sock

# Register your application
spire-server entry create \
  -parentID spiffe://example.org/agent/myhost \
  -spiffeID spiffe://example.org/myapp \
  -selector unix:uid:$(id -u) \
  -selector unix:path:/path/to/your/app

Quick Start

Fetch X.509 Certificate

require 'spiffe'

client = Spiffe.workload_api_client
svid = client.x509_svid

puts "My identity: #{svid.spiffe_id}"
puts "Expires: #{svid.leaf_certificate.not_after}"

client.shutdown

Fetch JWT Token

require 'spiffe'

client = Spiffe.workload_api_client
jwt = client.jwt_svid(audience: 'my-service')

puts "Token: #{jwt.token}"
puts "Expires: #{jwt.expiration}"

client.shutdown

mTLS HTTP Request

require 'spiffe'
require 'net/http'

client = Spiffe.workload_api_client
uri = URI('https://api.example.com/data')

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.ssl_context = client.tls_context

response = http.get(uri.path)
puts response.body

client.shutdown

Configuration

Socket Path

Auto-detected from:

  1. socket_path parameter
  2. SPIFFE_ENDPOINT_SOCKET environment variable
  3. Default: /run/spire/sockets/agent.sock
client = Spiffe.workload_api_client(
  socket_path: '/var/run/spire/agent.sock'
)

Timeout

client = Spiffe.workload_api_client(timeout: 10)

Puppet Integration

Custom Function Example

# modules/spiffe/lib/puppet/functions/spiffe_jwt.rb
Puppet::Functions.create_function(:spiffe_jwt) do
  def spiffe_jwt(audience)
    require 'spiffe'
    client = Spiffe.workload_api_client
    jwt = client.jwt_svid(audience: audience)
    jwt.token
  ensure
    client&.shutdown
  end
end

Use in Puppet manifest:

$token = Deferred('spiffe_jwt', ['vault.example.com'])

file { '/etc/app/token':
  content => $token,
  mode    => '0600',
}

SPIRE Configuration for Puppet

# Puppet agent workload entry
selectors:
  - "unix:uid:0"                                    # Root user
  - "unix:path:/opt/puppetlabs/puppet/bin/ruby"   # Puppet Ruby binary

API Reference

Spiffe.workload_api_client

Creates a new Workload API client.

Parameters:

  • socket_path (String, optional): Path to SPIRE agent socket
  • timeout (Integer, optional): Request timeout in seconds

Returns: Spiffe::Workload::Client

Client#x509_svid

Fetches X.509 SVID.

Returns: Spiffe::Workload::X509SVIDWrapper

Properties:

  • spiffe_id - SPIFFE ID string
  • leaf_certificate - OpenSSL::X509::Certificate
  • certificate_chain - Array of certificates
  • private_key - OpenSSL::PKey::RSA
  • ttl - Time to live in seconds

Client#jwt_svid(audience:, spiffe_id: nil)

Fetches JWT SVID.

Parameters:

  • audience (String|Array): Target audience(s)
  • spiffe_id (String, optional): Specific SPIFFE ID

Returns: Spiffe::Workload::JWTSVIDWrapper

Properties:

  • spiffe_id - SPIFFE ID string
  • token - JWT token string
  • expiration - Expiration time
  • claims - Decoded JWT claims

Client#tls_context

Creates OpenSSL context with current SVID.

Returns: OpenSSL::SSL::SSLContext

Client#on_x509_svid_update { |svid| ... }

Registers callback for SVID rotation.

Client#shutdown

Cleanly shuts down the client.

Troubleshooting

"Socket does not exist"

# Check SPIRE agent status
systemctl status spire-agent

# Verify socket location
sudo find /run /var/run -name "agent.sock" 2>/dev/null

"No identity issued"

# Check workload registration
spire-server entry show

# Verify selectors match your process
ps aux | grep your-app

"Permission denied"

# Check socket permissions
ls -la /run/spire/sockets/agent.sock

# Add user to spire group
sudo usermod -a -G spire $USER

Architecture

This gem implements a client-side library for the SPIFFE Workload API:

Key points:

  • No subprocess attestation issues (unlike CLI tools)
  • Direct process attestation by SPIRE
  • Thread-safe credential caching
  • Automatic rotation support

Contributing

We welcome contributions! This project follows the SPIFFE contribution guidelines.

Getting Started

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Ensure tests pass (bundle exec rspec)
  5. Ensure code style passes (bundle exec rubocop)
  6. Commit with DCO sign-off (git commit -s -m 'Add amazing feature')
  7. Push to your fork (git push origin feature/amazing-feature)
  8. Open a Pull Request

DCO Sign-off

All commits must include a Signed-off-by line to certify the Developer Certificate of Origin (DCO):

git commit -s -m "Your commit message"

Similar SPIFFE Projects

See SPIFFE CONTRIBUTING.md for more details on contributing to SPIFFE projects.

Development

Build from Source

git clone https://github.com/halradaideh/spiffe-rubygem.git
cd spiffe-rubygem
bundle install
gem build spiffe-workload.gemspec
gem install spiffe-workload-*.gem

Run Tests

bundle exec rspec

Generate Protobuf

gem install grpc-tools
rake generate_protos

License

Apache License 2.0 - see LICENSE file

Support

Version

Current version: 1.0.0

See CHANGELOG.md for version history.