From 36f6edffcadf2c652d70b449ab6610034e195b32 Mon Sep 17 00:00:00 2001 From: sechmachine <97589681+sechmachine727@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:28:25 +0700 Subject: [PATCH] feat(protocol): include Apollo client identity in session work --- gen/go/protocol/protocol.go | 15 ++++++++++++++- gen/manifest.json | 2 +- gen/protobuf/tunnel-v1.pb | Bin 17221 -> 17309 bytes gen/rust/protocol.rs | 11 ++++++++--- gen/swift/Protocol.swift | 12 +++++++++--- proto/versevdi/tunnel/v1/tunnel.proto | 1 + schemas/control-v1.schema.json | 3 ++- tests/go/protocol_test.go | 2 +- 8 files changed, 36 insertions(+), 10 deletions(-) diff --git a/gen/go/protocol/protocol.go b/gen/go/protocol/protocol.go index 24d2017..30ac905 100644 --- a/gen/go/protocol/protocol.go +++ b/gen/go/protocol/protocol.go @@ -12,7 +12,7 @@ import ( "time" ) -const SchemaSHA256 = "c075c6fc472ce858337c3ad6c4b2f4dcfa88446d5471afb46c2f6931cbcf1b1d" +const SchemaSHA256 = "792abfb9cfe70e79911d499d76c009ab848713278bc240b88576df520580e480" const ProtocolVersion = "1.0.0" const CurrentWireVersion = "1" const NMinus1WireVersion = "0" @@ -250,6 +250,7 @@ type ProviderSessionWork struct { ProviderIdentity string `json:"provider_identity"` PolicyVersionID string `json:"policy_version_id"` ApplicationID string `json:"application_id"` + ClientID string `json:"client_id"` ManagementHost string `json:"management_host"` ManagementPort int64 `json:"management_port"` StreamHost string `json:"stream_host"` @@ -3009,6 +3010,15 @@ func (v ProviderSessionWork) Validate() error { if len(v.ApplicationID) > 128 { violations = append(violations, FieldViolation{Field: "application_id", Code: "max_length"}) } + if v.ClientID == "" { + violations = append(violations, FieldViolation{Field: "client_id", Code: "required"}) + } + if len(v.ClientID) < 1 && v.ClientID != "" { + violations = append(violations, FieldViolation{Field: "client_id", Code: "min_length"}) + } + if len(v.ClientID) > 128 { + violations = append(violations, FieldViolation{Field: "client_id", Code: "max_length"}) + } if v.ManagementHost == "" { violations = append(violations, FieldViolation{Field: "management_host", Code: "required"}) } @@ -3093,6 +3103,9 @@ func DecodeProviderSessionWork(data []byte) (ProviderSessionWork, error) { if raw, ok := fields["client_certificate_pem"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { return value, ValidationError{Violations: []FieldViolation{{Field: "client_certificate_pem", Code: "required"}}} } + if raw, ok := fields["client_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { + return value, ValidationError{Violations: []FieldViolation{{Field: "client_id", Code: "required"}}} + } if raw, ok := fields["client_private_key_pem"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { return value, ValidationError{Violations: []FieldViolation{{Field: "client_private_key_pem", Code: "required"}}} } diff --git a/gen/manifest.json b/gen/manifest.json index c7ddd05..82590a9 100644 --- a/gen/manifest.json +++ b/gen/manifest.json @@ -14,5 +14,5 @@ }, "generator_sha256": "e9c6ee1541585fcb00dcc5e94a5a6d93dbe3a719a5c545f31e5eda268f2638ab", "protocol_version": "1.0.0", - "schema_sha256": "c075c6fc472ce858337c3ad6c4b2f4dcfa88446d5471afb46c2f6931cbcf1b1d" + "schema_sha256": "792abfb9cfe70e79911d499d76c009ab848713278bc240b88576df520580e480" } diff --git a/gen/protobuf/tunnel-v1.pb b/gen/protobuf/tunnel-v1.pb index 9e3c607950f8a0e2b335c34345c72c310821b28f..cb2466377b0e391c4b68cbd114a799695b263346 100644 GIT binary patch delta 1254 zcmYL|y>1gh6oq}pvup2$c-IpZAc~SR2Jl|RTeXwnko5yL*MU~3+;_0Jq6JHlw&*s&hwaX@V_6y8< zx|~KrQGs7dITzr7aHy~X~tG2<;1JqkAS=%rbXZtYi!Xt4i(N86^ zw~r+6X_;gDSW%bN!_oy^qPi%mNgh;CJuFq@>=DdrC>p9nRSnf6Bu%YWd%~@DxH2al z!>ojAl`c`;D&1ow4XqZZiNY=DU^#t7ws?5J3cRVfK$fLRu z-P6*I=$?94)Y#bpOg9XUsS;IA4!yG*t-)`v)OPjq!8szg;~@I+U~D%A;!)iM7}@Em3%TuRnXj1}pgqvjtJ zdyD2yHyq2gPhswcUh6Sc@@6=ena;~& xB~zlhGI?GeDq`8rQ(iLj*3R)3APcprtyB&Y{xVOLeev4ka$fw-*u0tLiAJ3vj-Tu_rVC~4CtK=J}s z?06a^o`L`PoROEGX1*EwAA9=+e?H^K>w)_Bs(L&4-NMhR|Kyjd{ysfZ+4y;$m?#MD zt2fVv9ek}myxfN)rQ0UHK=2L7TY0G4F!};%FMp70-By9oW2nRYAdf6XHKu~>9V06R zLyKKeS-fmiP$i0rx|)?E?}F}S<0_*kP<>xibcy0Bx)Wp_sZyPwcj%v(bOqJ*#gHmd z)R5{GvbI#ik>!r@t~PoK_0U%}U81;}?iAUPRJmx1;fYt-J8RU)p{NnnS))c&XHFg2 zkYl6gP(wdBrb`qzraMO_Cy43JaXj#A6QdVUJ+CHIiHRW91=2#0>H?-qRq4XS*9g9k z{Mie(UL$oO7WV8*xgCjPiSCJr>|M&)i5$C>(-rnOE2!goCy6RiRD1RnGIt$TZT>6t zkLtH66wkhv+mYxJ#kG^yaywSJb}gqXEZsnz__g-z#8{B(4HEY~vA1p>47rv07m8=! zK%M$#?JyVQs0~`~Yhy1*ZD8E7h#a-SqsMiorHQu)?tP>sJGV$}+*+45(w#&~;#6Yl zzJD^CcakZQV|S8iY2W-7%G6IpqDyqS5bJ7oAXTcjC_8>FBfV?Z+DM6_D!jAo|KPID l^sZUUN&CjNnG#*5rLw=$9*O(;iFI?gcNV|@c>d}9#eWgQWw`(V diff --git a/gen/rust/protocol.rs b/gen/rust/protocol.rs index 59527ed..58995ab 100644 --- a/gen/rust/protocol.rs +++ b/gen/rust/protocol.rs @@ -1,6 +1,6 @@ // Code generated by tools/generate.py; DO NOT EDIT. #![allow(non_snake_case)] -pub const SCHEMA_SHA256: &str = "c075c6fc472ce858337c3ad6c4b2f4dcfa88446d5471afb46c2f6931cbcf1b1d"; +pub const SCHEMA_SHA256: &str = "792abfb9cfe70e79911d499d76c009ab848713278bc240b88576df520580e480"; pub const CURRENT_WIRE_VERSION: &str = "1"; pub const N_MINUS_1_WIRE_VERSION: &str = "0"; pub const N_MINUS_2_WIRE_VERSION: &str = "-1"; @@ -993,6 +993,7 @@ pub struct ProviderSessionWork { providerIdentity: String, policyVersionId: String, applicationId: String, + clientId: String, managementHost: String, managementPort: i64, streamHost: String, @@ -1003,8 +1004,8 @@ pub struct ProviderSessionWork { } impl ProviderSessionWork { - pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, expiresAt: String, providerProfile: String, providerIdentity: String, policyVersionId: String, applicationId: String, managementHost: String, managementPort: i64, streamHost: String, streamPort: i64, clientCertificatePem: String, clientPrivateKeyPem: String, serverCertificatePem: String) -> Result { - let value = Self { version, sessionId, gatewayId, reconnectSequence, expiresAt, providerProfile, providerIdentity, policyVersionId, applicationId, managementHost, managementPort, streamHost, streamPort, clientCertificatePem, clientPrivateKeyPem, serverCertificatePem }; + pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, expiresAt: String, providerProfile: String, providerIdentity: String, policyVersionId: String, applicationId: String, clientId: String, managementHost: String, managementPort: i64, streamHost: String, streamPort: i64, clientCertificatePem: String, clientPrivateKeyPem: String, serverCertificatePem: String) -> Result { + let value = Self { version, sessionId, gatewayId, reconnectSequence, expiresAt, providerProfile, providerIdentity, policyVersionId, applicationId, clientId, managementHost, managementPort, streamHost, streamPort, clientCertificatePem, clientPrivateKeyPem, serverCertificatePem }; value.validate()?; Ok(value) } @@ -1028,6 +1029,9 @@ impl ProviderSessionWork { if self.applicationId.is_empty() { return Err(ValidationError::new("application_id", "required")); } if !self.applicationId.is_empty() && self.applicationId.len() < 1 { return Err(ValidationError::new("application_id", "min_length")); } if self.applicationId.len() > 128 { return Err(ValidationError::new("application_id", "max_length")); } + if self.clientId.is_empty() { return Err(ValidationError::new("client_id", "required")); } + if !self.clientId.is_empty() && self.clientId.len() < 1 { return Err(ValidationError::new("client_id", "min_length")); } + if self.clientId.len() > 128 { return Err(ValidationError::new("client_id", "max_length")); } if self.managementHost.is_empty() { return Err(ValidationError::new("management_host", "required")); } if !self.managementHost.is_empty() && self.managementHost.len() < 1 { return Err(ValidationError::new("management_host", "min_length")); } if self.managementHost.len() > 256 { return Err(ValidationError::new("management_host", "max_length")); } @@ -1058,6 +1062,7 @@ impl ProviderSessionWork { pub fn providerIdentity(&self) -> &String { &self.providerIdentity } pub fn policyVersionId(&self) -> &String { &self.policyVersionId } pub fn applicationId(&self) -> &String { &self.applicationId } + pub fn clientId(&self) -> &String { &self.clientId } pub fn managementHost(&self) -> &String { &self.managementHost } pub fn managementPort(&self) -> &i64 { &self.managementPort } pub fn streamHost(&self) -> &String { &self.streamHost } diff --git a/gen/swift/Protocol.swift b/gen/swift/Protocol.swift index 6a349f2..e41054e 100644 --- a/gen/swift/Protocol.swift +++ b/gen/swift/Protocol.swift @@ -1,7 +1,7 @@ // Code generated by tools/generate.py; DO NOT EDIT. import Foundation public typealias JSONObject = [String: String] -public let schemaSHA256 = "c075c6fc472ce858337c3ad6c4b2f4dcfa88446d5471afb46c2f6931cbcf1b1d" +public let schemaSHA256 = "792abfb9cfe70e79911d499d76c009ab848713278bc240b88576df520580e480" public let currentWireVersion = "1" public let nMinus1WireVersion = "0" public let nMinus2WireVersion = "-1" @@ -1320,6 +1320,7 @@ public struct ProviderSessionWork: Codable, Equatable { public let providerIdentity: String public let policyVersionId: String public let applicationId: String + public let clientId: String public let managementHost: String public let managementPort: Int64 public let streamHost: String @@ -1337,6 +1338,7 @@ public struct ProviderSessionWork: Codable, Equatable { case providerIdentity = "provider_identity" case policyVersionId = "policy_version_id" case applicationId = "application_id" + case clientId = "client_id" case managementHost = "management_host" case managementPort = "management_port" case streamHost = "stream_host" @@ -1346,7 +1348,7 @@ public struct ProviderSessionWork: Codable, Equatable { case serverCertificatePem = "server_certificate_pem" } - public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, expiresAt: String, providerProfile: String, providerIdentity: String, policyVersionId: String, applicationId: String, managementHost: String, managementPort: Int64, streamHost: String, streamPort: Int64, clientCertificatePem: String, clientPrivateKeyPem: String, serverCertificatePem: String) throws { + public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, expiresAt: String, providerProfile: String, providerIdentity: String, policyVersionId: String, applicationId: String, clientId: String, managementHost: String, managementPort: Int64, streamHost: String, streamPort: Int64, clientCertificatePem: String, clientPrivateKeyPem: String, serverCertificatePem: String) throws { self.version = version self.sessionId = sessionId self.gatewayId = gatewayId @@ -1356,6 +1358,7 @@ public struct ProviderSessionWork: Codable, Equatable { self.providerIdentity = providerIdentity self.policyVersionId = policyVersionId self.applicationId = applicationId + self.clientId = clientId self.managementHost = managementHost self.managementPort = managementPort self.streamHost = streamHost @@ -1370,7 +1373,7 @@ public struct ProviderSessionWork: Codable, Equatable { let all = try decoder.container(keyedBy: AnyCodingKey.self) for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") } let c = try decoder.container(keyedBy: CodingKeys.self) - try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), expiresAt: try c.decode(String.self, forKey: .expiresAt), providerProfile: try c.decode(String.self, forKey: .providerProfile), providerIdentity: try c.decode(String.self, forKey: .providerIdentity), policyVersionId: try c.decode(String.self, forKey: .policyVersionId), applicationId: try c.decode(String.self, forKey: .applicationId), managementHost: try c.decode(String.self, forKey: .managementHost), managementPort: try c.decode(Int64.self, forKey: .managementPort), streamHost: try c.decode(String.self, forKey: .streamHost), streamPort: try c.decode(Int64.self, forKey: .streamPort), clientCertificatePem: try c.decode(String.self, forKey: .clientCertificatePem), clientPrivateKeyPem: try c.decode(String.self, forKey: .clientPrivateKeyPem), serverCertificatePem: try c.decode(String.self, forKey: .serverCertificatePem)) + try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), expiresAt: try c.decode(String.self, forKey: .expiresAt), providerProfile: try c.decode(String.self, forKey: .providerProfile), providerIdentity: try c.decode(String.self, forKey: .providerIdentity), policyVersionId: try c.decode(String.self, forKey: .policyVersionId), applicationId: try c.decode(String.self, forKey: .applicationId), clientId: try c.decode(String.self, forKey: .clientId), managementHost: try c.decode(String.self, forKey: .managementHost), managementPort: try c.decode(Int64.self, forKey: .managementPort), streamHost: try c.decode(String.self, forKey: .streamHost), streamPort: try c.decode(Int64.self, forKey: .streamPort), clientCertificatePem: try c.decode(String.self, forKey: .clientCertificatePem), clientPrivateKeyPem: try c.decode(String.self, forKey: .clientPrivateKeyPem), serverCertificatePem: try c.decode(String.self, forKey: .serverCertificatePem)) } public func validate() throws { @@ -1394,6 +1397,9 @@ public struct ProviderSessionWork: Codable, Equatable { if self.applicationId.isEmpty { throw ContractValidationError(field: "application_id", code: "required") } if !self.applicationId.isEmpty && self.applicationId.utf8.count < 1 { throw ContractValidationError(field: "application_id", code: "min_length") } if self.applicationId.utf8.count > 128 { throw ContractValidationError(field: "application_id", code: "max_length") } + if self.clientId.isEmpty { throw ContractValidationError(field: "client_id", code: "required") } + if !self.clientId.isEmpty && self.clientId.utf8.count < 1 { throw ContractValidationError(field: "client_id", code: "min_length") } + if self.clientId.utf8.count > 128 { throw ContractValidationError(field: "client_id", code: "max_length") } if self.managementHost.isEmpty { throw ContractValidationError(field: "management_host", code: "required") } if !self.managementHost.isEmpty && self.managementHost.utf8.count < 1 { throw ContractValidationError(field: "management_host", code: "min_length") } if self.managementHost.utf8.count > 256 { throw ContractValidationError(field: "management_host", code: "max_length") } diff --git a/proto/versevdi/tunnel/v1/tunnel.proto b/proto/versevdi/tunnel/v1/tunnel.proto index cbe0888..f474580 100644 --- a/proto/versevdi/tunnel/v1/tunnel.proto +++ b/proto/versevdi/tunnel/v1/tunnel.proto @@ -134,6 +134,7 @@ message ProviderSessionWork { string client_certificate_pem = 14; string client_private_key_pem = 15; string server_certificate_pem = 16; + string client_id = 17; } message ChannelFrame { diff --git a/schemas/control-v1.schema.json b/schemas/control-v1.schema.json index 425bcc3..6cd8c66 100644 --- a/schemas/control-v1.schema.json +++ b/schemas/control-v1.schema.json @@ -438,7 +438,7 @@ "ProviderSessionWork": { "type": "object", "additionalProperties": false, - "required": ["version", "session_id", "gateway_id", "reconnect_sequence", "expires_at", "provider_profile", "provider_identity", "policy_version_id", "application_id", "management_host", "management_port", "stream_host", "stream_port", "client_certificate_pem", "client_private_key_pem", "server_certificate_pem"], + "required": ["version", "session_id", "gateway_id", "reconnect_sequence", "expires_at", "provider_profile", "provider_identity", "policy_version_id", "application_id", "client_id", "management_host", "management_port", "stream_host", "stream_port", "client_certificate_pem", "client_private_key_pem", "server_certificate_pem"], "properties": { "version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128}, @@ -449,6 +449,7 @@ "provider_identity": {"type": "string", "minLength": 1, "maxLength": 256}, "policy_version_id": {"type": "string", "minLength": 1, "maxLength": 128}, "application_id": {"type": "string", "minLength": 1, "maxLength": 128}, + "client_id": {"type": "string", "minLength": 1, "maxLength": 128}, "management_host": {"type": "string", "minLength": 1, "maxLength": 256}, "management_port": {"type": "integer", "minimum": 1, "maximum": 65535}, "stream_host": {"type": "string", "minLength": 1, "maxLength": 256}, diff --git a/tests/go/protocol_test.go b/tests/go/protocol_test.go index cf402aa..43da19e 100644 --- a/tests/go/protocol_test.go +++ b/tests/go/protocol_test.go @@ -114,7 +114,7 @@ func TestSessionAuthorityRejectsProviderRoute(t *testing.T) { } func TestProviderSessionWorkIsStrictAndSessionBound(t *testing.T) { - valid := `{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":0,"expires_at":"2099-01-01T00:00:00Z","provider_profile":"apollo","provider_identity":"provider-1","policy_version_id":"policy-1","application_id":"42","management_host":"apollo.test","management_port":47990,"stream_host":"apollo.test","stream_port":47984,"client_certificate_pem":"certificate","client_private_key_pem":"private-key","server_certificate_pem":"server-certificate"}` + valid := `{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":0,"expires_at":"2099-01-01T00:00:00Z","provider_profile":"apollo","provider_identity":"provider-1","policy_version_id":"policy-1","application_id":"42","client_id":"paired-client-1","management_host":"apollo.test","management_port":47990,"stream_host":"apollo.test","stream_port":47984,"client_certificate_pem":"certificate","client_private_key_pem":"private-key","server_certificate_pem":"server-certificate"}` if _, err := protocol.DecodeProviderSessionWork([]byte(valid)); err != nil { t.Fatalf("valid provider work rejected: %v", err) }