feat(protocol): define native session credentials
Verify Protocol / module (push) Successful in 1m13s
Verify Protocol / verify (push) Successful in 35s

This commit is contained in:
sechmachine
2026-08-11 18:38:19 +07:00
parent 79d9e49497
commit afcd5d99db
32 changed files with 1565 additions and 149 deletions
+474 -19
View File
@@ -13,11 +13,11 @@ import (
"time"
)
const SchemaSHA256 = "b2bb0a8ac8ef56dbc0e1443eeb5b3028be9e71ec2f5fd8e73928d71b7cd9340c"
const SchemaSHA256 = "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f"
const ProtocolVersion = "1.0.0"
const CurrentWireVersion = "1"
const NMinus1WireVersion = "0"
const NMinus2WireVersion = "-1"
const CurrentWireVersion = "2"
const NMinus1WireVersion = "1"
const NMinus2WireVersion = "0"
type FieldViolation struct {
Field string `json:"field"`
@@ -69,6 +69,13 @@ type BrokerSession struct {
EffectiveDisplayMode *DisplayMode `json:"effective_display_mode,omitempty"`
}
type BrowserAuthenticatedSession struct {
Username string `json:"username"`
Provider string `json:"provider"`
Roles []string `json:"roles"`
Role string `json:"role"`
}
type CapabilityProfile struct {
Transport string `json:"transport"`
Framing string `json:"framing"`
@@ -281,6 +288,14 @@ type ManifestTunnel struct {
Features []string `json:"features"`
}
type NativeAuthenticatedSession struct {
Username string `json:"username"`
Provider string `json:"provider"`
Roles []string `json:"roles"`
Role string `json:"role"`
NativeIdentity NativeSessionIdentity `json:"native_identity"`
}
type NativeCredential struct {
DeviceID string `json:"device_id,omitempty"`
FamilyID string `json:"family_id"`
@@ -290,6 +305,19 @@ type NativeCredential struct {
RefreshExpiresAt string `json:"refresh_expires_at,omitempty"`
}
type NativeSessionIdentity struct {
ClientDeviceID string `json:"client_device_id"`
DeviceKeyID string `json:"device_key_id"`
}
type NativeTunnelCredential struct {
ClientDeviceID string `json:"client_device_id"`
DeviceKeyID string `json:"device_key_id"`
CertificateChainPem string `json:"certificate_chain_pem"`
TrustBundlePem string `json:"trust_bundle_pem"`
ExpiresAt string `json:"expires_at"`
}
type PageInfo struct {
Limit int64 `json:"limit"`
NextCursor string `json:"next_cursor"`
@@ -392,12 +420,11 @@ type SessionAuthority struct {
}
type SessionRequest struct {
ClientDeviceID string `json:"client_device_id"`
DeviceKeyID string `json:"device_key_id"`
PoolID string `json:"pool_id"`
IdempotencyKey string `json:"idempotency_key"`
PolicySnapshot AllocationPolicy `json:"policy_snapshot"`
RequestedDisplayMode *DisplayMode `json:"requested_display_mode,omitempty"`
ClientDeviceID string `json:"client_device_id"`
DeviceKeyID string `json:"device_key_id"`
PoolID string `json:"pool_id"`
IdempotencyKey string `json:"idempotency_key"`
RequestedDisplayMode *DisplayMode `json:"requested_display_mode,omitempty"`
}
type StableError struct {
@@ -863,6 +890,105 @@ func EncodeBrokerSession(value BrokerSession) ([]byte, error) {
return json.Marshal(value)
}
func (v BrowserAuthenticatedSession) Validate() error {
var violations []FieldViolation
if v.Username == "" {
violations = append(violations, FieldViolation{Field: "username", Code: "required"})
}
if len(v.Username) < 1 && v.Username != "" {
violations = append(violations, FieldViolation{Field: "username", Code: "min_length"})
}
if len(v.Username) > 256 {
violations = append(violations, FieldViolation{Field: "username", Code: "max_length"})
}
if v.Provider == "" {
violations = append(violations, FieldViolation{Field: "provider", Code: "required"})
}
if len(v.Provider) < 1 && v.Provider != "" {
violations = append(violations, FieldViolation{Field: "provider", Code: "min_length"})
}
if len(v.Provider) > 64 {
violations = append(violations, FieldViolation{Field: "provider", Code: "max_length"})
}
if v.Roles == nil {
violations = append(violations, FieldViolation{Field: "roles", Code: "required"})
}
if len(v.Roles) > 16 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_items"})
}
for _, item := range v.Roles {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "roles", Code: "min_item_length"})
}
}
for _, item := range v.Roles {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_length"})
}
}
for _, item := range v.Roles {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_bytes"})
}
}
if v.Role == "" {
violations = append(violations, FieldViolation{Field: "role", Code: "required"})
}
if v.Role != "" && !(v.Role == "user" || v.Role == "admin") {
violations = append(violations, FieldViolation{Field: "role", Code: "invalid_value"})
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
return nil
}
func DecodeBrowserAuthenticatedSession(data []byte) (BrowserAuthenticatedSession, error) {
var value BrowserAuthenticatedSession
if len(data) > 1024*1024 {
return value, errors.New("protocol payload exceeds limit")
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil {
return value, err
}
if raw, ok := fields["provider"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "provider", Code: "required"}}}
}
if raw, ok := fields["role"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "role", Code: "required"}}}
}
if raw, ok := fields["roles"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "roles", Code: "required"}}}
}
if raw, ok := fields["username"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "username", Code: "required"}}}
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil {
return value, err
}
var trailing any
if err := decoder.Decode(&trailing); err != io.EOF {
if err == nil {
return value, errors.New("trailing JSON value")
}
return value, err
}
if err := value.Validate(); err != nil {
return value, err
}
return value, nil
}
func EncodeBrowserAuthenticatedSession(value BrowserAuthenticatedSession) ([]byte, error) {
if err := value.Validate(); err != nil {
return nil, err
}
return json.Marshal(value)
}
func (v CapabilityProfile) Validate() error {
var violations []FieldViolation
if v.Transport == "" {
@@ -2689,6 +2815,16 @@ func (v GatewayRegistration) Validate() error {
if len(v.Features) > 64 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_items"})
}
for _, item := range v.Features {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "features", Code: "min_item_length"})
}
}
for _, item := range v.Features {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_item_length"})
}
}
if reflect.DeepEqual(v.Capabilities, CapabilityProfile{}) {
violations = append(violations, FieldViolation{Field: "capabilities", Code: "required"})
}
@@ -3231,6 +3367,16 @@ func (v ManifestGateway) Validate() error {
if len(v.Addresses) > 4 {
violations = append(violations, FieldViolation{Field: "addresses", Code: "max_items"})
}
for _, item := range v.Addresses {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "addresses", Code: "min_item_length"})
}
}
for _, item := range v.Addresses {
if len(item) > 256 {
violations = append(violations, FieldViolation{Field: "addresses", Code: "max_item_length"})
}
}
if v.PublicIdentity == "" {
violations = append(violations, FieldViolation{Field: "public_identity", Code: "required"})
}
@@ -3371,12 +3517,32 @@ func (v ManifestTunnel) Validate() error {
if len(v.Versions) > 4 {
violations = append(violations, FieldViolation{Field: "versions", Code: "max_items"})
}
for _, item := range v.Versions {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "versions", Code: "min_item_length"})
}
}
for _, item := range v.Versions {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "versions", Code: "max_item_length"})
}
}
if v.Features == nil {
violations = append(violations, FieldViolation{Field: "features", Code: "required"})
}
if len(v.Features) > 32 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_items"})
}
for _, item := range v.Features {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "features", Code: "min_item_length"})
}
}
for _, item := range v.Features {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_item_length"})
}
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
@@ -3423,6 +3589,114 @@ func EncodeManifestTunnel(value ManifestTunnel) ([]byte, error) {
return json.Marshal(value)
}
func (v NativeAuthenticatedSession) Validate() error {
var violations []FieldViolation
if v.Username == "" {
violations = append(violations, FieldViolation{Field: "username", Code: "required"})
}
if len(v.Username) < 1 && v.Username != "" {
violations = append(violations, FieldViolation{Field: "username", Code: "min_length"})
}
if len(v.Username) > 256 {
violations = append(violations, FieldViolation{Field: "username", Code: "max_length"})
}
if v.Provider == "" {
violations = append(violations, FieldViolation{Field: "provider", Code: "required"})
}
if len(v.Provider) < 1 && v.Provider != "" {
violations = append(violations, FieldViolation{Field: "provider", Code: "min_length"})
}
if len(v.Provider) > 64 {
violations = append(violations, FieldViolation{Field: "provider", Code: "max_length"})
}
if v.Roles == nil {
violations = append(violations, FieldViolation{Field: "roles", Code: "required"})
}
if len(v.Roles) > 16 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_items"})
}
for _, item := range v.Roles {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "roles", Code: "min_item_length"})
}
}
for _, item := range v.Roles {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_length"})
}
}
for _, item := range v.Roles {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_bytes"})
}
}
if v.Role == "" {
violations = append(violations, FieldViolation{Field: "role", Code: "required"})
}
if v.Role != "" && !(v.Role == "user" || v.Role == "admin") {
violations = append(violations, FieldViolation{Field: "role", Code: "invalid_value"})
}
if reflect.DeepEqual(v.NativeIdentity, NativeSessionIdentity{}) {
violations = append(violations, FieldViolation{Field: "native_identity", Code: "required"})
}
if err := v.NativeIdentity.Validate(); err != nil {
violations = append(violations, FieldViolation{Field: "native_identity", Code: "invalid_object"})
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
return nil
}
func DecodeNativeAuthenticatedSession(data []byte) (NativeAuthenticatedSession, error) {
var value NativeAuthenticatedSession
if len(data) > 1024*1024 {
return value, errors.New("protocol payload exceeds limit")
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil {
return value, err
}
if raw, ok := fields["native_identity"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "native_identity", Code: "required"}}}
}
if raw, ok := fields["provider"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "provider", Code: "required"}}}
}
if raw, ok := fields["role"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "role", Code: "required"}}}
}
if raw, ok := fields["roles"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "roles", Code: "required"}}}
}
if raw, ok := fields["username"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "username", Code: "required"}}}
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil {
return value, err
}
var trailing any
if err := decoder.Decode(&trailing); err != io.EOF {
if err == nil {
return value, errors.New("trailing JSON value")
}
return value, err
}
if err := value.Validate(); err != nil {
return value, err
}
return value, nil
}
func EncodeNativeAuthenticatedSession(value NativeAuthenticatedSession) ([]byte, error) {
if err := value.Validate(); err != nil {
return nil, err
}
return json.Marshal(value)
}
func (v NativeCredential) Validate() error {
var violations []FieldViolation
if len(v.DeviceID) > 128 {
@@ -3526,6 +3800,176 @@ func EncodeNativeCredential(value NativeCredential) ([]byte, error) {
return json.Marshal(value)
}
func (v NativeSessionIdentity) Validate() error {
var violations []FieldViolation
if v.ClientDeviceID == "" {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "required"})
}
if len(v.ClientDeviceID) < 1 && v.ClientDeviceID != "" {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "min_length"})
}
if len(v.ClientDeviceID) > 128 {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "max_length"})
}
if v.DeviceKeyID == "" {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "required"})
}
if len(v.DeviceKeyID) < 1 && v.DeviceKeyID != "" {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "min_length"})
}
if len(v.DeviceKeyID) > 128 {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "max_length"})
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
return nil
}
func DecodeNativeSessionIdentity(data []byte) (NativeSessionIdentity, error) {
var value NativeSessionIdentity
if len(data) > 1024*1024 {
return value, errors.New("protocol payload exceeds limit")
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil {
return value, err
}
if raw, ok := fields["client_device_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "client_device_id", Code: "required"}}}
}
if raw, ok := fields["device_key_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "device_key_id", Code: "required"}}}
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil {
return value, err
}
var trailing any
if err := decoder.Decode(&trailing); err != io.EOF {
if err == nil {
return value, errors.New("trailing JSON value")
}
return value, err
}
if err := value.Validate(); err != nil {
return value, err
}
return value, nil
}
func EncodeNativeSessionIdentity(value NativeSessionIdentity) ([]byte, error) {
if err := value.Validate(); err != nil {
return nil, err
}
return json.Marshal(value)
}
func (v NativeTunnelCredential) Validate() error {
var violations []FieldViolation
if v.ClientDeviceID == "" {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "required"})
}
if len(v.ClientDeviceID) < 1 && v.ClientDeviceID != "" {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "min_length"})
}
if len(v.ClientDeviceID) > 128 {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "max_length"})
}
if v.DeviceKeyID == "" {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "required"})
}
if len(v.DeviceKeyID) < 1 && v.DeviceKeyID != "" {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "min_length"})
}
if len(v.DeviceKeyID) > 128 {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "max_length"})
}
if v.CertificateChainPem == "" {
violations = append(violations, FieldViolation{Field: "certificate_chain_pem", Code: "required"})
}
if len(v.CertificateChainPem) < 1 && v.CertificateChainPem != "" {
violations = append(violations, FieldViolation{Field: "certificate_chain_pem", Code: "min_length"})
}
if len(v.CertificateChainPem) > 65536 {
violations = append(violations, FieldViolation{Field: "certificate_chain_pem", Code: "max_length"})
}
if v.TrustBundlePem == "" {
violations = append(violations, FieldViolation{Field: "trust_bundle_pem", Code: "required"})
}
if len(v.TrustBundlePem) < 1 && v.TrustBundlePem != "" {
violations = append(violations, FieldViolation{Field: "trust_bundle_pem", Code: "min_length"})
}
if len(v.TrustBundlePem) > 65536 {
violations = append(violations, FieldViolation{Field: "trust_bundle_pem", Code: "max_length"})
}
if v.ExpiresAt == "" {
violations = append(violations, FieldViolation{Field: "expires_at", Code: "required"})
}
if len(v.ExpiresAt) > 64 {
violations = append(violations, FieldViolation{Field: "expires_at", Code: "max_length"})
}
if v.ExpiresAt != "" {
if parsed, err := time.Parse(time.RFC3339Nano, v.ExpiresAt); err != nil || parsed.UTC().Format(time.RFC3339Nano) != v.ExpiresAt {
violations = append(violations, FieldViolation{Field: "expires_at", Code: "invalid_time"})
}
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
return nil
}
func DecodeNativeTunnelCredential(data []byte) (NativeTunnelCredential, error) {
var value NativeTunnelCredential
if len(data) > 1024*1024 {
return value, errors.New("protocol payload exceeds limit")
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil {
return value, err
}
if raw, ok := fields["certificate_chain_pem"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "certificate_chain_pem", Code: "required"}}}
}
if raw, ok := fields["client_device_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "client_device_id", Code: "required"}}}
}
if raw, ok := fields["device_key_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "device_key_id", Code: "required"}}}
}
if raw, ok := fields["expires_at"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "expires_at", Code: "required"}}}
}
if raw, ok := fields["trust_bundle_pem"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "trust_bundle_pem", Code: "required"}}}
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil {
return value, err
}
var trailing any
if err := decoder.Decode(&trailing); err != io.EOF {
if err == nil {
return value, errors.New("trailing JSON value")
}
return value, err
}
if err := value.Validate(); err != nil {
return value, err
}
return value, nil
}
func EncodeNativeTunnelCredential(value NativeTunnelCredential) ([]byte, error) {
if err := value.Validate(); err != nil {
return nil, err
}
return json.Marshal(value)
}
func (v PageInfo) Validate() error {
var violations []FieldViolation
if v.Limit == 0 {
@@ -3875,6 +4319,16 @@ func (v ProviderState) Validate() error {
if len(v.Channels) > 8 {
violations = append(violations, FieldViolation{Field: "channels", Code: "max_items"})
}
for _, item := range v.Channels {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "channels", Code: "min_item_length"})
}
}
for _, item := range v.Channels {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "channels", Code: "max_item_length"})
}
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
@@ -4761,12 +5215,6 @@ func (v SessionRequest) Validate() error {
if len(v.IdempotencyKey) > 256 {
violations = append(violations, FieldViolation{Field: "idempotency_key", Code: "max_length"})
}
if reflect.DeepEqual(v.PolicySnapshot, AllocationPolicy{}) {
violations = append(violations, FieldViolation{Field: "policy_snapshot", Code: "required"})
}
if err := v.PolicySnapshot.Validate(); err != nil {
violations = append(violations, FieldViolation{Field: "policy_snapshot", Code: "invalid_object"})
}
if v.RequestedDisplayMode != nil {
if err := v.RequestedDisplayMode.Validate(); err != nil {
violations = append(violations, FieldViolation{Field: "requested_display_mode", Code: "invalid_object"})
@@ -4796,9 +5244,6 @@ func DecodeSessionRequest(data []byte) (SessionRequest, error) {
if raw, ok := fields["idempotency_key"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "idempotency_key", Code: "required"}}}
}
if raw, ok := fields["policy_snapshot"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "policy_snapshot", Code: "required"}}}
}
if raw, ok := fields["pool_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "pool_id", Code: "required"}}}
}
@@ -5057,12 +5502,22 @@ func (v VersionNegotiation) Validate() error {
if len(v.SupportedVersions) > 3 {
violations = append(violations, FieldViolation{Field: "supported_versions", Code: "max_items"})
}
for _, item := range v.SupportedVersions {
if len(item) > 16 {
violations = append(violations, FieldViolation{Field: "supported_versions", Code: "max_item_length"})
}
}
if v.Features == nil {
violations = append(violations, FieldViolation{Field: "features", Code: "required"})
}
if len(v.Features) > 64 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_items"})
}
for _, item := range v.Features {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_item_length"})
}
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}