spiffe-workload 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +20 -0
- data/LICENSE +17 -0
- data/README.md +342 -0
- data/lib/spiffe/version.rb +5 -0
- data/lib/spiffe/workload/client.rb +348 -0
- data/lib/spiffe/workload/grpc_stub.rb +122 -0
- data/lib/spiffe/workload/http_client.rb +67 -0
- data/lib/spiffe/workload/jwt_svid.rb +75 -0
- data/lib/spiffe/workload/messages.rb +102 -0
- data/lib/spiffe/workload/proto_helper.rb +46 -0
- data/lib/spiffe/workload/service.rb +36 -0
- data/lib/spiffe/workload/tls_config.rb +43 -0
- data/lib/spiffe/workload/x509_svid.rb +93 -0
- data/lib/spiffe/workload_pb.rb +30 -0
- data/lib/spiffe/workload_services_pb.rb +51 -0
- data/lib/spiffe.rb +22 -0
- data/proto/spiffe/workload.proto +168 -0
- metadata +166 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'google/protobuf'
|
|
4
|
+
require 'google/protobuf/struct_pb'
|
|
5
|
+
|
|
6
|
+
# Protobuf message definitions for SPIFFE Workload API
|
|
7
|
+
# These mirror the definitions in proto/spiffe/workload.proto
|
|
8
|
+
|
|
9
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
10
|
+
add_file('spiffe/workload.proto', syntax: :proto3) do
|
|
11
|
+
# X509SVID message
|
|
12
|
+
add_message 'spiffe.workload.X509SVID' do
|
|
13
|
+
optional :spiffe_id, :string, 1
|
|
14
|
+
optional :x509_svid, :bytes, 2
|
|
15
|
+
optional :x509_svid_key, :bytes, 3
|
|
16
|
+
optional :bundle, :bytes, 4
|
|
17
|
+
optional :hint, :string, 5
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# X509SVIDRequest message (empty)
|
|
21
|
+
add_message 'spiffe.workload.X509SVIDRequest' do
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# X509SVIDResponse message
|
|
25
|
+
add_message 'spiffe.workload.X509SVIDResponse' do
|
|
26
|
+
repeated :svids, :message, 1, 'spiffe.workload.X509SVID'
|
|
27
|
+
repeated :crl, :bytes, 2
|
|
28
|
+
map :federated_bundles, :string, :bytes, 3
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# X509BundlesRequest message (empty)
|
|
32
|
+
add_message 'spiffe.workload.X509BundlesRequest' do
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# X509BundlesResponse message
|
|
36
|
+
add_message 'spiffe.workload.X509BundlesResponse' do
|
|
37
|
+
repeated :crl, :bytes, 1
|
|
38
|
+
map :bundles, :string, :bytes, 2
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# JWTSVID message
|
|
42
|
+
add_message 'spiffe.workload.JWTSVID' do
|
|
43
|
+
optional :spiffe_id, :string, 1
|
|
44
|
+
optional :svid, :string, 2
|
|
45
|
+
optional :hint, :string, 3
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# JWTSVIDRequest message
|
|
49
|
+
add_message 'spiffe.workload.JWTSVIDRequest' do
|
|
50
|
+
repeated :audience, :string, 1
|
|
51
|
+
optional :spiffe_id, :string, 2
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# JWTSVIDResponse message
|
|
55
|
+
add_message 'spiffe.workload.JWTSVIDResponse' do
|
|
56
|
+
repeated :svids, :message, 1, 'spiffe.workload.JWTSVID'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# JWTBundlesRequest message (empty)
|
|
60
|
+
add_message 'spiffe.workload.JWTBundlesRequest' do
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# JWTBundlesResponse message
|
|
64
|
+
add_message 'spiffe.workload.JWTBundlesResponse' do
|
|
65
|
+
map :bundles, :string, :bytes, 1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# ValidateJWTSVIDRequest message
|
|
69
|
+
add_message 'spiffe.workload.ValidateJWTSVIDRequest' do
|
|
70
|
+
optional :audience, :string, 1
|
|
71
|
+
optional :svid, :string, 2
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# ValidateJWTSVIDResponse message
|
|
75
|
+
add_message 'spiffe.workload.ValidateJWTSVIDResponse' do
|
|
76
|
+
optional :spiffe_id, :string, 1
|
|
77
|
+
optional :claims, :message, 2, 'google.protobuf.Struct'
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
module Spiffe
|
|
83
|
+
module Workload
|
|
84
|
+
# Load message classes
|
|
85
|
+
X509SVIDProto = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.X509SVID').msgclass
|
|
86
|
+
X509SVIDRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.X509SVIDRequest').msgclass
|
|
87
|
+
X509SVIDResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.X509SVIDResponse').msgclass
|
|
88
|
+
|
|
89
|
+
X509BundlesRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.X509BundlesRequest').msgclass
|
|
90
|
+
X509BundlesResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.X509BundlesResponse').msgclass
|
|
91
|
+
|
|
92
|
+
JWTSVIDProto = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.JWTSVID').msgclass
|
|
93
|
+
JWTSVIDRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.JWTSVIDRequest').msgclass
|
|
94
|
+
JWTSVIDResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.JWTSVIDResponse').msgclass
|
|
95
|
+
|
|
96
|
+
JWTBundlesRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.JWTBundlesRequest').msgclass
|
|
97
|
+
JWTBundlesResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.JWTBundlesResponse').msgclass
|
|
98
|
+
|
|
99
|
+
ValidateJWTSVIDRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.ValidateJWTSVIDRequest').msgclass
|
|
100
|
+
ValidateJWTSVIDResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup('spiffe.workload.ValidateJWTSVIDResponse').msgclass
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This file provides proto generation instructions
|
|
4
|
+
# Run `rake generate_protos` to generate proper gRPC stubs
|
|
5
|
+
|
|
6
|
+
# The generate_protos task will:
|
|
7
|
+
# 1. Use grpc-tools-ruby-protoc to compile proto/spiffe/workload.proto
|
|
8
|
+
# 2. Generate workload_pb.rb and workload_services_pb.rb
|
|
9
|
+
# 3. Place them in lib/spiffe/workload/
|
|
10
|
+
#
|
|
11
|
+
# After generation, update client.rb to use:
|
|
12
|
+
# require 'spiffe/workload/workload_pb'
|
|
13
|
+
# require 'spiffe/workload/workload_services_pb'
|
|
14
|
+
#
|
|
15
|
+
# And replace grpc_stub.rb with the generated service stub
|
|
16
|
+
|
|
17
|
+
module Spiffe
|
|
18
|
+
module Workload
|
|
19
|
+
module ProtoHelper
|
|
20
|
+
PROTO_PATH = File.expand_path('../../../proto/spiffe/workload.proto', __dir__)
|
|
21
|
+
|
|
22
|
+
def self.generate_instructions
|
|
23
|
+
<<~INSTRUCTIONS
|
|
24
|
+
To generate Ruby gRPC stubs from proto files:
|
|
25
|
+
|
|
26
|
+
1. Install grpc-tools:
|
|
27
|
+
gem install grpc-tools
|
|
28
|
+
|
|
29
|
+
2. Generate the stubs:
|
|
30
|
+
rake generate_protos
|
|
31
|
+
|
|
32
|
+
Or manually:
|
|
33
|
+
grpc_tools_ruby_protoc \\
|
|
34
|
+
-I proto \\
|
|
35
|
+
--ruby_out=lib \\
|
|
36
|
+
--grpc_out=lib \\
|
|
37
|
+
proto/spiffe/workload.proto
|
|
38
|
+
|
|
39
|
+
This will create:
|
|
40
|
+
- lib/spiffe/workload_pb.rb (message definitions)
|
|
41
|
+
- lib/spiffe/workload_services_pb.rb (gRPC service client)
|
|
42
|
+
INSTRUCTIONS
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'grpc'
|
|
4
|
+
require_relative 'messages'
|
|
5
|
+
|
|
6
|
+
module Spiffe
|
|
7
|
+
module Workload
|
|
8
|
+
# gRPC service definition for SPIFFE Workload API
|
|
9
|
+
module SpiffeWorkloadAPI
|
|
10
|
+
class Service
|
|
11
|
+
include GRPC::GenericService
|
|
12
|
+
|
|
13
|
+
self.marshal_class_method = :encode
|
|
14
|
+
self.unmarshal_class_method = :decode
|
|
15
|
+
self.service_name = 'SpiffeWorkloadAPI'
|
|
16
|
+
|
|
17
|
+
# FetchX509SVID - streaming server RPC
|
|
18
|
+
rpc :FetchX509SVID, X509SVIDRequest, stream(X509SVIDResponse)
|
|
19
|
+
|
|
20
|
+
# FetchX509Bundles - streaming server RPC
|
|
21
|
+
rpc :FetchX509Bundles, X509BundlesRequest, stream(X509BundlesResponse)
|
|
22
|
+
|
|
23
|
+
# FetchJWTSVID - unary RPC
|
|
24
|
+
rpc :FetchJWTSVID, JWTSVIDRequest, JWTSVIDResponse
|
|
25
|
+
|
|
26
|
+
# FetchJWTBundles - streaming server RPC
|
|
27
|
+
rpc :FetchJWTBundles, JWTBundlesRequest, stream(JWTBundlesResponse)
|
|
28
|
+
|
|
29
|
+
# ValidateJWTSVID - unary RPC
|
|
30
|
+
rpc :ValidateJWTSVID, ValidateJWTSVIDRequest, ValidateJWTSVIDResponse
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Stub = Service.rpc_stub_class
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
|
|
5
|
+
module Spiffe
|
|
6
|
+
module Workload
|
|
7
|
+
# TLS configuration helper for SPIFFE
|
|
8
|
+
class TLSConfig
|
|
9
|
+
# Create an SSL context from an X.509 SVID
|
|
10
|
+
# @param svid [X509SVID] The SVID to use
|
|
11
|
+
# @param verify_mode [Integer] OpenSSL verify mode
|
|
12
|
+
# @return [OpenSSL::SSL::SSLContext]
|
|
13
|
+
def self.create_context(svid, verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
|
14
|
+
context = OpenSSL::SSL::SSLContext.new
|
|
15
|
+
context.cert = svid.leaf_certificate
|
|
16
|
+
context.key = svid.private_key
|
|
17
|
+
context.extra_chain_cert = svid.cert_chain[1..-1] if svid.cert_chain.length > 1
|
|
18
|
+
context.cert_store = svid.trust_bundle
|
|
19
|
+
context.verify_mode = verify_mode
|
|
20
|
+
context.min_version = OpenSSL::SSL::TLS1_2_VERSION
|
|
21
|
+
context
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Create an SSL context that automatically updates with SVID rotation
|
|
25
|
+
# @param client [Client] The workload API client
|
|
26
|
+
# @param verify_mode [Integer] OpenSSL verify mode
|
|
27
|
+
# @return [OpenSSL::SSL::SSLContext]
|
|
28
|
+
def self.create_auto_updating_context(client, verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
|
29
|
+
context = create_context(client.x509_svid, verify_mode: verify_mode)
|
|
30
|
+
|
|
31
|
+
# Register callback to update context on rotation
|
|
32
|
+
client.on_x509_svid_update do |new_svid|
|
|
33
|
+
context.cert = new_svid.leaf_certificate
|
|
34
|
+
context.key = new_svid.private_key
|
|
35
|
+
context.extra_chain_cert = new_svid.cert_chain[1..-1] if new_svid.cert_chain.length > 1
|
|
36
|
+
context.cert_store = new_svid.trust_bundle
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
|
|
5
|
+
module Spiffe
|
|
6
|
+
module Workload
|
|
7
|
+
# Represents an X.509 SVID with certificate chain, private key, and trust bundle
|
|
8
|
+
class X509SVIDWrapper
|
|
9
|
+
attr_reader :spiffe_id, :cert_chain, :private_key, :trust_bundle, :hint
|
|
10
|
+
|
|
11
|
+
# @param spiffe_id [String] The SPIFFE ID of this SVID
|
|
12
|
+
# @param cert_chain [Array<OpenSSL::X509::Certificate>] Certificate chain
|
|
13
|
+
# @param private_key [OpenSSL::PKey::RSA, OpenSSL::PKey::EC] Private key
|
|
14
|
+
# @param trust_bundle [OpenSSL::X509::Store] Trust bundle for the trust domain
|
|
15
|
+
# @param hint [String, nil] Optional hint for SVID usage
|
|
16
|
+
def initialize(spiffe_id:, cert_chain:, private_key:, trust_bundle:, hint: nil)
|
|
17
|
+
@spiffe_id = spiffe_id
|
|
18
|
+
@cert_chain = cert_chain
|
|
19
|
+
@private_key = private_key
|
|
20
|
+
@trust_bundle = trust_bundle
|
|
21
|
+
@hint = hint
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Get the leaf certificate (first in chain)
|
|
25
|
+
# @return [OpenSSL::X509::Certificate]
|
|
26
|
+
def leaf_certificate
|
|
27
|
+
@cert_chain.first
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Check if the SVID is expired
|
|
31
|
+
# @return [Boolean]
|
|
32
|
+
def expired?
|
|
33
|
+
leaf_certificate.not_after < Time.now
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Get time until expiration
|
|
37
|
+
# @return [Float] Seconds until expiration
|
|
38
|
+
def ttl
|
|
39
|
+
leaf_certificate.not_after - Time.now
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Parse X.509 SVID from proto response
|
|
43
|
+
# @param proto_svid [Spiffe::Workload::X509SVIDProto] Proto message
|
|
44
|
+
# @return [X509SVID]
|
|
45
|
+
def self.from_proto(proto_svid)
|
|
46
|
+
# Parse certificate chain
|
|
47
|
+
cert_chain = []
|
|
48
|
+
cert_data = proto_svid.x509_svid
|
|
49
|
+
|
|
50
|
+
# The cert data may contain multiple certificates
|
|
51
|
+
# We need to extract all of them
|
|
52
|
+
offset = 0
|
|
53
|
+
while offset < cert_data.bytesize
|
|
54
|
+
begin
|
|
55
|
+
cert = OpenSSL::X509::Certificate.new(cert_data[offset..-1])
|
|
56
|
+
cert_chain << cert
|
|
57
|
+
# Move offset forward by the size of the DER-encoded cert
|
|
58
|
+
offset += cert.to_der.bytesize
|
|
59
|
+
rescue OpenSSL::X509::CertificateError
|
|
60
|
+
break
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Parse private key (PKCS#8 unencrypted)
|
|
65
|
+
private_key = OpenSSL::PKey.read(proto_svid.x509_svid_key)
|
|
66
|
+
|
|
67
|
+
# Parse trust bundle
|
|
68
|
+
trust_bundle = OpenSSL::X509::Store.new
|
|
69
|
+
bundle_data = proto_svid.bundle
|
|
70
|
+
|
|
71
|
+
# Parse all certificates in the bundle
|
|
72
|
+
offset = 0
|
|
73
|
+
while offset < bundle_data.bytesize
|
|
74
|
+
begin
|
|
75
|
+
ca_cert = OpenSSL::X509::Certificate.new(bundle_data[offset..-1])
|
|
76
|
+
trust_bundle.add_cert(ca_cert)
|
|
77
|
+
offset += ca_cert.to_der.bytesize
|
|
78
|
+
rescue OpenSSL::X509::CertificateError
|
|
79
|
+
break
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
new(
|
|
84
|
+
spiffe_id: proto_svid.spiffe_id,
|
|
85
|
+
cert_chain: cert_chain,
|
|
86
|
+
private_key: private_key,
|
|
87
|
+
trust_bundle: trust_bundle,
|
|
88
|
+
hint: proto_svid.hint.empty? ? nil : proto_svid.hint
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: spiffe/workload.proto
|
|
4
|
+
|
|
5
|
+
require 'google/protobuf'
|
|
6
|
+
|
|
7
|
+
require 'google/protobuf/struct_pb'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
descriptor_data = "\n\x15spiffe/workload.proto\x12\x0fspiffe.workload\x1a\x1cgoogle/protobuf/struct.proto\"\x11\n\x0fX509SVIDRequest\"\xd6\x01\n\x10X509SVIDResponse\x12(\n\x05svids\x18\x01 \x03(\x0b\x32\x19.spiffe.workload.X509SVID\x12\x0b\n\x03\x63rl\x18\x02 \x03(\x0c\x12R\n\x11\x66\x65\x64\x65rated_bundles\x18\x03 \x03(\x0b\x32\x37.spiffe.workload.X509SVIDResponse.FederatedBundlesEntry\x1a\x37\n\x15\x46\x65\x64\x65ratedBundlesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"e\n\x08X509SVID\x12\x11\n\tspiffe_id\x18\x01 \x01(\t\x12\x11\n\tx509_svid\x18\x02 \x01(\x0c\x12\x15\n\rx509_svid_key\x18\x03 \x01(\x0c\x12\x0e\n\x06\x62undle\x18\x04 \x01(\x0c\x12\x0c\n\x04hint\x18\x05 \x01(\t\"\x14\n\x12X509BundlesRequest\"\x96\x01\n\x13X509BundlesResponse\x12\x0b\n\x03\x63rl\x18\x01 \x03(\x0c\x12\x42\n\x07\x62undles\x18\x02 \x03(\x0b\x32\x31.spiffe.workload.X509BundlesResponse.BundlesEntry\x1a.\n\x0c\x42undlesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"5\n\x0eJWTSVIDRequest\x12\x10\n\x08\x61udience\x18\x01 \x03(\t\x12\x11\n\tspiffe_id\x18\x02 \x01(\t\":\n\x0fJWTSVIDResponse\x12\'\n\x05svids\x18\x01 \x03(\x0b\x32\x18.spiffe.workload.JWTSVID\"8\n\x07JWTSVID\x12\x11\n\tspiffe_id\x18\x01 \x01(\t\x12\x0c\n\x04svid\x18\x02 \x01(\t\x12\x0c\n\x04hint\x18\x03 \x01(\t\"\x13\n\x11JWTBundlesRequest\"\x87\x01\n\x12JWTBundlesResponse\x12\x41\n\x07\x62undles\x18\x01 \x03(\x0b\x32\x30.spiffe.workload.JWTBundlesResponse.BundlesEntry\x1a.\n\x0c\x42undlesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"8\n\x16ValidateJWTSVIDRequest\x12\x10\n\x08\x61udience\x18\x01 \x01(\t\x12\x0c\n\x04svid\x18\x02 \x01(\t\"U\n\x17ValidateJWTSVIDResponse\x12\x11\n\tspiffe_id\x18\x01 \x01(\t\x12\'\n\x06\x63laims\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct2\xe3\x03\n\x11SpiffeWorkloadAPI\x12V\n\rFetchX509SVID\x12 .spiffe.workload.X509SVIDRequest\x1a!.spiffe.workload.X509SVIDResponse0\x01\x12_\n\x10\x46\x65tchX509Bundles\x12#.spiffe.workload.X509BundlesRequest\x1a$.spiffe.workload.X509BundlesResponse0\x01\x12Q\n\x0c\x46\x65tchJWTSVID\x12\x1f.spiffe.workload.JWTSVIDRequest\x1a .spiffe.workload.JWTSVIDResponse\x12\\\n\x0f\x46\x65tchJWTBundles\x12\".spiffe.workload.JWTBundlesRequest\x1a#.spiffe.workload.JWTBundlesResponse0\x01\x12\x64\n\x0fValidateJWTSVID\x12\'.spiffe.workload.ValidateJWTSVIDRequest\x1a(.spiffe.workload.ValidateJWTSVIDResponseB?Z=github.com/spiffe/go-spiffe/v2/proto/spiffe/workload;workloadb\x06proto3"
|
|
11
|
+
|
|
12
|
+
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
13
|
+
pool.add_serialized_file(descriptor_data)
|
|
14
|
+
|
|
15
|
+
module Spiffe
|
|
16
|
+
module Workload
|
|
17
|
+
X509SVIDRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.X509SVIDRequest").msgclass
|
|
18
|
+
X509SVIDResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.X509SVIDResponse").msgclass
|
|
19
|
+
X509SVID = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.X509SVID").msgclass
|
|
20
|
+
X509BundlesRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.X509BundlesRequest").msgclass
|
|
21
|
+
X509BundlesResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.X509BundlesResponse").msgclass
|
|
22
|
+
JWTSVIDRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.JWTSVIDRequest").msgclass
|
|
23
|
+
JWTSVIDResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.JWTSVIDResponse").msgclass
|
|
24
|
+
JWTSVID = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.JWTSVID").msgclass
|
|
25
|
+
JWTBundlesRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.JWTBundlesRequest").msgclass
|
|
26
|
+
JWTBundlesResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.JWTBundlesResponse").msgclass
|
|
27
|
+
ValidateJWTSVIDRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.ValidateJWTSVIDRequest").msgclass
|
|
28
|
+
ValidateJWTSVIDResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("spiffe.workload.ValidateJWTSVIDResponse").msgclass
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
2
|
+
# Source: spiffe/workload.proto for package 'spiffe.workload'
|
|
3
|
+
|
|
4
|
+
require 'grpc'
|
|
5
|
+
require 'spiffe/workload_pb'
|
|
6
|
+
|
|
7
|
+
module Spiffe
|
|
8
|
+
module Workload
|
|
9
|
+
module SpiffeWorkloadAPI
|
|
10
|
+
class Service
|
|
11
|
+
|
|
12
|
+
include ::GRPC::GenericService
|
|
13
|
+
|
|
14
|
+
self.marshal_class_method = :encode
|
|
15
|
+
self.unmarshal_class_method = :decode
|
|
16
|
+
self.service_name = 'SpiffeWorkloadAPI'
|
|
17
|
+
|
|
18
|
+
# Fetch X.509-SVIDs for all SPIFFE identities the workload is entitled to,
|
|
19
|
+
# as well as related information like trust bundles and CRLs. As this
|
|
20
|
+
# information changes, subsequent messages will be streamed from the
|
|
21
|
+
# server.
|
|
22
|
+
rpc :FetchX509SVID, ::Spiffe::Workload::X509SVIDRequest, stream(::Spiffe::Workload::X509SVIDResponse)
|
|
23
|
+
# Fetch trust bundles and CRLs. Useful for clients that only need to
|
|
24
|
+
# validate SVIDs without obtaining an SVID for themself. As this
|
|
25
|
+
# information changes, subsequent messages will be streamed from the
|
|
26
|
+
# server.
|
|
27
|
+
rpc :FetchX509Bundles, ::Spiffe::Workload::X509BundlesRequest, stream(::Spiffe::Workload::X509BundlesResponse)
|
|
28
|
+
# ///////////////////////////////////////////////////////////////////////
|
|
29
|
+
# JWT-SVID Profile
|
|
30
|
+
# ///////////////////////////////////////////////////////////////////////
|
|
31
|
+
#
|
|
32
|
+
# Fetch JWT-SVIDs for all SPIFFE identities the workload is entitled to,
|
|
33
|
+
# for the requested audience. If an optional SPIFFE ID is requested, only
|
|
34
|
+
# the JWT-SVID for that SPIFFE ID is returned.
|
|
35
|
+
rpc :FetchJWTSVID, ::Spiffe::Workload::JWTSVIDRequest, ::Spiffe::Workload::JWTSVIDResponse
|
|
36
|
+
# Fetches the JWT bundles, formatted as JWKS documents, keyed by the
|
|
37
|
+
# SPIFFE ID of the trust domain. As this information changes, subsequent
|
|
38
|
+
# messages will be streamed from the server.
|
|
39
|
+
rpc :FetchJWTBundles, ::Spiffe::Workload::JWTBundlesRequest, stream(::Spiffe::Workload::JWTBundlesResponse)
|
|
40
|
+
# Validates a JWT-SVID against the requested audience. Returns the SPIFFE
|
|
41
|
+
# ID of the JWT-SVID and JWT claims.
|
|
42
|
+
rpc :ValidateJWTSVID, ::Spiffe::Workload::ValidateJWTSVIDRequest, ::Spiffe::Workload::ValidateJWTSVIDResponse
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
Stub = Service.rpc_stub_class
|
|
46
|
+
end
|
|
47
|
+
# ///////////////////////////////////////////////////////////////////////
|
|
48
|
+
# X509-SVID Profile
|
|
49
|
+
# ///////////////////////////////////////////////////////////////////////
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/spiffe.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'spiffe/version'
|
|
4
|
+
require_relative 'spiffe/workload/client'
|
|
5
|
+
require_relative 'spiffe/workload/x509_svid'
|
|
6
|
+
require_relative 'spiffe/workload/jwt_svid'
|
|
7
|
+
require_relative 'spiffe/workload/tls_config'
|
|
8
|
+
require_relative 'spiffe/workload/http_client'
|
|
9
|
+
|
|
10
|
+
module Spiffe
|
|
11
|
+
class Error < StandardError; end
|
|
12
|
+
class SocketError < Error; end
|
|
13
|
+
class AuthenticationError < Error; end
|
|
14
|
+
class RotationError < Error; end
|
|
15
|
+
|
|
16
|
+
# Convenience method to create a new Workload API client
|
|
17
|
+
# @param socket_path [String, nil] Optional socket path
|
|
18
|
+
# @return [Workload::Client]
|
|
19
|
+
def self.workload_api_client(socket_path: nil)
|
|
20
|
+
Workload::Client.new(socket_path: socket_path)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package spiffe.workload;
|
|
4
|
+
|
|
5
|
+
import "google/protobuf/struct.proto";
|
|
6
|
+
|
|
7
|
+
service SpiffeWorkloadAPI {
|
|
8
|
+
/////////////////////////////////////////////////////////////////////////
|
|
9
|
+
// X509-SVID Profile
|
|
10
|
+
/////////////////////////////////////////////////////////////////////////
|
|
11
|
+
|
|
12
|
+
// Fetch X.509-SVIDs for all SPIFFE identities the workload is entitled to,
|
|
13
|
+
// as well as related information like trust bundles and CRLs. As this
|
|
14
|
+
// information changes, subsequent messages will be streamed from the
|
|
15
|
+
// server.
|
|
16
|
+
rpc FetchX509SVID(X509SVIDRequest) returns (stream X509SVIDResponse);
|
|
17
|
+
|
|
18
|
+
// Fetch trust bundles and CRLs. Useful for clients that only need to
|
|
19
|
+
// validate SVIDs without obtaining an SVID for themself. As this
|
|
20
|
+
// information changes, subsequent messages will be streamed from the
|
|
21
|
+
// server.
|
|
22
|
+
rpc FetchX509Bundles(X509BundlesRequest) returns (stream X509BundlesResponse);
|
|
23
|
+
|
|
24
|
+
/////////////////////////////////////////////////////////////////////////
|
|
25
|
+
// JWT-SVID Profile
|
|
26
|
+
/////////////////////////////////////////////////////////////////////////
|
|
27
|
+
|
|
28
|
+
// Fetch JWT-SVIDs for all SPIFFE identities the workload is entitled to,
|
|
29
|
+
// for the requested audience. If an optional SPIFFE ID is requested, only
|
|
30
|
+
// the JWT-SVID for that SPIFFE ID is returned.
|
|
31
|
+
rpc FetchJWTSVID(JWTSVIDRequest) returns (JWTSVIDResponse);
|
|
32
|
+
|
|
33
|
+
// Fetches the JWT bundles, formatted as JWKS documents, keyed by the
|
|
34
|
+
// SPIFFE ID of the trust domain. As this information changes, subsequent
|
|
35
|
+
// messages will be streamed from the server.
|
|
36
|
+
rpc FetchJWTBundles(JWTBundlesRequest) returns (stream JWTBundlesResponse);
|
|
37
|
+
|
|
38
|
+
// Validates a JWT-SVID against the requested audience. Returns the SPIFFE
|
|
39
|
+
// ID of the JWT-SVID and JWT claims.
|
|
40
|
+
rpc ValidateJWTSVID(ValidateJWTSVIDRequest) returns (ValidateJWTSVIDResponse);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The X509SVIDRequest message conveys parameters for requesting an X.509-SVID.
|
|
44
|
+
// There are currently no request parameters.
|
|
45
|
+
message X509SVIDRequest { }
|
|
46
|
+
|
|
47
|
+
// The X509SVIDResponse message carries X.509-SVIDs and related information,
|
|
48
|
+
// including a set of global CRLs and a list of bundles the workload may use
|
|
49
|
+
// for federating with foreign trust domains.
|
|
50
|
+
message X509SVIDResponse {
|
|
51
|
+
// Required. A list of X509SVID messages, each of which includes a single
|
|
52
|
+
// X.509-SVID, its private key, and the bundle for the trust domain.
|
|
53
|
+
repeated X509SVID svids = 1;
|
|
54
|
+
|
|
55
|
+
// Optional. ASN.1 DER encoded certificate revocation lists.
|
|
56
|
+
repeated bytes crl = 2;
|
|
57
|
+
|
|
58
|
+
// Optional. CA certificate bundles belonging to foreign trust domains that
|
|
59
|
+
// the workload should trust, keyed by the SPIFFE ID of the foreign trust
|
|
60
|
+
// domain. Bundles are ASN.1 DER encoded.
|
|
61
|
+
map<string, bytes> federated_bundles = 3;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The X509SVID message carries a single SVID and all associated information,
|
|
65
|
+
// including the X.509 bundle for the trust domain.
|
|
66
|
+
message X509SVID {
|
|
67
|
+
// Required. The SPIFFE ID of the SVID in this entry
|
|
68
|
+
string spiffe_id = 1;
|
|
69
|
+
|
|
70
|
+
// Required. ASN.1 DER encoded certificate chain. MAY include
|
|
71
|
+
// intermediates, the leaf certificate (or SVID itself) MUST come first.
|
|
72
|
+
bytes x509_svid = 2;
|
|
73
|
+
|
|
74
|
+
// Required. ASN.1 DER encoded PKCS#8 private key. MUST be unencrypted.
|
|
75
|
+
bytes x509_svid_key = 3;
|
|
76
|
+
|
|
77
|
+
// Required. ASN.1 DER encoded X.509 bundle for the trust domain.
|
|
78
|
+
bytes bundle = 4;
|
|
79
|
+
|
|
80
|
+
// Optional. An operator-specified string used to provide guidance on how this
|
|
81
|
+
// identity should be used by a workload when more than one SVID is returned.
|
|
82
|
+
// For example, `internal` and `external` to indicate an SVID for internal or
|
|
83
|
+
// external use, respectively.
|
|
84
|
+
string hint = 5;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The X509BundlesRequest message conveys parameters for requesting X.509
|
|
88
|
+
// bundles. There are currently no such parameters.
|
|
89
|
+
message X509BundlesRequest {
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The X509BundlesResponse message carries a set of global CRLs and a map of
|
|
93
|
+
// trust bundles the workload should trust.
|
|
94
|
+
message X509BundlesResponse {
|
|
95
|
+
// Optional. ASN.1 DER encoded certificate revocation lists.
|
|
96
|
+
repeated bytes crl = 1;
|
|
97
|
+
|
|
98
|
+
// Required. CA certificate bundles belonging to trust domains that the
|
|
99
|
+
// workload should trust, keyed by the SPIFFE ID of the trust domain.
|
|
100
|
+
// Bundles are ASN.1 DER encoded.
|
|
101
|
+
map<string, bytes> bundles = 2;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
message JWTSVIDRequest {
|
|
105
|
+
// Required. The audience(s) the workload intends to authenticate against.
|
|
106
|
+
repeated string audience = 1;
|
|
107
|
+
|
|
108
|
+
// Optional. The requested SPIFFE ID for the JWT-SVID. If unset, all
|
|
109
|
+
// JWT-SVIDs to which the workload is entitled are requested.
|
|
110
|
+
string spiffe_id = 2;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// The JWTSVIDResponse message conveys JWT-SVIDs.
|
|
114
|
+
message JWTSVIDResponse {
|
|
115
|
+
// Required. The list of returned JWT-SVIDs.
|
|
116
|
+
repeated JWTSVID svids = 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// The JWTSVID message carries the JWT-SVID token and associated metadata.
|
|
120
|
+
message JWTSVID {
|
|
121
|
+
// Required. The SPIFFE ID of the JWT-SVID.
|
|
122
|
+
string spiffe_id = 1;
|
|
123
|
+
|
|
124
|
+
// Required. Encoded JWT using JWS Compact Serialization.
|
|
125
|
+
string svid = 2;
|
|
126
|
+
|
|
127
|
+
// Optional. An operator-specified string used to provide guidance on how this
|
|
128
|
+
// identity should be used by a workload when more than one SVID is returned.
|
|
129
|
+
// For example, `internal` and `external` to indicate an SVID for internal or
|
|
130
|
+
// external use, respectively.
|
|
131
|
+
string hint = 3;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// The JWTBundlesRequest message conveys parameters for requesting JWT bundles.
|
|
135
|
+
// There are currently no such parameters.
|
|
136
|
+
message JWTBundlesRequest { }
|
|
137
|
+
|
|
138
|
+
// The JWTBundlesReponse conveys JWT bundles.
|
|
139
|
+
message JWTBundlesResponse {
|
|
140
|
+
// Required. JWK encoded JWT bundles, keyed by the SPIFFE ID of the trust
|
|
141
|
+
// domain.
|
|
142
|
+
map<string, bytes> bundles = 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// The ValidateJWTSVIDRequest message conveys request parameters for
|
|
146
|
+
// JWT-SVID validation.
|
|
147
|
+
message ValidateJWTSVIDRequest {
|
|
148
|
+
// Required. The audience of the validating party. The JWT-SVID must
|
|
149
|
+
// contain an audience claim which contains this value in order to
|
|
150
|
+
// succesfully validate.
|
|
151
|
+
string audience = 1;
|
|
152
|
+
|
|
153
|
+
// Required. The JWT-SVID to validate, encoded using JWS Compact
|
|
154
|
+
// Serialization.
|
|
155
|
+
string svid = 2;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// The ValidateJWTSVIDReponse message conveys the JWT-SVID validation results.
|
|
159
|
+
message ValidateJWTSVIDResponse {
|
|
160
|
+
// Required. The SPIFFE ID of the validated JWT-SVID.
|
|
161
|
+
string spiffe_id = 1;
|
|
162
|
+
|
|
163
|
+
// Optional. Arbitrary claims contained within the payload of the validated
|
|
164
|
+
// JWT-SVID.
|
|
165
|
+
google.protobuf.Struct claims = 2;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
option go_package = "github.com/spiffe/go-spiffe/v2/proto/spiffe/workload;workload";
|