[management, reverse proxy] Add reverse proxy feature (#5291)

* implement reverse proxy


---------

Co-authored-by: Alisdair MacLeod <git@alisdairmacleod.co.uk>
Co-authored-by: mlsmaycon <mlsmaycon@gmail.com>
Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
Co-authored-by: Viktor Liu <viktor@netbird.io>
Co-authored-by: Diego Noguês <diego.sure@gmail.com>
Co-authored-by: Diego Noguês <49420+diegocn@users.noreply.github.com>
Co-authored-by: Bethuel Mmbaga <bethuelmbaga12@gmail.com>
Co-authored-by: Zoltan Papp <zoltan.pmail@gmail.com>
Co-authored-by: Ashley Mensah <ashleyamo982@gmail.com>
This commit is contained in:
Pascal Fischer
2026-02-13 19:37:43 +01:00
committed by GitHub
co-authored by Alisdair MacLeod mlsmaycon Eduard Gert Viktor Liu Diego Noguês Diego Noguês Bethuel Mmbaga Zoltan Papp Ashley Mensah
parent edce11b34d
commit f53155562f
225 changed files with 35513 additions and 235 deletions
+136
View File
@@ -0,0 +1,136 @@
package argon2id
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strings"
"golang.org/x/crypto/argon2"
)
const (
argon2Memory = 19456
argon2Iterations = 2
argon2Parallelism = 1
argon2SaltLength = 16
argon2KeyLength = 32
)
var (
// ErrInvalidHash is returned when the hash string format is invalid
ErrInvalidHash = errors.New("invalid hash format")
// ErrIncompatibleVersion is returned when the Argon2 version is not supported
ErrIncompatibleVersion = errors.New("incompatible argon2 version")
// ErrMismatchedHashAndPassword is returned when password verification fails
ErrMismatchedHashAndPassword = errors.New("password does not match hash")
)
func Hash(secret string) (string, error) {
salt := make([]byte, argon2SaltLength)
if _, err := rand.Read(salt); err != nil {
return "", fmt.Errorf("failed to generate salt: %w", err)
}
hash := argon2.IDKey(
[]byte(secret),
salt,
argon2Iterations,
argon2Memory,
argon2Parallelism,
argon2KeyLength,
)
encodedSalt := base64.RawStdEncoding.EncodeToString(salt)
encodedHash := base64.RawStdEncoding.EncodeToString(hash)
return fmt.Sprintf(
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
argon2.Version,
argon2Memory,
argon2Iterations,
argon2Parallelism,
encodedSalt,
encodedHash,
), nil
}
func Verify(secret, encodedHash string) error {
params, salt, hash, err := decodeHash(encodedHash)
if err != nil {
return err
}
computedHash := argon2.IDKey(
[]byte(secret),
salt,
params.iterations,
params.memory,
params.parallelism,
params.keyLength,
)
if subtle.ConstantTimeCompare(hash, computedHash) == 1 {
return nil
}
return ErrMismatchedHashAndPassword
}
type hashParams struct {
memory uint32
iterations uint32
parallelism uint8
keyLength uint32
version int
}
func decodeHash(encodedHash string) (*hashParams, []byte, []byte, error) {
parts := strings.Split(encodedHash, "$")
if len(parts) != 6 {
return nil, nil, nil, ErrInvalidHash
}
if parts[1] != "argon2id" {
return nil, nil, nil, ErrInvalidHash
}
var version int
if _, err := fmt.Sscanf(parts[2], "v=%d", &version); err != nil {
return nil, nil, nil, fmt.Errorf("%w: invalid version: %v", ErrInvalidHash, err)
}
if version != argon2.Version {
return nil, nil, nil, ErrIncompatibleVersion
}
var memory, iterations uint32
var parallelism uint8
if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, &parallelism); err != nil {
return nil, nil, nil, fmt.Errorf("%w: invalid parameters: %v", ErrInvalidHash, err)
}
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
if err != nil {
return nil, nil, nil, fmt.Errorf("%w: invalid salt encoding: %v", ErrInvalidHash, err)
}
hash, err := base64.RawStdEncoding.DecodeString(parts[5])
if err != nil {
return nil, nil, nil, fmt.Errorf("%w: invalid hash encoding: %v", ErrInvalidHash, err)
}
params := &hashParams{
memory: memory,
iterations: iterations,
parallelism: parallelism,
keyLength: uint32(len(hash)),
version: version,
}
return params, salt, hash, nil
}
+327
View File
@@ -0,0 +1,327 @@
package argon2id
import (
"errors"
"strings"
"testing"
"golang.org/x/crypto/argon2"
)
func TestHash(t *testing.T) {
tests := []struct {
name string
secret string
}{
{
name: "simple password",
secret: "password123",
},
{
name: "complex password with special chars",
secret: "P@ssw0rd!#$%^&*()",
},
{
name: "long password",
secret: strings.Repeat("a", 100),
},
{
name: "empty password",
secret: "",
},
{
name: "unicode password",
secret: "пароль密码🔐",
},
{
name: "numeric PIN",
secret: "123456",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hash, err := Hash(tt.secret)
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
// Verify hash format
if !strings.HasPrefix(hash, "$argon2id$") {
t.Errorf("Hash() = %v, want hash starting with $argon2id$", hash)
}
// Verify hash has correct number of components
parts := strings.Split(hash, "$")
if len(parts) != 6 {
t.Errorf("Hash() has %d parts, want 6", len(parts))
}
// Verify version is present
if !strings.HasPrefix(hash, "$argon2id$v=") {
t.Errorf("Hash() missing version, got %v", hash)
}
// Verify each hash is unique (different salt)
hash2, err := Hash(tt.secret)
if err != nil {
t.Fatalf("Hash() second call error = %v", err)
}
if hash == hash2 {
t.Error("Hash() produces identical hashes for same input (salt not random)")
}
})
}
}
func TestVerify(t *testing.T) {
tests := []struct {
name string
secret string
wantError error
}{
{
name: "valid password",
secret: "correctPassword",
wantError: nil,
},
{
name: "valid PIN",
secret: "1234",
wantError: nil,
},
{
name: "empty secret",
secret: "",
wantError: nil,
},
{
name: "unicode secret",
secret: "密码🔐",
wantError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Generate hash
hash, err := Hash(tt.secret)
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
// Verify correct secret
err = Verify(tt.secret, hash)
if !errors.Is(err, tt.wantError) {
t.Errorf("Verify() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestVerifyIncorrectPassword(t *testing.T) {
secret := "correctPassword"
wrongSecret := "wrongPassword"
hash, err := Hash(secret)
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
err = Verify(wrongSecret, hash)
if !errors.Is(err, ErrMismatchedHashAndPassword) {
t.Errorf("Verify() error = %v, want %v", err, ErrMismatchedHashAndPassword)
}
}
func TestVerifyInvalidHashFormat(t *testing.T) {
tests := []struct {
name string
invalidHash string
expectedError error
}{
{
name: "empty hash",
invalidHash: "",
expectedError: ErrInvalidHash,
},
{
name: "wrong algorithm",
invalidHash: "$bcrypt$v=19$m=19456,t=2,p=1$c2FsdA$aGFzaA",
expectedError: ErrInvalidHash,
},
{
name: "missing parts",
invalidHash: "$argon2id$v=19$m=19456",
expectedError: ErrInvalidHash,
},
{
name: "too many parts",
invalidHash: "$argon2id$v=19$m=19456,t=2,p=1$salt$hash$extra",
expectedError: ErrInvalidHash,
},
{
name: "invalid version format",
invalidHash: "$argon2id$vXX$m=19456,t=2,p=1$c2FsdA$aGFzaA",
expectedError: ErrInvalidHash,
},
{
name: "invalid parameters format",
invalidHash: "$argon2id$v=19$mXX,tYY,pZZ$c2FsdA$aGFzaA",
expectedError: ErrInvalidHash,
},
{
name: "invalid salt base64",
invalidHash: "$argon2id$v=19$m=19456,t=2,p=1$not-valid-base64!@#$aGFzaA",
expectedError: ErrInvalidHash,
},
{
name: "invalid hash base64",
invalidHash: "$argon2id$v=19$m=19456,t=2,p=1$c2FsdA$not-valid-base64!@#",
expectedError: ErrInvalidHash,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Verify("password", tt.invalidHash)
if err == nil {
t.Errorf("Verify() expected error, got nil")
return
}
if !errors.Is(err, tt.expectedError) && !strings.Contains(err.Error(), tt.expectedError.Error()) {
t.Errorf("Verify() error = %v, want error containing %v", err, tt.expectedError)
}
})
}
}
func TestVerifyIncompatibleVersion(t *testing.T) {
// Manually craft a hash with wrong version
invalidVersionHash := "$argon2id$v=18$m=19456,t=2,p=1$c2FsdDEyMzQ1Njc4OTA$aGFzaDEyMzQ1Njc4OTBhYmNkZWZnaGlqa2xtbm9w"
err := Verify("password", invalidVersionHash)
if !errors.Is(err, ErrIncompatibleVersion) {
t.Errorf("Verify() error = %v, want %v", err, ErrIncompatibleVersion)
}
}
func TestHashDeterminism(t *testing.T) {
// Ensure different hashes for same password (random salt)
password := "testPassword"
hashes := make(map[string]bool)
for i := 0; i < 10; i++ {
hash, err := Hash(password)
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
if hashes[hash] {
t.Error("Hash() produced duplicate hash (salt generation may be broken)")
}
hashes[hash] = true
}
if len(hashes) != 10 {
t.Errorf("Expected 10 unique hashes, got %d", len(hashes))
}
}
func TestOWASPCompliance(t *testing.T) {
// Test that generated hashes use OWASP-recommended parameters
secret := "testPassword"
hash, err := Hash(secret)
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
params, _, _, err := decodeHash(hash)
if err != nil {
t.Fatalf("decodeHash() error = %v", err)
}
// Verify OWASP minimum baseline parameters
if params.memory != 19456 {
t.Errorf("memory = %d, want 19456 (OWASP baseline)", params.memory)
}
if params.iterations != 2 {
t.Errorf("iterations = %d, want 2 (OWASP baseline)", params.iterations)
}
if params.parallelism != 1 {
t.Errorf("parallelism = %d, want 1 (OWASP baseline)", params.parallelism)
}
if params.keyLength != 32 {
t.Errorf("keyLength = %d, want 32", params.keyLength)
}
if params.version != argon2.Version {
t.Errorf("version = %d, want %d", params.version, argon2.Version)
}
}
func TestConstantTimeComparison(t *testing.T) {
// This test verifies that Verify() is using constant-time comparison
// by ensuring it doesn't fail differently for similar vs different hashes
secret := "password123"
wrongSecret := "password124" // One character different
hash, err := Hash(secret)
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
// Both wrong passwords should return the same error
err1 := Verify(wrongSecret, hash)
err2 := Verify("completelydifferent", hash)
if !errors.Is(err1, ErrMismatchedHashAndPassword) {
t.Errorf("Verify() error = %v, want %v", err1, ErrMismatchedHashAndPassword)
}
if !errors.Is(err2, ErrMismatchedHashAndPassword) {
t.Errorf("Verify() error = %v, want %v", err2, ErrMismatchedHashAndPassword)
}
// Errors should be identical (same error type and message)
if err1.Error() != err2.Error() {
t.Error("Verify() returns different errors for different wrong passwords (potential timing attack)")
}
}
func TestCaseSensitivity(t *testing.T) {
// Passwords should be case-sensitive
secret := "Password123"
wrongSecret := "password123"
hash, err := Hash(secret)
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
// Correct password should verify
if err := Verify(secret, hash); err != nil {
t.Errorf("Verify() with correct password error = %v, want nil", err)
}
// Wrong case should not verify
if err := Verify(wrongSecret, hash); !errors.Is(err, ErrMismatchedHashAndPassword) {
t.Errorf("Verify() with wrong case error = %v, want %v", err, ErrMismatchedHashAndPassword)
}
}
// Benchmark tests
func BenchmarkHash(b *testing.B) {
secret := "benchmarkPassword123"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = Hash(secret)
}
}
func BenchmarkVerify(b *testing.B) {
secret := "benchmarkPassword123"
hash, _ := Hash(secret)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Verify(secret, hash)
}
}
+776 -1
View File
@@ -36,6 +36,8 @@ tags:
x-cloud-only: true
- name: Identity Providers
description: Interact with and view information about identity providers.
- name: Services
description: Interact with and view information about reverse proxy services.
- name: Instance
description: Instance setup and status endpoints for initial configuration.
- name: Jobs
@@ -2244,7 +2246,53 @@ components:
activity_code:
description: The string code of the activity that occurred during the event
type: string
enum: [ "peer.user.add", "peer.setupkey.add", "user.join", "user.invite", "account.create", "account.delete", "user.peer.delete", "rule.add", "rule.update", "rule.delete", "policy.add", "policy.update", "policy.delete", "setupkey.add", "setupkey.update", "setupkey.revoke", "setupkey.overuse", "setupkey.delete", "group.add", "group.update", "group.delete", "peer.group.add", "peer.group.delete", "user.group.add", "user.group.delete", "user.role.update", "setupkey.group.add", "setupkey.group.delete", "dns.setting.disabled.management.group.add", "dns.setting.disabled.management.group.delete", "route.add", "route.delete", "route.update", "peer.ssh.enable", "peer.ssh.disable", "peer.rename", "peer.login.expiration.enable", "peer.login.expiration.disable", "nameserver.group.add", "nameserver.group.delete", "nameserver.group.update", "account.setting.peer.login.expiration.update", "account.setting.peer.login.expiration.enable", "account.setting.peer.login.expiration.disable", "personal.access.token.create", "personal.access.token.delete", "service.user.create", "service.user.delete", "user.block", "user.unblock", "user.delete", "user.peer.login", "peer.login.expire", "dashboard.login", "integration.create", "integration.update", "integration.delete", "account.setting.peer.approval.enable", "account.setting.peer.approval.disable", "peer.approve", "peer.approval.revoke", "transferred.owner.role", "posture.check.create", "posture.check.update", "posture.check.delete", "peer.inactivity.expiration.enable", "peer.inactivity.expiration.disable", "account.peer.inactivity.expiration.enable", "account.peer.inactivity.expiration.disable", "account.peer.inactivity.expiration.update", "account.setting.group.propagation.enable", "account.setting.group.propagation.disable", "account.setting.routing.peer.dns.resolution.enable", "account.setting.routing.peer.dns.resolution.disable", "network.create", "network.update", "network.delete", "network.resource.create", "network.resource.update", "network.resource.delete", "network.router.create", "network.router.update", "network.router.delete", "resource.group.add", "resource.group.delete", "account.dns.domain.update", "account.setting.lazy.connection.enable", "account.setting.lazy.connection.disable", "account.network.range.update", "peer.ip.update", "user.approve", "user.reject", "user.create", "account.settings.auto.version.update", "identityprovider.create", "identityprovider.update", "identityprovider.delete", "dns.zone.create", "dns.zone.update", "dns.zone.delete", "dns.zone.record.create", "dns.zone.record.update", "dns.zone.record.delete", "peer.job.create", "user.password.change", "user.invite.link.create", "user.invite.link.accept", "user.invite.link.regenerate", "user.invite.link.delete" ]
enum: [
"peer.user.add", "peer.setupkey.add", "user.join", "user.invite", "account.create", "account.delete",
"user.peer.delete", "rule.add", "rule.update", "rule.delete",
"policy.add", "policy.update", "policy.delete",
"setupkey.add", "setupkey.update", "setupkey.revoke", "setupkey.overuse", "setupkey.delete",
"group.add", "group.update", "group.delete",
"peer.group.add", "peer.group.delete",
"user.group.add", "user.group.delete", "user.role.update",
"setupkey.group.add", "setupkey.group.delete",
"dns.setting.disabled.management.group.add", "dns.setting.disabled.management.group.delete",
"route.add", "route.delete", "route.update",
"peer.ssh.enable", "peer.ssh.disable", "peer.rename",
"peer.login.expiration.enable", "peer.login.expiration.disable",
"nameserver.group.add", "nameserver.group.delete", "nameserver.group.update",
"account.setting.peer.login.expiration.update", "account.setting.peer.login.expiration.enable", "account.setting.peer.login.expiration.disable",
"personal.access.token.create", "personal.access.token.delete",
"service.user.create", "service.user.delete",
"user.block", "user.unblock", "user.delete",
"user.peer.login", "peer.login.expire",
"dashboard.login",
"integration.create", "integration.update", "integration.delete",
"account.setting.peer.approval.enable", "account.setting.peer.approval.disable",
"peer.approve", "peer.approval.revoke",
"transferred.owner.role",
"posture.check.create", "posture.check.update", "posture.check.delete",
"peer.inactivity.expiration.enable", "peer.inactivity.expiration.disable",
"account.peer.inactivity.expiration.enable", "account.peer.inactivity.expiration.disable", "account.peer.inactivity.expiration.update",
"account.setting.group.propagation.enable", "account.setting.group.propagation.disable",
"account.setting.routing.peer.dns.resolution.enable", "account.setting.routing.peer.dns.resolution.disable",
"network.create", "network.update", "network.delete",
"network.resource.create", "network.resource.update", "network.resource.delete",
"network.router.create", "network.router.update", "network.router.delete",
"resource.group.add", "resource.group.delete",
"account.dns.domain.update",
"account.setting.lazy.connection.enable", "account.setting.lazy.connection.disable",
"account.network.range.update",
"peer.ip.update",
"user.approve", "user.reject", "user.create",
"account.settings.auto.version.update",
"identityprovider.create", "identityprovider.update", "identityprovider.delete",
"dns.zone.create", "dns.zone.update", "dns.zone.delete",
"dns.zone.record.create", "dns.zone.record.update", "dns.zone.record.delete",
"peer.job.create",
"user.password.change",
"user.invite.link.create", "user.invite.link.accept", "user.invite.link.regenerate", "user.invite.link.delete",
"service.create", "service.update", "service.delete"
]
example: route.add
initiator_id:
description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
@@ -2702,6 +2750,105 @@ components:
- page_size
- total_records
- total_pages
ProxyAccessLog:
type: object
properties:
id:
type: string
description: "Unique identifier for the access log entry"
example: "ch8i4ug6lnn4g9hqv7m0"
service_id:
type: string
description: "ID of the service that handled the request"
example: "ch8i4ug6lnn4g9hqv7m0"
timestamp:
type: string
format: date-time
description: "Timestamp when the request was made"
example: "2024-01-31T15:30:00Z"
method:
type: string
description: "HTTP method of the request"
example: "GET"
host:
type: string
description: "Host header of the request"
example: "example.com"
path:
type: string
description: "Path of the request"
example: "/api/users"
duration_ms:
type: integer
description: "Duration of the request in milliseconds"
example: 150
status_code:
type: integer
description: "HTTP status code returned"
example: 200
source_ip:
type: string
description: "Source IP address of the request"
example: "192.168.1.100"
reason:
type: string
description: "Reason for the request result (e.g., authentication failure)"
example: "Authentication failed"
user_id:
type: string
description: "ID of the authenticated user, if applicable"
example: "user-123"
auth_method_used:
type: string
description: "Authentication method used (e.g., password, pin, oidc)"
example: "oidc"
country_code:
type: string
description: "Country code from geolocation"
example: "US"
city_name:
type: string
description: "City name from geolocation"
example: "San Francisco"
required:
- id
- service_id
- timestamp
- method
- host
- path
- duration_ms
- status_code
ProxyAccessLogsResponse:
type: object
properties:
data:
type: array
description: List of proxy access log entries
items:
$ref: "#/components/schemas/ProxyAccessLog"
page:
type: integer
description: Current page number
example: 1
page_size:
type: integer
description: Number of items per page
example: 50
total_records:
type: integer
description: Total number of log records available
example: 523
total_pages:
type: integer
description: Total number of pages available
example: 11
required:
- data
- page
- page_size
- total_records
- total_pages
IdentityProviderType:
type: string
description: Type of identity provider
@@ -2767,6 +2914,251 @@ components:
- issuer
- client_id
- client_secret
Service:
type: object
properties:
id:
type: string
description: Service ID
name:
type: string
description: Service name
domain:
type: string
description: Domain for the service
proxy_cluster:
type: string
description: The proxy cluster handling this service (derived from domain)
example: "eu.proxy.netbird.io"
targets:
type: array
items:
$ref: '#/components/schemas/ServiceTarget'
description: List of target backends for this service
enabled:
type: boolean
description: Whether the service is enabled
pass_host_header:
type: boolean
description: When true, the original client Host header is passed through to the backend instead of being rewritten to the backend's address
rewrite_redirects:
type: boolean
description: When true, Location headers in backend responses are rewritten to replace the backend address with the public-facing domain
auth:
$ref: '#/components/schemas/ServiceAuthConfig'
meta:
$ref: '#/components/schemas/ServiceMeta'
required:
- id
- name
- domain
- targets
- enabled
- auth
- meta
ServiceMeta:
type: object
properties:
created_at:
type: string
format: date-time
description: Timestamp when the service was created
example: "2024-02-03T10:30:00Z"
certificate_issued_at:
type: string
format: date-time
description: Timestamp when the certificate was issued (empty if not yet issued)
example: "2024-02-03T10:35:00Z"
status:
type: string
enum:
- pending
- active
- tunnel_not_created
- certificate_pending
- certificate_failed
- error
description: Current status of the service
example: "active"
required:
- created_at
- status
ServiceRequest:
type: object
properties:
name:
type: string
description: Service name
domain:
type: string
description: Domain for the service
targets:
type: array
items:
$ref: '#/components/schemas/ServiceTarget'
description: List of target backends for this service
enabled:
type: boolean
description: Whether the service is enabled
default: true
pass_host_header:
type: boolean
description: When true, the original client Host header is passed through to the backend instead of being rewritten to the backend's address
rewrite_redirects:
type: boolean
description: When true, Location headers in backend responses are rewritten to replace the backend address with the public-facing domain
auth:
$ref: '#/components/schemas/ServiceAuthConfig'
required:
- name
- domain
- targets
- auth
- enabled
ServiceTarget:
type: object
properties:
target_id:
type: string
description: Target ID
target_type:
type: string
description: Target type (e.g., "peer", "resource")
enum: [peer, resource]
path:
type: string
description: URL path prefix for this target
protocol:
type: string
description: Protocol to use when connecting to the backend
enum: [http, https]
host:
type: string
description: Backend ip or domain for this target
port:
type: integer
description: Backend port for this target. Use 0 or omit to use the scheme default (80 for http, 443 for https).
enabled:
type: boolean
description: Whether this target is enabled
required:
- target_id
- target_type
- protocol
- port
- enabled
ServiceAuthConfig:
type: object
properties:
password_auth:
$ref: '#/components/schemas/PasswordAuthConfig'
pin_auth:
$ref: '#/components/schemas/PINAuthConfig'
bearer_auth:
$ref: '#/components/schemas/BearerAuthConfig'
link_auth:
$ref: '#/components/schemas/LinkAuthConfig'
PasswordAuthConfig:
type: object
properties:
enabled:
type: boolean
description: Whether password auth is enabled
password:
type: string
description: Auth password
required:
- enabled
- password
PINAuthConfig:
type: object
properties:
enabled:
type: boolean
description: Whether PIN auth is enabled
pin:
type: string
description: PIN value
required:
- enabled
- pin
BearerAuthConfig:
type: object
properties:
enabled:
type: boolean
description: Whether bearer auth is enabled
distribution_groups:
type: array
items:
type: string
description: List of group IDs that can use bearer auth
required:
- enabled
LinkAuthConfig:
type: object
properties:
enabled:
type: boolean
description: Whether link auth is enabled
required:
- enabled
ProxyCluster:
type: object
description: A proxy cluster represents a group of proxy nodes serving the same address
properties:
address:
type: string
description: Cluster address used for CNAME targets
example: "eu.proxy.netbird.io"
connected_proxies:
type: integer
description: Number of proxy nodes connected in this cluster
example: 3
required:
- address
- connected_proxies
ReverseProxyDomainType:
type: string
description: Type of Reverse Proxy Domain
enum:
- free
- custom
example: free
ReverseProxyDomain:
type: object
properties:
id:
type: string
description: Domain ID
domain:
type: string
description: Domain name
validated:
type: boolean
description: Whether the domain has been validated
type:
$ref: '#/components/schemas/ReverseProxyDomainType'
target_cluster:
type: string
description: The proxy cluster this domain is validated against (only for custom domains)
required:
- id
- domain
- validated
- type
ReverseProxyDomainRequest:
type: object
properties:
domain:
type: string
description: Domain name
target_cluster:
type: string
description: The proxy cluster this domain should be validated against
required:
- domain
- target_cluster
InstanceStatus:
type: object
description: Instance status information
@@ -6996,6 +7388,106 @@ paths:
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/events/proxy:
get:
summary: List all Reverse Proxy Access Logs
description: Returns a paginated list of all reverse proxy access log entries
tags: [ Events ]
parameters:
- in: query
name: page
schema:
type: integer
default: 1
minimum: 1
description: Page number for pagination (1-indexed)
- in: query
name: page_size
schema:
type: integer
default: 50
minimum: 1
maximum: 100
description: Number of items per page (max 100)
- in: query
name: search
schema:
type: string
description: General search across request ID, host, path, source IP, user email, and user name
- in: query
name: source_ip
schema:
type: string
description: Filter by source IP address
- in: query
name: host
schema:
type: string
description: Filter by host header
- in: query
name: path
schema:
type: string
description: Filter by request path (supports partial matching)
- in: query
name: user_id
schema:
type: string
description: Filter by authenticated user ID
- in: query
name: user_email
schema:
type: string
description: Filter by user email (partial matching)
- in: query
name: user_name
schema:
type: string
description: Filter by user name (partial matching)
- in: query
name: method
schema:
type: string
enum: [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS]
description: Filter by HTTP method
- in: query
name: status
schema:
type: string
enum: [success, failed]
description: Filter by status (success = 2xx/3xx, failed = 1xx/4xx/5xx)
- in: query
name: status_code
schema:
type: integer
minimum: 100
maximum: 599
description: Filter by HTTP status code
- in: query
name: start_date
schema:
type: string
format: date-time
description: Filter by timestamp >= start_date (RFC3339 format)
- in: query
name: end_date
schema:
type: string
format: date-time
description: Filter by timestamp <= end_date (RFC3339 format)
responses:
"200":
description: Paginated list of reverse proxy access logs
content:
application/json:
schema:
$ref: "#/components/schemas/ProxyAccessLogsResponse"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/posture-checks:
get:
summary: List all Posture Checks
@@ -9063,3 +9555,286 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/reverse-proxies/services:
get:
summary: List all Services
description: Returns a list of all reverse proxy services
tags: [ Services ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
responses:
'200':
description: A JSON Array of services
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Service'
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Service
description: Creates a new reverse proxy service
tags: [ Services ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
requestBody:
description: New service request
content:
application/json:
schema:
$ref: '#/components/schemas/ServiceRequest'
responses:
'200':
description: Service created
content:
application/json:
schema:
$ref: '#/components/schemas/Service'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/reverse-proxies/clusters:
get:
summary: List available proxy clusters
description: Returns a list of available proxy clusters with their connection status
tags: [ Services ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
responses:
'200':
description: A JSON Array of proxy clusters
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ProxyCluster'
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/reverse-proxies/services/{serviceId}:
get:
summary: Retrieve a Service
description: Get information about a specific reverse proxy service
tags: [ Services ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
parameters:
- in: path
name: serviceId
required: true
schema:
type: string
description: The unique identifier of a service
responses:
'200':
description: A service object
content:
application/json:
schema:
$ref: '#/components/schemas/Service'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Service
description: Update an existing service
tags: [ Services ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
parameters:
- in: path
name: serviceId
required: true
schema:
type: string
description: The unique identifier of a service
requestBody:
description: Service update request
content:
application/json:
schema:
$ref: '#/components/schemas/ServiceRequest'
responses:
'200':
description: Service updated
content:
application/json:
schema:
$ref: '#/components/schemas/Service'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Service
description: Delete an existing service
tags: [ Services ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
parameters:
- in: path
name: serviceId
required: true
schema:
type: string
description: The unique identifier of a service
responses:
'200':
description: Service deleted
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
/api/reverse-proxies/domains:
get:
summary: Retrieve Service Domains
description: Get information about domains that can be used for service endpoints.
tags: [ Services ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
responses:
'200':
description: A JSON Array of ReverseProxyDomains
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ReverseProxyDomain'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Custom domain
description: Create a new Custom domain for use with service endpoints, this will trigger an initial validation check
tags: [ Services ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
requestBody:
description: Custom domain creation request
content:
application/json:
schema:
$ref: '#/components/schemas/ReverseProxyDomainRequest'
responses:
'200':
description: Service created
content:
application/json:
schema:
$ref: '#/components/schemas/Service'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
/api/reverse-proxies/domains/{domainId}:
delete:
summary: Delete a Custom domain
description: Delete an existing service custom domain
tags: [ Services ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
parameters:
- in: path
name: domainId
required: true
schema:
type: string
description: The custom domain ID
responses:
'204':
description: Service custom domain deleted
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
/api/reverse-proxies/domains/{domainId}/validate:
get:
summary: Validate a custom domain
description: Trigger domain ownership validation for a custom domain
tags: [ Services ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
parameters:
- in: path
name: domainId
required: true
schema:
type: string
description: The custom domain ID
responses:
'202':
description: Reverse proxy custom domain validation triggered
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
+349
View File
@@ -114,6 +114,9 @@ const (
EventActivityCodeRuleAdd EventActivityCode = "rule.add"
EventActivityCodeRuleDelete EventActivityCode = "rule.delete"
EventActivityCodeRuleUpdate EventActivityCode = "rule.update"
EventActivityCodeServiceCreate EventActivityCode = "service.create"
EventActivityCodeServiceDelete EventActivityCode = "service.delete"
EventActivityCodeServiceUpdate EventActivityCode = "service.update"
EventActivityCodeServiceUserCreate EventActivityCode = "service.user.create"
EventActivityCodeServiceUserDelete EventActivityCode = "service.user.delete"
EventActivityCodeSetupkeyAdd EventActivityCode = "setupkey.add"
@@ -288,6 +291,12 @@ const (
ResourceTypeSubnet ResourceType = "subnet"
)
// Defines values for ReverseProxyDomainType.
const (
ReverseProxyDomainTypeCustom ReverseProxyDomainType = "custom"
ReverseProxyDomainTypeFree ReverseProxyDomainType = "free"
)
// Defines values for SentinelOneMatchAttributesNetworkStatus.
const (
SentinelOneMatchAttributesNetworkStatusConnected SentinelOneMatchAttributesNetworkStatus = "connected"
@@ -295,6 +304,28 @@ const (
SentinelOneMatchAttributesNetworkStatusQuarantined SentinelOneMatchAttributesNetworkStatus = "quarantined"
)
// Defines values for ServiceMetaStatus.
const (
ServiceMetaStatusActive ServiceMetaStatus = "active"
ServiceMetaStatusCertificateFailed ServiceMetaStatus = "certificate_failed"
ServiceMetaStatusCertificatePending ServiceMetaStatus = "certificate_pending"
ServiceMetaStatusError ServiceMetaStatus = "error"
ServiceMetaStatusPending ServiceMetaStatus = "pending"
ServiceMetaStatusTunnelNotCreated ServiceMetaStatus = "tunnel_not_created"
)
// Defines values for ServiceTargetProtocol.
const (
ServiceTargetProtocolHttp ServiceTargetProtocol = "http"
ServiceTargetProtocolHttps ServiceTargetProtocol = "https"
)
// Defines values for ServiceTargetTargetType.
const (
ServiceTargetTargetTypePeer ServiceTargetTargetType = "peer"
ServiceTargetTargetTypeResource ServiceTargetTargetType = "resource"
)
// Defines values for TenantResponseStatus.
const (
TenantResponseStatusActive TenantResponseStatus = "active"
@@ -336,6 +367,23 @@ const (
GetApiEventsNetworkTrafficParamsDirectionINGRESS GetApiEventsNetworkTrafficParamsDirection = "INGRESS"
)
// Defines values for GetApiEventsProxyParamsMethod.
const (
GetApiEventsProxyParamsMethodDELETE GetApiEventsProxyParamsMethod = "DELETE"
GetApiEventsProxyParamsMethodGET GetApiEventsProxyParamsMethod = "GET"
GetApiEventsProxyParamsMethodHEAD GetApiEventsProxyParamsMethod = "HEAD"
GetApiEventsProxyParamsMethodOPTIONS GetApiEventsProxyParamsMethod = "OPTIONS"
GetApiEventsProxyParamsMethodPATCH GetApiEventsProxyParamsMethod = "PATCH"
GetApiEventsProxyParamsMethodPOST GetApiEventsProxyParamsMethod = "POST"
GetApiEventsProxyParamsMethodPUT GetApiEventsProxyParamsMethod = "PUT"
)
// Defines values for GetApiEventsProxyParamsStatus.
const (
GetApiEventsProxyParamsStatusFailed GetApiEventsProxyParamsStatus = "failed"
GetApiEventsProxyParamsStatusSuccess GetApiEventsProxyParamsStatus = "success"
)
// Defines values for PutApiIntegrationsMspTenantsIdInviteJSONBodyValue.
const (
PutApiIntegrationsMspTenantsIdInviteJSONBodyValueAccept PutApiIntegrationsMspTenantsIdInviteJSONBodyValue = "accept"
@@ -492,6 +540,15 @@ type AvailablePorts struct {
Udp int `json:"udp"`
}
// BearerAuthConfig defines model for BearerAuthConfig.
type BearerAuthConfig struct {
// DistributionGroups List of group IDs that can use bearer auth
DistributionGroups *[]string `json:"distribution_groups,omitempty"`
// Enabled Whether bearer auth is enabled
Enabled bool `json:"enabled"`
}
// BundleParameters These parameters control what gets included in the bundle and how it is processed.
type BundleParameters struct {
// Anonymize Whether sensitive data should be anonymized in the bundle.
@@ -1329,6 +1386,12 @@ type JobResponse struct {
// JobResponseStatus defines model for JobResponse.Status.
type JobResponseStatus string
// LinkAuthConfig defines model for LinkAuthConfig.
type LinkAuthConfig struct {
// Enabled Whether link auth is enabled
Enabled bool `json:"enabled"`
}
// Location Describe geographical location information
type Location struct {
// CityName Commonly used English name of the city
@@ -1699,6 +1762,24 @@ type OSVersionCheck struct {
Windows *MinKernelVersionCheck `json:"windows,omitempty"`
}
// PINAuthConfig defines model for PINAuthConfig.
type PINAuthConfig struct {
// Enabled Whether PIN auth is enabled
Enabled bool `json:"enabled"`
// Pin PIN value
Pin string `json:"pin"`
}
// PasswordAuthConfig defines model for PasswordAuthConfig.
type PasswordAuthConfig struct {
// Enabled Whether password auth is enabled
Enabled bool `json:"enabled"`
// Password Auth password
Password string `json:"password"`
}
// PasswordChangeRequest defines model for PasswordChangeRequest.
type PasswordChangeRequest struct {
// NewPassword The new password to set
@@ -2301,6 +2382,78 @@ type Product struct {
Prices []Price `json:"prices"`
}
// ProxyAccessLog defines model for ProxyAccessLog.
type ProxyAccessLog struct {
// AuthMethodUsed Authentication method used (e.g., password, pin, oidc)
AuthMethodUsed *string `json:"auth_method_used,omitempty"`
// CityName City name from geolocation
CityName *string `json:"city_name,omitempty"`
// CountryCode Country code from geolocation
CountryCode *string `json:"country_code,omitempty"`
// DurationMs Duration of the request in milliseconds
DurationMs int `json:"duration_ms"`
// Host Host header of the request
Host string `json:"host"`
// Id Unique identifier for the access log entry
Id string `json:"id"`
// Method HTTP method of the request
Method string `json:"method"`
// Path Path of the request
Path string `json:"path"`
// Reason Reason for the request result (e.g., authentication failure)
Reason *string `json:"reason,omitempty"`
// ServiceId ID of the service that handled the request
ServiceId string `json:"service_id"`
// SourceIp Source IP address of the request
SourceIp *string `json:"source_ip,omitempty"`
// StatusCode HTTP status code returned
StatusCode int `json:"status_code"`
// Timestamp Timestamp when the request was made
Timestamp time.Time `json:"timestamp"`
// UserId ID of the authenticated user, if applicable
UserId *string `json:"user_id,omitempty"`
}
// ProxyAccessLogsResponse defines model for ProxyAccessLogsResponse.
type ProxyAccessLogsResponse struct {
// Data List of proxy access log entries
Data []ProxyAccessLog `json:"data"`
// Page Current page number
Page int `json:"page"`
// PageSize Number of items per page
PageSize int `json:"page_size"`
// TotalPages Total number of pages available
TotalPages int `json:"total_pages"`
// TotalRecords Total number of log records available
TotalRecords int `json:"total_records"`
}
// ProxyCluster A proxy cluster represents a group of proxy nodes serving the same address
type ProxyCluster struct {
// Address Cluster address used for CNAME targets
Address string `json:"address"`
// ConnectedProxies Number of proxy nodes connected in this cluster
ConnectedProxies int `json:"connected_proxies"`
}
// Resource defines model for Resource.
type Resource struct {
// Id ID of the resource
@@ -2311,6 +2464,36 @@ type Resource struct {
// ResourceType defines model for ResourceType.
type ResourceType string
// ReverseProxyDomain defines model for ReverseProxyDomain.
type ReverseProxyDomain struct {
// Domain Domain name
Domain string `json:"domain"`
// Id Domain ID
Id string `json:"id"`
// TargetCluster The proxy cluster this domain is validated against (only for custom domains)
TargetCluster *string `json:"target_cluster,omitempty"`
// Type Type of Reverse Proxy Domain
Type ReverseProxyDomainType `json:"type"`
// Validated Whether the domain has been validated
Validated bool `json:"validated"`
}
// ReverseProxyDomainRequest defines model for ReverseProxyDomainRequest.
type ReverseProxyDomainRequest struct {
// Domain Domain name
Domain string `json:"domain"`
// TargetCluster The proxy cluster this domain should be validated against
TargetCluster string `json:"target_cluster"`
}
// ReverseProxyDomainType Type of Reverse Proxy Domain
type ReverseProxyDomainType string
// Route defines model for Route.
type Route struct {
// AccessControlGroups Access control group identifier associated with route.
@@ -2470,6 +2653,112 @@ type SentinelOneMatchAttributes struct {
// SentinelOneMatchAttributesNetworkStatus The current network connectivity status of the device
type SentinelOneMatchAttributesNetworkStatus string
// Service defines model for Service.
type Service struct {
Auth ServiceAuthConfig `json:"auth"`
// Domain Domain for the service
Domain string `json:"domain"`
// Enabled Whether the service is enabled
Enabled bool `json:"enabled"`
// Id Service ID
Id string `json:"id"`
Meta ServiceMeta `json:"meta"`
// Name Service name
Name string `json:"name"`
// PassHostHeader When true, the original client Host header is passed through to the backend instead of being rewritten to the backend's address
PassHostHeader *bool `json:"pass_host_header,omitempty"`
// ProxyCluster The proxy cluster handling this service (derived from domain)
ProxyCluster *string `json:"proxy_cluster,omitempty"`
// RewriteRedirects When true, Location headers in backend responses are rewritten to replace the backend address with the public-facing domain
RewriteRedirects *bool `json:"rewrite_redirects,omitempty"`
// Targets List of target backends for this service
Targets []ServiceTarget `json:"targets"`
}
// ServiceAuthConfig defines model for ServiceAuthConfig.
type ServiceAuthConfig struct {
BearerAuth *BearerAuthConfig `json:"bearer_auth,omitempty"`
LinkAuth *LinkAuthConfig `json:"link_auth,omitempty"`
PasswordAuth *PasswordAuthConfig `json:"password_auth,omitempty"`
PinAuth *PINAuthConfig `json:"pin_auth,omitempty"`
}
// ServiceMeta defines model for ServiceMeta.
type ServiceMeta struct {
// CertificateIssuedAt Timestamp when the certificate was issued (empty if not yet issued)
CertificateIssuedAt *time.Time `json:"certificate_issued_at,omitempty"`
// CreatedAt Timestamp when the service was created
CreatedAt time.Time `json:"created_at"`
// Status Current status of the service
Status ServiceMetaStatus `json:"status"`
}
// ServiceMetaStatus Current status of the service
type ServiceMetaStatus string
// ServiceRequest defines model for ServiceRequest.
type ServiceRequest struct {
Auth ServiceAuthConfig `json:"auth"`
// Domain Domain for the service
Domain string `json:"domain"`
// Enabled Whether the service is enabled
Enabled bool `json:"enabled"`
// Name Service name
Name string `json:"name"`
// PassHostHeader When true, the original client Host header is passed through to the backend instead of being rewritten to the backend's address
PassHostHeader *bool `json:"pass_host_header,omitempty"`
// RewriteRedirects When true, Location headers in backend responses are rewritten to replace the backend address with the public-facing domain
RewriteRedirects *bool `json:"rewrite_redirects,omitempty"`
// Targets List of target backends for this service
Targets []ServiceTarget `json:"targets"`
}
// ServiceTarget defines model for ServiceTarget.
type ServiceTarget struct {
// Enabled Whether this target is enabled
Enabled bool `json:"enabled"`
// Host Backend ip or domain for this target
Host *string `json:"host,omitempty"`
// Path URL path prefix for this target
Path *string `json:"path,omitempty"`
// Port Backend port for this target. Use 0 or omit to use the scheme default (80 for http, 443 for https).
Port int `json:"port"`
// Protocol Protocol to use when connecting to the backend
Protocol ServiceTargetProtocol `json:"protocol"`
// TargetId Target ID
TargetId string `json:"target_id"`
// TargetType Target type (e.g., "peer", "resource")
TargetType ServiceTargetTargetType `json:"target_type"`
}
// ServiceTargetProtocol Protocol to use when connecting to the backend
type ServiceTargetProtocol string
// ServiceTargetTargetType Target type (e.g., "peer", "resource")
type ServiceTargetTargetType string
// SetupKey defines model for SetupKey.
type SetupKey struct {
// AllowExtraDnsLabels Allow extra DNS labels to be added to the peer
@@ -3032,6 +3321,57 @@ type GetApiEventsNetworkTrafficParamsConnectionType string
// GetApiEventsNetworkTrafficParamsDirection defines parameters for GetApiEventsNetworkTraffic.
type GetApiEventsNetworkTrafficParamsDirection string
// GetApiEventsProxyParams defines parameters for GetApiEventsProxy.
type GetApiEventsProxyParams struct {
// Page Page number for pagination (1-indexed)
Page *int `form:"page,omitempty" json:"page,omitempty"`
// PageSize Number of items per page (max 100)
PageSize *int `form:"page_size,omitempty" json:"page_size,omitempty"`
// Search General search across request ID, host, path, source IP, user email, and user name
Search *string `form:"search,omitempty" json:"search,omitempty"`
// SourceIp Filter by source IP address
SourceIp *string `form:"source_ip,omitempty" json:"source_ip,omitempty"`
// Host Filter by host header
Host *string `form:"host,omitempty" json:"host,omitempty"`
// Path Filter by request path (supports partial matching)
Path *string `form:"path,omitempty" json:"path,omitempty"`
// UserId Filter by authenticated user ID
UserId *string `form:"user_id,omitempty" json:"user_id,omitempty"`
// UserEmail Filter by user email (partial matching)
UserEmail *string `form:"user_email,omitempty" json:"user_email,omitempty"`
// UserName Filter by user name (partial matching)
UserName *string `form:"user_name,omitempty" json:"user_name,omitempty"`
// Method Filter by HTTP method
Method *GetApiEventsProxyParamsMethod `form:"method,omitempty" json:"method,omitempty"`
// Status Filter by status (success = 2xx/3xx, failed = 1xx/4xx/5xx)
Status *GetApiEventsProxyParamsStatus `form:"status,omitempty" json:"status,omitempty"`
// StatusCode Filter by HTTP status code
StatusCode *int `form:"status_code,omitempty" json:"status_code,omitempty"`
// StartDate Filter by timestamp >= start_date (RFC3339 format)
StartDate *time.Time `form:"start_date,omitempty" json:"start_date,omitempty"`
// EndDate Filter by timestamp <= end_date (RFC3339 format)
EndDate *time.Time `form:"end_date,omitempty" json:"end_date,omitempty"`
}
// GetApiEventsProxyParamsMethod defines parameters for GetApiEventsProxy.
type GetApiEventsProxyParamsMethod string
// GetApiEventsProxyParamsStatus defines parameters for GetApiEventsProxy.
type GetApiEventsProxyParamsStatus string
// GetApiGroupsParams defines parameters for GetApiGroups.
type GetApiGroupsParams struct {
// Name Filter groups by name (exact match)
@@ -3269,6 +3609,15 @@ type PostApiPostureChecksJSONRequestBody = PostureCheckUpdate
// PutApiPostureChecksPostureCheckIdJSONRequestBody defines body for PutApiPostureChecksPostureCheckId for application/json ContentType.
type PutApiPostureChecksPostureCheckIdJSONRequestBody = PostureCheckUpdate
// PostApiReverseProxiesDomainsJSONRequestBody defines body for PostApiReverseProxiesDomains for application/json ContentType.
type PostApiReverseProxiesDomainsJSONRequestBody = ReverseProxyDomainRequest
// PostApiReverseProxiesServicesJSONRequestBody defines body for PostApiReverseProxiesServices for application/json ContentType.
type PostApiReverseProxiesServicesJSONRequestBody = ServiceRequest
// PutApiReverseProxiesServicesServiceIdJSONRequestBody defines body for PutApiReverseProxiesServicesServiceId for application/json ContentType.
type PutApiReverseProxiesServicesServiceIdJSONRequestBody = ServiceRequest
// PostApiRoutesJSONRequestBody defines body for PostApiRoutes for application/json ContentType.
type PostApiRoutesJSONRequestBody = RouteRequest
+1
View File
@@ -14,4 +14,5 @@ cd "$script_path"
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
protoc -I ./ ./management.proto --go_out=../ --go-grpc_out=../
protoc -I ./ ./proxy_service.proto --go_out=../ --go-grpc_out=../
cd "$old_pwd"
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v6.33.1
// protoc v6.33.0
// source: management.proto
package proto
File diff suppressed because it is too large Load Diff
+185
View File
@@ -0,0 +1,185 @@
syntax = "proto3";
package management;
option go_package = "/proto";
import "google/protobuf/timestamp.proto";
// ProxyService - Management is the SERVER, Proxy is the CLIENT
// Proxy initiates connection to management
service ProxyService {
rpc GetMappingUpdate(GetMappingUpdateRequest) returns (stream GetMappingUpdateResponse);
rpc SendAccessLog(SendAccessLogRequest) returns (SendAccessLogResponse);
rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse);
rpc SendStatusUpdate(SendStatusUpdateRequest) returns (SendStatusUpdateResponse);
rpc CreateProxyPeer(CreateProxyPeerRequest) returns (CreateProxyPeerResponse);
rpc GetOIDCURL(GetOIDCURLRequest) returns (GetOIDCURLResponse);
// ValidateSession validates a session token and checks user access permissions.
// Called by the proxy after receiving a session token from OIDC callback.
rpc ValidateSession(ValidateSessionRequest) returns (ValidateSessionResponse);
}
// GetMappingUpdateRequest is sent to initialise a mapping stream.
message GetMappingUpdateRequest {
string proxy_id = 1;
string version = 2;
google.protobuf.Timestamp started_at = 3;
string address = 4;
}
// GetMappingUpdateResponse contains zero or more ProxyMappings.
// No mappings may be sent to test the liveness of the Proxy.
// Mappings that are sent should be interpreted by the Proxy appropriately.
message GetMappingUpdateResponse {
repeated ProxyMapping mapping = 1;
// initial_sync_complete is set on the last message of the initial snapshot.
// The proxy uses this to signal that startup is complete.
bool initial_sync_complete = 2;
}
enum ProxyMappingUpdateType {
UPDATE_TYPE_CREATED = 0;
UPDATE_TYPE_MODIFIED = 1;
UPDATE_TYPE_REMOVED = 2;
}
message PathMapping {
string path = 1;
string target = 2;
}
message Authentication {
string session_key = 1;
int64 max_session_age_seconds = 2;
bool password = 3;
bool pin = 4;
bool oidc = 5;
}
message ProxyMapping {
ProxyMappingUpdateType type = 1;
string id = 2;
string account_id = 3;
string domain = 4;
repeated PathMapping path = 5;
string auth_token = 6;
Authentication auth = 7;
// When true, the original Host header from the client request is passed
// through to the backend instead of being rewritten to the backend's address.
bool pass_host_header = 8;
// When true, Location headers in backend responses are rewritten to replace
// the backend address with the public-facing domain.
bool rewrite_redirects = 9;
}
// SendAccessLogRequest consists of one or more AccessLogs from a Proxy.
message SendAccessLogRequest {
AccessLog log = 1;
}
// SendAccessLogResponse is intentionally empty to allow for future expansion.
message SendAccessLogResponse {}
message AccessLog {
google.protobuf.Timestamp timestamp = 1;
string log_id = 2;
string account_id = 3;
string service_id = 4;
string host = 5;
string path = 6;
int64 duration_ms = 7;
string method = 8;
int32 response_code = 9;
string source_ip = 10;
string auth_mechanism = 11;
string user_id = 12;
bool auth_success = 13;
}
message AuthenticateRequest {
string id = 1;
string account_id = 2;
oneof request {
PasswordRequest password = 3;
PinRequest pin = 4;
}
}
message PasswordRequest {
string password = 1;
}
message PinRequest {
string pin = 1;
}
message AuthenticateResponse {
bool success = 1;
string session_token = 2;
}
enum ProxyStatus {
PROXY_STATUS_PENDING = 0;
PROXY_STATUS_ACTIVE = 1;
PROXY_STATUS_TUNNEL_NOT_CREATED = 2;
PROXY_STATUS_CERTIFICATE_PENDING = 3;
PROXY_STATUS_CERTIFICATE_FAILED = 4;
PROXY_STATUS_ERROR = 5;
}
// SendStatusUpdateRequest is sent by the proxy to update its status
message SendStatusUpdateRequest {
string service_id = 1;
string account_id = 2;
ProxyStatus status = 3;
bool certificate_issued = 4;
optional string error_message = 5;
}
// SendStatusUpdateResponse is intentionally empty to allow for future expansion
message SendStatusUpdateResponse {}
// CreateProxyPeerRequest is sent by the proxy to create a peer connection
// The token is a one-time authentication token sent via ProxyMapping
message CreateProxyPeerRequest {
string service_id = 1;
string account_id = 2;
string token = 3;
string wireguard_public_key = 4;
string cluster = 5;
}
// CreateProxyPeerResponse contains the result of peer creation
message CreateProxyPeerResponse {
bool success = 1;
optional string error_message = 2;
}
message GetOIDCURLRequest {
string id = 1;
string account_id = 2;
string redirect_url = 3;
}
message GetOIDCURLResponse {
string url = 1;
}
message ValidateSessionRequest {
string domain = 1;
string session_token = 2;
}
message ValidateSessionResponse {
bool valid = 1;
string user_id = 2;
string user_email = 3;
string denied_reason = 4;
}
@@ -0,0 +1,349 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// ProxyServiceClient is the client API for ProxyService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ProxyServiceClient interface {
GetMappingUpdate(ctx context.Context, in *GetMappingUpdateRequest, opts ...grpc.CallOption) (ProxyService_GetMappingUpdateClient, error)
SendAccessLog(ctx context.Context, in *SendAccessLogRequest, opts ...grpc.CallOption) (*SendAccessLogResponse, error)
Authenticate(ctx context.Context, in *AuthenticateRequest, opts ...grpc.CallOption) (*AuthenticateResponse, error)
SendStatusUpdate(ctx context.Context, in *SendStatusUpdateRequest, opts ...grpc.CallOption) (*SendStatusUpdateResponse, error)
CreateProxyPeer(ctx context.Context, in *CreateProxyPeerRequest, opts ...grpc.CallOption) (*CreateProxyPeerResponse, error)
GetOIDCURL(ctx context.Context, in *GetOIDCURLRequest, opts ...grpc.CallOption) (*GetOIDCURLResponse, error)
// ValidateSession validates a session token and checks user access permissions.
// Called by the proxy after receiving a session token from OIDC callback.
ValidateSession(ctx context.Context, in *ValidateSessionRequest, opts ...grpc.CallOption) (*ValidateSessionResponse, error)
}
type proxyServiceClient struct {
cc grpc.ClientConnInterface
}
func NewProxyServiceClient(cc grpc.ClientConnInterface) ProxyServiceClient {
return &proxyServiceClient{cc}
}
func (c *proxyServiceClient) GetMappingUpdate(ctx context.Context, in *GetMappingUpdateRequest, opts ...grpc.CallOption) (ProxyService_GetMappingUpdateClient, error) {
stream, err := c.cc.NewStream(ctx, &ProxyService_ServiceDesc.Streams[0], "/management.ProxyService/GetMappingUpdate", opts...)
if err != nil {
return nil, err
}
x := &proxyServiceGetMappingUpdateClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type ProxyService_GetMappingUpdateClient interface {
Recv() (*GetMappingUpdateResponse, error)
grpc.ClientStream
}
type proxyServiceGetMappingUpdateClient struct {
grpc.ClientStream
}
func (x *proxyServiceGetMappingUpdateClient) Recv() (*GetMappingUpdateResponse, error) {
m := new(GetMappingUpdateResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *proxyServiceClient) SendAccessLog(ctx context.Context, in *SendAccessLogRequest, opts ...grpc.CallOption) (*SendAccessLogResponse, error) {
out := new(SendAccessLogResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/SendAccessLog", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *proxyServiceClient) Authenticate(ctx context.Context, in *AuthenticateRequest, opts ...grpc.CallOption) (*AuthenticateResponse, error) {
out := new(AuthenticateResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/Authenticate", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *proxyServiceClient) SendStatusUpdate(ctx context.Context, in *SendStatusUpdateRequest, opts ...grpc.CallOption) (*SendStatusUpdateResponse, error) {
out := new(SendStatusUpdateResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/SendStatusUpdate", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *proxyServiceClient) CreateProxyPeer(ctx context.Context, in *CreateProxyPeerRequest, opts ...grpc.CallOption) (*CreateProxyPeerResponse, error) {
out := new(CreateProxyPeerResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/CreateProxyPeer", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *proxyServiceClient) GetOIDCURL(ctx context.Context, in *GetOIDCURLRequest, opts ...grpc.CallOption) (*GetOIDCURLResponse, error) {
out := new(GetOIDCURLResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/GetOIDCURL", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *proxyServiceClient) ValidateSession(ctx context.Context, in *ValidateSessionRequest, opts ...grpc.CallOption) (*ValidateSessionResponse, error) {
out := new(ValidateSessionResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/ValidateSession", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ProxyServiceServer is the server API for ProxyService service.
// All implementations must embed UnimplementedProxyServiceServer
// for forward compatibility
type ProxyServiceServer interface {
GetMappingUpdate(*GetMappingUpdateRequest, ProxyService_GetMappingUpdateServer) error
SendAccessLog(context.Context, *SendAccessLogRequest) (*SendAccessLogResponse, error)
Authenticate(context.Context, *AuthenticateRequest) (*AuthenticateResponse, error)
SendStatusUpdate(context.Context, *SendStatusUpdateRequest) (*SendStatusUpdateResponse, error)
CreateProxyPeer(context.Context, *CreateProxyPeerRequest) (*CreateProxyPeerResponse, error)
GetOIDCURL(context.Context, *GetOIDCURLRequest) (*GetOIDCURLResponse, error)
// ValidateSession validates a session token and checks user access permissions.
// Called by the proxy after receiving a session token from OIDC callback.
ValidateSession(context.Context, *ValidateSessionRequest) (*ValidateSessionResponse, error)
mustEmbedUnimplementedProxyServiceServer()
}
// UnimplementedProxyServiceServer must be embedded to have forward compatible implementations.
type UnimplementedProxyServiceServer struct {
}
func (UnimplementedProxyServiceServer) GetMappingUpdate(*GetMappingUpdateRequest, ProxyService_GetMappingUpdateServer) error {
return status.Errorf(codes.Unimplemented, "method GetMappingUpdate not implemented")
}
func (UnimplementedProxyServiceServer) SendAccessLog(context.Context, *SendAccessLogRequest) (*SendAccessLogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendAccessLog not implemented")
}
func (UnimplementedProxyServiceServer) Authenticate(context.Context, *AuthenticateRequest) (*AuthenticateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Authenticate not implemented")
}
func (UnimplementedProxyServiceServer) SendStatusUpdate(context.Context, *SendStatusUpdateRequest) (*SendStatusUpdateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendStatusUpdate not implemented")
}
func (UnimplementedProxyServiceServer) CreateProxyPeer(context.Context, *CreateProxyPeerRequest) (*CreateProxyPeerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateProxyPeer not implemented")
}
func (UnimplementedProxyServiceServer) GetOIDCURL(context.Context, *GetOIDCURLRequest) (*GetOIDCURLResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetOIDCURL not implemented")
}
func (UnimplementedProxyServiceServer) ValidateSession(context.Context, *ValidateSessionRequest) (*ValidateSessionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ValidateSession not implemented")
}
func (UnimplementedProxyServiceServer) mustEmbedUnimplementedProxyServiceServer() {}
// UnsafeProxyServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ProxyServiceServer will
// result in compilation errors.
type UnsafeProxyServiceServer interface {
mustEmbedUnimplementedProxyServiceServer()
}
func RegisterProxyServiceServer(s grpc.ServiceRegistrar, srv ProxyServiceServer) {
s.RegisterService(&ProxyService_ServiceDesc, srv)
}
func _ProxyService_GetMappingUpdate_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(GetMappingUpdateRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ProxyServiceServer).GetMappingUpdate(m, &proxyServiceGetMappingUpdateServer{stream})
}
type ProxyService_GetMappingUpdateServer interface {
Send(*GetMappingUpdateResponse) error
grpc.ServerStream
}
type proxyServiceGetMappingUpdateServer struct {
grpc.ServerStream
}
func (x *proxyServiceGetMappingUpdateServer) Send(m *GetMappingUpdateResponse) error {
return x.ServerStream.SendMsg(m)
}
func _ProxyService_SendAccessLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendAccessLogRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ProxyServiceServer).SendAccessLog(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/management.ProxyService/SendAccessLog",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).SendAccessLog(ctx, req.(*SendAccessLogRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ProxyService_Authenticate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AuthenticateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ProxyServiceServer).Authenticate(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/management.ProxyService/Authenticate",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).Authenticate(ctx, req.(*AuthenticateRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ProxyService_SendStatusUpdate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendStatusUpdateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ProxyServiceServer).SendStatusUpdate(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/management.ProxyService/SendStatusUpdate",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).SendStatusUpdate(ctx, req.(*SendStatusUpdateRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ProxyService_CreateProxyPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateProxyPeerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ProxyServiceServer).CreateProxyPeer(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/management.ProxyService/CreateProxyPeer",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).CreateProxyPeer(ctx, req.(*CreateProxyPeerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ProxyService_GetOIDCURL_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetOIDCURLRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ProxyServiceServer).GetOIDCURL(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/management.ProxyService/GetOIDCURL",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).GetOIDCURL(ctx, req.(*GetOIDCURLRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ProxyService_ValidateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ValidateSessionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ProxyServiceServer).ValidateSession(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/management.ProxyService/ValidateSession",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).ValidateSession(ctx, req.(*ValidateSessionRequest))
}
return interceptor(ctx, in, info, handler)
}
// ProxyService_ServiceDesc is the grpc.ServiceDesc for ProxyService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ProxyService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "management.ProxyService",
HandlerType: (*ProxyServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SendAccessLog",
Handler: _ProxyService_SendAccessLog_Handler,
},
{
MethodName: "Authenticate",
Handler: _ProxyService_Authenticate_Handler,
},
{
MethodName: "SendStatusUpdate",
Handler: _ProxyService_SendStatusUpdate_Handler,
},
{
MethodName: "CreateProxyPeer",
Handler: _ProxyService_CreateProxyPeer_Handler,
},
{
MethodName: "GetOIDCURL",
Handler: _ProxyService_GetOIDCURL_Handler,
},
{
MethodName: "ValidateSession",
Handler: _ProxyService_ValidateSession_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "GetMappingUpdate",
Handler: _ProxyService_GetMappingUpdate_Handler,
ServerStreams: true,
},
},
Metadata: "proxy_service.proto",
}
+8
View File
@@ -262,3 +262,11 @@ func NewZoneNotFoundError(zoneID string) error {
func NewDNSRecordNotFoundError(recordID string) error {
return Errorf(NotFound, "dns record: %s not found", recordID)
}
func NewResourceInUseError(resourceID string, proxyID string) error {
return Errorf(PreconditionFailed, "resource %s is in use by proxy %s", resourceID, proxyID)
}
func NewPeerInUseError(peerID string, proxyID string) error {
return Errorf(PreconditionFailed, "peer %s is in use by proxy %s", peerID, proxyID)
}