diff --git a/idp/dex/connector.go b/idp/dex/connector.go index ba2bb1f000f..112b517d6a1 100644 --- a/idp/dex/connector.go +++ b/idp/dex/connector.go @@ -27,6 +27,10 @@ type ConnectorConfig struct { ClientSecret string // RedirectURI is the OAuth2 redirect URI RedirectURI string + // PKCE enables Proof Key for Code Exchange for the upstream OIDC provider + PKCE bool + // JWKSURL is the URL to the JSON Web Key Set of the identity provider + JWKSURL string } // CreateConnector creates a new connector in Dex storage. @@ -129,6 +133,13 @@ func mergeConnectorConfig(cfg, oldCfg *ConnectorConfig) { if cfg.Name == "" { cfg.Name = oldCfg.Name } + if cfg.JWKSURL == "" { + cfg.JWKSURL = oldCfg.JWKSURL + } + // Note: for boolean PKCE, we don't have an easy way to detect if it was missing in the request + // if we don't use pointers. Given the current pattern, we assume if it's false it might be intentional + // or it might be missing. However, the API request uses *bool, so we could have used that if we wanted. + // For now, we'll just let it be updated as is. } // DeleteConnector removes a connector from Dex storage. @@ -210,7 +221,13 @@ func buildOIDCConnectorConfig(cfg *ConnectorConfig, redirectURI string) ([]byte, "insecureEnableGroups": true, //some providers don't return email verified, so we need to skip it if not present (e.g., Entra, Okta, Duo) "insecureSkipEmailVerified": true, + "jwksURL": cfg.JWKSURL, + } + + if cfg.PKCE { + oidcConfig["pkceChallenge"] = "S256" } + switch cfg.Type { case "zitadel": oidcConfig["getUserInfo"] = true @@ -264,6 +281,12 @@ func (p *Provider) parseStorageConnector(conn storage.Connector) (*ConnectorConf if v, ok := configMap["issuer"].(string); ok { cfg.Issuer = v } + if v, ok := configMap["pkceChallenge"].(string); ok { + cfg.PKCE = v == "S256" || v == "plain" + } + if v, ok := configMap["jwksURL"].(string); ok { + cfg.JWKSURL = v + } // Infer the original identity provider type from Dex connector type and ID cfg.Type = inferIdentityProviderType(conn.Type, conn.ID, configMap) diff --git a/management/server/http/handlers/idp/idp_handler.go b/management/server/http/handlers/idp/idp_handler.go index 077507b898c..47deec24d31 100644 --- a/management/server/http/handlers/idp/idp_handler.go +++ b/management/server/http/handlers/idp/idp_handler.go @@ -177,6 +177,8 @@ func toAPIResponse(idp *types.IdentityProvider) api.IdentityProvider { Name: idp.Name, Issuer: idp.Issuer, ClientId: idp.ClientID, + Pkce: &idp.PKCE, + JwksUrl: &idp.JWKSURL, } if idp.ID != "" { resp.Id = &idp.ID @@ -186,11 +188,18 @@ func toAPIResponse(idp *types.IdentityProvider) api.IdentityProvider { } func fromAPIRequest(req *api.IdentityProviderRequest) *types.IdentityProvider { - return &types.IdentityProvider{ + idp := &types.IdentityProvider{ Type: types.IdentityProviderType(req.Type), Name: req.Name, Issuer: req.Issuer, ClientID: req.ClientId, ClientSecret: req.ClientSecret, } + if req.Pkce != nil { + idp.PKCE = *req.Pkce + } + if req.JwksUrl != nil { + idp.JWKSURL = *req.JwksUrl + } + return idp } diff --git a/management/server/http/handlers/idp/idp_handler_test.go b/management/server/http/handlers/idp/idp_handler_test.go index 74b20404812..98bd6afbd8b 100644 --- a/management/server/http/handlers/idp/idp_handler_test.go +++ b/management/server/http/handlers/idp/idp_handler_test.go @@ -400,6 +400,7 @@ func TestDeleteIdentityProvider(t *testing.T) { } func TestToAPIResponse(t *testing.T) { + pkce := true idp := &types.IdentityProvider{ ID: "test-id", Name: "Test IDP", @@ -407,6 +408,8 @@ func TestToAPIResponse(t *testing.T) { Issuer: "https://accounts.google.com", ClientID: "client-id", ClientSecret: "should-not-be-returned", + PKCE: pkce, + JWKSURL: "https://example.com/jwks", } response := toAPIResponse(idp) @@ -416,16 +419,22 @@ func TestToAPIResponse(t *testing.T) { assert.Equal(t, api.IdentityProviderTypeGoogle, response.Type) assert.Equal(t, "https://accounts.google.com", response.Issuer) assert.Equal(t, "client-id", response.ClientId) + assert.Equal(t, pkce, *response.Pkce) + assert.Equal(t, "https://example.com/jwks", *response.JwksUrl) // Note: ClientSecret is not included in response type by design } func TestFromAPIRequest(t *testing.T) { + pkce := true + jwksURL := "https://example.com/jwks" req := &api.IdentityProviderRequest{ Name: "New IDP", Type: api.IdentityProviderTypeOkta, Issuer: "https://dev-123456.okta.com", ClientId: "okta-client-id", ClientSecret: "okta-client-secret", + Pkce: &pkce, + JwksUrl: &jwksURL, } idp := fromAPIRequest(req) @@ -435,4 +444,6 @@ func TestFromAPIRequest(t *testing.T) { assert.Equal(t, "https://dev-123456.okta.com", idp.Issuer) assert.Equal(t, "okta-client-id", idp.ClientID) assert.Equal(t, "okta-client-secret", idp.ClientSecret) + assert.Equal(t, pkce, idp.PKCE) + assert.Equal(t, jwksURL, idp.JWKSURL) } diff --git a/management/server/identity_provider.go b/management/server/identity_provider.go index 8fd96c23859..bc1a963fdcd 100644 --- a/management/server/identity_provider.go +++ b/management/server/identity_provider.go @@ -258,6 +258,8 @@ func connectorConfigToIdentityProvider(conn *dex.ConnectorConfig, accountID stri Issuer: conn.Issuer, ClientID: conn.ClientID, ClientSecret: conn.ClientSecret, + PKCE: conn.PKCE, + JWKSURL: conn.JWKSURL, } } @@ -270,6 +272,8 @@ func identityProviderToConnectorConfig(idpConfig *types.IdentityProvider) *dex.C Issuer: idpConfig.Issuer, ClientID: idpConfig.ClientID, ClientSecret: idpConfig.ClientSecret, + PKCE: idpConfig.PKCE, + JWKSURL: idpConfig.JWKSURL, } } diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go index 0ff57b75219..afba4b959a6 100644 --- a/management/server/store/sql_store.go +++ b/management/server/store/sql_store.go @@ -2723,12 +2723,24 @@ func (s *SqlStore) SaveUserLastLogin(ctx context.Context, accountID, userID stri return status.NewGetUserFromStoreError() } - if !lastLogin.IsZero() { - user.LastLogin = &lastLogin - return s.db.Save(&user).Error + if lastLogin.IsZero() { + return nil } - return nil + if err := user.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return fmt.Errorf("decrypt user: %w", err) + } + + user.LastLogin = &lastLogin + + userCopy := user.Copy() + userCopy.Email = user.Email + userCopy.Name = user.Name + if err := userCopy.EncryptSensitiveData(s.fieldEncrypt); err != nil { + return fmt.Errorf("encrypt user: %w", err) + } + + return s.db.Save(userCopy).Error } func (s *SqlStore) GetPostureCheckByChecksDefinition(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error) { diff --git a/management/server/store/sql_store.go.orig b/management/server/store/sql_store.go.orig new file mode 100644 index 00000000000..0ff57b75219 --- /dev/null +++ b/management/server/store/sql_store.go.orig @@ -0,0 +1,5697 @@ +package store + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "net" + "os" + "path/filepath" + "runtime" + "runtime/debug" + "strconv" + "strings" + "sync" + "time" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/rs/xid" + log "github.com/sirupsen/logrus" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/logger" + + nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/accesslogs" + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/domain" + + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxy" + rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service" + "github.com/netbirdio/netbird/management/internals/modules/zones" + "github.com/netbirdio/netbird/management/internals/modules/zones/records" + resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types" + routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types" + networkTypes "github.com/netbirdio/netbird/management/server/networks/types" + nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/management/server/posture" + "github.com/netbirdio/netbird/management/server/telemetry" + "github.com/netbirdio/netbird/management/server/types" + "github.com/netbirdio/netbird/management/server/util" + "github.com/netbirdio/netbird/route" + "github.com/netbirdio/netbird/shared/management/status" + "github.com/netbirdio/netbird/util/crypt" +) + +const ( + storeSqliteFileName = "store.db" + idQueryCondition = "id = ?" + keyQueryCondition = "key = ?" + mysqlKeyQueryCondition = "`key` = ?" + accountAndIDQueryCondition = "account_id = ? and id = ?" + accountAndPeerIDQueryCondition = "account_id = ? and peer_id = ?" + accountAndIDsQueryCondition = "account_id = ? AND id IN ?" + accountIDCondition = "account_id = ?" + peerNotFoundFMT = "peer %s not found" + + pgMaxConnections = 30 + pgMinConnections = 1 + pgMaxConnLifetime = 60 * time.Minute + pgHealthCheckPeriod = 1 * time.Minute +) + +// SqlStore represents an account storage backed by a Sql DB persisted to disk +type SqlStore struct { + db *gorm.DB + globalAccountLock sync.Mutex + metrics telemetry.AppMetrics + installationPK int + storeEngine types.Engine + pool *pgxpool.Pool + fieldEncrypt *crypt.FieldEncrypt + transactionTimeout time.Duration +} + +type installation struct { + ID uint `gorm:"primaryKey"` + InstallationIDValue string +} + +type migrationFunc func(*gorm.DB) error + +// NewSqlStore creates a new SqlStore instance. +func NewSqlStore(ctx context.Context, db *gorm.DB, storeEngine types.Engine, metrics telemetry.AppMetrics, skipMigration bool) (*SqlStore, error) { + sql, err := db.DB() + if err != nil { + return nil, err + } + + conns, err := strconv.Atoi(os.Getenv("NB_SQL_MAX_OPEN_CONNS")) + if err != nil { + conns = runtime.NumCPU() + } + + transactionTimeout := 5 * time.Minute + if v := os.Getenv("NB_STORE_TRANSACTION_TIMEOUT"); v != "" { + if parsed, err := time.ParseDuration(v); err == nil { + transactionTimeout = parsed + } + } + log.WithContext(ctx).Infof("Setting transaction timeout to %v", transactionTimeout) + + if storeEngine == types.SqliteStoreEngine { + if err == nil { + log.WithContext(ctx).Warnf("setting NB_SQL_MAX_OPEN_CONNS is not supported for sqlite, using default value 1") + } + conns = 1 + } + + sql.SetMaxOpenConns(conns) + sql.SetMaxIdleConns(conns) + sql.SetConnMaxLifetime(time.Hour) + sql.SetConnMaxIdleTime(3 * time.Minute) + + log.WithContext(ctx).Infof("Set max open db connections to %d, max idle to %d, max lifetime to %v, max idle time to %v", + conns, conns, time.Hour, 3*time.Minute) + + if skipMigration { + log.WithContext(ctx).Infof("skipping migration") + return &SqlStore{db: db, storeEngine: storeEngine, metrics: metrics, installationPK: 1, transactionTimeout: transactionTimeout}, nil + } + + if err := migratePreAuto(ctx, db); err != nil { + return nil, fmt.Errorf("migratePreAuto: %w", err) + } + err = db.AutoMigrate( + &types.SetupKey{}, &nbpeer.Peer{}, &types.User{}, &types.PersonalAccessToken{}, &types.ProxyAccessToken{}, + &types.Group{}, &types.GroupPeer{}, + &types.Account{}, &types.Policy{}, &types.PolicyRule{}, &route.Route{}, &nbdns.NameServerGroup{}, + &installation{}, &types.ExtraSettings{}, &posture.Checks{}, &nbpeer.NetworkAddress{}, + &networkTypes.Network{}, &routerTypes.NetworkRouter{}, &resourceTypes.NetworkResource{}, &types.AccountOnboarding{}, + &types.Job{}, &zones.Zone{}, &records.Record{}, &types.UserInviteRecord{}, &rpservice.Service{}, &rpservice.Target{}, &domain.Domain{}, + &accesslogs.AccessLogEntry{}, &proxy.Proxy{}, + ) + if err != nil { + return nil, fmt.Errorf("auto migratePreAuto: %w", err) + } + if err := migratePostAuto(ctx, db); err != nil { + return nil, fmt.Errorf("migratePostAuto: %w", err) + } + + return &SqlStore{db: db, storeEngine: storeEngine, metrics: metrics, installationPK: 1, transactionTimeout: transactionTimeout}, nil +} + +func GetKeyQueryCondition(s *SqlStore) string { + if s.storeEngine == types.MysqlStoreEngine { + return mysqlKeyQueryCondition + } + return keyQueryCondition +} + +// SaveJob persists a job in DB +func (s *SqlStore) CreatePeerJob(ctx context.Context, job *types.Job) error { + result := s.db.Create(job) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to create job in store: %s", result.Error) + return status.Errorf(status.Internal, "failed to create job in store") + } + return nil +} + +func (s *SqlStore) CompletePeerJob(ctx context.Context, job *types.Job) error { + result := s.db. + Model(&types.Job{}). + Where(idQueryCondition, job.ID). + Updates(job) + + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to update job in store: %s", result.Error) + return status.Errorf(status.Internal, "failed to update job in store") + } + return nil +} + +// job was pending for too long and has been cancelled +func (s *SqlStore) MarkPendingJobsAsFailed(ctx context.Context, accountID, peerID, jobID, reason string) error { + now := time.Now().UTC() + result := s.db. + Model(&types.Job{}). + Where(accountAndPeerIDQueryCondition+" AND id = ?"+" AND status = ?", accountID, peerID, jobID, types.JobStatusPending). + Updates(types.Job{ + Status: types.JobStatusFailed, + FailedReason: reason, + CompletedAt: &now, + }) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to mark pending jobs as Failed job in store: %s", result.Error) + return status.Errorf(status.Internal, "failed to mark pending job as Failed in store") + } + return nil +} + +// job was pending for too long and has been cancelled +func (s *SqlStore) MarkAllPendingJobsAsFailed(ctx context.Context, accountID, peerID, reason string) error { + now := time.Now().UTC() + result := s.db. + Model(&types.Job{}). + Where(accountAndPeerIDQueryCondition+" AND status = ?", accountID, peerID, types.JobStatusPending). + Updates(types.Job{ + Status: types.JobStatusFailed, + FailedReason: reason, + CompletedAt: &now, + }) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to mark pending jobs as Failed job in store: %s", result.Error) + return status.Errorf(status.Internal, "failed to mark pending job as Failed in store") + } + return nil +} + +// GetJobByID fetches job by ID +func (s *SqlStore) GetPeerJobByID(ctx context.Context, accountID, jobID string) (*types.Job, error) { + var job types.Job + err := s.db. + Where(accountAndIDQueryCondition, accountID, jobID). + First(&job).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "job %s not found", jobID) + } + if err != nil { + log.WithContext(ctx).Errorf("failed to fetch job from store: %s", err) + return nil, err + } + return &job, nil +} + +// get all jobs +func (s *SqlStore) GetPeerJobs(ctx context.Context, accountID, peerID string) ([]*types.Job, error) { + var jobs []*types.Job + err := s.db. + Where(accountAndPeerIDQueryCondition, accountID, peerID). + Order("created_at DESC"). + Find(&jobs).Error + + if err != nil { + log.WithContext(ctx).Errorf("failed to fetch jobs from store: %s", err) + return nil, err + } + + return jobs, nil +} + +// AcquireGlobalLock acquires global lock across all the accounts and returns a function that releases the lock +func (s *SqlStore) AcquireGlobalLock(ctx context.Context) (unlock func()) { + log.WithContext(ctx).Tracef("acquiring global lock") + start := time.Now() + s.globalAccountLock.Lock() + + unlock = func() { + s.globalAccountLock.Unlock() + log.WithContext(ctx).Tracef("released global lock in %v", time.Since(start)) + } + + took := time.Since(start) + log.WithContext(ctx).Tracef("took %v to acquire global lock", took) + if s.metrics != nil { + s.metrics.StoreMetrics().CountGlobalLockAcquisitionDuration(took) + } + + return unlock +} + +// Deprecated: Full account operations are no longer supported +func (s *SqlStore) SaveAccount(ctx context.Context, account *types.Account) error { + start := time.Now() + defer func() { + elapsed := time.Since(start) + if elapsed > 1*time.Second { + log.WithContext(ctx).Tracef("SaveAccount for account %s exceeded 1s, took: %v", account.Id, elapsed) + } + }() + + // todo: remove this check after the issue is resolved + s.checkAccountDomainBeforeSave(ctx, account.Id, account.Domain) + + generateAccountSQLTypes(account) + + // Encrypt sensitive user data before saving + for i := range account.UsersG { + if err := account.UsersG[i].EncryptSensitiveData(s.fieldEncrypt); err != nil { + return fmt.Errorf("encrypt user: %w", err) + } + } + + for _, group := range account.GroupsG { + group.StoreGroupPeers() + } + + err := s.transaction(func(tx *gorm.DB) error { + result := tx.Select(clause.Associations).Delete(account.Policies, "account_id = ?", account.Id) + if result.Error != nil { + return result.Error + } + + result = tx.Select(clause.Associations).Delete(account.UsersG, "account_id = ?", account.Id) + if result.Error != nil { + return result.Error + } + + result = tx.Select(clause.Associations).Delete(account) + if result.Error != nil { + return result.Error + } + + result = tx. + Session(&gorm.Session{FullSaveAssociations: true}). + Clauses(clause.OnConflict{UpdateAll: true}). + Create(account) + if result.Error != nil { + return result.Error + } + return nil + }) + + took := time.Since(start) + if s.metrics != nil { + s.metrics.StoreMetrics().CountPersistenceDuration(took) + } + log.WithContext(ctx).Debugf("took %d ms to persist an account to the store", took.Milliseconds()) + + return err +} + +// generateAccountSQLTypes generates the GORM compatible types for the account +func generateAccountSQLTypes(account *types.Account) { + for _, key := range account.SetupKeys { + account.SetupKeysG = append(account.SetupKeysG, *key) + } + + if len(account.SetupKeys) != len(account.SetupKeysG) { + log.Warnf("SetupKeysG length mismatch for account %s", account.Id) + } + + for id, peer := range account.Peers { + peer.ID = id + account.PeersG = append(account.PeersG, *peer) + } + + for id, user := range account.Users { + user.Id = id + for id, pat := range user.PATs { + pat.ID = id + user.PATsG = append(user.PATsG, *pat) + } + account.UsersG = append(account.UsersG, *user) + } + + for id, group := range account.Groups { + group.ID = id + group.AccountID = account.Id + account.GroupsG = append(account.GroupsG, group) + } + + for id, route := range account.Routes { + route.ID = id + account.RoutesG = append(account.RoutesG, *route) + } + + for id, ns := range account.NameServerGroups { + ns.ID = id + account.NameServerGroupsG = append(account.NameServerGroupsG, *ns) + } +} + +// checkAccountDomainBeforeSave temporary method to troubleshoot an issue with domains getting blank +func (s *SqlStore) checkAccountDomainBeforeSave(ctx context.Context, accountID, newDomain string) { + var acc types.Account + var domain string + result := s.db.Model(&acc).Select("domain").Where(idQueryCondition, accountID).Take(&domain) + if result.Error != nil { + if !errors.Is(result.Error, gorm.ErrRecordNotFound) { + log.WithContext(ctx).Errorf("error when getting account %s from the store to check domain: %s", accountID, result.Error) + } + return + } + if domain != "" && newDomain == "" { + log.WithContext(ctx).Warnf("saving an account with empty domain when there was a domain set. Previous domain %s, Account ID: %s, Trace: %s", domain, accountID, debug.Stack()) + } +} + +func (s *SqlStore) DeleteAccount(ctx context.Context, account *types.Account) error { + start := time.Now() + + err := s.transaction(func(tx *gorm.DB) error { + result := tx.Select(clause.Associations).Delete(account.Policies, "account_id = ?", account.Id) + if result.Error != nil { + return result.Error + } + + result = tx.Select(clause.Associations).Delete(account.UsersG, "account_id = ?", account.Id) + if result.Error != nil { + return result.Error + } + + result = tx.Select(clause.Associations).Delete(account.Services, "account_id = ?", account.Id) + if result.Error != nil { + return result.Error + } + + result = tx.Select(clause.Associations).Delete(account) + if result.Error != nil { + return result.Error + } + + return nil + }) + + took := time.Since(start) + if s.metrics != nil { + s.metrics.StoreMetrics().CountPersistenceDuration(took) + } + log.WithContext(ctx).Tracef("took %d ms to delete an account to the store", took.Milliseconds()) + + return err +} + +func (s *SqlStore) SaveInstallationID(_ context.Context, ID string) error { + installation := installation{InstallationIDValue: ID} + installation.ID = uint(s.installationPK) + + return s.db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&installation).Error +} + +func (s *SqlStore) GetInstallationID() string { + var installation installation + + if result := s.db.Take(&installation, idQueryCondition, s.installationPK); result.Error != nil { + return "" + } + + return installation.InstallationIDValue +} + +func (s *SqlStore) SavePeer(ctx context.Context, accountID string, peer *nbpeer.Peer) error { + // To maintain data integrity, we create a copy of the peer's to prevent unintended updates to other fields. + peerCopy := peer.Copy() + peerCopy.AccountID = accountID + + err := s.transaction(func(tx *gorm.DB) error { + // check if peer exists before saving + var peerID string + result := tx.Model(&nbpeer.Peer{}).Select("id").Take(&peerID, accountAndIDQueryCondition, accountID, peer.ID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return status.Errorf(status.NotFound, peerNotFoundFMT, peer.ID) + } + return result.Error + } + + if peerID == "" { + return status.Errorf(status.NotFound, peerNotFoundFMT, peer.ID) + } + + result = tx.Model(&nbpeer.Peer{}).Where(accountAndIDQueryCondition, accountID, peer.ID).Save(peerCopy) + if result.Error != nil { + return status.Errorf(status.Internal, "failed to save peer to store: %v", result.Error) + } + + return nil + }) + + if err != nil { + return err + } + + return nil +} + +func (s *SqlStore) UpdateAccountDomainAttributes(ctx context.Context, accountID string, domain string, category string, isPrimaryDomain bool) error { + accountCopy := types.Account{ + Domain: domain, + DomainCategory: category, + IsDomainPrimaryAccount: isPrimaryDomain, + } + + fieldsToUpdate := []string{"domain", "domain_category", "is_domain_primary_account"} + result := s.db.Model(&types.Account{}). + Select(fieldsToUpdate). + Where(idQueryCondition, accountID). + Updates(&accountCopy) + if result.Error != nil { + return status.Errorf(status.Internal, "failed to update account domain attributes to store: %v", result.Error) + } + + if result.RowsAffected == 0 { + return status.Errorf(status.NotFound, "account %s", accountID) + } + + return nil +} + +func (s *SqlStore) SavePeerStatus(ctx context.Context, accountID, peerID string, peerStatus nbpeer.PeerStatus) error { + var peerCopy nbpeer.Peer + peerCopy.Status = &peerStatus + + fieldsToUpdate := []string{ + "peer_status_last_seen", "peer_status_connected", + "peer_status_login_expired", "peer_status_required_approval", + } + result := s.db.Model(&nbpeer.Peer{}). + Select(fieldsToUpdate). + Where(accountAndIDQueryCondition, accountID, peerID). + Updates(&peerCopy) + if result.Error != nil { + return status.Errorf(status.Internal, "failed to save peer status to store: %v", result.Error) + } + + if result.RowsAffected == 0 { + return status.Errorf(status.NotFound, peerNotFoundFMT, peerID) + } + + return nil +} + +func (s *SqlStore) SavePeerLocation(ctx context.Context, accountID string, peerWithLocation *nbpeer.Peer) error { + // To maintain data integrity, we create a copy of the peer's location to prevent unintended updates to other fields. + var peerCopy nbpeer.Peer + // Since the location field has been migrated to JSON serialization, + // updating the struct ensures the correct data format is inserted into the database. + peerCopy.Location = peerWithLocation.Location + + result := s.db.Model(&nbpeer.Peer{}). + Where(accountAndIDQueryCondition, accountID, peerWithLocation.ID). + Updates(peerCopy) + + if result.Error != nil { + return status.Errorf(status.Internal, "failed to save peer locations to store: %v", result.Error) + } + + if result.RowsAffected == 0 { + return status.Errorf(status.NotFound, peerNotFoundFMT, peerWithLocation.ID) + } + + return nil +} + +// ApproveAccountPeers marks all peers that currently require approval in the given account as approved. +func (s *SqlStore) ApproveAccountPeers(ctx context.Context, accountID string) (int, error) { + result := s.db.Model(&nbpeer.Peer{}). + Where("account_id = ? AND peer_status_requires_approval = ?", accountID, true). + Update("peer_status_requires_approval", false) + if result.Error != nil { + return 0, status.Errorf(status.Internal, "failed to approve pending account peers: %v", result.Error) + } + + return int(result.RowsAffected), nil +} + +// SaveUsers saves the given list of users to the database. +func (s *SqlStore) SaveUsers(ctx context.Context, users []*types.User) error { + if len(users) == 0 { + return nil + } + + usersCopy := make([]*types.User, len(users)) + for i, user := range users { + userCopy := user.Copy() + userCopy.Email = user.Email + userCopy.Name = user.Name + if err := userCopy.EncryptSensitiveData(s.fieldEncrypt); err != nil { + return fmt.Errorf("encrypt user: %w", err) + } + usersCopy[i] = userCopy + } + + result := s.db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&usersCopy) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save users to store: %s", result.Error) + return status.Errorf(status.Internal, "failed to save users to store") + } + return nil +} + +// SaveUser saves the given user to the database. +func (s *SqlStore) SaveUser(ctx context.Context, user *types.User) error { + userCopy := user.Copy() + userCopy.Email = user.Email + userCopy.Name = user.Name + + if err := userCopy.EncryptSensitiveData(s.fieldEncrypt); err != nil { + return fmt.Errorf("encrypt user: %w", err) + } + + result := s.db.Save(userCopy) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save user to store: %s", result.Error) + return status.Errorf(status.Internal, "failed to save user to store") + } + return nil +} + +// CreateGroups creates the given list of groups to the database. +func (s *SqlStore) CreateGroups(ctx context.Context, accountID string, groups []*types.Group) error { + if len(groups) == 0 { + return nil + } + + return s.db.Transaction(func(tx *gorm.DB) error { + result := tx. + Clauses( + clause.OnConflict{ + Where: clause.Where{Exprs: []clause.Expression{clause.Eq{Column: "groups.account_id", Value: accountID}}}, + UpdateAll: true, + }, + ). + Omit(clause.Associations). + Create(&groups) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save groups to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to save groups to store") + } + + return nil + }) +} + +// UpdateGroups updates the given list of groups to the database. +func (s *SqlStore) UpdateGroups(ctx context.Context, accountID string, groups []*types.Group) error { + if len(groups) == 0 { + return nil + } + + return s.db.Transaction(func(tx *gorm.DB) error { + result := tx. + Clauses( + clause.OnConflict{ + Where: clause.Where{Exprs: []clause.Expression{clause.Eq{Column: "groups.account_id", Value: accountID}}}, + UpdateAll: true, + }, + ). + Omit(clause.Associations). + Create(&groups) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save groups to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to save groups to store") + } + + return nil + }) +} + +// DeleteHashedPAT2TokenIDIndex is noop in SqlStore +func (s *SqlStore) DeleteHashedPAT2TokenIDIndex(hashedToken string) error { + return nil +} + +// DeleteTokenID2UserIDIndex is noop in SqlStore +func (s *SqlStore) DeleteTokenID2UserIDIndex(tokenID string) error { + return nil +} + +func (s *SqlStore) GetAccountByPrivateDomain(ctx context.Context, domain string) (*types.Account, error) { + accountID, err := s.GetAccountIDByPrivateDomain(ctx, LockingStrengthNone, domain) + if err != nil { + return nil, err + } + + // TODO: rework to not call GetAccount + return s.GetAccount(ctx, accountID) +} + +func (s *SqlStore) GetAccountIDByPrivateDomain(ctx context.Context, lockStrength LockingStrength, domain string) (string, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var accountID string + result := tx.Model(&types.Account{}).Select("id"). + Where("domain = ? and is_domain_primary_account = ? and domain_category = ?", + strings.ToLower(domain), true, types.PrivateCategory, + ).Take(&accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return "", status.Errorf(status.NotFound, "account not found: provided domain is not registered or is not private") + } + log.WithContext(ctx).Errorf("error when getting account from the store: %s", result.Error) + return "", status.NewGetAccountFromStoreError(result.Error) + } + + return accountID, nil +} + +func (s *SqlStore) GetAccountBySetupKey(ctx context.Context, setupKey string) (*types.Account, error) { + var key types.SetupKey + result := s.db.Select("account_id").Take(&key, GetKeyQueryCondition(s), setupKey) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewSetupKeyNotFoundError(setupKey) + } + log.WithContext(ctx).Errorf("failed to get account by setup key from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get account by setup key from store") + } + + if key.AccountID == "" { + return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") + } + + return s.GetAccount(ctx, key.AccountID) +} + +func (s *SqlStore) GetTokenIDByHashedToken(ctx context.Context, hashedToken string) (string, error) { + var token types.PersonalAccessToken + result := s.db.Take(&token, "hashed_token = ?", hashedToken) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return "", status.Errorf(status.NotFound, "account not found: index lookup failed") + } + log.WithContext(ctx).Errorf("error when getting token from the store: %s", result.Error) + return "", status.NewGetAccountFromStoreError(result.Error) + } + + return token.ID, nil +} + +func (s *SqlStore) GetUserByPATID(ctx context.Context, lockStrength LockingStrength, patID string) (*types.User, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var user types.User + result := tx. + Joins("JOIN personal_access_tokens ON personal_access_tokens.user_id = users.id"). + Where("personal_access_tokens.id = ?", patID).Take(&user) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewPATNotFoundError(patID) + } + log.WithContext(ctx).Errorf("failed to get token user from the store: %s", result.Error) + return nil, status.NewGetUserFromStoreError() + } + + if err := user.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt user: %w", err) + } + + return &user, nil +} + +func (s *SqlStore) GetUserByUserID(ctx context.Context, lockStrength LockingStrength, userID string) (*types.User, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var user types.User + result := tx.Take(&user, idQueryCondition, userID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewUserNotFoundError(userID) + } + return nil, status.NewGetUserFromStoreError() + } + + if err := user.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt user: %w", err) + } + + return &user, nil +} + +func (s *SqlStore) DeleteUser(ctx context.Context, accountID, userID string) error { + err := s.transaction(func(tx *gorm.DB) error { + result := tx.Delete(&types.PersonalAccessToken{}, "user_id = ?", userID) + if result.Error != nil { + return result.Error + } + + return tx.Delete(&types.User{}, accountAndIDQueryCondition, accountID, userID).Error + }) + if err != nil { + log.WithContext(ctx).Errorf("failed to delete user from the store: %s", err) + return status.Errorf(status.Internal, "failed to delete user from store") + } + + return nil +} + +func (s *SqlStore) GetAccountUsers(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*types.User, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var users []*types.User + result := tx.Find(&users, accountIDCondition, accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "accountID not found: index lookup failed") + } + log.WithContext(ctx).Errorf("error when getting users from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "issue getting users from store") + } + + for _, user := range users { + if err := user.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt user: %w", err) + } + } + + return users, nil +} + +func (s *SqlStore) GetAccountOwner(ctx context.Context, lockStrength LockingStrength, accountID string) (*types.User, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var user types.User + result := tx.Take(&user, "account_id = ? AND role = ?", accountID, types.UserRoleOwner) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "account owner not found: index lookup failed") + } + return nil, status.Errorf(status.Internal, "failed to get account owner from the store") + } + + if err := user.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt user: %w", err) + } + + return &user, nil +} + +// SaveUserInvite saves a user invite to the database +func (s *SqlStore) SaveUserInvite(ctx context.Context, invite *types.UserInviteRecord) error { + inviteCopy := invite.Copy() + if err := inviteCopy.EncryptSensitiveData(s.fieldEncrypt); err != nil { + return fmt.Errorf("encrypt invite: %w", err) + } + + result := s.db.Save(inviteCopy) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save user invite to store: %s", result.Error) + return status.Errorf(status.Internal, "failed to save user invite to store") + } + return nil +} + +// GetUserInviteByID retrieves a user invite by its ID and account ID +func (s *SqlStore) GetUserInviteByID(ctx context.Context, lockStrength LockingStrength, accountID, inviteID string) (*types.UserInviteRecord, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var invite types.UserInviteRecord + result := tx.Where("account_id = ?", accountID).Take(&invite, idQueryCondition, inviteID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "user invite not found") + } + log.WithContext(ctx).Errorf("failed to get user invite from store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get user invite from store") + } + + if err := invite.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt invite: %w", err) + } + + return &invite, nil +} + +// GetUserInviteByHashedToken retrieves a user invite by its hashed token +func (s *SqlStore) GetUserInviteByHashedToken(ctx context.Context, lockStrength LockingStrength, hashedToken string) (*types.UserInviteRecord, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var invite types.UserInviteRecord + result := tx.Take(&invite, "hashed_token = ?", hashedToken) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "user invite not found") + } + log.WithContext(ctx).Errorf("failed to get user invite from store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get user invite from store") + } + + if err := invite.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt invite: %w", err) + } + + return &invite, nil +} + +// GetUserInviteByEmail retrieves a user invite by account ID and email. +// Since email is encrypted with random IVs, we fetch all invites for the account +// and compare emails in memory after decryption. +func (s *SqlStore) GetUserInviteByEmail(ctx context.Context, lockStrength LockingStrength, accountID, email string) (*types.UserInviteRecord, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var invites []*types.UserInviteRecord + result := tx.Find(&invites, "account_id = ?", accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get user invites from store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get user invites from store") + } + + for _, invite := range invites { + if err := invite.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt invite: %w", err) + } + if strings.EqualFold(invite.Email, email) { + return invite, nil + } + } + + return nil, status.Errorf(status.NotFound, "user invite not found for email") +} + +// GetAccountUserInvites retrieves all user invites for an account +func (s *SqlStore) GetAccountUserInvites(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*types.UserInviteRecord, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var invites []*types.UserInviteRecord + result := tx.Find(&invites, "account_id = ?", accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get user invites from store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get user invites from store") + } + + for _, invite := range invites { + if err := invite.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt invite: %w", err) + } + } + + return invites, nil +} + +// DeleteUserInvite deletes a user invite by its ID +func (s *SqlStore) DeleteUserInvite(ctx context.Context, inviteID string) error { + result := s.db.Delete(&types.UserInviteRecord{}, idQueryCondition, inviteID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete user invite from store: %s", result.Error) + return status.Errorf(status.Internal, "failed to delete user invite from store") + } + return nil +} + +func (s *SqlStore) GetAccountGroups(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*types.Group, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var groups []*types.Group + result := tx.Preload(clause.Associations).Find(&groups, accountIDCondition, accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "accountID not found: index lookup failed") + } + log.WithContext(ctx).Errorf("failed to get account groups from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get account groups from the store") + } + + for _, g := range groups { + g.LoadGroupPeers() + } + + return groups, nil +} + +func (s *SqlStore) GetResourceGroups(ctx context.Context, lockStrength LockingStrength, accountID, resourceID string) ([]*types.Group, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var groups []*types.Group + + likePattern := `%"ID":"` + resourceID + `"%` + + result := tx. + Preload(clause.Associations). + Where("resources LIKE ?", likePattern). + Find(&groups) + + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, result.Error + } + + for _, g := range groups { + g.LoadGroupPeers() + } + + return groups, nil +} + +func (s *SqlStore) GetAccountsCounter(ctx context.Context) (int64, error) { + var count int64 + result := s.db.Model(&types.Account{}).Count(&count) + if result.Error != nil { + return 0, fmt.Errorf("failed to get all accounts counter: %w", result.Error) + } + + return count, nil +} + +// GetCustomDomainsCounts returns the total and validated custom domain counts. +func (s *SqlStore) GetCustomDomainsCounts(ctx context.Context) (int64, int64, error) { + var total, validated int64 + if err := s.db.Model(&domain.Domain{}).Count(&total).Error; err != nil { + return 0, 0, err + } + if err := s.db.Model(&domain.Domain{}).Where("validated = ?", true).Count(&validated).Error; err != nil { + return 0, 0, err + } + return total, validated, nil +} + +func (s *SqlStore) GetAllAccounts(ctx context.Context) (all []*types.Account) { + var accounts []types.Account + result := s.db.Find(&accounts) + if result.Error != nil { + return all + } + + for _, account := range accounts { + if acc, err := s.GetAccount(ctx, account.Id); err == nil { + all = append(all, acc) + } + } + + return all +} + +func (s *SqlStore) GetAccountMeta(ctx context.Context, lockStrength LockingStrength, accountID string) (*types.AccountMeta, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var accountMeta types.AccountMeta + result := tx.Model(&types.Account{}). + Take(&accountMeta, idQueryCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("error when getting account meta %s from the store: %s", accountID, result.Error) + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewAccountNotFoundError(accountID) + } + return nil, status.NewGetAccountFromStoreError(result.Error) + } + + return &accountMeta, nil +} + +// GetAccountOnboarding retrieves the onboarding information for a specific account. +func (s *SqlStore) GetAccountOnboarding(ctx context.Context, accountID string) (*types.AccountOnboarding, error) { + var accountOnboarding types.AccountOnboarding + result := s.db.Model(&accountOnboarding).Take(&accountOnboarding, accountIDCondition, accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewAccountOnboardingNotFoundError(accountID) + } + log.WithContext(ctx).Errorf("error when getting account onboarding %s from the store: %s", accountID, result.Error) + return nil, status.NewGetAccountFromStoreError(result.Error) + } + + return &accountOnboarding, nil +} + +// SaveAccountOnboarding updates the onboarding information for a specific account. +func (s *SqlStore) SaveAccountOnboarding(ctx context.Context, onboarding *types.AccountOnboarding) error { + result := s.db.Clauses(clause.OnConflict{UpdateAll: true}).Create(onboarding) + if result.Error != nil { + log.WithContext(ctx).Errorf("error when saving account onboarding %s in the store: %s", onboarding.AccountID, result.Error) + return status.Errorf(status.Internal, "error when saving account onboarding %s in the store: %s", onboarding.AccountID, result.Error) + } + + return nil +} + +func (s *SqlStore) GetAccount(ctx context.Context, accountID string) (*types.Account, error) { + if s.pool != nil { + return s.getAccountPgx(ctx, accountID) + } + return s.getAccountGorm(ctx, accountID) +} + +func (s *SqlStore) getAccountGorm(ctx context.Context, accountID string) (*types.Account, error) { + start := time.Now() + defer func() { + elapsed := time.Since(start) + if elapsed > 1*time.Second { + log.WithContext(ctx).Tracef("GetAccount for account %s exceeded 1s, took: %v", accountID, elapsed) + } + }() + + var account types.Account + result := s.db.Model(&account). + Preload("UsersG.PATsG"). // have to be specified as this is nested reference + Preload("Policies.Rules"). + Preload("SetupKeysG"). + Preload("PeersG"). + Preload("UsersG"). + Preload("GroupsG.GroupPeers"). + Preload("RoutesG"). + Preload("NameServerGroupsG"). + Preload("PostureChecks"). + Preload("Networks"). + Preload("NetworkRouters"). + Preload("NetworkResources"). + Preload("Onboarding"). + Preload("Services.Targets"). + Take(&account, idQueryCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("error when getting account %s from the store: %s", accountID, result.Error) + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewAccountNotFoundError(accountID) + } + return nil, status.NewGetAccountFromStoreError(result.Error) + } + + account.SetupKeys = make(map[string]*types.SetupKey, len(account.SetupKeysG)) + for _, key := range account.SetupKeysG { + if key.UpdatedAt.IsZero() { + key.UpdatedAt = key.CreatedAt + } + if key.AutoGroups == nil { + key.AutoGroups = []string{} + } + account.SetupKeys[key.Key] = &key + } + account.SetupKeysG = nil + + account.Peers = make(map[string]*nbpeer.Peer, len(account.PeersG)) + for _, peer := range account.PeersG { + account.Peers[peer.ID] = &peer + } + account.PeersG = nil + account.Users = make(map[string]*types.User, len(account.UsersG)) + for _, user := range account.UsersG { + user.PATs = make(map[string]*types.PersonalAccessToken, len(user.PATs)) + for _, pat := range user.PATsG { + pat.UserID = "" + user.PATs[pat.ID] = &pat + } + if user.AutoGroups == nil { + user.AutoGroups = []string{} + } + if err := user.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt user: %w", err) + } + account.Users[user.Id] = &user + user.PATsG = nil + } + account.UsersG = nil + account.Groups = make(map[string]*types.Group, len(account.GroupsG)) + for _, group := range account.GroupsG { + group.Peers = make([]string, len(group.GroupPeers)) + for i, gp := range group.GroupPeers { + group.Peers[i] = gp.PeerID + } + if group.Resources == nil { + group.Resources = []types.Resource{} + } + account.Groups[group.ID] = group + } + account.GroupsG = nil + + account.Routes = make(map[route.ID]*route.Route, len(account.RoutesG)) + for _, route := range account.RoutesG { + account.Routes[route.ID] = &route + } + account.RoutesG = nil + account.NameServerGroups = make(map[string]*nbdns.NameServerGroup, len(account.NameServerGroupsG)) + for _, ns := range account.NameServerGroupsG { + ns.AccountID = "" + if ns.NameServers == nil { + ns.NameServers = []nbdns.NameServer{} + } + if ns.Groups == nil { + ns.Groups = []string{} + } + if ns.Domains == nil { + ns.Domains = []string{} + } + account.NameServerGroups[ns.ID] = &ns + } + account.NameServerGroupsG = nil + account.InitOnce() + return &account, nil +} + +func (s *SqlStore) getAccountPgx(ctx context.Context, accountID string) (*types.Account, error) { + account, err := s.getAccount(ctx, accountID) + if err != nil { + return nil, err + } + + var wg sync.WaitGroup + errChan := make(chan error, 12) + + wg.Add(1) + go func() { + defer wg.Done() + keys, err := s.getSetupKeys(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.SetupKeysG = keys + }() + + wg.Add(1) + go func() { + defer wg.Done() + peers, err := s.getPeers(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.PeersG = peers + }() + + wg.Add(1) + go func() { + defer wg.Done() + users, err := s.getUsers(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.UsersG = users + }() + + wg.Add(1) + go func() { + defer wg.Done() + groups, err := s.getGroups(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.GroupsG = groups + }() + + wg.Add(1) + go func() { + defer wg.Done() + policies, err := s.getPolicies(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.Policies = policies + }() + + wg.Add(1) + go func() { + defer wg.Done() + routes, err := s.getRoutes(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.RoutesG = routes + }() + + wg.Add(1) + go func() { + defer wg.Done() + nsgs, err := s.getNameServerGroups(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.NameServerGroupsG = nsgs + }() + + wg.Add(1) + go func() { + defer wg.Done() + checks, err := s.getPostureChecks(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.PostureChecks = checks + }() + + wg.Add(1) + go func() { + defer wg.Done() + services, err := s.getServices(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.Services = services + }() + + wg.Add(1) + go func() { + defer wg.Done() + networks, err := s.getNetworks(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.Networks = networks + }() + + wg.Add(1) + go func() { + defer wg.Done() + routers, err := s.getNetworkRouters(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.NetworkRouters = routers + }() + + wg.Add(1) + go func() { + defer wg.Done() + resources, err := s.getNetworkResources(ctx, accountID) + if err != nil { + errChan <- err + return + } + account.NetworkResources = resources + }() + + wg.Add(1) + go func() { + defer wg.Done() + err := s.getAccountOnboarding(ctx, accountID, account) + if err != nil { + errChan <- err + return + } + }() + + wg.Wait() + close(errChan) + for e := range errChan { + if e != nil { + return nil, e + } + } + + var userIDs []string + for _, u := range account.UsersG { + userIDs = append(userIDs, u.Id) + } + var policyIDs []string + for _, p := range account.Policies { + policyIDs = append(policyIDs, p.ID) + } + var groupIDs []string + for _, g := range account.GroupsG { + groupIDs = append(groupIDs, g.ID) + } + + wg.Add(3) + errChan = make(chan error, 3) + + var pats []types.PersonalAccessToken + go func() { + defer wg.Done() + var err error + pats, err = s.getPersonalAccessTokens(ctx, userIDs) + if err != nil { + errChan <- err + } + }() + + var rules []*types.PolicyRule + go func() { + defer wg.Done() + var err error + rules, err = s.getPolicyRules(ctx, policyIDs) + if err != nil { + errChan <- err + } + }() + + var groupPeers []types.GroupPeer + go func() { + defer wg.Done() + var err error + groupPeers, err = s.getGroupPeers(ctx, groupIDs) + if err != nil { + errChan <- err + } + }() + + wg.Wait() + close(errChan) + for e := range errChan { + if e != nil { + return nil, e + } + } + + patsByUserID := make(map[string][]*types.PersonalAccessToken) + for i := range pats { + pat := &pats[i] + patsByUserID[pat.UserID] = append(patsByUserID[pat.UserID], pat) + pat.UserID = "" + } + + rulesByPolicyID := make(map[string][]*types.PolicyRule) + for _, rule := range rules { + rulesByPolicyID[rule.PolicyID] = append(rulesByPolicyID[rule.PolicyID], rule) + } + + peersByGroupID := make(map[string][]string) + for _, gp := range groupPeers { + peersByGroupID[gp.GroupID] = append(peersByGroupID[gp.GroupID], gp.PeerID) + } + + account.SetupKeys = make(map[string]*types.SetupKey, len(account.SetupKeysG)) + for i := range account.SetupKeysG { + key := &account.SetupKeysG[i] + account.SetupKeys[key.Key] = key + } + + account.Peers = make(map[string]*nbpeer.Peer, len(account.PeersG)) + for i := range account.PeersG { + peer := &account.PeersG[i] + account.Peers[peer.ID] = peer + } + + account.Users = make(map[string]*types.User, len(account.UsersG)) + for i := range account.UsersG { + user := &account.UsersG[i] + if err := user.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt user: %w", err) + } + user.PATs = make(map[string]*types.PersonalAccessToken) + if userPats, ok := patsByUserID[user.Id]; ok { + for j := range userPats { + pat := userPats[j] + user.PATs[pat.ID] = pat + } + } + account.Users[user.Id] = user + } + + for i := range account.Policies { + policy := account.Policies[i] + if policyRules, ok := rulesByPolicyID[policy.ID]; ok { + policy.Rules = policyRules + } + } + + account.Groups = make(map[string]*types.Group, len(account.GroupsG)) + for i := range account.GroupsG { + group := account.GroupsG[i] + if peerIDs, ok := peersByGroupID[group.ID]; ok { + group.Peers = peerIDs + } + account.Groups[group.ID] = group + } + + account.Routes = make(map[route.ID]*route.Route, len(account.RoutesG)) + for i := range account.RoutesG { + route := &account.RoutesG[i] + account.Routes[route.ID] = route + } + + account.NameServerGroups = make(map[string]*nbdns.NameServerGroup, len(account.NameServerGroupsG)) + for i := range account.NameServerGroupsG { + nsg := &account.NameServerGroupsG[i] + nsg.AccountID = "" + account.NameServerGroups[nsg.ID] = nsg + } + + account.SetupKeysG = nil + account.PeersG = nil + account.UsersG = nil + account.GroupsG = nil + account.RoutesG = nil + account.NameServerGroupsG = nil + + return account, nil +} + +func (s *SqlStore) getAccount(ctx context.Context, accountID string) (*types.Account, error) { + var account types.Account + account.Network = &types.Network{} + const accountQuery = ` + SELECT + id, created_by, created_at, domain, domain_category, is_domain_primary_account, + -- Embedded Network + network_identifier, network_net, network_dns, network_serial, + -- Embedded DNSSettings + dns_settings_disabled_management_groups, + -- Embedded Settings + settings_peer_login_expiration_enabled, settings_peer_login_expiration, + settings_peer_inactivity_expiration_enabled, settings_peer_inactivity_expiration, + settings_regular_users_view_blocked, settings_groups_propagation_enabled, + settings_jwt_groups_enabled, settings_jwt_groups_claim_name, settings_jwt_allow_groups, + settings_routing_peer_dns_resolution_enabled, settings_dns_domain, settings_network_range, + settings_lazy_connection_enabled, + -- Embedded ExtraSettings + settings_extra_peer_approval_enabled, settings_extra_user_approval_required, + settings_extra_integrated_validator, settings_extra_integrated_validator_groups + FROM accounts WHERE id = $1` + + var ( + sPeerLoginExpirationEnabled sql.NullBool + sPeerLoginExpiration sql.NullInt64 + sPeerInactivityExpirationEnabled sql.NullBool + sPeerInactivityExpiration sql.NullInt64 + sRegularUsersViewBlocked sql.NullBool + sGroupsPropagationEnabled sql.NullBool + sJWTGroupsEnabled sql.NullBool + sJWTGroupsClaimName sql.NullString + sJWTAllowGroups sql.NullString + sRoutingPeerDNSResolutionEnabled sql.NullBool + sDNSDomain sql.NullString + sNetworkRange sql.NullString + sLazyConnectionEnabled sql.NullBool + sExtraPeerApprovalEnabled sql.NullBool + sExtraUserApprovalRequired sql.NullBool + sExtraIntegratedValidator sql.NullString + sExtraIntegratedValidatorGroups sql.NullString + networkNet sql.NullString + dnsSettingsDisabledGroups sql.NullString + networkIdentifier sql.NullString + networkDns sql.NullString + networkSerial sql.NullInt64 + createdAt sql.NullTime + ) + err := s.pool.QueryRow(ctx, accountQuery, accountID).Scan( + &account.Id, &account.CreatedBy, &createdAt, &account.Domain, &account.DomainCategory, &account.IsDomainPrimaryAccount, + &networkIdentifier, &networkNet, &networkDns, &networkSerial, + &dnsSettingsDisabledGroups, + &sPeerLoginExpirationEnabled, &sPeerLoginExpiration, + &sPeerInactivityExpirationEnabled, &sPeerInactivityExpiration, + &sRegularUsersViewBlocked, &sGroupsPropagationEnabled, + &sJWTGroupsEnabled, &sJWTGroupsClaimName, &sJWTAllowGroups, + &sRoutingPeerDNSResolutionEnabled, &sDNSDomain, &sNetworkRange, + &sLazyConnectionEnabled, + &sExtraPeerApprovalEnabled, &sExtraUserApprovalRequired, + &sExtraIntegratedValidator, &sExtraIntegratedValidatorGroups, + ) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, status.NewAccountNotFoundError(accountID) + } + return nil, status.NewGetAccountFromStoreError(err) + } + + account.Settings = &types.Settings{Extra: &types.ExtraSettings{}} + if networkNet.Valid { + _ = json.Unmarshal([]byte(networkNet.String), &account.Network.Net) + } + if createdAt.Valid { + account.CreatedAt = createdAt.Time + } + if dnsSettingsDisabledGroups.Valid { + _ = json.Unmarshal([]byte(dnsSettingsDisabledGroups.String), &account.DNSSettings.DisabledManagementGroups) + } + if networkIdentifier.Valid { + account.Network.Identifier = networkIdentifier.String + } + if networkDns.Valid { + account.Network.Dns = networkDns.String + } + if networkSerial.Valid { + account.Network.Serial = uint64(networkSerial.Int64) + } + if sPeerLoginExpirationEnabled.Valid { + account.Settings.PeerLoginExpirationEnabled = sPeerLoginExpirationEnabled.Bool + } + if sPeerLoginExpiration.Valid { + account.Settings.PeerLoginExpiration = time.Duration(sPeerLoginExpiration.Int64) + } + if sPeerInactivityExpirationEnabled.Valid { + account.Settings.PeerInactivityExpirationEnabled = sPeerInactivityExpirationEnabled.Bool + } + if sPeerInactivityExpiration.Valid { + account.Settings.PeerInactivityExpiration = time.Duration(sPeerInactivityExpiration.Int64) + } + if sRegularUsersViewBlocked.Valid { + account.Settings.RegularUsersViewBlocked = sRegularUsersViewBlocked.Bool + } + if sGroupsPropagationEnabled.Valid { + account.Settings.GroupsPropagationEnabled = sGroupsPropagationEnabled.Bool + } + if sJWTGroupsEnabled.Valid { + account.Settings.JWTGroupsEnabled = sJWTGroupsEnabled.Bool + } + if sJWTGroupsClaimName.Valid { + account.Settings.JWTGroupsClaimName = sJWTGroupsClaimName.String + } + if sRoutingPeerDNSResolutionEnabled.Valid { + account.Settings.RoutingPeerDNSResolutionEnabled = sRoutingPeerDNSResolutionEnabled.Bool + } + if sDNSDomain.Valid { + account.Settings.DNSDomain = sDNSDomain.String + } + if sLazyConnectionEnabled.Valid { + account.Settings.LazyConnectionEnabled = sLazyConnectionEnabled.Bool + } + if sJWTAllowGroups.Valid { + _ = json.Unmarshal([]byte(sJWTAllowGroups.String), &account.Settings.JWTAllowGroups) + } + if sNetworkRange.Valid { + _ = json.Unmarshal([]byte(sNetworkRange.String), &account.Settings.NetworkRange) + } + + if sExtraPeerApprovalEnabled.Valid { + account.Settings.Extra.PeerApprovalEnabled = sExtraPeerApprovalEnabled.Bool + } + if sExtraUserApprovalRequired.Valid { + account.Settings.Extra.UserApprovalRequired = sExtraUserApprovalRequired.Bool + } + if sExtraIntegratedValidator.Valid { + account.Settings.Extra.IntegratedValidator = sExtraIntegratedValidator.String + } + if sExtraIntegratedValidatorGroups.Valid { + _ = json.Unmarshal([]byte(sExtraIntegratedValidatorGroups.String), &account.Settings.Extra.IntegratedValidatorGroups) + } + account.InitOnce() + return &account, nil +} + +func (s *SqlStore) getSetupKeys(ctx context.Context, accountID string) ([]types.SetupKey, error) { + const query = `SELECT id, account_id, key, key_secret, name, type, created_at, expires_at, updated_at, + revoked, used_times, last_used, auto_groups, usage_limit, ephemeral, allow_extra_dns_labels FROM setup_keys WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + + keys, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (types.SetupKey, error) { + var sk types.SetupKey + var autoGroups []byte + var skCreatedAt, expiresAt, updatedAt, lastUsed sql.NullTime + var revoked, ephemeral, allowExtraDNSLabels sql.NullBool + var usedTimes, usageLimit sql.NullInt64 + + err := row.Scan(&sk.Id, &sk.AccountID, &sk.Key, &sk.KeySecret, &sk.Name, &sk.Type, &skCreatedAt, + &expiresAt, &updatedAt, &revoked, &usedTimes, &lastUsed, &autoGroups, &usageLimit, &ephemeral, &allowExtraDNSLabels) + + if err == nil { + if expiresAt.Valid { + sk.ExpiresAt = &expiresAt.Time + } + if skCreatedAt.Valid { + sk.CreatedAt = skCreatedAt.Time + } + if updatedAt.Valid { + sk.UpdatedAt = updatedAt.Time + if sk.UpdatedAt.IsZero() { + sk.UpdatedAt = sk.CreatedAt + } + } + if lastUsed.Valid { + sk.LastUsed = &lastUsed.Time + } + if revoked.Valid { + sk.Revoked = revoked.Bool + } + if usedTimes.Valid { + sk.UsedTimes = int(usedTimes.Int64) + } + if usageLimit.Valid { + sk.UsageLimit = int(usageLimit.Int64) + } + if ephemeral.Valid { + sk.Ephemeral = ephemeral.Bool + } + if allowExtraDNSLabels.Valid { + sk.AllowExtraDNSLabels = allowExtraDNSLabels.Bool + } + if autoGroups != nil { + _ = json.Unmarshal(autoGroups, &sk.AutoGroups) + } else { + sk.AutoGroups = []string{} + } + } + return sk, err + }) + if err != nil { + return nil, err + } + return keys, nil +} + +func (s *SqlStore) getPeers(ctx context.Context, accountID string) ([]nbpeer.Peer, error) { + const query = `SELECT id, account_id, key, ip, name, dns_label, user_id, ssh_key, ssh_enabled, login_expiration_enabled, + inactivity_expiration_enabled, last_login, created_at, ephemeral, extra_dns_labels, allow_extra_dns_labels, meta_hostname, + meta_go_os, meta_kernel, meta_core, meta_platform, meta_os, meta_os_version, meta_wt_version, meta_ui_version, + meta_kernel_version, meta_network_addresses, meta_system_serial_number, meta_system_product_name, meta_system_manufacturer, + meta_environment, meta_flags, meta_files, peer_status_last_seen, peer_status_connected, peer_status_login_expired, + peer_status_requires_approval, location_connection_ip, location_country_code, location_city_name, + location_geo_name_id, proxy_meta_embedded, proxy_meta_cluster FROM peers WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + + peers, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (nbpeer.Peer, error) { + var p nbpeer.Peer + p.Status = &nbpeer.PeerStatus{} + var ( + lastLogin, createdAt sql.NullTime + sshEnabled, loginExpirationEnabled, inactivityExpirationEnabled, ephemeral, allowExtraDNSLabels sql.NullBool + peerStatusLastSeen sql.NullTime + peerStatusConnected, peerStatusLoginExpired, peerStatusRequiresApproval, proxyEmbedded sql.NullBool + ip, extraDNS, netAddr, env, flags, files, connIP []byte + metaHostname, metaGoOS, metaKernel, metaCore, metaPlatform sql.NullString + metaOS, metaOSVersion, metaWtVersion, metaUIVersion, metaKernelVersion sql.NullString + metaSystemSerialNumber, metaSystemProductName, metaSystemManufacturer sql.NullString + locationCountryCode, locationCityName, proxyCluster sql.NullString + locationGeoNameID sql.NullInt64 + ) + + err := row.Scan(&p.ID, &p.AccountID, &p.Key, &ip, &p.Name, &p.DNSLabel, &p.UserID, &p.SSHKey, &sshEnabled, + &loginExpirationEnabled, &inactivityExpirationEnabled, &lastLogin, &createdAt, &ephemeral, &extraDNS, + &allowExtraDNSLabels, &metaHostname, &metaGoOS, &metaKernel, &metaCore, &metaPlatform, + &metaOS, &metaOSVersion, &metaWtVersion, &metaUIVersion, &metaKernelVersion, &netAddr, + &metaSystemSerialNumber, &metaSystemProductName, &metaSystemManufacturer, &env, &flags, &files, + &peerStatusLastSeen, &peerStatusConnected, &peerStatusLoginExpired, &peerStatusRequiresApproval, &connIP, + &locationCountryCode, &locationCityName, &locationGeoNameID, &proxyEmbedded, &proxyCluster) + + if err == nil { + if lastLogin.Valid { + p.LastLogin = &lastLogin.Time + } + if createdAt.Valid { + p.CreatedAt = createdAt.Time + } + if sshEnabled.Valid { + p.SSHEnabled = sshEnabled.Bool + } + if loginExpirationEnabled.Valid { + p.LoginExpirationEnabled = loginExpirationEnabled.Bool + } + if inactivityExpirationEnabled.Valid { + p.InactivityExpirationEnabled = inactivityExpirationEnabled.Bool + } + if ephemeral.Valid { + p.Ephemeral = ephemeral.Bool + } + if allowExtraDNSLabels.Valid { + p.AllowExtraDNSLabels = allowExtraDNSLabels.Bool + } + if peerStatusLastSeen.Valid { + p.Status.LastSeen = peerStatusLastSeen.Time + } + if peerStatusConnected.Valid { + p.Status.Connected = peerStatusConnected.Bool + } + if peerStatusLoginExpired.Valid { + p.Status.LoginExpired = peerStatusLoginExpired.Bool + } + if peerStatusRequiresApproval.Valid { + p.Status.RequiresApproval = peerStatusRequiresApproval.Bool + } + if metaHostname.Valid { + p.Meta.Hostname = metaHostname.String + } + if metaGoOS.Valid { + p.Meta.GoOS = metaGoOS.String + } + if metaKernel.Valid { + p.Meta.Kernel = metaKernel.String + } + if metaCore.Valid { + p.Meta.Core = metaCore.String + } + if metaPlatform.Valid { + p.Meta.Platform = metaPlatform.String + } + if metaOS.Valid { + p.Meta.OS = metaOS.String + } + if metaOSVersion.Valid { + p.Meta.OSVersion = metaOSVersion.String + } + if metaWtVersion.Valid { + p.Meta.WtVersion = metaWtVersion.String + } + if metaUIVersion.Valid { + p.Meta.UIVersion = metaUIVersion.String + } + if metaKernelVersion.Valid { + p.Meta.KernelVersion = metaKernelVersion.String + } + if metaSystemSerialNumber.Valid { + p.Meta.SystemSerialNumber = metaSystemSerialNumber.String + } + if metaSystemProductName.Valid { + p.Meta.SystemProductName = metaSystemProductName.String + } + if metaSystemManufacturer.Valid { + p.Meta.SystemManufacturer = metaSystemManufacturer.String + } + if locationCountryCode.Valid { + p.Location.CountryCode = locationCountryCode.String + } + if locationCityName.Valid { + p.Location.CityName = locationCityName.String + } + if locationGeoNameID.Valid { + p.Location.GeoNameID = uint(locationGeoNameID.Int64) + } + if proxyEmbedded.Valid { + p.ProxyMeta.Embedded = proxyEmbedded.Bool + } + if proxyCluster.Valid { + p.ProxyMeta.Cluster = proxyCluster.String + } + if ip != nil { + _ = json.Unmarshal(ip, &p.IP) + } + if extraDNS != nil { + _ = json.Unmarshal(extraDNS, &p.ExtraDNSLabels) + } + if netAddr != nil { + _ = json.Unmarshal(netAddr, &p.Meta.NetworkAddresses) + } + if env != nil { + _ = json.Unmarshal(env, &p.Meta.Environment) + } + if flags != nil { + _ = json.Unmarshal(flags, &p.Meta.Flags) + } + if files != nil { + _ = json.Unmarshal(files, &p.Meta.Files) + } + if connIP != nil { + _ = json.Unmarshal(connIP, &p.Location.ConnectionIP) + } + } + return p, err + }) + if err != nil { + return nil, err + } + return peers, nil +} + +func (s *SqlStore) getUsers(ctx context.Context, accountID string) ([]types.User, error) { + const query = `SELECT id, account_id, role, is_service_user, non_deletable, service_user_name, auto_groups, blocked, pending_approval, last_login, created_at, issued, integration_ref_id, integration_ref_integration_type, email, name FROM users WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + users, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (types.User, error) { + var u types.User + var autoGroups []byte + var lastLogin, createdAt sql.NullTime + var isServiceUser, nonDeletable, blocked, pendingApproval sql.NullBool + err := row.Scan(&u.Id, &u.AccountID, &u.Role, &isServiceUser, &nonDeletable, &u.ServiceUserName, &autoGroups, &blocked, &pendingApproval, &lastLogin, &createdAt, &u.Issued, &u.IntegrationReference.ID, &u.IntegrationReference.IntegrationType, &u.Email, &u.Name) + if err == nil { + if lastLogin.Valid { + u.LastLogin = &lastLogin.Time + } + if createdAt.Valid { + u.CreatedAt = createdAt.Time + } + if isServiceUser.Valid { + u.IsServiceUser = isServiceUser.Bool + } + if nonDeletable.Valid { + u.NonDeletable = nonDeletable.Bool + } + if blocked.Valid { + u.Blocked = blocked.Bool + } + if pendingApproval.Valid { + u.PendingApproval = pendingApproval.Bool + } + if autoGroups != nil { + _ = json.Unmarshal(autoGroups, &u.AutoGroups) + } else { + u.AutoGroups = []string{} + } + } + return u, err + }) + if err != nil { + return nil, err + } + return users, nil +} + +func (s *SqlStore) getGroups(ctx context.Context, accountID string) ([]*types.Group, error) { + const query = `SELECT id, account_id, name, issued, resources, integration_ref_id, integration_ref_integration_type FROM groups WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + groups, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (*types.Group, error) { + var g types.Group + var resources []byte + var refID sql.NullInt64 + var refType sql.NullString + err := row.Scan(&g.ID, &g.AccountID, &g.Name, &g.Issued, &resources, &refID, &refType) + if err == nil { + if refID.Valid { + g.IntegrationReference.ID = int(refID.Int64) + } + if refType.Valid { + g.IntegrationReference.IntegrationType = refType.String + } + if resources != nil { + _ = json.Unmarshal(resources, &g.Resources) + } else { + g.Resources = []types.Resource{} + } + g.GroupPeers = []types.GroupPeer{} + g.Peers = []string{} + } + return &g, err + }) + if err != nil { + return nil, err + } + return groups, nil +} + +func (s *SqlStore) getPolicies(ctx context.Context, accountID string) ([]*types.Policy, error) { + const query = `SELECT id, account_id, name, description, enabled, source_posture_checks FROM policies WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + policies, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (*types.Policy, error) { + var p types.Policy + var checks []byte + var enabled sql.NullBool + err := row.Scan(&p.ID, &p.AccountID, &p.Name, &p.Description, &enabled, &checks) + if err == nil { + if enabled.Valid { + p.Enabled = enabled.Bool + } + if checks != nil { + _ = json.Unmarshal(checks, &p.SourcePostureChecks) + } + } + return &p, err + }) + if err != nil { + return nil, err + } + return policies, nil +} + +func (s *SqlStore) getRoutes(ctx context.Context, accountID string) ([]route.Route, error) { + const query = `SELECT id, account_id, network, domains, keep_route, net_id, description, peer, peer_groups, network_type, masquerade, metric, enabled, groups, access_control_groups, skip_auto_apply FROM routes WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + routes, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (route.Route, error) { + var r route.Route + var network, domains, peerGroups, groups, accessGroups []byte + var keepRoute, masquerade, enabled, skipAutoApply sql.NullBool + var metric sql.NullInt64 + err := row.Scan(&r.ID, &r.AccountID, &network, &domains, &keepRoute, &r.NetID, &r.Description, &r.Peer, &peerGroups, &r.NetworkType, &masquerade, &metric, &enabled, &groups, &accessGroups, &skipAutoApply) + if err == nil { + if keepRoute.Valid { + r.KeepRoute = keepRoute.Bool + } + if masquerade.Valid { + r.Masquerade = masquerade.Bool + } + if enabled.Valid { + r.Enabled = enabled.Bool + } + if skipAutoApply.Valid { + r.SkipAutoApply = skipAutoApply.Bool + } + if metric.Valid { + r.Metric = int(metric.Int64) + } + if network != nil { + _ = json.Unmarshal(network, &r.Network) + } + if domains != nil { + _ = json.Unmarshal(domains, &r.Domains) + } + if peerGroups != nil { + _ = json.Unmarshal(peerGroups, &r.PeerGroups) + } + if groups != nil { + _ = json.Unmarshal(groups, &r.Groups) + } + if accessGroups != nil { + _ = json.Unmarshal(accessGroups, &r.AccessControlGroups) + } + } + return r, err + }) + if err != nil { + return nil, err + } + return routes, nil +} + +func (s *SqlStore) getNameServerGroups(ctx context.Context, accountID string) ([]nbdns.NameServerGroup, error) { + const query = `SELECT id, account_id, name, description, name_servers, groups, "primary", domains, enabled, search_domains_enabled FROM name_server_groups WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + nsgs, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (nbdns.NameServerGroup, error) { + var n nbdns.NameServerGroup + var ns, groups, domains []byte + var primary, enabled, searchDomainsEnabled sql.NullBool + err := row.Scan(&n.ID, &n.AccountID, &n.Name, &n.Description, &ns, &groups, &primary, &domains, &enabled, &searchDomainsEnabled) + if err == nil { + if primary.Valid { + n.Primary = primary.Bool + } + if enabled.Valid { + n.Enabled = enabled.Bool + } + if searchDomainsEnabled.Valid { + n.SearchDomainsEnabled = searchDomainsEnabled.Bool + } + if ns != nil { + _ = json.Unmarshal(ns, &n.NameServers) + } else { + n.NameServers = []nbdns.NameServer{} + } + if groups != nil { + _ = json.Unmarshal(groups, &n.Groups) + } else { + n.Groups = []string{} + } + if domains != nil { + _ = json.Unmarshal(domains, &n.Domains) + } else { + n.Domains = []string{} + } + } + return n, err + }) + if err != nil { + return nil, err + } + return nsgs, nil +} + +func (s *SqlStore) getPostureChecks(ctx context.Context, accountID string) ([]*posture.Checks, error) { + const query = `SELECT id, account_id, name, description, checks FROM posture_checks WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + checks, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (*posture.Checks, error) { + var c posture.Checks + var checksDef []byte + err := row.Scan(&c.ID, &c.AccountID, &c.Name, &c.Description, &checksDef) + if err == nil && checksDef != nil { + _ = json.Unmarshal(checksDef, &c.Checks) + } + return &c, err + }) + if err != nil { + return nil, err + } + return checks, nil +} + +func (s *SqlStore) getServices(ctx context.Context, accountID string) ([]*rpservice.Service, error) { + const serviceQuery = `SELECT id, account_id, name, domain, enabled, auth, + meta_created_at, meta_certificate_issued_at, meta_status, proxy_cluster, + pass_host_header, rewrite_redirects, session_private_key, session_public_key, + mode, listen_port, port_auto_assigned, source, source_peer, terminated + FROM services WHERE account_id = $1` + + const targetsQuery = `SELECT id, account_id, service_id, path, host, port, protocol, + target_id, target_type, enabled + FROM targets WHERE service_id = ANY($1)` + + serviceRows, err := s.pool.Query(ctx, serviceQuery, accountID) + if err != nil { + return nil, err + } + + services, err := pgx.CollectRows(serviceRows, func(row pgx.CollectableRow) (*rpservice.Service, error) { + var s rpservice.Service + var auth []byte + var createdAt, certIssuedAt sql.NullTime + var status, proxyCluster, sessionPrivateKey, sessionPublicKey sql.NullString + var mode, source, sourcePeer sql.NullString + var terminated, portAutoAssigned sql.NullBool + var listenPort sql.NullInt64 + err := row.Scan( + &s.ID, + &s.AccountID, + &s.Name, + &s.Domain, + &s.Enabled, + &auth, + &createdAt, + &certIssuedAt, + &status, + &proxyCluster, + &s.PassHostHeader, + &s.RewriteRedirects, + &sessionPrivateKey, + &sessionPublicKey, + &mode, + &listenPort, + &portAutoAssigned, + &source, + &sourcePeer, + &terminated, + ) + if err != nil { + return nil, err + } + + if auth != nil { + if err := json.Unmarshal(auth, &s.Auth); err != nil { + return nil, err + } + } + + s.Meta = rpservice.Meta{} + if createdAt.Valid { + s.Meta.CreatedAt = createdAt.Time + } + if certIssuedAt.Valid { + t := certIssuedAt.Time + s.Meta.CertificateIssuedAt = &t + } + if status.Valid { + s.Meta.Status = status.String + } + if proxyCluster.Valid { + s.ProxyCluster = proxyCluster.String + } + if sessionPrivateKey.Valid { + s.SessionPrivateKey = sessionPrivateKey.String + } + if sessionPublicKey.Valid { + s.SessionPublicKey = sessionPublicKey.String + } + if mode.Valid { + s.Mode = mode.String + } + if source.Valid { + s.Source = source.String + } + if sourcePeer.Valid { + s.SourcePeer = sourcePeer.String + } + if terminated.Valid { + s.Terminated = terminated.Bool + } + if portAutoAssigned.Valid { + s.PortAutoAssigned = portAutoAssigned.Bool + } + if listenPort.Valid { + s.ListenPort = uint16(listenPort.Int64) + } + s.Targets = []*rpservice.Target{} + return &s, nil + }) + if err != nil { + return nil, err + } + + if len(services) == 0 { + return services, nil + } + + serviceIDs := make([]string, len(services)) + serviceMap := make(map[string]*rpservice.Service) + for i, s := range services { + serviceIDs[i] = s.ID + serviceMap[s.ID] = s + } + + targetRows, err := s.pool.Query(ctx, targetsQuery, serviceIDs) + if err != nil { + return nil, err + } + + targets, err := pgx.CollectRows(targetRows, func(row pgx.CollectableRow) (*rpservice.Target, error) { + var t rpservice.Target + var path sql.NullString + err := row.Scan( + &t.ID, + &t.AccountID, + &t.ServiceID, + &path, + &t.Host, + &t.Port, + &t.Protocol, + &t.TargetId, + &t.TargetType, + &t.Enabled, + ) + if err != nil { + return nil, err + } + if path.Valid { + t.Path = &path.String + } + return &t, nil + }) + if err != nil { + return nil, err + } + + for _, target := range targets { + if service, ok := serviceMap[target.ServiceID]; ok { + service.Targets = append(service.Targets, target) + } + } + + return services, nil +} + +func (s *SqlStore) getNetworks(ctx context.Context, accountID string) ([]*networkTypes.Network, error) { + const query = `SELECT id, account_id, name, description FROM networks WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + networks, err := pgx.CollectRows(rows, pgx.RowToStructByName[networkTypes.Network]) + if err != nil { + return nil, err + } + result := make([]*networkTypes.Network, len(networks)) + for i := range networks { + result[i] = &networks[i] + } + return result, nil +} + +func (s *SqlStore) getNetworkRouters(ctx context.Context, accountID string) ([]*routerTypes.NetworkRouter, error) { + const query = `SELECT id, network_id, account_id, peer, peer_groups, masquerade, metric, enabled FROM network_routers WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + routers, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (routerTypes.NetworkRouter, error) { + var r routerTypes.NetworkRouter + var peerGroups []byte + var masquerade, enabled sql.NullBool + var metric sql.NullInt64 + err := row.Scan(&r.ID, &r.NetworkID, &r.AccountID, &r.Peer, &peerGroups, &masquerade, &metric, &enabled) + if err == nil { + if masquerade.Valid { + r.Masquerade = masquerade.Bool + } + if enabled.Valid { + r.Enabled = enabled.Bool + } + if metric.Valid { + r.Metric = int(metric.Int64) + } + if peerGroups != nil { + _ = json.Unmarshal(peerGroups, &r.PeerGroups) + } + } + return r, err + }) + if err != nil { + return nil, err + } + result := make([]*routerTypes.NetworkRouter, len(routers)) + for i := range routers { + result[i] = &routers[i] + } + return result, nil +} + +func (s *SqlStore) getNetworkResources(ctx context.Context, accountID string) ([]*resourceTypes.NetworkResource, error) { + const query = `SELECT id, network_id, account_id, name, description, type, domain, prefix, enabled FROM network_resources WHERE account_id = $1` + rows, err := s.pool.Query(ctx, query, accountID) + if err != nil { + return nil, err + } + resources, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (resourceTypes.NetworkResource, error) { + var r resourceTypes.NetworkResource + var prefix []byte + var enabled sql.NullBool + err := row.Scan(&r.ID, &r.NetworkID, &r.AccountID, &r.Name, &r.Description, &r.Type, &r.Domain, &prefix, &enabled) + if err == nil { + if enabled.Valid { + r.Enabled = enabled.Bool + } + if prefix != nil { + _ = json.Unmarshal(prefix, &r.Prefix) + } + } + return r, err + }) + if err != nil { + return nil, err + } + result := make([]*resourceTypes.NetworkResource, len(resources)) + for i := range resources { + result[i] = &resources[i] + } + return result, nil +} + +func (s *SqlStore) getAccountOnboarding(ctx context.Context, accountID string, account *types.Account) error { + const query = `SELECT account_id, onboarding_flow_pending, signup_form_pending, created_at, updated_at FROM account_onboardings WHERE account_id = $1` + var onboardingFlowPending, signupFormPending sql.NullBool + var createdAt, updatedAt sql.NullTime + err := s.pool.QueryRow(ctx, query, accountID).Scan( + &account.Onboarding.AccountID, + &onboardingFlowPending, + &signupFormPending, + &createdAt, + &updatedAt, + ) + if err != nil && !errors.Is(err, pgx.ErrNoRows) { + return err + } + if createdAt.Valid { + account.Onboarding.CreatedAt = createdAt.Time + } + if updatedAt.Valid { + account.Onboarding.UpdatedAt = updatedAt.Time + } + if onboardingFlowPending.Valid { + account.Onboarding.OnboardingFlowPending = onboardingFlowPending.Bool + } + if signupFormPending.Valid { + account.Onboarding.SignupFormPending = signupFormPending.Bool + } + return nil +} + +func (s *SqlStore) getPersonalAccessTokens(ctx context.Context, userIDs []string) ([]types.PersonalAccessToken, error) { + if len(userIDs) == 0 { + return nil, nil + } + const query = `SELECT id, user_id, name, hashed_token, expiration_date, created_by, created_at, last_used FROM personal_access_tokens WHERE user_id = ANY($1)` + rows, err := s.pool.Query(ctx, query, userIDs) + if err != nil { + return nil, err + } + pats, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (types.PersonalAccessToken, error) { + var pat types.PersonalAccessToken + var expirationDate, lastUsed, createdAt sql.NullTime + err := row.Scan(&pat.ID, &pat.UserID, &pat.Name, &pat.HashedToken, &expirationDate, &pat.CreatedBy, &createdAt, &lastUsed) + if err == nil { + if expirationDate.Valid { + pat.ExpirationDate = &expirationDate.Time + } + if createdAt.Valid { + pat.CreatedAt = createdAt.Time + } + if lastUsed.Valid { + pat.LastUsed = &lastUsed.Time + } + } + return pat, err + }) + if err != nil { + return nil, err + } + return pats, nil +} + +func (s *SqlStore) getPolicyRules(ctx context.Context, policyIDs []string) ([]*types.PolicyRule, error) { + if len(policyIDs) == 0 { + return nil, nil + } + const query = `SELECT id, policy_id, name, description, enabled, action, destinations, destination_resource, sources, source_resource, bidirectional, protocol, ports, port_ranges, authorized_groups, authorized_user FROM policy_rules WHERE policy_id = ANY($1)` + rows, err := s.pool.Query(ctx, query, policyIDs) + if err != nil { + return nil, err + } + rules, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (*types.PolicyRule, error) { + var r types.PolicyRule + var dest, destRes, sources, sourceRes, ports, portRanges, authorizedGroups []byte + var enabled, bidirectional sql.NullBool + var authorizedUser sql.NullString + err := row.Scan(&r.ID, &r.PolicyID, &r.Name, &r.Description, &enabled, &r.Action, &dest, &destRes, &sources, &sourceRes, &bidirectional, &r.Protocol, &ports, &portRanges, &authorizedGroups, &authorizedUser) + if err == nil { + if enabled.Valid { + r.Enabled = enabled.Bool + } + if bidirectional.Valid { + r.Bidirectional = bidirectional.Bool + } + if dest != nil { + _ = json.Unmarshal(dest, &r.Destinations) + } + if destRes != nil { + _ = json.Unmarshal(destRes, &r.DestinationResource) + } + if sources != nil { + _ = json.Unmarshal(sources, &r.Sources) + } + if sourceRes != nil { + _ = json.Unmarshal(sourceRes, &r.SourceResource) + } + if ports != nil { + _ = json.Unmarshal(ports, &r.Ports) + } + if portRanges != nil { + _ = json.Unmarshal(portRanges, &r.PortRanges) + } + if authorizedGroups != nil { + _ = json.Unmarshal(authorizedGroups, &r.AuthorizedGroups) + } + if authorizedUser.Valid { + r.AuthorizedUser = authorizedUser.String + } + } + return &r, err + }) + if err != nil { + return nil, err + } + return rules, nil +} + +func (s *SqlStore) getGroupPeers(ctx context.Context, groupIDs []string) ([]types.GroupPeer, error) { + if len(groupIDs) == 0 { + return nil, nil + } + const query = `SELECT account_id, group_id, peer_id FROM group_peers WHERE group_id = ANY($1)` + rows, err := s.pool.Query(ctx, query, groupIDs) + if err != nil { + return nil, err + } + groupPeers, err := pgx.CollectRows(rows, pgx.RowToStructByName[types.GroupPeer]) + if err != nil { + return nil, err + } + return groupPeers, nil +} + +func (s *SqlStore) GetAccountByUser(ctx context.Context, userID string) (*types.Account, error) { + var user types.User + result := s.db.Select("account_id").Take(&user, idQueryCondition, userID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") + } + return nil, status.NewGetAccountFromStoreError(result.Error) + } + + if user.AccountID == "" { + return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") + } + + return s.GetAccount(ctx, user.AccountID) +} + +func (s *SqlStore) GetAccountByPeerID(ctx context.Context, peerID string) (*types.Account, error) { + var peer nbpeer.Peer + result := s.db.Select("account_id").Take(&peer, idQueryCondition, peerID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") + } + return nil, status.NewGetAccountFromStoreError(result.Error) + } + + if peer.AccountID == "" { + return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") + } + + return s.GetAccount(ctx, peer.AccountID) +} + +func (s *SqlStore) GetAccountByPeerPubKey(ctx context.Context, peerKey string) (*types.Account, error) { + var peer nbpeer.Peer + result := s.db.Select("account_id").Take(&peer, GetKeyQueryCondition(s), peerKey) + + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") + } + return nil, status.NewGetAccountFromStoreError(result.Error) + } + + if peer.AccountID == "" { + return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") + } + + return s.GetAccount(ctx, peer.AccountID) +} + +func (s *SqlStore) GetAnyAccountID(ctx context.Context) (string, error) { + var account types.Account + result := s.db.Select("id").Order("created_at desc").Limit(1).Find(&account) + if result.Error != nil { + return "", status.NewGetAccountFromStoreError(result.Error) + } + if result.RowsAffected == 0 { + return "", status.Errorf(status.NotFound, "account not found: index lookup failed") + } + + return account.Id, nil +} + +func (s *SqlStore) GetAccountIDByPeerPubKey(ctx context.Context, peerKey string) (string, error) { + var peer nbpeer.Peer + var accountID string + result := s.db.Model(&peer).Select("account_id").Where(GetKeyQueryCondition(s), peerKey).Take(&accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return "", status.Errorf(status.NotFound, "account not found: index lookup failed") + } + return "", status.NewGetAccountFromStoreError(result.Error) + } + + return accountID, nil +} + +func (s *SqlStore) GetAccountIDByUserID(ctx context.Context, lockStrength LockingStrength, userID string) (string, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var accountID string + result := tx.Model(&types.User{}). + Select("account_id").Where(idQueryCondition, userID).Take(&accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return "", status.Errorf(status.NotFound, "account not found: index lookup failed") + } + return "", status.NewGetAccountFromStoreError(result.Error) + } + + return accountID, nil +} + +func (s *SqlStore) GetAccountIDByPeerID(ctx context.Context, lockStrength LockingStrength, peerID string) (string, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var accountID string + result := tx.Model(&nbpeer.Peer{}). + Select("account_id").Where(idQueryCondition, peerID).Take(&accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return "", status.Errorf(status.NotFound, "peer %s account not found", peerID) + } + return "", status.NewGetAccountFromStoreError(result.Error) + } + + return accountID, nil +} + +func (s *SqlStore) GetAccountIDBySetupKey(ctx context.Context, setupKey string) (string, error) { + var accountID string + result := s.db.Model(&types.SetupKey{}).Select("account_id").Where(GetKeyQueryCondition(s), setupKey).Take(&accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return "", status.NewSetupKeyNotFoundError(setupKey) + } + log.WithContext(ctx).Errorf("failed to get account ID by setup key from store: %v", result.Error) + return "", status.Errorf(status.Internal, "failed to get account ID by setup key from store") + } + + if accountID == "" { + return "", status.Errorf(status.NotFound, "account not found: index lookup failed") + } + + return accountID, nil +} + +func (s *SqlStore) GetTakenIPs(ctx context.Context, lockStrength LockingStrength, accountID string) ([]net.IP, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var ipJSONStrings []string + + // Fetch the IP addresses as JSON strings + result := tx.Model(&nbpeer.Peer{}). + Where("account_id = ?", accountID). + Pluck("ip", &ipJSONStrings) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "no peers found for the account") + } + return nil, status.Errorf(status.Internal, "issue getting IPs from store: %s", result.Error) + } + + // Convert the JSON strings to net.IP objects + ips := make([]net.IP, len(ipJSONStrings)) + for i, ipJSON := range ipJSONStrings { + var ip net.IP + if err := json.Unmarshal([]byte(ipJSON), &ip); err != nil { + return nil, status.Errorf(status.Internal, "issue parsing IP JSON from store") + } + ips[i] = ip + } + + return ips, nil +} + +func (s *SqlStore) GetPeerLabelsInAccount(ctx context.Context, lockStrength LockingStrength, accountID string, dnsLabel string) ([]string, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var labels []string + result := tx.Model(&nbpeer.Peer{}). + Where("account_id = ? AND dns_label LIKE ?", accountID, dnsLabel+"%"). + Pluck("dns_label", &labels) + + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "no peers found for the account") + } + log.WithContext(ctx).Errorf("error when getting dns labels from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "issue getting dns labels from store: %s", result.Error) + } + + return labels, nil +} + +func (s *SqlStore) GetAccountNetwork(ctx context.Context, lockStrength LockingStrength, accountID string) (*types.Network, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var accountNetwork types.AccountNetwork + if err := tx.Model(&types.Account{}).Where(idQueryCondition, accountID).Take(&accountNetwork).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, status.NewAccountNotFoundError(accountID) + } + return nil, status.Errorf(status.Internal, "issue getting network from store: %s", err) + } + return accountNetwork.Network, nil +} + +func (s *SqlStore) GetPeerByPeerPubKey(ctx context.Context, lockStrength LockingStrength, peerKey string) (*nbpeer.Peer, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var peer nbpeer.Peer + result := tx.Take(&peer, GetKeyQueryCondition(s), peerKey) + + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewPeerNotFoundError(peerKey) + } + return nil, status.Errorf(status.Internal, "issue getting peer from store: %s", result.Error) + } + + return &peer, nil +} + +func (s *SqlStore) GetAccountSettings(ctx context.Context, lockStrength LockingStrength, accountID string) (*types.Settings, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var accountSettings types.AccountSettings + if err := tx.Model(&types.Account{}).Where(idQueryCondition, accountID).Take(&accountSettings).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "settings not found") + } + return nil, status.Errorf(status.Internal, "issue getting settings from store: %s", err) + } + return accountSettings.Settings, nil +} + +func (s *SqlStore) GetAccountCreatedBy(ctx context.Context, lockStrength LockingStrength, accountID string) (string, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var createdBy string + result := tx.Model(&types.Account{}). + Select("created_by").Take(&createdBy, idQueryCondition, accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return "", status.NewAccountNotFoundError(accountID) + } + return "", status.NewGetAccountFromStoreError(result.Error) + } + + return createdBy, nil +} + +// SaveUserLastLogin stores the last login time for a user in DB. +func (s *SqlStore) SaveUserLastLogin(ctx context.Context, accountID, userID string, lastLogin time.Time) error { + var user types.User + result := s.db.Take(&user, accountAndIDQueryCondition, accountID, userID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return status.NewUserNotFoundError(userID) + } + return status.NewGetUserFromStoreError() + } + + if !lastLogin.IsZero() { + user.LastLogin = &lastLogin + return s.db.Save(&user).Error + } + + return nil +} + +func (s *SqlStore) GetPostureCheckByChecksDefinition(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error) { + definitionJSON, err := json.Marshal(checks) + if err != nil { + return nil, err + } + + var postureCheck posture.Checks + err = s.db.Where("account_id = ? AND checks = ?", accountID, string(definitionJSON)).Take(&postureCheck).Error + if err != nil { + return nil, err + } + + return &postureCheck, nil +} + +// Close closes the underlying DB connection +func (s *SqlStore) Close(_ context.Context) error { + sql, err := s.db.DB() + if err != nil { + return fmt.Errorf("get db: %w", err) + } + return sql.Close() +} + +// GetStoreEngine returns underlying store engine +func (s *SqlStore) GetStoreEngine() types.Engine { + return s.storeEngine +} + +// NewSqliteStore creates a new SQLite store. +func NewSqliteStore(ctx context.Context, dataDir string, metrics telemetry.AppMetrics, skipMigration bool) (*SqlStore, error) { + storeFile := storeSqliteFileName + if envFile, ok := os.LookupEnv("NB_STORE_ENGINE_SQLITE_FILE"); ok && envFile != "" { + storeFile = envFile + } + + // Separate file path from any SQLite URI query parameters (e.g., "store.db?mode=rwc") + filePath, query, hasQuery := strings.Cut(storeFile, "?") + + connStr := filePath + if !filepath.IsAbs(filePath) { + connStr = filepath.Join(dataDir, filePath) + } + + // Append query parameters: user-provided take precedence, otherwise default to cache=shared on non-Windows + if hasQuery { + connStr += "?" + query + } else if runtime.GOOS != "windows" { + // To avoid `The process cannot access the file because it is being used by another process` on Windows + connStr += "?cache=shared" + } + + db, err := gorm.Open(sqlite.Open(connStr), getGormConfig()) + if err != nil { + return nil, err + } + + return NewSqlStore(ctx, db, types.SqliteStoreEngine, metrics, skipMigration) +} + +// NewPostgresqlStore creates a new Postgres store. +func NewPostgresqlStore(ctx context.Context, dsn string, metrics telemetry.AppMetrics, skipMigration bool) (*SqlStore, error) { + db, err := gorm.Open(postgres.Open(dsn), getGormConfig()) + if err != nil { + return nil, err + } + pool, err := connectToPgDb(context.Background(), dsn) + if err != nil { + return nil, err + } + store, err := NewSqlStore(ctx, db, types.PostgresStoreEngine, metrics, skipMigration) + if err != nil { + pool.Close() + return nil, err + } + store.pool = pool + return store, nil +} + +func connectToPgDb(ctx context.Context, dsn string) (*pgxpool.Pool, error) { + config, err := pgxpool.ParseConfig(dsn) + if err != nil { + return nil, fmt.Errorf("unable to parse database config: %w", err) + } + + config.MaxConns = pgMaxConnections + config.MinConns = pgMinConnections + config.MaxConnLifetime = pgMaxConnLifetime + config.HealthCheckPeriod = pgHealthCheckPeriod + + pool, err := pgxpool.NewWithConfig(ctx, config) + if err != nil { + return nil, fmt.Errorf("unable to create connection pool: %w", err) + } + + if err := pool.Ping(ctx); err != nil { + pool.Close() + return nil, fmt.Errorf("unable to ping database: %w", err) + } + + return pool, nil +} + +// NewMysqlStore creates a new MySQL store. +func NewMysqlStore(ctx context.Context, dsn string, metrics telemetry.AppMetrics, skipMigration bool) (*SqlStore, error) { + db, err := gorm.Open(mysql.Open(dsn+"?charset=utf8&parseTime=True&loc=Local"), getGormConfig()) + if err != nil { + return nil, err + } + + return NewSqlStore(ctx, db, types.MysqlStoreEngine, metrics, skipMigration) +} + +func getGormConfig() *gorm.Config { + return &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + CreateBatchSize: 400, + } +} + +// newPostgresStore initializes a new Postgres store. +func newPostgresStore(ctx context.Context, metrics telemetry.AppMetrics, skipMigration bool) (Store, error) { + dsn, ok := lookupDSNEnv(postgresDsnEnv, postgresDsnEnvLegacy) + if !ok { + return nil, fmt.Errorf("%s is not set", postgresDsnEnv) + } + return NewPostgresqlStore(ctx, dsn, metrics, skipMigration) +} + +// newMysqlStore initializes a new MySQL store. +func newMysqlStore(ctx context.Context, metrics telemetry.AppMetrics, skipMigration bool) (Store, error) { + dsn, ok := lookupDSNEnv(mysqlDsnEnv, mysqlDsnEnvLegacy) + if !ok { + return nil, fmt.Errorf("%s is not set", mysqlDsnEnv) + } + return NewMysqlStore(ctx, dsn, metrics, skipMigration) +} + +// NewSqliteStoreFromFileStore restores a store from FileStore and stores SQLite DB in the file located in datadir. +func NewSqliteStoreFromFileStore(ctx context.Context, fileStore *FileStore, dataDir string, metrics telemetry.AppMetrics, skipMigration bool) (*SqlStore, error) { + store, err := NewSqliteStore(ctx, dataDir, metrics, skipMigration) + if err != nil { + return nil, err + } + + err = store.SaveInstallationID(ctx, fileStore.InstallationID) + if err != nil { + return nil, err + } + + for _, account := range fileStore.GetAllAccounts(ctx) { + _, err = account.GetGroupAll() + if err != nil { + if err := account.AddAllGroup(false); err != nil { + return nil, err + } + } + + err := store.SaveAccount(ctx, account) + if err != nil { + return nil, err + } + } + + return store, nil +} + +// NewPostgresqlStoreFromSqlStore restores a store from SqlStore and stores Postgres DB. +func NewPostgresqlStoreFromSqlStore(ctx context.Context, sqliteStore *SqlStore, dsn string, metrics telemetry.AppMetrics) (*SqlStore, error) { + store, err := NewPostgresqlStoreForTests(ctx, dsn, metrics, false) + if err != nil { + return nil, err + } + + err = store.SaveInstallationID(ctx, sqliteStore.GetInstallationID()) + if err != nil { + return nil, err + } + + for _, account := range sqliteStore.GetAllAccounts(ctx) { + err := store.SaveAccount(ctx, account) + if err != nil { + return nil, err + } + } + + return store, nil +} + +// used for tests only +func NewPostgresqlStoreForTests(ctx context.Context, dsn string, metrics telemetry.AppMetrics, skipMigration bool) (*SqlStore, error) { + db, err := gorm.Open(postgres.Open(dsn), getGormConfig()) + if err != nil { + return nil, err + } + pool, err := connectToPgDbForTests(context.Background(), dsn) + if err != nil { + return nil, err + } + store, err := NewSqlStore(ctx, db, types.PostgresStoreEngine, metrics, skipMigration) + if err != nil { + pool.Close() + return nil, err + } + store.pool = pool + return store, nil +} + +// used for tests only +func connectToPgDbForTests(ctx context.Context, dsn string) (*pgxpool.Pool, error) { + config, err := pgxpool.ParseConfig(dsn) + if err != nil { + return nil, fmt.Errorf("unable to parse database config: %w", err) + } + + config.MaxConns = 5 + config.MinConns = 1 + config.MaxConnLifetime = 30 * time.Second + config.HealthCheckPeriod = 10 * time.Second + + pool, err := pgxpool.NewWithConfig(ctx, config) + if err != nil { + return nil, fmt.Errorf("unable to create connection pool: %w", err) + } + + if err := pool.Ping(ctx); err != nil { + pool.Close() + return nil, fmt.Errorf("unable to ping database: %w", err) + } + + return pool, nil +} + +// NewMysqlStoreFromSqlStore restores a store from SqlStore and stores MySQL DB. +func NewMysqlStoreFromSqlStore(ctx context.Context, sqliteStore *SqlStore, dsn string, metrics telemetry.AppMetrics) (*SqlStore, error) { + store, err := NewMysqlStore(ctx, dsn, metrics, false) + if err != nil { + return nil, err + } + + err = store.SaveInstallationID(ctx, sqliteStore.GetInstallationID()) + if err != nil { + return nil, err + } + + for _, account := range sqliteStore.GetAllAccounts(ctx) { + err := store.SaveAccount(ctx, account) + if err != nil { + return nil, err + } + } + + return store, nil +} + +func (s *SqlStore) GetSetupKeyBySecret(ctx context.Context, lockStrength LockingStrength, key string) (*types.SetupKey, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var setupKey types.SetupKey + result := tx. + Take(&setupKey, GetKeyQueryCondition(s), key) + + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.PreconditionFailed, "setup key not found") + } + log.WithContext(ctx).Errorf("failed to get setup key by secret from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get setup key by secret from store") + } + return &setupKey, nil +} + +func (s *SqlStore) IncrementSetupKeyUsage(ctx context.Context, setupKeyID string) error { + result := s.db.Model(&types.SetupKey{}). + Where(idQueryCondition, setupKeyID). + Updates(map[string]interface{}{ + "used_times": gorm.Expr("used_times + 1"), + "last_used": time.Now(), + }) + + if result.Error != nil { + return status.Errorf(status.Internal, "issue incrementing setup key usage count: %s", result.Error) + } + + if result.RowsAffected == 0 { + return status.NewSetupKeyNotFoundError(setupKeyID) + } + + return nil +} + +// AddPeerToAllGroup adds a peer to the 'All' group. Method always needs to run in a transaction +func (s *SqlStore) AddPeerToAllGroup(ctx context.Context, accountID string, peerID string) error { + var groupID string + _ = s.db.Model(types.Group{}). + Select("id"). + Where("account_id = ? AND name = ?", accountID, "All"). + Limit(1). + Scan(&groupID) + + if groupID == "" { + return status.Errorf(status.NotFound, "group 'All' not found for account %s", accountID) + } + + err := s.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "group_id"}, {Name: "peer_id"}}, + DoNothing: true, + }).Create(&types.GroupPeer{ + AccountID: accountID, + GroupID: groupID, + PeerID: peerID, + }).Error + + if err != nil { + return status.Errorf(status.Internal, "error adding peer to group 'All': %v", err) + } + + return nil +} + +// AddPeerToGroup adds a peer to a group +func (s *SqlStore) AddPeerToGroup(ctx context.Context, accountID, peerID, groupID string) error { + peer := &types.GroupPeer{ + AccountID: accountID, + GroupID: groupID, + PeerID: peerID, + } + + err := s.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "group_id"}, {Name: "peer_id"}}, + DoNothing: true, + }).Create(peer).Error + + if err != nil { + log.WithContext(ctx).Errorf("failed to add peer %s to group %s for account %s: %v", peerID, groupID, accountID, err) + return status.Errorf(status.Internal, "failed to add peer to group") + } + + return nil +} + +// RemovePeerFromGroup removes a peer from a group +func (s *SqlStore) RemovePeerFromGroup(ctx context.Context, peerID string, groupID string) error { + err := s.db. + Delete(&types.GroupPeer{}, "group_id = ? AND peer_id = ?", groupID, peerID).Error + + if err != nil { + log.WithContext(ctx).Errorf("failed to remove peer %s from group %s: %v", peerID, groupID, err) + return status.Errorf(status.Internal, "failed to remove peer from group") + } + + return nil +} + +// RemovePeerFromAllGroups removes a peer from all groups +func (s *SqlStore) RemovePeerFromAllGroups(ctx context.Context, peerID string) error { + err := s.db. + Delete(&types.GroupPeer{}, "peer_id = ?", peerID).Error + + if err != nil { + log.WithContext(ctx).Errorf("failed to remove peer %s from all groups: %v", peerID, err) + return status.Errorf(status.Internal, "failed to remove peer from all groups") + } + + return nil +} + +// AddResourceToGroup adds a resource to a group. Method always needs to run n a transaction +func (s *SqlStore) AddResourceToGroup(ctx context.Context, accountId string, groupID string, resource *types.Resource) error { + var group types.Group + result := s.db.Where(accountAndIDQueryCondition, accountId, groupID).Take(&group) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return status.NewGroupNotFoundError(groupID) + } + + return status.Errorf(status.Internal, "issue finding group: %s", result.Error) + } + + for _, res := range group.Resources { + if res.ID == resource.ID { + return nil + } + } + + group.Resources = append(group.Resources, *resource) + + if err := s.db.Save(&group).Error; err != nil { + return status.Errorf(status.Internal, "issue updating group: %s", err) + } + + return nil +} + +// RemoveResourceFromGroup removes a resource from a group. Method always needs to run in a transaction +func (s *SqlStore) RemoveResourceFromGroup(ctx context.Context, accountId string, groupID string, resourceID string) error { + var group types.Group + result := s.db.Where(accountAndIDQueryCondition, accountId, groupID).Take(&group) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return status.NewGroupNotFoundError(groupID) + } + + return status.Errorf(status.Internal, "issue finding group: %s", result.Error) + } + + for i, res := range group.Resources { + if res.ID == resourceID { + group.Resources = append(group.Resources[:i], group.Resources[i+1:]...) + break + } + } + + if err := s.db.Save(&group).Error; err != nil { + return status.Errorf(status.Internal, "issue updating group: %s", err) + } + + return nil +} + +// GetPeerGroups retrieves all groups assigned to a specific peer in a given account. +func (s *SqlStore) GetPeerGroups(ctx context.Context, lockStrength LockingStrength, accountId string, peerId string) ([]*types.Group, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var groups []*types.Group + query := tx. + Joins("JOIN group_peers ON group_peers.group_id = groups.id"). + Where("group_peers.peer_id = ?", peerId). + Preload(clause.Associations). + Find(&groups) + + if query.Error != nil { + return nil, query.Error + } + + for _, group := range groups { + group.LoadGroupPeers() + } + + return groups, nil +} + +// GetPeerGroupIDs retrieves all group IDs assigned to a specific peer in a given account. +func (s *SqlStore) GetPeerGroupIDs(ctx context.Context, lockStrength LockingStrength, accountId string, peerId string) ([]string, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var groupIDs []string + query := tx. + Model(&types.GroupPeer{}). + Where("account_id = ? AND peer_id = ?", accountId, peerId). + Pluck("group_id", &groupIDs) + + if query.Error != nil { + if errors.Is(query.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "no groups found for peer %s in account %s", peerId, accountId) + } + log.WithContext(ctx).Errorf("failed to get group IDs for peer %s in account %s: %v", peerId, accountId, query.Error) + return nil, status.Errorf(status.Internal, "failed to get group IDs for peer from store") + } + + return groupIDs, nil +} + +// GetAccountPeers retrieves peers for an account. +func (s *SqlStore) GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) { + var peers []*nbpeer.Peer + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + query := tx.Where(accountIDCondition, accountID) + + if nameFilter != "" { + query = query.Where("name LIKE ?", "%"+nameFilter+"%") + } + if ipFilter != "" { + query = query.Where("ip LIKE ?", "%"+ipFilter+"%") + } + + if err := query.Find(&peers).Error; err != nil { + log.WithContext(ctx).Errorf("failed to get peers from the store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get peers from store") + } + + return peers, nil +} + +// GetUserPeers retrieves peers for a user. +func (s *SqlStore) GetUserPeers(ctx context.Context, lockStrength LockingStrength, accountID, userID string) ([]*nbpeer.Peer, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var peers []*nbpeer.Peer + + // Exclude peers added via setup keys, as they are not user-specific and have an empty user_id. + if userID == "" { + return peers, nil + } + + result := tx. + Find(&peers, "account_id = ? AND user_id = ?", accountID, userID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to get peers from the store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get peers from store") + } + + return peers, nil +} + +func (s *SqlStore) AddPeerToAccount(ctx context.Context, peer *nbpeer.Peer) error { + if err := s.db.Create(peer).Error; err != nil { + return status.Errorf(status.Internal, "issue adding peer to account: %s", err) + } + + return nil +} + +// GetPeerByID retrieves a peer by its ID and account ID. +func (s *SqlStore) GetPeerByID(ctx context.Context, lockStrength LockingStrength, accountID, peerID string) (*nbpeer.Peer, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var peer *nbpeer.Peer + result := tx. + Take(&peer, accountAndIDQueryCondition, accountID, peerID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewPeerNotFoundError(peerID) + } + return nil, status.Errorf(status.Internal, "failed to get peer from store") + } + + return peer, nil +} + +// GetPeersByIDs retrieves peers by their IDs and account ID. +func (s *SqlStore) GetPeersByIDs(ctx context.Context, lockStrength LockingStrength, accountID string, peerIDs []string) (map[string]*nbpeer.Peer, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var peers []*nbpeer.Peer + result := tx.Find(&peers, accountAndIDsQueryCondition, accountID, peerIDs) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get peers by ID's from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get peers by ID's from the store") + } + + peersMap := make(map[string]*nbpeer.Peer) + for _, peer := range peers { + peersMap[peer.ID] = peer + } + + return peersMap, nil +} + +// GetAccountPeersWithExpiration retrieves a list of peers that have login expiration enabled and added by a user. +func (s *SqlStore) GetAccountPeersWithExpiration(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*nbpeer.Peer, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var peers []*nbpeer.Peer + result := tx. + Where("login_expiration_enabled = ? AND peer_status_login_expired != ? AND user_id IS NOT NULL AND user_id != ''", true, true). + Find(&peers, accountIDCondition, accountID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to get peers with expiration from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get peers with expiration from store") + } + + return peers, nil +} + +// GetAccountPeersWithInactivity retrieves a list of peers that have login expiration enabled and added by a user. +func (s *SqlStore) GetAccountPeersWithInactivity(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*nbpeer.Peer, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var peers []*nbpeer.Peer + result := tx. + Where("inactivity_expiration_enabled = ? AND user_id IS NOT NULL AND user_id != ''", true). + Find(&peers, accountIDCondition, accountID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to get peers with inactivity from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get peers with inactivity from store") + } + + return peers, nil +} + +// GetAllEphemeralPeers retrieves all peers with Ephemeral set to true across all accounts, optimized for batch processing. +func (s *SqlStore) GetAllEphemeralPeers(ctx context.Context, lockStrength LockingStrength) ([]*nbpeer.Peer, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var allEphemeralPeers, batchPeers []*nbpeer.Peer + result := tx. + Where("ephemeral = ?", true). + FindInBatches(&batchPeers, 1000, func(tx *gorm.DB, batch int) error { + allEphemeralPeers = append(allEphemeralPeers, batchPeers...) + return nil + }) + + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to retrieve ephemeral peers: %s", result.Error) + return nil, fmt.Errorf("failed to retrieve ephemeral peers") + } + + return allEphemeralPeers, nil +} + +// DeletePeer removes a peer from the store. +func (s *SqlStore) DeletePeer(ctx context.Context, accountID string, peerID string) error { + result := s.db.Delete(&nbpeer.Peer{}, accountAndIDQueryCondition, accountID, peerID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to delete peer from the store: %s", err) + return status.Errorf(status.Internal, "failed to delete peer from store") + } + + if result.RowsAffected == 0 { + return status.NewPeerNotFoundError(peerID) + } + + return nil +} + +func (s *SqlStore) IncrementNetworkSerial(ctx context.Context, accountId string) error { + result := s.db.Model(&types.Account{}).Where(idQueryCondition, accountId).Update("network_serial", gorm.Expr("network_serial + 1")) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to increment network serial count in store: %v", result.Error) + return status.Errorf(status.Internal, "failed to increment network serial count in store") + } + return nil +} + +func (s *SqlStore) ExecuteInTransaction(ctx context.Context, operation func(store Store) error) error { + timeoutCtx, cancel := context.WithTimeout(context.Background(), s.transactionTimeout) + defer cancel() + + startTime := time.Now() + tx := s.db.WithContext(timeoutCtx).Begin() + if tx.Error != nil { + return tx.Error + } + defer func() { + if r := recover(); r != nil { + tx.Rollback() + panic(r) + } + }() + + if s.storeEngine == types.PostgresStoreEngine { + if err := tx.Exec("SET LOCAL statement_timeout = '1min'").Error; err != nil { + tx.Rollback() + return fmt.Errorf("failed to set statement timeout: %w", err) + } + if err := tx.Exec("SET LOCAL lock_timeout = '1min'").Error; err != nil { + tx.Rollback() + return fmt.Errorf("failed to set lock timeout: %w", err) + } + } + + // For MySQL, disable FK checks within this transaction to avoid deadlocks + // This is session-scoped and doesn't require SUPER privileges + if s.storeEngine == types.MysqlStoreEngine { + if err := tx.Exec("SET FOREIGN_KEY_CHECKS = 0").Error; err != nil { + tx.Rollback() + return fmt.Errorf("failed to disable FK checks: %w", err) + } + } + + repo := s.withTx(tx) + err := operation(repo) + if err != nil { + tx.Rollback() + if errors.Is(err, context.DeadlineExceeded) || errors.Is(timeoutCtx.Err(), context.DeadlineExceeded) { + log.WithContext(ctx).Warnf("transaction exceeded %s timeout after %v, stack: %s", s.transactionTimeout, time.Since(startTime), debug.Stack()) + } + return err + } + + // Re-enable FK checks before commit (optional, as transaction end resets it) + if s.storeEngine == types.MysqlStoreEngine { + if err := tx.Exec("SET FOREIGN_KEY_CHECKS = 1").Error; err != nil { + tx.Rollback() + return fmt.Errorf("failed to re-enable FK checks: %w", err) + } + } + + err = tx.Commit().Error + if err != nil { + if errors.Is(err, context.DeadlineExceeded) || errors.Is(timeoutCtx.Err(), context.DeadlineExceeded) { + log.WithContext(ctx).Warnf("transaction commit exceeded %s timeout after %v, stack: %s", s.transactionTimeout, time.Since(startTime), debug.Stack()) + } + return err + } + + log.WithContext(ctx).Tracef("transaction took %v", time.Since(startTime)) + if s.metrics != nil { + s.metrics.StoreMetrics().CountTransactionDuration(time.Since(startTime)) + } + + return nil +} + +func (s *SqlStore) withTx(tx *gorm.DB) Store { + return &SqlStore{ + db: tx, + storeEngine: s.storeEngine, + fieldEncrypt: s.fieldEncrypt, + } +} + +// transaction wraps a GORM transaction with MySQL-specific FK checks handling +// Use this instead of db.Transaction() directly to avoid deadlocks on MySQL/Aurora +func (s *SqlStore) transaction(fn func(*gorm.DB) error) error { + return s.db.Transaction(func(tx *gorm.DB) error { + // For MySQL, disable FK checks within this transaction to avoid deadlocks + // This is session-scoped and doesn't require SUPER privileges + if s.storeEngine == types.MysqlStoreEngine { + if err := tx.Exec("SET FOREIGN_KEY_CHECKS = 0").Error; err != nil { + return fmt.Errorf("failed to disable FK checks: %w", err) + } + } + + err := fn(tx) + + // Re-enable FK checks before commit (optional, as transaction end resets it) + if s.storeEngine == types.MysqlStoreEngine && err == nil { + if fkErr := tx.Exec("SET FOREIGN_KEY_CHECKS = 1").Error; fkErr != nil { + return fmt.Errorf("failed to re-enable FK checks: %w", fkErr) + } + } + + return err + }) +} + +func (s *SqlStore) GetDB() *gorm.DB { + return s.db +} + +// SetFieldEncrypt sets the field encryptor for encrypting sensitive user data. +func (s *SqlStore) SetFieldEncrypt(enc *crypt.FieldEncrypt) { + s.fieldEncrypt = enc +} + +func (s *SqlStore) GetAccountDNSSettings(ctx context.Context, lockStrength LockingStrength, accountID string) (*types.DNSSettings, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var accountDNSSettings types.AccountDNSSettings + result := tx.Model(&types.Account{}). + Take(&accountDNSSettings, idQueryCondition, accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewAccountNotFoundError(accountID) + } + log.WithContext(ctx).Errorf("failed to get dns settings from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get dns settings from store") + } + return &accountDNSSettings.DNSSettings, nil +} + +// AccountExists checks whether an account exists by the given ID. +func (s *SqlStore) AccountExists(ctx context.Context, lockStrength LockingStrength, id string) (bool, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var accountID string + result := tx.Model(&types.Account{}). + Select("id").Take(&accountID, idQueryCondition, id) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return false, nil + } + return false, result.Error + } + + return accountID != "", nil +} + +// GetAccountDomainAndCategory retrieves the Domain and DomainCategory fields for an account based on the given accountID. +func (s *SqlStore) GetAccountDomainAndCategory(ctx context.Context, lockStrength LockingStrength, accountID string) (string, string, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var account types.Account + result := tx.Model(&types.Account{}).Select("domain", "domain_category"). + Where(idQueryCondition, accountID).Take(&account) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return "", "", status.Errorf(status.NotFound, "account not found") + } + return "", "", status.Errorf(status.Internal, "failed to get domain category from store: %v", result.Error) + } + + return account.Domain, account.DomainCategory, nil +} + +// GetGroupByID retrieves a group by ID and account ID. +func (s *SqlStore) GetGroupByID(ctx context.Context, lockStrength LockingStrength, accountID, groupID string) (*types.Group, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var group *types.Group + result := tx.Preload(clause.Associations).Take(&group, accountAndIDQueryCondition, accountID, groupID) + if err := result.Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, status.NewGroupNotFoundError(groupID) + } + log.WithContext(ctx).Errorf("failed to get group from store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get group from store") + } + + group.LoadGroupPeers() + + return group, nil +} + +// GetGroupByName retrieves a group by name and account ID. +func (s *SqlStore) GetGroupByName(ctx context.Context, lockStrength LockingStrength, accountID, groupName string) (*types.Group, error) { + tx := s.db + + var group types.Group + + // TODO: This fix is accepted for now, but if we need to handle this more frequently + // we may need to reconsider changing the types. + query := tx.Preload(clause.Associations) + + result := query. + Model(&types.Group{}). + Joins("LEFT JOIN group_peers ON group_peers.group_id = groups.id"). + Where("groups.account_id = ? AND groups.name = ?", accountID, groupName). + Group("groups.id"). + Order("COUNT(group_peers.peer_id) DESC"). + Limit(1). + First(&group) + if err := result.Error; err != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewGroupNotFoundError(groupName) + } + log.WithContext(ctx).Errorf("failed to get group by name from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get group by name from store") + } + + group.LoadGroupPeers() + + return &group, nil +} + +// GetGroupsByIDs retrieves groups by their IDs and account ID. +func (s *SqlStore) GetGroupsByIDs(ctx context.Context, lockStrength LockingStrength, accountID string, groupIDs []string) (map[string]*types.Group, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var groups []*types.Group + result := tx.Preload(clause.Associations).Find(&groups, accountAndIDsQueryCondition, accountID, groupIDs) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get groups by ID's from store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get groups by ID's from store") + } + + groupsMap := make(map[string]*types.Group) + for _, group := range groups { + group.LoadGroupPeers() + groupsMap[group.ID] = group + } + + return groupsMap, nil +} + +// CreateGroup creates a group in the store. +func (s *SqlStore) CreateGroup(ctx context.Context, group *types.Group) error { + if group == nil { + return status.Errorf(status.InvalidArgument, "group is nil") + } + + if err := s.db.Omit(clause.Associations).Create(group).Error; err != nil { + log.WithContext(ctx).Errorf("failed to save group to store: %v", err) + return status.Errorf(status.Internal, "failed to save group to store") + } + + return nil +} + +// UpdateGroup updates a group in the store. +func (s *SqlStore) UpdateGroup(ctx context.Context, group *types.Group) error { + if group == nil { + return status.Errorf(status.InvalidArgument, "group is nil") + } + + if err := s.db.Omit(clause.Associations).Save(group).Error; err != nil { + log.WithContext(ctx).Errorf("failed to save group to store: %v", err) + return status.Errorf(status.Internal, "failed to save group to store") + } + + return nil +} + +// DeleteGroup deletes a group from the database. +func (s *SqlStore) DeleteGroup(ctx context.Context, accountID, groupID string) error { + result := s.db.Select(clause.Associations). + Delete(&types.Group{}, accountAndIDQueryCondition, accountID, groupID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to delete group from store: %s", result.Error) + return status.Errorf(status.Internal, "failed to delete group from store") + } + + if result.RowsAffected == 0 { + return status.NewGroupNotFoundError(groupID) + } + + return nil +} + +// DeleteGroups deletes groups from the database. +func (s *SqlStore) DeleteGroups(ctx context.Context, accountID string, groupIDs []string) error { + result := s.db.Select(clause.Associations). + Delete(&types.Group{}, accountAndIDsQueryCondition, accountID, groupIDs) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete groups from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete groups from store") + } + + return nil +} + +// GetAccountPolicies retrieves policies for an account. +func (s *SqlStore) GetAccountPolicies(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*types.Policy, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var policies []*types.Policy + result := tx. + Preload(clause.Associations).Find(&policies, accountIDCondition, accountID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to get policies from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get policies from store") + } + + return policies, nil +} + +// GetPolicyByID retrieves a policy by its ID and account ID. +func (s *SqlStore) GetPolicyByID(ctx context.Context, lockStrength LockingStrength, accountID, policyID string) (*types.Policy, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var policy *types.Policy + + result := tx.Preload(clause.Associations). + Take(&policy, accountAndIDQueryCondition, accountID, policyID) + if err := result.Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, status.NewPolicyNotFoundError(policyID) + } + log.WithContext(ctx).Errorf("failed to get policy from store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get policy from store") + } + + return policy, nil +} + +func (s *SqlStore) CreatePolicy(ctx context.Context, policy *types.Policy) error { + result := s.db.Create(policy) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to create policy in store: %s", result.Error) + return status.Errorf(status.Internal, "failed to create policy in store") + } + + return nil +} + +// SavePolicy saves a policy to the database. +func (s *SqlStore) SavePolicy(ctx context.Context, policy *types.Policy) error { + result := s.db.Session(&gorm.Session{FullSaveAssociations: true}).Save(policy) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to save policy to the store: %s", err) + return status.Errorf(status.Internal, "failed to save policy to store") + } + return nil +} + +func (s *SqlStore) DeletePolicy(ctx context.Context, accountID, policyID string) error { + return s.transaction(func(tx *gorm.DB) error { + if err := tx.Where("policy_id = ?", policyID).Delete(&types.PolicyRule{}).Error; err != nil { + return fmt.Errorf("delete policy rules: %w", err) + } + + result := tx. + Where(accountAndIDQueryCondition, accountID, policyID). + Delete(&types.Policy{}) + + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to delete policy from store: %s", err) + return status.Errorf(status.Internal, "failed to delete policy from store") + } + + if result.RowsAffected == 0 { + return status.NewPolicyNotFoundError(policyID) + } + + return nil + }) +} + +func (s *SqlStore) GetPolicyRulesByResourceID(ctx context.Context, lockStrength LockingStrength, accountID string, resourceID string) ([]*types.PolicyRule, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var policyRules []*types.PolicyRule + resourceIDPattern := `%"ID":"` + resourceID + `"%` + result := tx.Where("source_resource LIKE ? OR destination_resource LIKE ?", resourceIDPattern, resourceIDPattern). + Find(&policyRules) + + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get policy rules for resource id from store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get policy rules for resource id from store") + } + + return policyRules, nil +} + +// GetAccountPostureChecks retrieves posture checks for an account. +func (s *SqlStore) GetAccountPostureChecks(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*posture.Checks, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var postureChecks []*posture.Checks + result := tx.Find(&postureChecks, accountIDCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get posture checks from store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get posture checks from store") + } + + return postureChecks, nil +} + +// GetPostureChecksByID retrieves posture checks by their ID and account ID. +func (s *SqlStore) GetPostureChecksByID(ctx context.Context, lockStrength LockingStrength, accountID, postureChecksID string) (*posture.Checks, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var postureCheck *posture.Checks + result := tx. + Take(&postureCheck, accountAndIDQueryCondition, accountID, postureChecksID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewPostureChecksNotFoundError(postureChecksID) + } + log.WithContext(ctx).Errorf("failed to get posture check from store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get posture check from store") + } + + return postureCheck, nil +} + +// GetPostureChecksByIDs retrieves posture checks by their IDs and account ID. +func (s *SqlStore) GetPostureChecksByIDs(ctx context.Context, lockStrength LockingStrength, accountID string, postureChecksIDs []string) (map[string]*posture.Checks, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var postureChecks []*posture.Checks + result := tx.Find(&postureChecks, accountAndIDsQueryCondition, accountID, postureChecksIDs) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get posture checks by ID's from store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get posture checks by ID's from store") + } + + postureChecksMap := make(map[string]*posture.Checks) + for _, postureCheck := range postureChecks { + postureChecksMap[postureCheck.ID] = postureCheck + } + + return postureChecksMap, nil +} + +// SavePostureChecks saves a posture checks to the database. +func (s *SqlStore) SavePostureChecks(ctx context.Context, postureCheck *posture.Checks) error { + result := s.db.Save(postureCheck) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save posture checks to store: %s", result.Error) + return status.Errorf(status.Internal, "failed to save posture checks to store") + } + + return nil +} + +// DeletePostureChecks deletes a posture checks from the database. +func (s *SqlStore) DeletePostureChecks(ctx context.Context, accountID, postureChecksID string) error { + result := s.db.Delete(&posture.Checks{}, accountAndIDQueryCondition, accountID, postureChecksID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete posture checks from store: %s", result.Error) + return status.Errorf(status.Internal, "failed to delete posture checks from store") + } + + if result.RowsAffected == 0 { + return status.NewPostureChecksNotFoundError(postureChecksID) + } + + return nil +} + +// GetAccountRoutes retrieves network routes for an account. +func (s *SqlStore) GetAccountRoutes(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*route.Route, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var routes []*route.Route + result := tx.Find(&routes, accountIDCondition, accountID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to get routes from the store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get routes from store") + } + + return routes, nil +} + +// GetRouteByID retrieves a route by its ID and account ID. +func (s *SqlStore) GetRouteByID(ctx context.Context, lockStrength LockingStrength, accountID string, routeID string) (*route.Route, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var route *route.Route + result := tx.Take(&route, accountAndIDQueryCondition, accountID, routeID) + if err := result.Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, status.NewRouteNotFoundError(routeID) + } + log.WithContext(ctx).Errorf("failed to get route from the store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get route from store") + } + + return route, nil +} + +// SaveRoute saves a route to the database. +func (s *SqlStore) SaveRoute(ctx context.Context, route *route.Route) error { + result := s.db.Save(route) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to save route to the store: %s", err) + return status.Errorf(status.Internal, "failed to save route to store") + } + + return nil +} + +// DeleteRoute deletes a route from the database. +func (s *SqlStore) DeleteRoute(ctx context.Context, accountID, routeID string) error { + result := s.db.Delete(&route.Route{}, accountAndIDQueryCondition, accountID, routeID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to delete route from the store: %s", err) + return status.Errorf(status.Internal, "failed to delete route from store") + } + + if result.RowsAffected == 0 { + return status.NewRouteNotFoundError(routeID) + } + + return nil +} + +// GetAccountSetupKeys retrieves setup keys for an account. +func (s *SqlStore) GetAccountSetupKeys(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*types.SetupKey, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var setupKeys []*types.SetupKey + result := tx. + Find(&setupKeys, accountIDCondition, accountID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to get setup keys from the store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get setup keys from store") + } + + return setupKeys, nil +} + +// GetSetupKeyByID retrieves a setup key by its ID and account ID. +func (s *SqlStore) GetSetupKeyByID(ctx context.Context, lockStrength LockingStrength, accountID, setupKeyID string) (*types.SetupKey, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var setupKey *types.SetupKey + result := tx.Take(&setupKey, accountAndIDQueryCondition, accountID, setupKeyID) + if err := result.Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, status.NewSetupKeyNotFoundError(setupKeyID) + } + log.WithContext(ctx).Errorf("failed to get setup key from the store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get setup key from store") + } + + return setupKey, nil +} + +// SaveSetupKey saves a setup key to the database. +func (s *SqlStore) SaveSetupKey(ctx context.Context, setupKey *types.SetupKey) error { + result := s.db.Save(setupKey) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save setup key to store: %s", result.Error) + return status.Errorf(status.Internal, "failed to save setup key to store") + } + + return nil +} + +// DeleteSetupKey deletes a setup key from the database. +func (s *SqlStore) DeleteSetupKey(ctx context.Context, accountID, keyID string) error { + result := s.db.Delete(&types.SetupKey{}, accountAndIDQueryCondition, accountID, keyID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete setup key from store: %s", result.Error) + return status.Errorf(status.Internal, "failed to delete setup key from store") + } + + if result.RowsAffected == 0 { + return status.NewSetupKeyNotFoundError(keyID) + } + + return nil +} + +// GetAccountNameServerGroups retrieves name server groups for an account. +func (s *SqlStore) GetAccountNameServerGroups(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*nbdns.NameServerGroup, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var nsGroups []*nbdns.NameServerGroup + result := tx.Find(&nsGroups, accountIDCondition, accountID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to get name server groups from the store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get name server groups from store") + } + + return nsGroups, nil +} + +// GetNameServerGroupByID retrieves a name server group by its ID and account ID. +func (s *SqlStore) GetNameServerGroupByID(ctx context.Context, lockStrength LockingStrength, accountID, nsGroupID string) (*nbdns.NameServerGroup, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var nsGroup *nbdns.NameServerGroup + result := tx. + Take(&nsGroup, accountAndIDQueryCondition, accountID, nsGroupID) + if err := result.Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, status.NewNameServerGroupNotFoundError(nsGroupID) + } + log.WithContext(ctx).Errorf("failed to get name server group from the store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get name server group from store") + } + + return nsGroup, nil +} + +// SaveNameServerGroup saves a name server group to the database. +func (s *SqlStore) SaveNameServerGroup(ctx context.Context, nameServerGroup *nbdns.NameServerGroup) error { + result := s.db.Save(nameServerGroup) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to save name server group to the store: %s", err) + return status.Errorf(status.Internal, "failed to save name server group to store") + } + return nil +} + +// DeleteNameServerGroup deletes a name server group from the database. +func (s *SqlStore) DeleteNameServerGroup(ctx context.Context, accountID, nsGroupID string) error { + result := s.db.Delete(&nbdns.NameServerGroup{}, accountAndIDQueryCondition, accountID, nsGroupID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to delete name server group from the store: %s", err) + return status.Errorf(status.Internal, "failed to delete name server group from store") + } + + if result.RowsAffected == 0 { + return status.NewNameServerGroupNotFoundError(nsGroupID) + } + + return nil +} + +// SaveDNSSettings saves the DNS settings to the store. +func (s *SqlStore) SaveDNSSettings(ctx context.Context, accountID string, settings *types.DNSSettings) error { + result := s.db.Model(&types.Account{}). + Where(idQueryCondition, accountID).Updates(&types.AccountDNSSettings{DNSSettings: *settings}) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save dns settings to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to save dns settings to store") + } + + if result.RowsAffected == 0 { + return status.NewAccountNotFoundError(accountID) + } + + return nil +} + +// SaveAccountSettings stores the account settings in DB. +func (s *SqlStore) SaveAccountSettings(ctx context.Context, accountID string, settings *types.Settings) error { + result := s.db.Model(&types.Account{}). + Select("*").Where(idQueryCondition, accountID).Updates(&types.AccountSettings{Settings: settings}) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save account settings to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to save account settings to store") + } + + if result.RowsAffected == 0 { + return status.NewAccountNotFoundError(accountID) + } + + return nil +} + +func (s *SqlStore) GetAccountNetworks(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*networkTypes.Network, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var networks []*networkTypes.Network + result := tx.Find(&networks, accountIDCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get networks from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get networks from store") + } + + return networks, nil +} + +func (s *SqlStore) GetNetworkByID(ctx context.Context, lockStrength LockingStrength, accountID, networkID string) (*networkTypes.Network, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var network *networkTypes.Network + result := tx.Take(&network, accountAndIDQueryCondition, accountID, networkID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewNetworkNotFoundError(networkID) + } + + log.WithContext(ctx).Errorf("failed to get network from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network from store") + } + + return network, nil +} + +func (s *SqlStore) SaveNetwork(ctx context.Context, network *networkTypes.Network) error { + result := s.db.Save(network) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save network to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to save network to store") + } + + return nil +} + +func (s *SqlStore) DeleteNetwork(ctx context.Context, accountID, networkID string) error { + result := s.db.Delete(&networkTypes.Network{}, accountAndIDQueryCondition, accountID, networkID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete network from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete network from store") + } + + if result.RowsAffected == 0 { + return status.NewNetworkNotFoundError(networkID) + } + + return nil +} + +func (s *SqlStore) GetNetworkRoutersByNetID(ctx context.Context, lockStrength LockingStrength, accountID, netID string) ([]*routerTypes.NetworkRouter, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var netRouters []*routerTypes.NetworkRouter + result := tx. + Find(&netRouters, "account_id = ? AND network_id = ?", accountID, netID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get network routers from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network routers from store") + } + + return netRouters, nil +} + +func (s *SqlStore) GetNetworkRoutersByAccountID(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*routerTypes.NetworkRouter, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var netRouters []*routerTypes.NetworkRouter + result := tx. + Find(&netRouters, accountIDCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get network routers from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network routers from store") + } + + return netRouters, nil +} + +func (s *SqlStore) GetNetworkRouterByID(ctx context.Context, lockStrength LockingStrength, accountID, routerID string) (*routerTypes.NetworkRouter, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var netRouter *routerTypes.NetworkRouter + result := tx. + Take(&netRouter, accountAndIDQueryCondition, accountID, routerID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewNetworkRouterNotFoundError(routerID) + } + log.WithContext(ctx).Errorf("failed to get network router from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network router from store") + } + + return netRouter, nil +} + +func (s *SqlStore) SaveNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error { + result := s.db.Save(router) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save network router to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to save network router to store") + } + + return nil +} + +func (s *SqlStore) DeleteNetworkRouter(ctx context.Context, accountID, routerID string) error { + result := s.db.Delete(&routerTypes.NetworkRouter{}, accountAndIDQueryCondition, accountID, routerID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete network router from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete network router from store") + } + + if result.RowsAffected == 0 { + return status.NewNetworkRouterNotFoundError(routerID) + } + + return nil +} + +func (s *SqlStore) GetNetworkResourcesByNetID(ctx context.Context, lockStrength LockingStrength, accountID, networkID string) ([]*resourceTypes.NetworkResource, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var netResources []*resourceTypes.NetworkResource + result := tx. + Find(&netResources, "account_id = ? AND network_id = ?", accountID, networkID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get network resources from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network resources from store") + } + + return netResources, nil +} + +func (s *SqlStore) GetNetworkResourcesByAccountID(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*resourceTypes.NetworkResource, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var netResources []*resourceTypes.NetworkResource + result := tx. + Find(&netResources, accountIDCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get network resources from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network resources from store") + } + + return netResources, nil +} + +func (s *SqlStore) GetNetworkResourceByID(ctx context.Context, lockStrength LockingStrength, accountID, resourceID string) (*resourceTypes.NetworkResource, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var netResources *resourceTypes.NetworkResource + result := tx. + Take(&netResources, accountAndIDQueryCondition, accountID, resourceID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewNetworkResourceNotFoundError(resourceID) + } + log.WithContext(ctx).Errorf("failed to get network resource from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network resource from store") + } + + return netResources, nil +} + +func (s *SqlStore) GetNetworkResourceByName(ctx context.Context, lockStrength LockingStrength, accountID, resourceName string) (*resourceTypes.NetworkResource, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var netResources *resourceTypes.NetworkResource + result := tx. + Take(&netResources, "account_id = ? AND name = ?", accountID, resourceName) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewNetworkResourceNotFoundError(resourceName) + } + log.WithContext(ctx).Errorf("failed to get network resource from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network resource from store") + } + + return netResources, nil +} + +func (s *SqlStore) SaveNetworkResource(ctx context.Context, resource *resourceTypes.NetworkResource) error { + result := s.db.Save(resource) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save network resource to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to save network resource to store") + } + + return nil +} + +func (s *SqlStore) DeleteNetworkResource(ctx context.Context, accountID, resourceID string) error { + result := s.db.Delete(&resourceTypes.NetworkResource{}, accountAndIDQueryCondition, accountID, resourceID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete network resource from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete network resource from store") + } + + if result.RowsAffected == 0 { + return status.NewNetworkResourceNotFoundError(resourceID) + } + + return nil +} + +// GetPATByHashedToken returns a PersonalAccessToken by its hashed token. +func (s *SqlStore) GetPATByHashedToken(ctx context.Context, lockStrength LockingStrength, hashedToken string) (*types.PersonalAccessToken, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var pat types.PersonalAccessToken + result := tx.Take(&pat, "hashed_token = ?", hashedToken) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewPATNotFoundError(hashedToken) + } + log.WithContext(ctx).Errorf("failed to get pat by hash from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get pat by hash from store") + } + + return &pat, nil +} + +// GetPATByID retrieves a personal access token by its ID and user ID. +func (s *SqlStore) GetPATByID(ctx context.Context, lockStrength LockingStrength, userID string, patID string) (*types.PersonalAccessToken, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var pat types.PersonalAccessToken + result := tx. + Take(&pat, "id = ? AND user_id = ?", patID, userID) + if err := result.Error; err != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewPATNotFoundError(patID) + } + log.WithContext(ctx).Errorf("failed to get pat from the store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get pat from store") + } + + return &pat, nil +} + +// GetUserPATs retrieves personal access tokens for a user. +func (s *SqlStore) GetUserPATs(ctx context.Context, lockStrength LockingStrength, userID string) ([]*types.PersonalAccessToken, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var pats []*types.PersonalAccessToken + result := tx.Find(&pats, "user_id = ?", userID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to get user pat's from the store: %s", err) + return nil, status.Errorf(status.Internal, "failed to get user pat's from store") + } + + return pats, nil +} + +// MarkPATUsed marks a personal access token as used. +func (s *SqlStore) MarkPATUsed(ctx context.Context, patID string) error { + patCopy := types.PersonalAccessToken{ + LastUsed: util.ToPtr(time.Now().UTC()), + } + + fieldsToUpdate := []string{"last_used"} + result := s.db.Select(fieldsToUpdate). + Where(idQueryCondition, patID).Updates(&patCopy) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to mark pat as used: %s", result.Error) + return status.Errorf(status.Internal, "failed to mark pat as used") + } + + if result.RowsAffected == 0 { + return status.NewPATNotFoundError(patID) + } + + return nil +} + +// SavePAT saves a personal access token to the database. +func (s *SqlStore) SavePAT(ctx context.Context, pat *types.PersonalAccessToken) error { + result := s.db.Save(pat) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to save pat to the store: %s", err) + return status.Errorf(status.Internal, "failed to save pat to store") + } + + return nil +} + +// DeletePAT deletes a personal access token from the database. +func (s *SqlStore) DeletePAT(ctx context.Context, userID, patID string) error { + result := s.db.Delete(&types.PersonalAccessToken{}, "user_id = ? AND id = ?", userID, patID) + if err := result.Error; err != nil { + log.WithContext(ctx).Errorf("failed to delete pat from the store: %s", err) + return status.Errorf(status.Internal, "failed to delete pat from store") + } + + if result.RowsAffected == 0 { + return status.NewPATNotFoundError(patID) + } + + return nil +} + +// GetProxyAccessTokenByHashedToken retrieves a proxy access token by its hashed value. +func (s *SqlStore) GetProxyAccessTokenByHashedToken(ctx context.Context, lockStrength LockingStrength, hashedToken types.HashedProxyToken) (*types.ProxyAccessToken, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var token types.ProxyAccessToken + result := tx.Take(&token, "hashed_token = ?", hashedToken) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "proxy access token not found") + } + return nil, status.Errorf(status.Internal, "get proxy access token: %v", result.Error) + } + + return &token, nil +} + +// GetAllProxyAccessTokens retrieves all proxy access tokens. +func (s *SqlStore) GetAllProxyAccessTokens(ctx context.Context, lockStrength LockingStrength) ([]*types.ProxyAccessToken, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var tokens []*types.ProxyAccessToken + result := tx.Find(&tokens) + if result.Error != nil { + return nil, status.Errorf(status.Internal, "get proxy access tokens: %v", result.Error) + } + + return tokens, nil +} + +// SaveProxyAccessToken saves a proxy access token to the database. +func (s *SqlStore) SaveProxyAccessToken(ctx context.Context, token *types.ProxyAccessToken) error { + if result := s.db.Create(token); result.Error != nil { + return status.Errorf(status.Internal, "save proxy access token: %v", result.Error) + } + return nil +} + +// RevokeProxyAccessToken revokes a proxy access token by its ID. +func (s *SqlStore) RevokeProxyAccessToken(ctx context.Context, tokenID string) error { + result := s.db.Model(&types.ProxyAccessToken{}).Where(idQueryCondition, tokenID).Update("revoked", true) + if result.Error != nil { + return status.Errorf(status.Internal, "revoke proxy access token: %v", result.Error) + } + + if result.RowsAffected == 0 { + return status.Errorf(status.NotFound, "proxy access token not found") + } + + return nil +} + +// MarkProxyAccessTokenUsed updates the last used timestamp for a proxy access token. +func (s *SqlStore) MarkProxyAccessTokenUsed(ctx context.Context, tokenID string) error { + result := s.db.Model(&types.ProxyAccessToken{}). + Where(idQueryCondition, tokenID). + Update("last_used", time.Now().UTC()) + if result.Error != nil { + return status.Errorf(status.Internal, "mark proxy access token as used: %v", result.Error) + } + + if result.RowsAffected == 0 { + return status.Errorf(status.NotFound, "proxy access token not found") + } + + return nil +} + +func (s *SqlStore) GetPeerByIP(ctx context.Context, lockStrength LockingStrength, accountID string, ip net.IP) (*nbpeer.Peer, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + jsonValue := fmt.Sprintf(`"%s"`, ip.String()) + + var peer nbpeer.Peer + result := tx. + Take(&peer, "account_id = ? AND ip = ?", accountID, jsonValue) + if result.Error != nil { + // no logging here + return nil, status.Errorf(status.Internal, "failed to get peer from store") + } + + return &peer, nil +} + +func (s *SqlStore) GetPeerIdByLabel(ctx context.Context, lockStrength LockingStrength, accountID string, hostname string) (string, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var peerID string + result := tx.Model(&nbpeer.Peer{}). + Select("id"). + // Where(" = ?", hostname). + Where("account_id = ? AND dns_label = ?", accountID, hostname). + Limit(1). + Scan(&peerID) + + if peerID == "" { + return "", gorm.ErrRecordNotFound + } + + return peerID, result.Error +} + +func (s *SqlStore) CountAccountsByPrivateDomain(ctx context.Context, domain string) (int64, error) { + var count int64 + result := s.db.Model(&types.Account{}). + Where("domain = ? AND domain_category = ?", + strings.ToLower(domain), types.PrivateCategory, + ).Count(&count) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to count accounts by private domain %s: %s", domain, result.Error) + return 0, status.Errorf(status.Internal, "failed to count accounts by private domain") + } + + return count, nil +} + +func (s *SqlStore) GetAccountGroupPeers(ctx context.Context, lockStrength LockingStrength, accountID string) (map[string]map[string]struct{}, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var peers []types.GroupPeer + result := tx.Find(&peers, accountIDCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get account group peers from store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get account group peers from store") + } + + groupPeers := make(map[string]map[string]struct{}) + for _, peer := range peers { + if _, exists := groupPeers[peer.GroupID]; !exists { + groupPeers[peer.GroupID] = make(map[string]struct{}) + } + groupPeers[peer.GroupID][peer.PeerID] = struct{}{} + } + + return groupPeers, nil +} + +func (s *SqlStore) IsPrimaryAccount(ctx context.Context, accountID string) (bool, string, error) { + var info types.PrimaryAccountInfo + result := s.db.Model(&types.Account{}). + Select("is_domain_primary_account, domain"). + Where(idQueryCondition, accountID). + Take(&info) + + if result.Error != nil { + return false, "", status.Errorf(status.Internal, "failed to get account info: %v", result.Error) + } + + return info.IsDomainPrimaryAccount, info.Domain, nil +} + +func (s *SqlStore) MarkAccountPrimary(ctx context.Context, accountID string) error { + result := s.db.Model(&types.Account{}). + Where(idQueryCondition, accountID). + Update("is_domain_primary_account", true) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to mark account as primary: %s", result.Error) + return status.Errorf(status.Internal, "failed to mark account as primary") + } + + if result.RowsAffected == 0 { + return status.NewAccountNotFoundError(accountID) + } + + return nil +} + +type accountNetworkPatch struct { + Network *types.Network `gorm:"embedded;embeddedPrefix:network_"` +} + +func (s *SqlStore) UpdateAccountNetwork(ctx context.Context, accountID string, ipNet net.IPNet) error { + patch := accountNetworkPatch{ + Network: &types.Network{Net: ipNet}, + } + + result := s.db. + Model(&types.Account{}). + Where(idQueryCondition, accountID). + Updates(&patch) + + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to update account network: %v", result.Error) + return status.Errorf(status.Internal, "failed to update account network") + } + if result.RowsAffected == 0 { + return status.NewAccountNotFoundError(accountID) + } + return nil +} + +func (s *SqlStore) GetPeersByGroupIDs(ctx context.Context, accountID string, groupIDs []string) ([]*nbpeer.Peer, error) { + if len(groupIDs) == 0 { + return []*nbpeer.Peer{}, nil + } + + var peers []*nbpeer.Peer + peerIDsSubquery := s.db.Model(&types.GroupPeer{}). + Select("DISTINCT peer_id"). + Where("account_id = ? AND group_id IN ?", accountID, groupIDs) + + result := s.db.Where("id IN (?)", peerIDsSubquery).Find(&peers) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get peers by group IDs: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get peers by group IDs") + } + + return peers, nil +} + +func (s *SqlStore) GetUserIDByPeerKey(ctx context.Context, lockStrength LockingStrength, peerKey string) (string, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var userID string + result := tx.Model(&nbpeer.Peer{}). + Select("user_id"). + Take(&userID, GetKeyQueryCondition(s), peerKey) + + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return "", status.Errorf(status.NotFound, "peer not found: index lookup failed") + } + return "", status.Errorf(status.Internal, "failed to get user ID by peer key") + } + + return userID, nil +} + +func (s *SqlStore) CreateZone(ctx context.Context, zone *zones.Zone) error { + result := s.db.Create(zone) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to create zone to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to create zone to store") + } + + return nil +} + +func (s *SqlStore) UpdateZone(ctx context.Context, zone *zones.Zone) error { + result := s.db.Select("*").Save(zone) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to update zone to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to update zone to store") + } + + return nil +} + +func (s *SqlStore) DeleteZone(ctx context.Context, accountID, zoneID string) error { + result := s.db.Delete(&zones.Zone{}, accountAndIDQueryCondition, accountID, zoneID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete zone from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete zone from store") + } + + if result.RowsAffected == 0 { + return status.NewZoneNotFoundError(zoneID) + } + + return nil +} + +func (s *SqlStore) GetZoneByID(ctx context.Context, lockStrength LockingStrength, accountID, zoneID string) (*zones.Zone, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var zone *zones.Zone + result := tx.Preload("Records").Take(&zone, accountAndIDQueryCondition, accountID, zoneID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewZoneNotFoundError(zoneID) + } + + log.WithContext(ctx).Errorf("failed to get zone from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get zone from store") + } + + return zone, nil +} + +func (s *SqlStore) GetZoneByDomain(ctx context.Context, accountID, domain string) (*zones.Zone, error) { + var zone *zones.Zone + result := s.db.Where("account_id = ? AND domain = ?", accountID, domain).First(&zone) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewZoneNotFoundError(domain) + } + + log.WithContext(ctx).Errorf("failed to get zone by domain from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get zone by domain from store") + } + + return zone, nil +} + +func (s *SqlStore) GetAccountZones(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*zones.Zone, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var zones []*zones.Zone + result := tx.Preload("Records").Find(&zones, accountIDCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get zones from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get zones from store") + } + + return zones, nil +} + +func (s *SqlStore) CreateDNSRecord(ctx context.Context, record *records.Record) error { + result := s.db.Create(record) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to create dns record to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to create dns record to store") + } + + return nil +} + +func (s *SqlStore) UpdateDNSRecord(ctx context.Context, record *records.Record) error { + result := s.db.Select("*").Save(record) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to update dns record to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to update dns record to store") + } + + return nil +} + +func (s *SqlStore) DeleteDNSRecord(ctx context.Context, accountID, zoneID, recordID string) error { + result := s.db.Delete(&records.Record{}, "account_id = ? AND zone_id = ? AND id = ?", accountID, zoneID, recordID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete dns record from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete dns record from store") + } + + if result.RowsAffected == 0 { + return status.NewDNSRecordNotFoundError(recordID) + } + + return nil +} + +func (s *SqlStore) GetDNSRecordByID(ctx context.Context, lockStrength LockingStrength, accountID, zoneID, recordID string) (*records.Record, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var record *records.Record + result := tx.Where("account_id = ? AND zone_id = ? AND id = ?", accountID, zoneID, recordID).Take(&record) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewDNSRecordNotFoundError(recordID) + } + + log.WithContext(ctx).Errorf("failed to get dns record from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get dns record from store") + } + + return record, nil +} + +func (s *SqlStore) GetZoneDNSRecords(ctx context.Context, lockStrength LockingStrength, accountID, zoneID string) ([]*records.Record, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var recordsList []*records.Record + result := tx.Where("account_id = ? AND zone_id = ?", accountID, zoneID).Find(&recordsList) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get zone dns records from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get zone dns records from store") + } + + return recordsList, nil +} + +func (s *SqlStore) GetZoneDNSRecordsByName(ctx context.Context, lockStrength LockingStrength, accountID, zoneID, name string) ([]*records.Record, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var recordsList []*records.Record + result := tx.Where("account_id = ? AND zone_id = ? AND name = ?", accountID, zoneID, name).Find(&recordsList) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get zone dns records by name from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get zone dns records by name from store") + } + + return recordsList, nil +} + +func (s *SqlStore) DeleteZoneDNSRecords(ctx context.Context, accountID, zoneID string) error { + result := s.db.Delete(&records.Record{}, "account_id = ? AND zone_id = ?", accountID, zoneID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete zone dns records from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete zone dns records from store") + } + + return nil +} + +func (s *SqlStore) GetPeerIDByKey(ctx context.Context, lockStrength LockingStrength, key string) (string, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var peerID string + result := tx.Model(&nbpeer.Peer{}). + Select("id"). + Where(GetKeyQueryCondition(s), key). + Limit(1). + Scan(&peerID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get peer ID by key: %s", result.Error) + return "", status.Errorf(status.Internal, "failed to get peer ID by key") + } + + return peerID, nil +} + +func (s *SqlStore) CreateService(ctx context.Context, service *rpservice.Service) error { + serviceCopy := service.Copy() + if err := serviceCopy.EncryptSensitiveData(s.fieldEncrypt); err != nil { + return fmt.Errorf("encrypt service data: %w", err) + } + result := s.db.Create(serviceCopy) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to create service to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to create service to store") + } + + return nil +} + +func (s *SqlStore) UpdateService(ctx context.Context, service *rpservice.Service) error { + serviceCopy := service.Copy() + if err := serviceCopy.EncryptSensitiveData(s.fieldEncrypt); err != nil { + return fmt.Errorf("encrypt service data: %w", err) + } + + // Create target type instance outside transaction to avoid variable shadowing + targetType := &rpservice.Target{} + + // Use a transaction to ensure atomic updates of the service and its targets + err := s.db.Transaction(func(tx *gorm.DB) error { + // Delete existing targets + if err := tx.Where("service_id = ?", serviceCopy.ID).Delete(targetType).Error; err != nil { + return err + } + + // Update the service and create new targets + if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Save(serviceCopy).Error; err != nil { + return err + } + + return nil + }) + + if err != nil { + log.WithContext(ctx).Errorf("failed to update service to store: %v", err) + return status.Errorf(status.Internal, "failed to update service to store") + } + + return nil +} + +func (s *SqlStore) DeleteService(ctx context.Context, accountID, serviceID string) error { + result := s.db.Delete(&rpservice.Service{}, accountAndIDQueryCondition, accountID, serviceID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete service from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete service from store") + } + + if result.RowsAffected == 0 { + return status.Errorf(status.NotFound, "service %s not found", serviceID) + } + + return nil +} + +func (s *SqlStore) DeleteTarget(ctx context.Context, accountID string, serviceID string, targetID uint) error { + result := s.db.Delete(&rpservice.Target{}, "account_id = ? AND service_id = ? AND id = ?", accountID, serviceID, targetID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete target from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete target from store") + } + + if result.RowsAffected == 0 { + return status.Errorf(status.NotFound, "target not found for service %s", serviceID) + } + + return nil +} + +func (s *SqlStore) DeleteServiceTargets(ctx context.Context, accountID string, serviceID string) error { + result := s.db.Delete(&rpservice.Target{}, "account_id = ? AND service_id = ?", accountID, serviceID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete targets from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete targets from store") + } + + return nil +} + +// GetTargetsByServiceID retrieves all targets for a given service +func (s *SqlStore) GetTargetsByServiceID(ctx context.Context, lockStrength LockingStrength, accountID string, serviceID string) ([]*rpservice.Target, error) { + var targets []*rpservice.Target + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + result := tx.Where("account_id = ? AND service_id = ?", accountID, serviceID).Find(&targets) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get targets from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get targets from store") + } + + return targets, nil +} + +func (s *SqlStore) GetServiceByID(ctx context.Context, lockStrength LockingStrength, accountID, serviceID string) (*rpservice.Service, error) { + tx := s.db.Preload("Targets") + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var service *rpservice.Service + result := tx.Take(&service, accountAndIDQueryCondition, accountID, serviceID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "service %s not found", serviceID) + } + + log.WithContext(ctx).Errorf("failed to get service from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get service from store") + } + + if err := service.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt service data: %w", err) + } + + return service, nil +} + +func (s *SqlStore) GetServiceByDomain(ctx context.Context, domain string) (*rpservice.Service, error) { + var service *rpservice.Service + result := s.db.Preload("Targets").Where("domain = ?", domain).First(&service) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "service with domain %s not found", domain) + } + + log.WithContext(ctx).Errorf("failed to get service by domain from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get service by domain from store") + } + + if err := service.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt service data: %w", err) + } + + return service, nil +} + +func (s *SqlStore) GetServices(ctx context.Context, lockStrength LockingStrength) ([]*rpservice.Service, error) { + tx := s.db.Preload("Targets") + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var serviceList []*rpservice.Service + result := tx.Find(&serviceList) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get services from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get services from store") + } + + for _, service := range serviceList { + if err := service.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt service data: %w", err) + } + } + + return serviceList, nil +} + +func (s *SqlStore) GetAccountServices(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*rpservice.Service, error) { + tx := s.db.Preload("Targets") + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var serviceList []*rpservice.Service + result := tx.Find(&serviceList, accountIDCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get services from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get services from store") + } + + for _, service := range serviceList { + if err := service.DecryptSensitiveData(s.fieldEncrypt); err != nil { + return nil, fmt.Errorf("decrypt service data: %w", err) + } + } + + return serviceList, nil +} + +// RenewEphemeralService updates the last_renewed_at timestamp for an ephemeral service. +func (s *SqlStore) RenewEphemeralService(ctx context.Context, accountID, peerID, serviceID string) error { + result := s.db.Model(&rpservice.Service{}). + Where("id = ? AND account_id = ? AND source_peer = ? AND source = ?", serviceID, accountID, peerID, rpservice.SourceEphemeral). + Update("meta_last_renewed_at", time.Now()) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to renew ephemeral service: %v", result.Error) + return status.Errorf(status.Internal, "renew ephemeral service") + } + if result.RowsAffected == 0 { + return status.Errorf(status.NotFound, "no active expose session for service %s", serviceID) + } + return nil +} + +// GetExpiredEphemeralServices returns ephemeral services whose last renewal exceeds the given TTL. +// Only the fields needed for reaping are selected. The limit parameter caps the batch size to +// avoid loading too many rows in a single tick. Rows with empty source_peer are excluded to +// skip malformed legacy data. +func (s *SqlStore) GetExpiredEphemeralServices(ctx context.Context, ttl time.Duration, limit int) ([]*rpservice.Service, error) { + cutoff := time.Now().Add(-ttl) + var services []*rpservice.Service + result := s.db. + Select("id", "account_id", "source_peer", "domain"). + Where("source = ? AND source_peer <> '' AND meta_last_renewed_at < ?", rpservice.SourceEphemeral, cutoff). + Limit(limit). + Find(&services) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get expired ephemeral services: %v", result.Error) + return nil, status.Errorf(status.Internal, "get expired ephemeral services") + } + return services, nil +} + +// CountEphemeralServicesByPeer returns the count of ephemeral services for a specific peer. +// Use LockingStrengthUpdate inside a transaction to serialize concurrent create operations. +// The locking is applied via a row-level SELECT ... FOR UPDATE (not on the aggregate) to +// stay compatible with Postgres, which disallows FOR UPDATE on COUNT(*). +func (s *SqlStore) CountEphemeralServicesByPeer(ctx context.Context, lockStrength LockingStrength, accountID, peerID string) (int64, error) { + if lockStrength == LockingStrengthNone { + var count int64 + result := s.db.Model(&rpservice.Service{}). + Where("account_id = ? AND source_peer = ? AND source = ?", accountID, peerID, rpservice.SourceEphemeral). + Count(&count) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to count ephemeral services: %v", result.Error) + return 0, status.Errorf(status.Internal, "count ephemeral services") + } + return count, nil + } + + var ids []string + result := s.db.Model(&rpservice.Service{}). + Clauses(clause.Locking{Strength: string(lockStrength)}). + Select("id"). + Where("account_id = ? AND source_peer = ? AND source = ?", accountID, peerID, rpservice.SourceEphemeral). + Pluck("id", &ids) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to count ephemeral services: %v", result.Error) + return 0, status.Errorf(status.Internal, "count ephemeral services") + } + return int64(len(ids)), nil +} + +// EphemeralServiceExists checks if an ephemeral service exists for the given peer and domain. +// Use LockingStrengthUpdate inside a transaction to serialize concurrent create operations. +func (s *SqlStore) EphemeralServiceExists(ctx context.Context, lockStrength LockingStrength, accountID, peerID, domain string) (bool, error) { + if lockStrength == LockingStrengthNone { + var count int64 + result := s.db.Model(&rpservice.Service{}). + Where("account_id = ? AND source_peer = ? AND domain = ? AND source = ?", accountID, peerID, domain, rpservice.SourceEphemeral). + Count(&count) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to check ephemeral service existence: %v", result.Error) + return false, status.Errorf(status.Internal, "check ephemeral service existence") + } + return count > 0, nil + } + + var id string + result := s.db.Model(&rpservice.Service{}). + Clauses(clause.Locking{Strength: string(lockStrength)}). + Select("id"). + Where("account_id = ? AND source_peer = ? AND domain = ? AND source = ?", accountID, peerID, domain, rpservice.SourceEphemeral). + Limit(1). + Pluck("id", &id) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to check ephemeral service existence: %v", result.Error) + return false, status.Errorf(status.Internal, "check ephemeral service existence") + } + return id != "", nil +} + +// GetServicesByClusterAndPort returns services matching the given proxy cluster, mode, and listen port. +func (s *SqlStore) GetServicesByClusterAndPort(ctx context.Context, lockStrength LockingStrength, proxyCluster string, mode string, listenPort uint16) ([]*rpservice.Service, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var services []*rpservice.Service + result := tx.Where("proxy_cluster = ? AND mode = ? AND listen_port = ?", proxyCluster, mode, listenPort).Find(&services) + if result.Error != nil { + return nil, status.Errorf(status.Internal, "query services by cluster and port") + } + + return services, nil +} + +// GetServicesByCluster returns all services for the given proxy cluster. +func (s *SqlStore) GetServicesByCluster(ctx context.Context, lockStrength LockingStrength, proxyCluster string) ([]*rpservice.Service, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var services []*rpservice.Service + result := tx.Where("proxy_cluster = ?", proxyCluster).Find(&services) + if result.Error != nil { + return nil, status.Errorf(status.Internal, "query services by cluster") + } + return services, nil +} + +func (s *SqlStore) GetCustomDomain(ctx context.Context, accountID string, domainID string) (*domain.Domain, error) { + tx := s.db + + customDomain := &domain.Domain{} + result := tx.Take(&customDomain, accountAndIDQueryCondition, accountID, domainID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "custom domain %s not found", domainID) + } + + log.WithContext(ctx).Errorf("failed to get custom domain from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get custom domain from store") + } + + return customDomain, nil +} + +func (s *SqlStore) ListFreeDomains(ctx context.Context, accountID string) ([]string, error) { + return nil, nil +} + +func (s *SqlStore) ListCustomDomains(ctx context.Context, accountID string) ([]*domain.Domain, error) { + tx := s.db + + var domains []*domain.Domain + result := tx.Find(&domains, accountIDCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get reverse proxy custom domains from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get reverse proxy custom domains from store") + } + + return domains, nil +} + +func (s *SqlStore) CreateCustomDomain(ctx context.Context, accountID string, domainName string, targetCluster string, validated bool) (*domain.Domain, error) { + newDomain := &domain.Domain{ + ID: xid.New().String(), // Generate our own ID because gorm doesn't always configure the database to handle this for us. + Domain: domainName, + AccountID: accountID, + TargetCluster: targetCluster, + Type: domain.TypeCustom, + Validated: validated, + } + result := s.db.Create(newDomain) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to create reverse proxy custom domain to store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to create reverse proxy custom domain to store") + } + + return newDomain, nil +} + +func (s *SqlStore) UpdateCustomDomain(ctx context.Context, accountID string, d *domain.Domain) (*domain.Domain, error) { + d.AccountID = accountID + result := s.db.Select("*").Save(d) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to update reverse proxy custom domain to store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to update reverse proxy custom domain to store") + } + + return d, nil +} + +func (s *SqlStore) DeleteCustomDomain(ctx context.Context, accountID string, domainID string) error { + result := s.db.Delete(domain.Domain{}, accountAndIDQueryCondition, accountID, domainID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete reverse proxy custom domain from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete reverse proxy custom domain from store") + } + + if result.RowsAffected == 0 { + return status.Errorf(status.NotFound, "reverse proxy custom domain %s not found", domainID) + } + + return nil +} + +// CreateAccessLog creates a new access log entry in the database +func (s *SqlStore) CreateAccessLog(ctx context.Context, logEntry *accesslogs.AccessLogEntry) error { + result := s.db.Create(logEntry) + if result.Error != nil { + log.WithContext(ctx).WithFields(log.Fields{ + "service_id": logEntry.ServiceID, + "method": logEntry.Method, + "host": logEntry.Host, + "path": logEntry.Path, + }).Errorf("failed to create access log entry in store: %v", result.Error) + return status.Errorf(status.Internal, "failed to create access log entry in store") + } + return nil +} + +// GetAccountAccessLogs retrieves access logs for a given account with pagination and filtering +func (s *SqlStore) GetAccountAccessLogs(ctx context.Context, lockStrength LockingStrength, accountID string, filter accesslogs.AccessLogFilter) ([]*accesslogs.AccessLogEntry, int64, error) { + var logs []*accesslogs.AccessLogEntry + var totalCount int64 + + baseQuery := s.db. + Model(&accesslogs.AccessLogEntry{}). + Where(accountIDCondition, accountID) + + baseQuery = s.applyAccessLogFilters(baseQuery, filter) + + if err := baseQuery.Count(&totalCount).Error; err != nil { + log.WithContext(ctx).Errorf("failed to count access logs: %v", err) + return nil, 0, status.Errorf(status.Internal, "failed to count access logs") + } + + query := s.db. + Where(accountIDCondition, accountID) + + query = s.applyAccessLogFilters(query, filter) + + sortColumns := filter.GetSortColumn() + sortOrder := strings.ToUpper(filter.GetSortOrder()) + + var orderClauses []string + for _, col := range strings.Split(sortColumns, ",") { + col = strings.TrimSpace(col) + if col != "" { + orderClauses = append(orderClauses, col+" "+sortOrder) + } + } + orderClause := strings.Join(orderClauses, ", ") + + query = query. + Order(orderClause). + Limit(filter.GetLimit()). + Offset(filter.GetOffset()) + + if lockStrength != LockingStrengthNone { + query = query.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + result := query.Find(&logs) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get access logs from store: %v", result.Error) + return nil, 0, status.Errorf(status.Internal, "failed to get access logs from store") + } + + return logs, totalCount, nil +} + +// DeleteOldAccessLogs deletes all access logs older than the specified time +func (s *SqlStore) DeleteOldAccessLogs(ctx context.Context, olderThan time.Time) (int64, error) { + result := s.db. + Where("timestamp < ?", olderThan). + Delete(&accesslogs.AccessLogEntry{}) + + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to delete old access logs: %v", result.Error) + return 0, status.Errorf(status.Internal, "failed to delete old access logs") + } + + return result.RowsAffected, nil +} + +// applyAccessLogFilters applies filter conditions to the query +func (s *SqlStore) applyAccessLogFilters(query *gorm.DB, filter accesslogs.AccessLogFilter) *gorm.DB { + if filter.Search != nil { + searchPattern := "%" + *filter.Search + "%" + query = query.Where( + "id LIKE ? OR location_connection_ip LIKE ? OR host LIKE ? OR path LIKE ? OR CONCAT(host, path) LIKE ? OR user_id IN (SELECT id FROM users WHERE email LIKE ? OR name LIKE ?)", + searchPattern, searchPattern, searchPattern, searchPattern, searchPattern, searchPattern, searchPattern, + ) + } + + if filter.SourceIP != nil { + query = query.Where("location_connection_ip = ?", *filter.SourceIP) + } + + if filter.Host != nil { + query = query.Where("host = ?", *filter.Host) + } + + if filter.Path != nil { + // Support LIKE pattern for path filtering + query = query.Where("path LIKE ?", "%"+*filter.Path+"%") + } + + if filter.UserID != nil { + query = query.Where("user_id = ?", *filter.UserID) + } + + if filter.Method != nil { + query = query.Where("method = ?", *filter.Method) + } + + if filter.Status != nil { + switch *filter.Status { + case "success": + query = query.Where("status_code >= ? AND status_code < ?", 200, 400) + case "failed": + query = query.Where("status_code < ? OR status_code >= ?", 200, 400) + } + } + + if filter.StatusCode != nil { + query = query.Where("status_code = ?", *filter.StatusCode) + } + + if filter.StartDate != nil { + query = query.Where("timestamp >= ?", *filter.StartDate) + } + + if filter.EndDate != nil { + query = query.Where("timestamp <= ?", *filter.EndDate) + } + + return query +} + +func (s *SqlStore) GetServiceTargetByTargetID(ctx context.Context, lockStrength LockingStrength, accountID string, targetID string) (*rpservice.Target, error) { + tx := s.db + if lockStrength != LockingStrengthNone { + tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) + } + + var target *rpservice.Target + result := tx.Take(&target, "account_id = ? AND target_id = ?", accountID, targetID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "service target with ID %s not found", targetID) + } + + log.WithContext(ctx).Errorf("failed to get service target from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get service target from store") + } + + return target, nil +} + +// SaveProxy saves or updates a proxy in the database +func (s *SqlStore) SaveProxy(ctx context.Context, p *proxy.Proxy) error { + result := s.db.Save(p) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to save proxy: %v", result.Error) + return status.Errorf(status.Internal, "failed to save proxy") + } + return nil +} + +// UpdateProxyHeartbeat updates the last_seen timestamp for a proxy or creates a new entry if it doesn't exist +func (s *SqlStore) UpdateProxyHeartbeat(ctx context.Context, proxyID, clusterAddress, ipAddress string) error { + now := time.Now() + + result := s.db. + Model(&proxy.Proxy{}). + Where("id = ? AND status = ?", proxyID, "connected"). + Update("last_seen", now) + + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to update proxy heartbeat: %v", result.Error) + return status.Errorf(status.Internal, "failed to update proxy heartbeat") + } + + if result.RowsAffected == 0 { + p := &proxy.Proxy{ + ID: proxyID, + ClusterAddress: clusterAddress, + IPAddress: ipAddress, + LastSeen: now, + ConnectedAt: &now, + Status: "connected", + } + if err := s.db.Save(p).Error; err != nil { + log.WithContext(ctx).Errorf("failed to create proxy on heartbeat: %v", err) + return status.Errorf(status.Internal, "failed to create proxy on heartbeat") + } + } + + return nil +} + +// GetActiveProxyClusterAddresses returns all unique cluster addresses for active proxies +func (s *SqlStore) GetActiveProxyClusterAddresses(ctx context.Context) ([]string, error) { + var addresses []string + + result := s.db. + Model(&proxy.Proxy{}). + Where("status = ? AND last_seen > ?", "connected", time.Now().Add(-proxyActiveThreshold)). + Distinct("cluster_address"). + Pluck("cluster_address", &addresses) + + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get active proxy cluster addresses: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get active proxy cluster addresses") + } + + return addresses, nil +} + +// GetActiveProxyClusters returns all active proxy clusters with their connected proxy count. +func (s *SqlStore) GetActiveProxyClusters(ctx context.Context) ([]proxy.Cluster, error) { + var clusters []proxy.Cluster + + result := s.db.Model(&proxy.Proxy{}). + Select("cluster_address as address, COUNT(*) as connected_proxies"). + Where("status = ? AND last_seen > ?", "connected", time.Now().Add(-proxyActiveThreshold)). + Group("cluster_address"). + Scan(&clusters) + + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get active proxy clusters: %v", result.Error) + return nil, status.Errorf(status.Internal, "get active proxy clusters") + } + + return clusters, nil +} + +// proxyActiveThreshold is the maximum age of a heartbeat for a proxy to be +// considered active. Must be at least 2x the heartbeat interval (1 min). +const proxyActiveThreshold = 2 * time.Minute + +var validCapabilityColumns = map[string]struct{}{ + "supports_custom_ports": {}, + "require_subdomain": {}, + "supports_crowdsec": {}, +} + +// GetClusterSupportsCustomPorts returns whether any active proxy in the cluster +// supports custom ports. Returns nil when no proxy reported the capability. +func (s *SqlStore) GetClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool { + return s.getClusterCapability(ctx, clusterAddr, "supports_custom_ports") +} + +// GetClusterRequireSubdomain returns whether any active proxy in the cluster +// requires a subdomain. Returns nil when no proxy reported the capability. +func (s *SqlStore) GetClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool { + return s.getClusterCapability(ctx, clusterAddr, "require_subdomain") +} + +// GetClusterSupportsCrowdSec returns whether all active proxies in the cluster +// have CrowdSec configured. Returns nil when no proxy reported the capability. +// Unlike other capabilities that use ANY-true (for rolling upgrades), CrowdSec +// requires unanimous support: a single unconfigured proxy would let requests +// bypass reputation checks. +func (s *SqlStore) GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool { + return s.getClusterUnanimousCapability(ctx, clusterAddr, "supports_crowdsec") +} + +// getClusterUnanimousCapability returns an aggregated boolean capability +// requiring all active proxies in the cluster to report true. +func (s *SqlStore) getClusterUnanimousCapability(ctx context.Context, clusterAddr, column string) *bool { + if _, ok := validCapabilityColumns[column]; !ok { + log.WithContext(ctx).Errorf("invalid capability column: %s", column) + return nil + } + + var result struct { + Total int64 + Reported int64 + AllTrue bool + } + + // All active proxies must have reported the capability (no NULLs) and all + // must report true. A single unreported or false proxy means the cluster + // does not unanimously support the capability. + err := s.db.WithContext(ctx). + Model(&proxy.Proxy{}). + Select("COUNT(*) AS total, "+ + "COUNT(CASE WHEN "+column+" IS NOT NULL THEN 1 END) AS reported, "+ + "COUNT(*) > 0 AND COUNT(*) = COUNT(CASE WHEN "+column+" = true THEN 1 END) AS all_true"). + Where("cluster_address = ? AND status = ? AND last_seen > ?", + clusterAddr, "connected", time.Now().Add(-proxyActiveThreshold)). + Scan(&result).Error + + if err != nil { + log.WithContext(ctx).Errorf("query cluster capability %s for %s: %v", column, clusterAddr, err) + return nil + } + + if result.Total == 0 || result.Reported == 0 { + return nil + } + + // If any proxy has not reported (NULL), we can't confirm unanimous support. + if result.Reported < result.Total { + v := false + return &v + } + + return &result.AllTrue +} + +// getClusterCapability returns an aggregated boolean capability for the given +// cluster. It checks active (connected, recently seen) proxies and returns: +// - *true if any proxy in the cluster has the capability set to true, +// - *false if at least one proxy reported but none set it to true, +// - nil if no proxy reported the capability at all. +func (s *SqlStore) getClusterCapability(ctx context.Context, clusterAddr, column string) *bool { + if _, ok := validCapabilityColumns[column]; !ok { + log.WithContext(ctx).Errorf("invalid capability column: %s", column) + return nil + } + + var result struct { + HasCapability bool + AnyTrue bool + } + + err := s.db. + Model(&proxy.Proxy{}). + Select("COUNT(CASE WHEN "+column+" IS NOT NULL THEN 1 END) > 0 AS has_capability, "+ + "COALESCE(MAX(CASE WHEN "+column+" = true THEN 1 ELSE 0 END), 0) = 1 AS any_true"). + Where("cluster_address = ? AND status = ? AND last_seen > ?", + clusterAddr, "connected", time.Now().Add(-proxyActiveThreshold)). + Scan(&result).Error + + if err != nil { + log.WithContext(ctx).Errorf("query cluster capability %s for %s: %v", column, clusterAddr, err) + return nil + } + + if !result.HasCapability { + return nil + } + + return &result.AnyTrue +} + +// CleanupStaleProxies deletes proxies that haven't sent heartbeat in the specified duration +func (s *SqlStore) CleanupStaleProxies(ctx context.Context, inactivityDuration time.Duration) error { + cutoffTime := time.Now().Add(-inactivityDuration) + + result := s.db. + Where("last_seen < ?", cutoffTime). + Delete(&proxy.Proxy{}) + + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to cleanup stale proxies: %v", result.Error) + return status.Errorf(status.Internal, "failed to cleanup stale proxies") + } + + if result.RowsAffected > 0 { + log.WithContext(ctx).Infof("Cleaned up %d stale proxies", result.RowsAffected) + } + + return nil +} + +// GetRoutingPeerNetworks returns the distinct network names where the peer is assigned as a routing peer +// in an enabled network router, either directly or via peer groups. +func (s *SqlStore) GetRoutingPeerNetworks(_ context.Context, accountID, peerID string) ([]string, error) { + var routers []*routerTypes.NetworkRouter + if err := s.db.Select("peer, peer_groups, network_id").Where("account_id = ? AND enabled = true", accountID).Find(&routers).Error; err != nil { + return nil, status.Errorf(status.Internal, "failed to get enabled routers: %v", err) + } + + if len(routers) == 0 { + return nil, nil + } + + var groupPeers []types.GroupPeer + if err := s.db.Select("group_id").Where("account_id = ? AND peer_id = ?", accountID, peerID).Find(&groupPeers).Error; err != nil { + return nil, status.Errorf(status.Internal, "failed to get peer group memberships: %v", err) + } + + groupSet := make(map[string]struct{}, len(groupPeers)) + for _, gp := range groupPeers { + groupSet[gp.GroupID] = struct{}{} + } + + networkIDs := make(map[string]struct{}) + for _, r := range routers { + if r.Peer == peerID { + networkIDs[r.NetworkID] = struct{}{} + } else if r.Peer == "" { + for _, pg := range r.PeerGroups { + if _, ok := groupSet[pg]; ok { + networkIDs[r.NetworkID] = struct{}{} + break + } + } + } + } + + if len(networkIDs) == 0 { + return nil, nil + } + + ids := make([]string, 0, len(networkIDs)) + for id := range networkIDs { + ids = append(ids, id) + } + + var networks []*networkTypes.Network + if err := s.db.Select("name").Where("account_id = ? AND id IN ?", accountID, ids).Find(&networks).Error; err != nil { + return nil, status.Errorf(status.Internal, "failed to get networks: %v", err) + } + + names := make([]string, 0, len(networks)) + for _, n := range networks { + names = append(names, n.Name) + } + + return names, nil +} diff --git a/management/server/types/identity_provider.go b/management/server/types/identity_provider.go index c4498e4d474..79b19a8df45 100644 --- a/management/server/types/identity_provider.go +++ b/management/server/types/identity_provider.go @@ -2,6 +2,7 @@ package types import ( "errors" + "fmt" "net/url" ) @@ -57,6 +58,10 @@ type IdentityProvider struct { ClientID string // ClientSecret is the OAuth2 client secret ClientSecret string + // PKCE enables Proof Key for Code Exchange for the upstream OIDC provider + PKCE bool + // JWKSURL is the URL to the JSON Web Key Set of the identity provider + JWKSURL string } // Copy returns a copy of the IdentityProvider @@ -69,15 +74,19 @@ func (idp *IdentityProvider) Copy() *IdentityProvider { Issuer: idp.Issuer, ClientID: idp.ClientID, ClientSecret: idp.ClientSecret, + PKCE: idp.PKCE, + JWKSURL: idp.JWKSURL, } } // EventMeta returns a map of metadata for activity events func (idp *IdentityProvider) EventMeta() map[string]any { return map[string]any{ - "name": idp.Name, - "type": string(idp.Type), - "issuer": idp.Issuer, + "name": idp.Name, + "type": string(idp.Type), + "issuer": idp.Issuer, + "pkce": idp.PKCE, + "jwks_url": idp.JWKSURL, } } @@ -104,6 +113,12 @@ func (idp *IdentityProvider) Validate() error { if idp.ClientID == "" { return ErrIdentityProviderClientIDRequired } + if idp.JWKSURL != "" { + parsedURL, err := url.Parse(idp.JWKSURL) + if err != nil || parsedURL.Scheme == "" || parsedURL.Host == "" { + return fmt.Errorf("invalid JWKS URL: %s", idp.JWKSURL) + } + } return nil } diff --git a/shared/management/http/api/openapi.yml b/shared/management/http/api/openapi.yml index 0b855db676b..6248c0c1675 100644 --- a/shared/management/http/api/openapi.yml +++ b/shared/management/http/api/openapi.yml @@ -2939,6 +2939,14 @@ components: description: OAuth2 client ID type: string example: 123456789.apps.googleusercontent.com + pkce: + description: Enables Proof Key for Code Exchange for the upstream OIDC provider + type: boolean + example: true + jwks_url: + description: URL to the JSON Web Key Set of the identity provider + type: string + example: https://accounts.google.com/o/oauth2/v1/certs required: - type - name @@ -2965,6 +2973,14 @@ components: description: OAuth2 client secret type: string example: secret123 + pkce: + description: Enables Proof Key for Code Exchange for the upstream OIDC provider + type: boolean + example: true + jwks_url: + description: URL to the JSON Web Key Set of the identity provider + type: string + example: https://accounts.google.com/o/oauth2/v1/certs required: - type - name diff --git a/shared/management/http/api/openapi.yml.orig b/shared/management/http/api/openapi.yml.orig new file mode 100644 index 00000000000..0b855db676b --- /dev/null +++ b/shared/management/http/api/openapi.yml.orig @@ -0,0 +1,11752 @@ +openapi: 3.1.0 +servers: + - url: https://api.netbird.io + description: Default server +info: + title: NetBird REST API + description: API to manipulate groups, rules, policies and retrieve information about peers and users + version: 0.0.1 +tags: + - name: Users + description: Interact with and view information about users. + - name: Tokens + description: Interact with and view information about tokens. + - name: Peers + description: Interact with and view information about peers. + - name: Setup Keys + description: Interact with and view information about setup keys. + - name: Groups + description: Interact with and view information about groups. + - name: Policies + description: Interact with and view information about policies. + - name: Posture Checks + description: Interact with and view information about posture checks. + - name: Routes + description: Interact with and view information about routes. + - name: DNS + description: Interact with and view information about DNS configuration. + - name: DNS Zones + description: Interact with and view information about custom DNS zones. + - name: Events + description: View information about the account and network events. + - name: Accounts + description: View information about the accounts. + - name: Ingress Ports + description: Interact with and view information about the ingress peers and ports. + 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 + description: Interact with and view information about remote jobs. + x-experimental: true + + - name: Usage + description: Retrieve current usage statistics for the account. + x-cloud-only: true + - name: Subscription + description: Manage and view information about account subscriptions. + x-cloud-only: true + - name: Plans + description: Retrieve available plans and products. + x-cloud-only: true + - name: Checkout + description: Manage checkout sessions for plan subscriptions. + x-cloud-only: true + - name: AWS Marketplace + description: Manage AWS Marketplace subscriptions. + x-cloud-only: true + - name: Portal + description: Access customer portal for subscription management. + x-cloud-only: true + - name: Invoice + description: Manage and retrieve account invoices. + x-cloud-only: true + - name: MSP + description: MSP portal for Tenant management. + x-cloud-only: true + - name: IDP SCIM Integrations + description: Manage generic SCIM identity provider integrations for user and group sync. + x-cloud-only: true + - name: IDP Google Integrations + description: Manage Google Workspace identity provider integrations for user and group sync. + x-cloud-only: true + - name: IDP Azure Integrations + description: Manage Azure AD identity provider integrations for user and group sync. + x-cloud-only: true + - name: IDP Okta SCIM Integrations + description: Manage Okta SCIM identity provider integrations for user and group sync. + x-cloud-only: true + - name: EDR Intune Integrations + description: Manage Microsoft Intune EDR integrations. + x-cloud-only: true + - name: EDR SentinelOne Integrations + description: Manage SentinelOne EDR integrations. + x-cloud-only: true + - name: EDR Falcon Integrations + description: Manage CrowdStrike Falcon EDR integrations. + x-cloud-only: true + - name: EDR Huntress Integrations + description: Manage Huntress EDR integrations. + x-cloud-only: true + - name: EDR FleetDM Integrations + description: Manage FleetDM EDR integrations. + x-cloud-only: true + - name: EDR Peers + description: Manage EDR compliance bypass for peers. + x-cloud-only: true + - name: Event Streaming Integrations + description: Manage event streaming integrations. + x-cloud-only: true + - name: Notifications + description: Manage notification channels for account event alerts. + x-cloud-only: true + + +components: + schemas: + PasswordChangeRequest: + type: object + properties: + old_password: + description: The current password + type: string + example: "currentPassword123" + new_password: + description: The new password to set + type: string + example: "newSecurePassword456" + required: + - old_password + - new_password + WorkloadType: + type: string + description: | + Identifies the type of workload the job will execute. + Currently only `"bundle"` is supported. + enum: + - bundle + example: "bundle" + BundleParameters: + type: object + description: These parameters control what gets included in the bundle and how it is processed. + properties: + bundle_for: + type: boolean + description: Whether to generate a bundle for the given timeframe. + example: true + bundle_for_time: + type: integer + minimum: 1 + maximum: 5 + description: Time period in minutes for which to generate the bundle. + example: 2 + log_file_count: + type: integer + minimum: 1 + maximum: 1000 + description: Maximum number of log files to include in the bundle. + example: 100 + anonymize: + type: boolean + description: Whether sensitive data should be anonymized in the bundle. + example: false + required: + - bundle_for + - bundle_for_time + - log_file_count + - anonymize + BundleResult: + type: object + properties: + upload_key: + type: string + example: "upload_key_123" + nullable: true + BundleWorkloadRequest: + type: object + properties: + type: + $ref: '#/components/schemas/WorkloadType' + parameters: + $ref: '#/components/schemas/BundleParameters' + required: + - type + - parameters + BundleWorkloadResponse: + type: object + properties: + type: + $ref: '#/components/schemas/WorkloadType' + parameters: + $ref: '#/components/schemas/BundleParameters' + result: + $ref: '#/components/schemas/BundleResult' + required: + - type + - parameters + - result + WorkloadRequest: + oneOf: + - $ref: '#/components/schemas/BundleWorkloadRequest' + discriminator: + propertyName: type + mapping: + bundle: '#/components/schemas/BundleWorkloadRequest' + WorkloadResponse: + oneOf: + - $ref: '#/components/schemas/BundleWorkloadResponse' + discriminator: + propertyName: type + mapping: + bundle: '#/components/schemas/BundleWorkloadResponse' + JobRequest: + type: object + properties: + workload: + $ref: '#/components/schemas/WorkloadRequest' + required: + - workload + JobResponse: + type: object + properties: + id: + type: string + created_at: + type: string + format: date-time + completed_at: + type: string + format: date-time + nullable: true + triggered_by: + type: string + status: + type: string + enum: [ pending, succeeded, failed ] + failed_reason: + type: string + nullable: true + workload: + $ref: '#/components/schemas/WorkloadResponse' + required: + - id + - created_at + - status + - triggered_by + - workload + Account: + type: object + properties: + id: + description: Account ID + type: string + example: ch8i4ug6lnn4g9hqv7l0 + settings: + $ref: '#/components/schemas/AccountSettings' + domain: + description: Account domain + type: string + example: netbird.io + domain_category: + description: Account domain category + type: string + example: private + created_at: + description: Account creation date (UTC) + type: string + format: date-time + example: "2023-05-05T09:00:35.477782Z" + created_by: + description: Account creator + type: string + example: google-oauth2|277474792786460067937 + onboarding: + $ref: '#/components/schemas/AccountOnboarding' + required: + - id + - settings + - domain + - domain_category + - created_at + - created_by + - onboarding + AccountOnboarding: + type: object + properties: + signup_form_pending: + description: Indicates whether the account signup form is pending + type: boolean + example: true + onboarding_flow_pending: + description: Indicates whether the account onboarding flow is pending + type: boolean + example: false + required: + - signup_form_pending + - onboarding_flow_pending + AccountSettings: + type: object + properties: + peer_login_expiration_enabled: + description: Enables or disables peer login expiration globally. After peer's login has expired the user has to log in (authenticate). Applies only to peers that were added by a user (interactive SSO login). + type: boolean + example: true + peer_login_expiration: + description: Period of time after which peer login expires (seconds). + type: integer + example: 43200 + peer_inactivity_expiration_enabled: + description: Enables or disables peer inactivity expiration globally. After peer's session has expired the user has to log in (authenticate). Applies only to peers that were added by a user (interactive SSO login). + type: boolean + example: true + peer_inactivity_expiration: + description: Period of time of inactivity after which peer session expires (seconds). + type: integer + example: 43200 + regular_users_view_blocked: + description: Allows blocking regular users from viewing parts of the system. + type: boolean + example: true + groups_propagation_enabled: + description: Allows propagate the new user auto groups to peers that belongs to the user + type: boolean + example: true + jwt_groups_enabled: + description: Allows extract groups from JWT claim and add it to account groups. + type: boolean + example: true + jwt_groups_claim_name: + description: Name of the claim from which we extract groups names to add it to account groups. + type: string + example: "roles" + jwt_allow_groups: + description: List of groups to which users are allowed access + type: array + items: + type: string + example: Administrators + routing_peer_dns_resolution_enabled: + description: Enables or disables DNS resolution on the routing peers + type: boolean + example: true + dns_domain: + description: Allows to define a custom dns domain for the account + type: string + example: my-organization.org + network_range: + description: Allows to define a custom network range for the account in CIDR format + type: string + format: cidr + example: 100.64.0.0/16 + peer_expose_enabled: + description: Enables or disables peer expose. If enabled, peers can expose local services through the reverse proxy using the CLI. + type: boolean + example: false + peer_expose_groups: + description: Limits which peer groups are allowed to expose services. If empty, all peers are allowed when peer expose is enabled. + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + extra: + $ref: '#/components/schemas/AccountExtraSettings' + lazy_connection_enabled: + x-experimental: true + description: Enables or disables experimental lazy connection + type: boolean + example: true + auto_update_version: + description: Set Clients auto-update version. "latest", "disabled", or a specific version (e.g "0.50.1") + type: string + example: "0.51.2" + auto_update_always: + description: When true, updates are installed automatically in the background. When false, updates require user interaction from the UI. + type: boolean + example: false + embedded_idp_enabled: + description: Indicates whether the embedded identity provider (Dex) is enabled for this account. This is a read-only field. + type: boolean + readOnly: true + example: false + local_auth_disabled: + description: Indicates whether local (email/password) authentication is disabled. When true, users can only authenticate via external identity providers. This is a read-only field. + type: boolean + readOnly: true + example: false + required: + - peer_login_expiration_enabled + - peer_login_expiration + - peer_inactivity_expiration_enabled + - peer_inactivity_expiration + - regular_users_view_blocked + - peer_expose_enabled + - peer_expose_groups + AccountExtraSettings: + type: object + properties: + peer_approval_enabled: + description: (Cloud only) Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. + type: boolean + example: true + user_approval_required: + description: Enables manual approval for new users joining via domain matching. When enabled, users are blocked with pending approval status until explicitly approved by an admin. + type: boolean + example: false + network_traffic_logs_enabled: + description: Enables or disables network traffic logging. If enabled, all network traffic events from peers will be stored. + type: boolean + example: true + network_traffic_logs_groups: + description: Limits traffic logging to these groups. If unset all peers are enabled. + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + network_traffic_packet_counter_enabled: + description: Enables or disables network traffic packet counter. If enabled, network packets and their size will be counted and reported. (This can have an slight impact on performance) + type: boolean + example: true + required: + - peer_approval_enabled + - user_approval_required + - network_traffic_logs_enabled + - network_traffic_logs_groups + - network_traffic_packet_counter_enabled + AccountRequest: + type: object + properties: + settings: + $ref: '#/components/schemas/AccountSettings' + onboarding: + $ref: '#/components/schemas/AccountOnboarding' + required: + - settings + User: + type: object + properties: + id: + description: User ID + type: string + example: google-oauth2|277474792786460067937 + email: + description: User's email address + type: string + example: demo@netbird.io + password: + description: User's password. Only present when user is created (create user endpoint is called) and only when IdP supports user creation with password. + type: string + example: super_secure_password + name: + description: User's name from idp provider + type: string + example: Tom Schulz + role: + description: User's NetBird account role + type: string + example: admin + status: + description: User's status + type: string + enum: [ "active", "invited", "blocked" ] + example: active + last_login: + description: Last time this user performed a login to the dashboard + type: string + format: date-time + example: "2023-05-05T09:00:35.477782Z" + auto_groups: + description: Group IDs to auto-assign to peers registered by this user + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + is_current: + description: Is true if authenticated user is the same as this user + type: boolean + readOnly: true + example: true + is_service_user: + description: Is true if this user is a service user + type: boolean + readOnly: true + example: false + is_blocked: + description: Is true if this user is blocked. Blocked users can't use the system + type: boolean + example: false + pending_approval: + description: Is true if this user requires approval before being activated. Only applicable for users joining via domain matching when user_approval_required is enabled. + type: boolean + example: false + issued: + description: How user was issued by API or Integration + type: string + example: api + idp_id: + description: Identity provider ID (connector ID) that the user authenticated with. Only populated for users with Dex-encoded user IDs. + type: string + example: okta-abc123 + permissions: + $ref: '#/components/schemas/UserPermissions' + required: + - id + - email + - name + - role + - auto_groups + - status + - is_blocked + - pending_approval + UserPermissions: + type: object + properties: + is_restricted: + type: boolean + description: Indicates whether this User's Peers view is restricted + modules: + type: object + additionalProperties: + type: object + additionalProperties: + type: boolean + propertyNames: + type: string + description: The operation type + propertyNames: + type: string + description: The module name + example: { "networks": { "read": true, "create": false, "update": false, "delete": false }, "peers": { "read": false, "create": false, "update": false, "delete": false } } + required: + - modules + - is_restricted + UserRequest: + type: object + properties: + role: + description: User's NetBird account role + type: string + example: admin + auto_groups: + description: Group IDs to auto-assign to peers registered by this user + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + is_blocked: + description: If set to true then user is blocked and can't use the system + type: boolean + example: false + required: + - role + - auto_groups + - is_blocked + UserCreateRequest: + type: object + properties: + email: + description: User's Email to send invite to + type: string + example: demo@netbird.io + name: + description: User's full name + type: string + example: Tom Schulz + role: + description: User's NetBird account role + type: string + example: admin + auto_groups: + description: Group IDs to auto-assign to peers registered by this user + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + is_service_user: + description: Is true if this user is a service user + type: boolean + example: false + required: + - role + - auto_groups + - is_service_user + UserInviteCreateRequest: + type: object + description: Request to create a user invite link + properties: + email: + description: User's email address + type: string + example: user@example.com + name: + description: User's full name + type: string + example: John Doe + role: + description: User's NetBird account role + type: string + example: user + auto_groups: + description: Group IDs to auto-assign to peers registered by this user + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + expires_in: + description: Invite expiration time in seconds (default 72 hours) + type: integer + example: 259200 + required: + - email + - name + - role + - auto_groups + UserInvite: + type: object + description: A user invite + properties: + id: + description: Invite ID + type: string + example: d5p7eedra0h0lt6f59hg + email: + description: User's email address + type: string + example: user@example.com + name: + description: User's full name + type: string + example: John Doe + role: + description: User's NetBird account role + type: string + example: user + auto_groups: + description: Group IDs to auto-assign to peers registered by this user + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + expires_at: + description: Invite expiration time + type: string + format: date-time + example: "2024-01-25T10:00:00Z" + created_at: + description: Invite creation time + type: string + format: date-time + example: "2024-01-22T10:00:00Z" + expired: + description: Whether the invite has expired + type: boolean + example: false + invite_token: + description: The invite link to be shared with the user. Only returned when the invite is created or regenerated. + type: string + example: nbi_Xk5Lz9mP2vQwRtYu1aN3bC4dE5fGh0ABC123 + required: + - id + - email + - name + - role + - auto_groups + - expires_at + - created_at + - expired + UserInviteInfo: + type: object + description: Public information about an invite + properties: + email: + description: User's email address + type: string + example: user@example.com + name: + description: User's full name + type: string + example: John Doe + expires_at: + description: Invite expiration time + type: string + format: date-time + example: "2024-01-25T10:00:00Z" + valid: + description: Whether the invite is still valid (not expired) + type: boolean + example: true + invited_by: + description: Name of the user who sent the invite + type: string + example: Admin User + required: + - email + - name + - expires_at + - valid + - invited_by + UserInviteAcceptRequest: + type: object + description: Request to accept an invite and set password + properties: + password: + description: >- + The password the user wants to set. Must be at least 8 characters long + and contain at least one uppercase letter, one digit, and one special + character (any character that is not a letter or digit, including spaces). + type: string + format: password + minLength: 8 + pattern: '^(?=.*[0-9])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).{8,}$' + example: SecurePass123! + required: + - password + UserInviteAcceptResponse: + type: object + description: Response after accepting an invite + properties: + success: + description: Whether the invite was accepted successfully + type: boolean + example: true + required: + - success + UserInviteRegenerateRequest: + type: object + description: Request to regenerate an invite link + properties: + expires_in: + description: Invite expiration time in seconds (default 72 hours) + type: integer + example: 259200 + UserInviteRegenerateResponse: + type: object + description: Response after regenerating an invite + properties: + invite_token: + description: The new invite token + type: string + example: nbi_Xk5Lz9mP2vQwRtYu1aN3bC4dE5fGh0ABC123 + invite_expires_at: + description: New invite expiration time + type: string + format: date-time + example: "2024-01-28T10:00:00Z" + required: + - invite_token + - invite_expires_at + PeerMinimum: + type: object + properties: + id: + description: Peer ID + type: string + example: chacbco6lnnbn6cg5s90 + name: + description: Peer's hostname + type: string + example: stage-host-1 + required: + - id + - name + PeerRequest: + type: object + properties: + name: + type: string + example: stage-host-1 + ssh_enabled: + type: boolean + example: true + login_expiration_enabled: + type: boolean + example: false + inactivity_expiration_enabled: + type: boolean + example: false + approval_required: + description: (Cloud only) Indicates whether peer needs approval + type: boolean + example: true + ip: + description: Peer's IP address + type: string + format: ipv4 + example: 100.64.0.15 + required: + - name + - ssh_enabled + - login_expiration_enabled + - inactivity_expiration_enabled + Peer: + allOf: + - $ref: '#/components/schemas/PeerMinimum' + - type: object + properties: + created_at: + description: Peer creation date (UTC) + type: string + format: date-time + example: "2023-05-05T09:00:35.477782Z" + ip: + description: Peer's IP address + type: string + example: 10.64.0.1 + connection_ip: + description: Peer's public connection IP address + type: string + example: 35.64.0.1 + connected: + description: Peer to Management connection status + type: boolean + example: true + last_seen: + description: Last time peer connected to Netbird's management service + type: string + format: date-time + example: "2023-05-05T10:05:26.420578Z" + os: + description: Peer's operating system and version + type: string + example: Darwin 13.2.1 + kernel_version: + description: Peer's operating system kernel version + type: string + example: 23.2.0 + geoname_id: + description: Unique identifier from the GeoNames database for a specific geographical location. + type: integer + example: 2643743 + version: + description: Peer's daemon or cli version + type: string + example: 0.14.0 + groups: + description: Groups that the peer belongs to + type: array + items: + $ref: '#/components/schemas/GroupMinimum' + ssh_enabled: + description: Indicates whether SSH server is enabled on this peer + type: boolean + example: true + user_id: + description: User ID of the user that enrolled this peer + type: string + example: google-oauth2|277474792786460067937 + hostname: + description: Hostname of the machine + type: string + example: stage-host-1 + ui_version: + description: Peer's desktop UI version + type: string + example: 0.14.0 + dns_label: + description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + type: string + example: stage-host-1.netbird.cloud + login_expiration_enabled: + description: Indicates whether peer login expiration has been enabled or not + type: boolean + example: false + login_expired: + description: Indicates whether peer's login expired or not + type: boolean + example: false + last_login: + description: Last time this peer performed log in (authentication). E.g., user authenticated. + type: string + format: date-time + example: "2023-05-05T09:00:35.477782Z" + inactivity_expiration_enabled: + description: Indicates whether peer inactivity expiration has been enabled or not + type: boolean + example: false + approval_required: + description: (Cloud only) Indicates whether peer needs approval + type: boolean + example: true + disapproval_reason: + description: (Cloud only) Reason why the peer requires approval + type: string + country_code: + $ref: '#/components/schemas/CountryCode' + city_name: + $ref: '#/components/schemas/CityName' + serial_number: + description: System serial number + type: string + example: "C02XJ0J0JGH7" + extra_dns_labels: + description: Extra DNS labels added to the peer + type: array + items: + type: string + example: "stage-host-1" + ephemeral: + description: Indicates whether the peer is ephemeral or not + type: boolean + example: false + local_flags: + $ref: '#/components/schemas/PeerLocalFlags' + required: + - city_name + - connected + - connection_ip + - country_code + - created_at + - dns_label + - geoname_id + - groups + - hostname + - ip + - kernel_version + - last_login + - last_seen + - login_expiration_enabled + - login_expired + - inactivity_expiration_enabled + - os + - ssh_enabled + - user_id + - version + - ui_version + - approval_required + - serial_number + - extra_dns_labels + - ephemeral + PeerLocalFlags: + type: object + properties: + rosenpass_enabled: + description: Indicates whether Rosenpass is enabled on this peer + type: boolean + example: true + rosenpass_permissive: + description: Indicates whether Rosenpass is in permissive mode or not + type: boolean + example: false + server_ssh_allowed: + description: Indicates whether SSH access this peer is allowed or not + type: boolean + example: true + disable_client_routes: + description: Indicates whether client routes are disabled on this peer or not + type: boolean + example: false + disable_server_routes: + description: Indicates whether server routes are disabled on this peer or not + type: boolean + example: false + disable_dns: + description: Indicates whether DNS management is disabled on this peer or not + type: boolean + example: false + disable_firewall: + description: Indicates whether firewall management is disabled on this peer or not + type: boolean + example: false + block_lan_access: + description: Indicates whether LAN access is blocked on this peer when used as a routing peer + type: boolean + example: false + block_inbound: + description: Indicates whether inbound traffic is blocked on this peer + type: boolean + example: false + lazy_connection_enabled: + description: Indicates whether lazy connection is enabled on this peer + type: boolean + example: false + PeerTemporaryAccessRequest: + type: object + properties: + name: + description: Peer's hostname + type: string + example: temp-host-1 + wg_pub_key: + description: Peer's WireGuard public key + type: string + example: "n0r3pL4c3h0ld3rK3y==" + rules: + description: List of temporary access rules + type: array + items: + type: string + example: "tcp/80" + required: + - name + - wg_pub_key + - rules + PeerTemporaryAccessResponse: + type: object + properties: + name: + description: Peer's hostname + type: string + example: temp-host-1 + id: + description: Peer ID + type: string + example: chacbco6lnnbn6cg5s90 + rules: + description: List of temporary access rules + type: array + items: + type: string + example: "tcp/80" + required: + - name + - id + - rules + AccessiblePeer: + allOf: + - $ref: '#/components/schemas/PeerMinimum' + - type: object + properties: + ip: + description: Peer's IP address + type: string + example: 10.64.0.1 + dns_label: + description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + type: string + example: stage-host-1.netbird.cloud + user_id: + description: User ID of the user that enrolled this peer + type: string + example: google-oauth2|277474792786460067937 + os: + description: Peer's operating system and version + type: string + example: linux + country_code: + $ref: '#/components/schemas/CountryCode' + city_name: + $ref: '#/components/schemas/CityName' + geoname_id: + description: Unique identifier from the GeoNames database for a specific geographical location. + type: integer + example: 2643743 + connected: + description: Peer to Management connection status + type: boolean + example: true + last_seen: + description: Last time peer connected to Netbird's management service + type: string + format: date-time + example: "2023-05-05T10:05:26.420578Z" + required: + - ip + - dns_label + - user_id + - os + - country_code + - city_name + - geoname_id + - connected + - last_seen + PeerBatch: + allOf: + - $ref: '#/components/schemas/Peer' + - type: object + properties: + created_at: + description: Peer creation date (UTC) + type: string + format: date-time + example: "2023-05-05T09:00:35.477782Z" + accessible_peers_count: + description: Number of accessible peers + type: integer + example: 5 + required: + - created_at + - accessible_peers_count + SetupKeyBase: + type: object + properties: + id: + description: Setup Key ID + type: string + example: 2531583362 + name: + description: Setup key name identifier + type: string + example: Default key + expires: + description: Setup Key expiration date + type: string + format: date-time + example: "2023-06-01T14:47:22.291057Z" + type: + description: Setup key type, one-off for single time usage and reusable + type: string + example: reusable + valid: + description: Setup key validity status + type: boolean + example: true + revoked: + description: Setup key revocation status + type: boolean + example: false + used_times: + description: Usage count of setup key + type: integer + example: 2 + last_used: + description: Setup key last usage date + type: string + format: date-time + example: "2023-05-05T09:00:35.477782Z" + state: + description: Setup key status, "valid", "overused","expired" or "revoked" + type: string + example: valid + auto_groups: + description: List of group IDs to auto-assign to peers registered with this key + type: array + items: + type: string + example: "ch8i4ug6lnn4g9hqv7m0" + updated_at: + description: Setup key last update date + type: string + format: date-time + example: "2023-05-05T09:00:35.477782Z" + usage_limit: + description: A number of times this key can be used. The value of 0 indicates the unlimited usage. + type: integer + example: 0 + ephemeral: + description: Indicate that the peer will be ephemeral or not + type: boolean + example: true + allow_extra_dns_labels: + description: Allow extra DNS labels to be added to the peer + type: boolean + example: true + required: + - id + - key + - name + - expires + - type + - valid + - revoked + - used_times + - last_used + - state + - auto_groups + - updated_at + - usage_limit + - ephemeral + - allow_extra_dns_labels + SetupKeyClear: + allOf: + - $ref: '#/components/schemas/SetupKeyBase' + - type: object + properties: + key: + description: Setup Key as plain text + type: string + example: A616097E-FCF0-48FA-9354-CA4A61142761 + required: + - key + SetupKey: + allOf: + - $ref: '#/components/schemas/SetupKeyBase' + - type: object + properties: + key: + description: Setup Key as secret + type: string + example: A6160**** + required: + - key + SetupKeyRequest: + type: object + properties: + revoked: + description: Setup key revocation status + type: boolean + example: false + auto_groups: + description: List of group IDs to auto-assign to peers registered with this key + type: array + items: + type: string + example: "ch8i4ug6lnn4g9hqv7m0" + required: + - revoked + - auto_groups + CreateSetupKeyRequest: + type: object + properties: + name: + description: Setup Key name + type: string + example: Default key + type: + description: Setup key type, one-off for single time usage and reusable + type: string + example: reusable + expires_in: + description: Expiration time in seconds + type: integer + minimum: 86400 + maximum: 31536000 + example: 86400 + auto_groups: + description: List of group IDs to auto-assign to peers registered with this key + type: array + items: + type: string + example: "ch8i4ug6lnn4g9hqv7m0" + usage_limit: + description: A number of times this key can be used. The value of 0 indicates the unlimited usage. + type: integer + example: 0 + ephemeral: + description: Indicate that the peer will be ephemeral or not + type: boolean + example: true + allow_extra_dns_labels: + description: Allow extra DNS labels to be added to the peer + type: boolean + example: true + required: + - name + - type + - expires_in + - auto_groups + - usage_limit + PersonalAccessToken: + type: object + properties: + id: + description: ID of a token + type: string + example: ch8i54g6lnn4g9hqv7n0 + name: + description: Name of the token + type: string + example: My first token + expiration_date: + description: Date the token expires + type: string + format: date-time + example: "2023-05-05T14:38:28.977616Z" + created_by: + description: User ID of the user who created the token + type: string + example: google-oauth2|277474792786460067937 + created_at: + description: Date the token was created + type: string + format: date-time + example: "2023-05-02T14:48:20.465209Z" + last_used: + description: Date the token was last used + type: string + format: date-time + example: "2023-05-04T12:45:25.9723616Z" + required: + - id + - name + - expiration_date + - created_by + - created_at + PersonalAccessTokenGenerated: + type: object + properties: + plain_token: + description: Plain text representation of the generated token + type: string + example: 2023-05-02T14:48:20.465209Z + personal_access_token: + $ref: '#/components/schemas/PersonalAccessToken' + required: + - plain_token + - personal_access_token + PersonalAccessTokenRequest: + type: object + properties: + name: + description: Name of the token + type: string + example: My first token + expires_in: + description: Expiration in days + type: integer + minimum: 1 + maximum: 365 + example: 30 + required: + - name + - expires_in + GroupMinimum: + type: object + properties: + id: + description: Group ID + type: string + example: ch8i4ug6lnn4g9hqv7m0 + name: + description: Group Name identifier + type: string + example: devs + peers_count: + description: Count of peers associated to the group + type: integer + example: 2 + resources_count: + description: Count of resources associated to the group + type: integer + example: 5 + issued: + description: How the group was issued (api, integration, jwt) + type: string + enum: [ "api", "integration", "jwt" ] + example: api + required: + - id + - name + - peers_count + - resources_count + GroupRequest: + type: object + properties: + name: + type: string + description: Group name identifier + example: devs + peers: + type: array + description: List of peers ids + items: + type: string + example: "ch8i4ug6lnn4g9hqv7m1" + resources: + type: array + items: + $ref: '#/components/schemas/Resource' + required: + - name + Group: + allOf: + - $ref: '#/components/schemas/GroupMinimum' + - type: object + properties: + peers: + description: List of peers object + type: array + items: + $ref: '#/components/schemas/PeerMinimum' + resources: + type: array + items: + $ref: '#/components/schemas/Resource' + required: + - peers + - resources + PolicyRuleMinimum: + type: object + properties: + name: + description: Policy rule name identifier + type: string + example: Default + description: + description: Policy rule friendly description + type: string + example: This is a default rule that allows connections between all the resources + enabled: + description: Policy rule status + type: boolean + example: true + action: + description: Policy rule accept or drops packets + type: string + enum: [ "accept", "drop" ] + example: "accept" + bidirectional: + description: Define if the rule is applicable in both directions, sources, and destinations. + type: boolean + example: true + protocol: + description: Policy rule type of the traffic + type: string + enum: [ "all", "tcp", "udp", "icmp", "netbird-ssh" ] + example: "tcp" + ports: + description: Policy rule affected ports + type: array + items: + type: string + example: "80" + port_ranges: + description: Policy rule affected ports ranges list + type: array + items: + $ref: '#/components/schemas/RulePortRange' + authorized_groups: + description: Map of user group ids to a list of local users + type: object + additionalProperties: + type: array + items: + type: string + example: "group1" + required: + - name + - enabled + - bidirectional + - protocol + - action + + RulePortRange: + description: Policy rule affected ports range + type: object + properties: + start: + description: The starting port of the range + type: integer + example: 80 + end: + description: The ending port of the range + type: integer + example: 320 + required: + - start + - end + + PolicyRuleUpdate: + allOf: + - $ref: '#/components/schemas/PolicyRuleMinimum' + - type: object + properties: + id: + description: Policy rule ID + type: string + example: ch8i4ug6lnn4g9hqv7mg + sources: + description: Policy rule source group IDs + type: array + items: + type: string + example: "ch8i4ug6lnn4g9hqv797" + sourceResource: + description: Policy rule source resource that the rule is applied to + $ref: '#/components/schemas/Resource' + destinations: + description: Policy rule destination group IDs + type: array + items: + type: string + example: "ch8i4ug6lnn4g9h7v7m0" + destinationResource: + description: Policy rule destination resource that the rule is applied to + $ref: '#/components/schemas/Resource' + + PolicyRuleCreate: + allOf: + - $ref: '#/components/schemas/PolicyRuleMinimum' + - type: object + properties: + sources: + description: Policy rule source group IDs + type: array + items: + type: string + example: "ch8i4ug6lnn4g9hqv797" + sourceResource: + description: Policy rule source resource that the rule is applied to + $ref: '#/components/schemas/Resource' + destinations: + description: Policy rule destination group IDs + type: array + items: + type: string + example: "ch8i4ug6lnn4g9h7v7m0" + destinationResource: + description: Policy rule destination resource that the rule is applied to + $ref: '#/components/schemas/Resource' + PolicyRule: + allOf: + - $ref: '#/components/schemas/PolicyRuleMinimum' + - type: object + properties: + id: + description: Policy rule ID + type: string + example: ch8i4ug6lnn4g9hqv7mg + sources: + description: Policy rule source group IDs + type: array + items: + $ref: '#/components/schemas/GroupMinimum' + sourceResource: + description: Policy rule source resource that the rule is applied to + $ref: '#/components/schemas/Resource' + destinations: + description: Policy rule destination group IDs + type: array + items: + $ref: '#/components/schemas/GroupMinimum' + destinationResource: + description: Policy rule destination resource that the rule is applied to + $ref: '#/components/schemas/Resource' + PolicyMinimum: + type: object + properties: + name: + description: Policy name identifier + type: string + example: ch8i4ug6lnn4g9hqv7mg + description: + description: Policy friendly description + type: string + example: This is a default policy that allows connections between all the resources + enabled: + description: Policy status + type: boolean + example: true + required: + - name + - enabled + PolicyUpdate: + allOf: + - $ref: '#/components/schemas/PolicyMinimum' + - type: object + properties: + source_posture_checks: + description: Posture checks ID's applied to policy source groups + type: array + items: + type: string + example: "chacdk86lnnboviihd70" + rules: + description: Policy rule object for policy UI editor + type: array + items: + $ref: '#/components/schemas/PolicyRuleUpdate' + required: + - rules + PolicyCreate: + allOf: + - $ref: '#/components/schemas/PolicyMinimum' + - type: object + properties: + source_posture_checks: + description: Posture checks ID's applied to policy source groups + type: array + items: + type: string + example: "chacdk86lnnboviihd70" + rules: + description: Policy rule object for policy UI editor + type: array + items: + $ref: '#/components/schemas/PolicyRuleUpdate' + required: + - rules + Policy: + allOf: + - $ref: '#/components/schemas/PolicyMinimum' + - type: object + properties: + id: + description: Policy ID + type: string + example: ch8i4ug6lnn4g9hqv7mg + source_posture_checks: + description: Posture checks ID's applied to policy source groups + type: array + items: + type: string + example: "chacdk86lnnboviihd70" + rules: + description: Policy rule object for policy UI editor + type: array + items: + $ref: '#/components/schemas/PolicyRule' + required: + - rules + - source_posture_checks + PostureCheck: + type: object + properties: + id: + description: Posture check ID + type: string + example: ch8i4ug6lnn4g9hqv7mg + name: + description: Posture check unique name identifier + type: string + example: Default + description: + description: Posture check friendly description + type: string + example: This checks if the peer is running required NetBird's version + checks: + $ref: '#/components/schemas/Checks' + required: + - id + - name + - checks + Checks: + description: List of objects that perform the actual checks + type: object + properties: + nb_version_check: + $ref: '#/components/schemas/NBVersionCheck' + os_version_check: + $ref: '#/components/schemas/OSVersionCheck' + geo_location_check: + $ref: '#/components/schemas/GeoLocationCheck' + peer_network_range_check: + $ref: '#/components/schemas/PeerNetworkRangeCheck' + process_check: + $ref: '#/components/schemas/ProcessCheck' + NBVersionCheck: + description: Posture check for the version of NetBird + type: object + $ref: '#/components/schemas/MinVersionCheck' + OSVersionCheck: + description: Posture check for the version of operating system + type: object + properties: + android: + description: Minimum version of Android + $ref: '#/components/schemas/MinVersionCheck' + darwin: + $ref: '#/components/schemas/MinVersionCheck' + ios: + description: Minimum version of iOS + $ref: '#/components/schemas/MinVersionCheck' + linux: + description: Minimum Linux kernel version + $ref: '#/components/schemas/MinKernelVersionCheck' + windows: + description: Minimum Windows kernel build version + $ref: '#/components/schemas/MinKernelVersionCheck' + example: + android: + min_version: "13" + ios: + min_version: "17.3.1" + darwin: + min_version: "14.2.1" + linux: + min_kernel_version: "5.3.3" + windows: + min_kernel_version: "10.0.1234" + MinVersionCheck: + description: Posture check for the version of operating system + type: object + properties: + min_version: + description: Minimum acceptable version + type: string + example: "14.3" + required: + - min_version + MinKernelVersionCheck: + description: Posture check with the kernel version + type: object + properties: + min_kernel_version: + description: Minimum acceptable version + type: string + example: "6.6.12" + required: + - min_kernel_version + GeoLocationCheck: + description: Posture check for geo location + type: object + properties: + locations: + description: List of geo locations to which the policy applies + type: array + items: + $ref: '#/components/schemas/Location' + action: + description: Action to take upon policy match + type: string + enum: [ "allow", "deny" ] + example: "allow" + required: + - locations + - action + PeerNetworkRangeCheck: + description: Posture check for allow or deny access based on peer local network addresses + type: object + properties: + ranges: + description: List of peer network ranges in CIDR notation + type: array + items: + type: string + example: [ "192.168.1.0/24", "10.0.0.0/8", "2001:db8:1234:1a00::/56" ] + action: + description: Action to take upon policy match + type: string + enum: [ "allow", "deny" ] + example: "allow" + required: + - ranges + - action + ProcessCheck: + description: Posture Check for binaries exist and are running in the peer’s system + type: object + properties: + processes: + type: array + items: + $ref: '#/components/schemas/Process' + required: + - processes + Process: + description: Describes the operational activity within a peer's system. + type: object + properties: + linux_path: + description: Path to the process executable file in a Linux operating system + type: string + example: "/usr/local/bin/netbird" + mac_path: + description: Path to the process executable file in a Mac operating system + type: string + example: "/Applications/NetBird.app/Contents/MacOS/netbird" + windows_path: + description: Path to the process executable file in a Windows operating system + type: string + example: "C:\ProgramData\NetBird\netbird.exe" + Location: + description: Describe geographical location information + type: object + properties: + country_code: + $ref: '#/components/schemas/CountryCode' + city_name: + $ref: '#/components/schemas/CityName' + required: + - country_code + CountryCode: + description: 2-letter ISO 3166-1 alpha-2 code that represents the country + type: string + example: "DE" + CityName: + description: Commonly used English name of the city + type: string + example: "Berlin" + Country: + description: Describe country geographical location information + type: object + properties: + country_name: + description: Commonly used English name of the country + type: string + example: "Germany" + country_code: + $ref: '#/components/schemas/CountryCode' + required: + - country_name + - country_code + City: + description: Describe city geographical location information + type: object + properties: + geoname_id: + description: Integer ID of the record in GeoNames database + type: integer + example: 2950158 + city_name: + description: Commonly used English name of the city + type: string + example: "Berlin" + required: + - geoname_id + - city_name + PostureCheckUpdate: + type: object + properties: + name: + description: Posture check name identifier + type: string + example: Default + description: + description: Posture check friendly description + type: string + example: This checks if the peer is running required NetBird's version + checks: + $ref: '#/components/schemas/Checks' + required: + - name + - description + RouteRequest: + type: object + properties: + description: + description: Route description + type: string + example: My first route + network_id: + description: Route network identifier, to group HA routes + type: string + maxLength: 40 + minLength: 1 + example: Route 1 + enabled: + description: Route status + type: boolean + example: true + peer: + description: Peer Identifier associated with route. This property can not be set together with `peer_groups` + type: string + example: chacbco6lnnbn6cg5s91 + peer_groups: + description: Peers Group Identifier associated with route. This property can not be set together with `peer` + type: array + items: + type: string + example: chacbco6lnnbn6cg5s91 + network: + description: Network range in CIDR format, Conflicts with domains + type: string + example: 10.64.0.0/24 + domains: + description: Domain list to be dynamically resolved. Max of 32 domains can be added per route configuration. Conflicts with network + type: array + items: + type: string + minLength: 1 + maxLength: 32 + example: "example.com" + metric: + description: Route metric number. Lowest number has higher priority + type: integer + maximum: 9999 + minimum: 1 + example: 9999 + masquerade: + description: Indicate if peer should masquerade traffic to this route's prefix + type: boolean + example: true + groups: + description: Group IDs containing routing peers + type: array + items: + type: string + example: "chacdk86lnnboviihd70" + keep_route: + description: Indicate if the route should be kept after a domain doesn't resolve that IP anymore + type: boolean + example: true + access_control_groups: + description: Access control group identifier associated with route. + type: array + items: + type: string + example: "chacbco6lnnbn6cg5s91" + skip_auto_apply: + description: Indicate if this exit node route (0.0.0.0/0) should skip auto-application for client routing + type: boolean + example: false + required: + - id + - description + - network_id + - enabled + # Only one property has to be set + #- peer + #- peer_groups + # Only one property has to be set + #- network + #- domains + - metric + - masquerade + - groups + - keep_route + Route: + allOf: + - type: object + properties: + id: + description: Route Id + type: string + example: chacdk86lnnboviihd7g + network_type: + description: Network type indicating if it is a domain route or a IPv4/IPv6 route + type: string + example: IPv4 + required: + - id + - network_type + - $ref: '#/components/schemas/RouteRequest' + Resource: + type: object + properties: + id: + description: ID of the resource + type: string + example: chacdk86lnnboviihd7g + type: + description: Type of the resource + $ref: '#/components/schemas/ResourceType' + required: + - id + - type + ResourceType: + allOf: + - $ref: '#/components/schemas/NetworkResourceType' + - type: string + enum: [ "peer" ] + example: peer + NetworkRequest: + type: object + properties: + name: + description: Network name + type: string + example: Remote Network 1 + description: + description: Network description + type: string + example: A remote network that needs to be accessed + required: + - name + Network: + allOf: + - type: object + properties: + id: + description: Network ID + type: string + example: chacdk86lnnboviihd7g + routers: + description: List of router IDs associated with the network + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + routing_peers_count: + description: Count of routing peers associated with the network + type: integer + example: 2 + resources: + description: List of network resource IDs associated with the network + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m1 + policies: + description: List of policy IDs associated with the network + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m2 + required: + - id + - routers + - resources + - routing_peers_count + - policies + - $ref: '#/components/schemas/NetworkRequest' + NetworkResourceMinimum: + type: object + properties: + name: + description: Network resource name + type: string + example: Remote Resource 1 + description: + description: Network resource description + type: string + example: A remote resource inside network 1 + address: + description: Network resource address (either a direct host like 1.1.1.1 or 1.1.1.1/32, or a subnet like 192.168.178.0/24, or domains like example.com and *.example.com) + type: string + example: "1.1.1.1" + enabled: + description: Network resource status + type: boolean + example: true + required: + - name + - address + - enabled + NetworkResourceRequest: + allOf: + - $ref: '#/components/schemas/NetworkResourceMinimum' + - type: object + properties: + groups: + description: Group IDs containing the resource + type: array + items: + type: string + example: "chacdk86lnnboviihd70" + required: + - groups + - address + NetworkResource: + allOf: + - type: object + properties: + id: + description: Network Resource ID + type: string + example: chacdk86lnnboviihd7g + type: + $ref: '#/components/schemas/NetworkResourceType' + groups: + description: Groups that the resource belongs to + type: array + items: + $ref: '#/components/schemas/GroupMinimum' + required: + - id + - type + - groups + - $ref: '#/components/schemas/NetworkResourceMinimum' + NetworkResourceType: + description: Network resource type based of the address + type: string + enum: [ "host", "subnet", "domain" ] + example: host + NetworkRouterRequest: + type: object + properties: + peer: + description: Peer Identifier associated with route. This property can not be set together with `peer_groups` + type: string + example: chacbco6lnnbn6cg5s91 + peer_groups: + description: Peers Group Identifier associated with route. This property can not be set together with `peer` + type: array + items: + type: string + example: chacbco6lnnbn6cg5s91 + metric: + description: Route metric number. Lowest number has higher priority + type: integer + maximum: 9999 + minimum: 1 + example: 9999 + masquerade: + description: Indicate if peer should masquerade traffic to this route's prefix + type: boolean + example: true + enabled: + description: Network router status + type: boolean + example: true + required: + # Only one property has to be set + #- peer + #- peer_groups + - metric + - masquerade + - enabled + NetworkRouter: + allOf: + - type: object + properties: + id: + description: Network Router Id + type: string + example: chacdk86lnnboviihd7g + required: + - id + - $ref: '#/components/schemas/NetworkRouterRequest' + Nameserver: + type: object + properties: + ip: + description: Nameserver IP + type: string + example: 8.8.8.8 + ns_type: + description: Nameserver Type + type: string + enum: [ "udp" ] + example: udp + port: + description: Nameserver Port + type: integer + example: 53 + required: + - ip + - ns_type + - port + NameserverGroupRequest: + type: object + properties: + name: + description: Name of nameserver group name + type: string + maxLength: 40 + minLength: 1 + example: Google DNS + description: + description: Description of the nameserver group + type: string + example: Google DNS servers + nameservers: + description: Nameserver list + minLength: 1 + maxLength: 3 + type: array + items: + $ref: '#/components/schemas/Nameserver' + enabled: + description: Nameserver group status + type: boolean + example: true + groups: + description: Distribution group IDs that defines group of peers that will use this nameserver group + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + primary: + description: Defines if a nameserver group is primary that resolves all domains. It should be true only if domains list is empty. + type: boolean + example: true + domains: + description: Match domain list. It should be empty only if primary is true. + type: array + items: + type: string + minLength: 1 + maxLength: 255 + example: "example.com" + search_domains_enabled: + description: Search domain status for match domains. It should be true only if domains list is not empty. + type: boolean + example: true + required: + - name + - description + - nameservers + - enabled + - groups + - primary + - domains + - search_domains_enabled + NameserverGroup: + allOf: + - type: object + properties: + id: + description: Nameserver group ID + type: string + example: ch8i4ug6lnn4g9hqv7m0 + required: + - id + - $ref: '#/components/schemas/NameserverGroupRequest' + DNSSettings: + type: object + properties: + disabled_management_groups: + description: Groups whose DNS management is disabled + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + required: + - disabled_management_groups + ZoneRequest: + type: object + properties: + name: + description: Zone name identifier + type: string + maxLength: 255 + minLength: 1 + example: Office Zone + domain: + description: Zone domain (FQDN) + type: string + example: example.com + enabled: + description: Zone status + type: boolean + default: true + enable_search_domain: + description: Enable this zone as a search domain + type: boolean + example: false + distribution_groups: + description: Group IDs that defines groups of peers that will resolve this zone + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + required: + - name + - domain + - enable_search_domain + - distribution_groups + Zone: + allOf: + - type: object + properties: + id: + description: Zone ID + type: string + example: ch8i4ug6lnn4g9hqv7m0 + records: + description: DNS records associated with this zone + type: array + items: + $ref: '#/components/schemas/DNSRecord' + required: + - id + - enabled + - records + - $ref: '#/components/schemas/ZoneRequest' + DNSRecordType: + type: string + description: DNS record type + enum: + - A + - AAAA + - CNAME + example: A + DNSRecordRequest: + type: object + properties: + name: + description: FQDN for the DNS record. Must be a subdomain within or match the zone's domain. + type: string + example: www.example.com + type: + $ref: '#/components/schemas/DNSRecordType' + content: + description: DNS record content (IP address for A/AAAA, domain for CNAME) + type: string + maxLength: 255 + minLength: 1 + example: 192.168.1.1 + ttl: + description: Time to live in seconds + type: integer + minimum: 0 + example: 300 + required: + - name + - type + - content + - ttl + DNSRecord: + allOf: + - type: object + properties: + id: + description: DNS record ID + type: string + example: ch8i4ug6lnn4g9hqv7m0 + required: + - id + - $ref: '#/components/schemas/DNSRecordRequest' + Event: + type: object + properties: + id: + description: Event unique identifier + type: string + example: 10 + timestamp: + description: The date and time when the event occurred + type: string + format: date-time + example: "2023-05-05T10:04:37.473542Z" + activity: + description: The activity that occurred during the event + type: string + example: Route created + 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", + "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. + type: string + example: google-oauth2|123456789012345678901 + initiator_name: + description: The name of the initiator of the event. + type: string + example: John Doe + initiator_email: + description: The e-mail address of the initiator of the event. E.g., an e-mail of a user that triggered the event. + type: string + example: demo@netbird.io + target_id: + description: The ID of the target of the event. E.g., an ID of the peer that a user removed. + type: string + example: chad9d86lnnc59g18ou0 + meta: + description: The metadata of the event + type: object + additionalProperties: + type: string + example: { "name": "my route", "network_range": "10.64.0.0/24", "peer_id": "chacbco6lnnbn6cg5s91" } + required: + - id + - timestamp + - activity + - activity_code + - initiator_id + - initiator_name + - initiator_email + - target_id + - meta + IngressPeerCreateRequest: + type: object + properties: + peer_id: + description: ID of the peer that is used as an ingress peer + type: string + example: ch8i4ug6lnn4g9hqv7m0 + enabled: + description: Defines if an ingress peer is enabled + type: boolean + example: true + fallback: + description: Defines if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer + type: boolean + example: true + required: + - peer_id + - enabled + - fallback + IngressPeerUpdateRequest: + type: object + properties: + enabled: + description: Defines if an ingress peer is enabled + type: boolean + example: true + fallback: + description: Defines if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer + type: boolean + example: true + required: + - enabled + - fallback + IngressPeer: + type: object + properties: + id: + description: ID of the ingress peer + type: string + example: ch8i4ug6lnn4g9hqv7m0 + peer_id: + description: ID of the peer that is used as an ingress peer + type: string + example: x7p3kqf2rdd8j5zxw4n9 + ingress_ip: + description: Ingress IP address of the ingress peer where the traffic arrives + type: string + example: 192.34.0.123 + available_ports: + $ref: '#/components/schemas/AvailablePorts' + enabled: + description: Indicates if an ingress peer is enabled + type: boolean + example: true + connected: + description: Indicates if an ingress peer is connected to the management server + type: boolean + example: true + fallback: + description: Indicates if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer + type: boolean + example: true + region: + description: Region of the ingress peer + type: string + example: germany + required: + - id + - peer_id + - ingress_ip + - available_ports + - enabled + - connected + - fallback + - region + AvailablePorts: + type: object + properties: + tcp: + description: Number of available TCP ports left on the ingress peer + type: integer + example: 45765 + udp: + description: Number of available UDP ports left on the ingress peer + type: integer + example: 50000 + required: + - tcp + - udp + IngressPortAllocationRequest: + type: object + properties: + name: + description: Name of the ingress port allocation + type: string + example: Ingress Port Allocation 1 + enabled: + description: Indicates if an ingress port allocation is enabled + type: boolean + example: true + port_ranges: + description: List of port ranges that are forwarded by the ingress peer + type: array + items: + $ref: '#/components/schemas/IngressPortAllocationRequestPortRange' + direct_port: + description: Direct port allocation + $ref: '#/components/schemas/IngressPortAllocationRequestDirectPort' + required: + - name + - enabled + IngressPortAllocationRequestPortRange: + type: object + properties: + start: + description: The starting port of the range of forwarded ports + type: integer + example: 80 + end: + description: The ending port of the range of forwarded ports + type: integer + example: 320 + protocol: + description: The protocol accepted by the port range + type: string + enum: [ "tcp", "udp", "tcp/udp" ] + example: tcp + required: + - start + - end + - protocol + IngressPortAllocationRequestDirectPort: + type: object + properties: + count: + description: The number of ports to be forwarded + type: integer + example: 5 + protocol: + description: The protocol accepted by the port + type: string + enum: [ "tcp", "udp", "tcp/udp" ] + example: udp + required: + - count + - protocol + IngressPortAllocation: + type: object + properties: + id: + description: ID of the ingress port allocation + type: string + example: ch8i4ug6lnn4g9hqv7m0 + name: + description: Name of the ingress port allocation + type: string + example: Ingress Peer Allocation 1 + ingress_peer_id: + description: ID of the ingress peer that forwards the ports + type: string + example: x7p3kqf2rdd8j5zxw4n9 + region: + description: Region of the ingress peer + type: string + example: germany + enabled: + description: Indicates if an ingress port allocation is enabled + type: boolean + example: true + ingress_ip: + description: Ingress IP address of the ingress peer where the traffic arrives + type: string + example: 192.34.0.123 + port_range_mappings: + description: List of port ranges that are allowed to be used by the ingress peer + type: array + items: + $ref: '#/components/schemas/IngressPortAllocationPortMapping' + required: + - id + - name + - ingress_peer_id + - region + - enabled + - ingress_ip + - port_range_mappings + IngressPortAllocationPortMapping: + type: object + properties: + translated_start: + description: The starting port of the translated range of forwarded ports + type: integer + example: 80 + translated_end: + description: The ending port of the translated range of forwarded ports + type: integer + example: 320 + ingress_start: + description: The starting port of the range of ingress ports mapped to the forwarded ports + type: integer + example: 1080 + ingress_end: + description: The ending port of the range of ingress ports mapped to the forwarded ports + type: integer + example: 1320 + protocol: + description: Protocol accepted by the ports + type: string + enum: [ "tcp", "udp", "tcp/udp" ] + example: tcp + required: + - translated_start + - translated_end + - ingress_start + - ingress_end + - protocol + NetworkTrafficLocation: + type: object + properties: + city_name: + type: string + description: "Name of the city (if known)." + example: "Berlin" + country_code: + type: string + description: "ISO country code (if known)." + example: "DE" + required: + - city_name + - country_code + NetworkTrafficEndpoint: + type: object + properties: + id: + type: string + description: "ID of this endpoint (e.g., peer ID or resource ID)." + example: "ch8i4ug6lnn4g9hqv7m0" + type: + type: string + description: "Type of the endpoint object (e.g., UNKNOWN, PEER, HOST_RESOURCE)." + example: "PEER" + name: + type: string + description: "Name is the name of the endpoint object (e.g., a peer name)." + example: "My Peer" + geo_location: + $ref: '#/components/schemas/NetworkTrafficLocation' + os: + type: string + nullable: true + description: "Operating system of the peer, if applicable." + example: "Linux" + address: + type: string + description: "IP address (and possibly port) in string form." + example: "100.64.0.10:51820" + dns_label: + type: string + nullable: true + description: "DNS label/name if available." + example: "*.mydomain.com" + required: + - id + - type + - name + - geo_location + - os + - address + - dns_label + NetworkTrafficUser: + type: object + properties: + id: + type: string + description: "UserID is the ID of the user that initiated the event (can be empty as not every event is user-initiated)." + example: "google-oauth2|123456789012345678901" + email: + type: string + description: "Email of the user who initiated the event (if any)." + example: "alice@netbird.io" + name: + type: string + description: "Name of the user who initiated the event (if any)." + example: "Alice Smith" + required: + - id + - email + - name + NetworkTrafficPolicy: + type: object + properties: + id: + type: string + description: "ID of the policy that allowed this event." + example: "ch8i4ug6lnn4g9hqv7m0" + name: + type: string + description: "Name of the policy that allowed this event." + example: "All to All" + required: + - id + - name + NetworkTrafficICMP: + type: object + properties: + type: + type: integer + description: "ICMP type (if applicable)." + example: 8 + code: + type: integer + description: "ICMP code (if applicable)." + example: 0 + required: + - type + - code + NetworkTrafficSubEvent: + type: object + properties: + type: + type: string + description: Type of the event (e.g., TYPE_UNKNOWN, TYPE_START, TYPE_END, TYPE_DROP). + example: TYPE_START + timestamp: + type: string + format: date-time + description: Timestamp of the event as sent by the peer. + example: 2025-03-20T16:23:58.125397Z + required: + - type + - timestamp + NetworkTrafficEvent: + type: object + properties: + flow_id: + type: string + description: "FlowID is the ID of the connection flow. Not unique because it can be the same for multiple events (e.g., start and end of the connection)." + example: "61092452-b17c-4b14-b7cf-a2158c549826" + reporter_id: + type: string + description: "ID of the reporter of the event (e.g., the peer that reported the event)." + example: "ch8i4ug6lnn4g9hqv7m0" + source: + $ref: '#/components/schemas/NetworkTrafficEndpoint' + destination: + $ref: '#/components/schemas/NetworkTrafficEndpoint' + user: + $ref: '#/components/schemas/NetworkTrafficUser' + policy: + $ref: '#/components/schemas/NetworkTrafficPolicy' + icmp: + $ref: '#/components/schemas/NetworkTrafficICMP' + protocol: + type: integer + description: "Protocol is the protocol of the traffic (e.g. 1 = ICMP, 6 = TCP, 17 = UDP, etc.)." + example: 6 + direction: + type: string + description: "Direction of the traffic (e.g. DIRECTION_UNKNOWN, INGRESS, EGRESS)." + example: "INGRESS" + rx_bytes: + type: integer + description: "Number of bytes received." + example: 1234 + rx_packets: + type: integer + description: "Number of packets received." + example: 5 + tx_bytes: + type: integer + description: "Number of bytes transmitted." + example: 1234 + tx_packets: + type: integer + description: "Number of packets transmitted." + example: 5 + events: + type: array + description: "List of events that are correlated to this flow (e.g., start, end)." + items: + $ref: '#/components/schemas/NetworkTrafficSubEvent' + required: + - id + - flow_id + - reporter_id + - receive_timestamp + - source + - destination + - user + - policy + - icmp + - protocol + - direction + - rx_bytes + - rx_packets + - tx_bytes + - tx_packets + - events + NetworkTrafficEventsResponse: + type: object + properties: + data: + type: array + description: List of network traffic events + items: + $ref: "#/components/schemas/NetworkTrafficEvent" + page: + type: integer + description: Current page number + page_size: + type: integer + description: Number of items per page + total_records: + type: integer + description: Total number of event records available + total_pages: + type: integer + description: Total number of pages available + required: + - data + - page + - 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" + subdivision_code: + type: string + description: "First-level administrative subdivision ISO code (e.g. state/province)" + example: "CA" + bytes_upload: + type: integer + format: int64 + description: "Bytes uploaded (request body size)" + example: 1024 + bytes_download: + type: integer + format: int64 + description: "Bytes downloaded (response body size)" + example: 8192 + protocol: + type: string + description: "Protocol type: http, tcp, or udp" + example: "http" + metadata: + type: object + additionalProperties: + type: string + description: "Extra context about the request (e.g. crowdsec_verdict)" + required: + - id + - service_id + - timestamp + - method + - host + - path + - duration_ms + - status_code + - bytes_upload + - bytes_download + 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 + enum: + - oidc + - zitadel + - entra + - google + - okta + - pocketid + - microsoft + example: oidc + IdentityProvider: + type: object + properties: + id: + description: Identity provider ID + type: string + example: ch8i4ug6lnn4g9hqv7l0 + type: + $ref: '#/components/schemas/IdentityProviderType' + name: + description: Human-readable name for the identity provider + type: string + example: My OIDC Provider + issuer: + description: OIDC issuer URL + type: string + example: https://accounts.google.com + client_id: + description: OAuth2 client ID + type: string + example: 123456789.apps.googleusercontent.com + required: + - type + - name + - issuer + - client_id + IdentityProviderRequest: + type: object + properties: + type: + $ref: '#/components/schemas/IdentityProviderType' + name: + description: Human-readable name for the identity provider + type: string + example: My OIDC Provider + issuer: + description: OIDC issuer URL + type: string + example: https://accounts.google.com + client_id: + description: OAuth2 client ID + type: string + example: 123456789.apps.googleusercontent.com + client_secret: + description: OAuth2 client secret + type: string + example: secret123 + required: + - type + - name + - issuer + - client_id + - client_secret + Service: + type: object + properties: + id: + type: string + description: Service ID + example: "cs8i4ug6lnn4g9hqv7mg" + name: + type: string + description: Service name + example: "myapp.example.netbird.app" + domain: + type: string + description: Domain for the service + example: "myapp.example.netbird.app" + mode: + type: string + description: Service mode. "http" for L7 reverse proxy, "tcp"/"udp"/"tls" for L4 passthrough. + enum: [http, tcp, udp, tls] + default: http + example: "http" + listen_port: + type: integer + minimum: 0 + maximum: 65535 + description: Port the proxy listens on (L4/TLS only) + example: 8443 + port_auto_assigned: + type: boolean + description: Whether the listen port was auto-assigned + readOnly: true + example: false + 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 + example: true + terminated: + type: boolean + description: Whether the service has been terminated. Terminated services cannot be updated. Services that violate the Terms of Service will be terminated. + readOnly: true + example: false + 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 + example: false + rewrite_redirects: + type: boolean + description: When true, Location headers in backend responses are rewritten to replace the backend address with the public-facing domain + example: false + auth: + $ref: '#/components/schemas/ServiceAuthConfig' + access_restrictions: + $ref: '#/components/schemas/AccessRestrictions' + 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 + example: "myapp.example.netbird.app" + domain: + type: string + description: Domain for the service + example: "myapp.example.netbird.app" + mode: + type: string + description: Service mode. "http" for L7 reverse proxy, "tcp"/"udp"/"tls" for L4 passthrough. + enum: [http, tcp, udp, tls] + default: http + example: "http" + listen_port: + type: integer + minimum: 0 + maximum: 65535 + description: Port the proxy listens on (L4/TLS only). Set to 0 for auto-assignment. + example: 5432 + 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 + example: 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 + example: false + rewrite_redirects: + type: boolean + description: When true, Location headers in backend responses are rewritten to replace the backend address with the public-facing domain + example: false + auth: + $ref: '#/components/schemas/ServiceAuthConfig' + access_restrictions: + $ref: '#/components/schemas/AccessRestrictions' + required: + - name + - domain + - enabled + ServiceTargetOptions: + type: object + properties: + skip_tls_verify: + type: boolean + description: Skip TLS certificate verification for this backend + example: false + request_timeout: + type: string + description: Per-target response timeout as a Go duration string (e.g. "30s", "2m") + example: "30s" + path_rewrite: + type: string + description: Controls how the request path is rewritten before forwarding to the backend. Default strips the matched prefix. "preserve" keeps the full original request path. + enum: [preserve] + example: "preserve" + custom_headers: + type: object + description: Extra headers sent to the backend. Hop-by-hop and proxy-managed headers (Host, Connection, Transfer-Encoding, etc.) are rejected. + propertyNames: + type: string + pattern: '^[!#$%&''*+.^_`|~0-9A-Za-z-]+$' + additionalProperties: + type: string + pattern: '^[^\r\n]*$' + example: {"X-Custom-Header": "value"} + proxy_protocol: + type: boolean + description: Send PROXY Protocol v2 header to this backend (TCP/TLS only) + example: false + session_idle_timeout: + type: string + description: Idle timeout before a UDP session is reaped, as a Go duration string (e.g. "30s", "2m"). + example: "2m" + ServiceTarget: + type: object + properties: + target_id: + type: string + description: Target ID + example: "cs8i4ug6lnn4g9hqv7mg" + target_type: + type: string + description: Target type + enum: [peer, host, domain, subnet] + example: "subnet" + path: + type: string + description: URL path prefix for this target (HTTP only) + example: "/" + protocol: + type: string + description: Protocol to use when connecting to the backend + enum: [http, https, tcp, udp] + example: "http" + host: + type: string + description: Backend ip or domain for this target + example: "10.10.0.1" + port: + type: integer + minimum: 1 + maximum: 65535 + description: Backend port for this target + example: 8080 + enabled: + type: boolean + description: Whether this target is enabled + example: true + options: + $ref: '#/components/schemas/ServiceTargetOptions' + 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' + header_auths: + type: array + items: + $ref: '#/components/schemas/HeaderAuthConfig' + HeaderAuthConfig: + type: object + description: Static header-value authentication. The proxy checks that the named header matches the configured value. + properties: + enabled: + type: boolean + description: Whether header auth is enabled + example: true + header: + type: string + description: HTTP header name to check (e.g. "Authorization", "X-API-Key") + example: "X-API-Key" + value: + type: string + description: Expected header value. For Basic auth use "Basic base64(user:pass)". For Bearer use "Bearer token". Cleared in responses. + example: "my-secret-api-key" + required: + - enabled + - header + - value + AccessRestrictions: + type: object + description: Connection-level access restrictions based on IP address or geography. Applies to both HTTP and L4 services. + properties: + allowed_cidrs: + type: array + items: + type: string + format: cidr + example: "192.168.1.0/24" + description: CIDR allowlist. If non-empty, only IPs matching these CIDRs are allowed. + blocked_cidrs: + type: array + items: + type: string + format: cidr + example: "10.0.0.0/8" + description: CIDR blocklist. Connections from these CIDRs are rejected. Evaluated after allowed_cidrs. + allowed_countries: + type: array + items: + type: string + pattern: '^[a-zA-Z]{2}$' + example: "US" + description: ISO 3166-1 alpha-2 country codes to allow. If non-empty, only these countries are permitted. + blocked_countries: + type: array + items: + type: string + pattern: '^[a-zA-Z]{2}$' + example: "DE" + description: ISO 3166-1 alpha-2 country codes to block. + crowdsec_mode: + type: string + enum: + - "off" + - "enforce" + - "observe" + default: "off" + description: CrowdSec IP reputation mode. Only available when the proxy cluster supports CrowdSec. + PasswordAuthConfig: + type: object + properties: + enabled: + type: boolean + description: Whether password auth is enabled + example: true + password: + type: string + description: Auth password + example: "s3cret" + required: + - enabled + - password + PINAuthConfig: + type: object + properties: + enabled: + type: boolean + description: Whether PIN auth is enabled + example: false + pin: + type: string + description: PIN value + example: "1234" + required: + - enabled + - pin + BearerAuthConfig: + type: object + properties: + enabled: + type: boolean + description: Whether bearer auth is enabled + example: true + distribution_groups: + type: array + items: + type: string + example: "ch8i4ug6lnn4g9hqv7mg" + 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 + example: false + 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 + example: "ds8i4ug6lnn4g9hqv7mg" + domain: + type: string + description: Domain name + example: "example.netbird.app" + validated: + type: boolean + description: Whether the domain has been validated + example: true + type: + $ref: '#/components/schemas/ReverseProxyDomainType' + target_cluster: + type: string + description: The proxy cluster this domain is validated against (only for custom domains) + example: "eu.proxy.netbird.io" + supports_custom_ports: + type: boolean + description: Whether the cluster supports binding arbitrary TCP/UDP ports + example: true + require_subdomain: + type: boolean + description: Whether a subdomain label is required in front of this domain. When true, the domain cannot be used bare. + example: false + supports_crowdsec: + type: boolean + description: Whether the proxy cluster has CrowdSec configured + example: false + required: + - id + - domain + - validated + - type + ReverseProxyDomainRequest: + type: object + properties: + domain: + type: string + description: Domain name + example: "myapp.example.com" + target_cluster: + type: string + description: The proxy cluster this domain should be validated against + example: "eu.proxy.netbird.io" + required: + - domain + - target_cluster + InstanceStatus: + type: object + description: Instance status information + properties: + setup_required: + description: Indicates whether the instance requires initial setup + type: boolean + example: true + required: + - setup_required + SetupRequest: + type: object + description: Request to set up the initial admin user + properties: + email: + description: Email address for the admin user + type: string + example: admin@example.com + password: + description: Password for the admin user (minimum 8 characters) + type: string + format: password + minLength: 8 + example: securepassword123 + name: + description: Display name for the admin user (defaults to email if not provided) + type: string + example: Admin User + required: + - email + - password + - name + SetupResponse: + type: object + description: Response after successful instance setup + properties: + user_id: + description: The ID of the created user + type: string + example: abc123def456 + email: + description: Email address of the created user + type: string + example: admin@example.com + required: + - user_id + - email + InstanceVersionInfo: + type: object + description: Version information for NetBird components + properties: + management_current_version: + description: The current running version of the management server + type: string + example: "0.35.0" + dashboard_available_version: + description: The latest available version of the dashboard (from GitHub releases) + type: string + example: "2.10.0" + management_available_version: + description: The latest available version of the management server (from GitHub releases) + type: string + example: "0.35.0" + management_update_available: + description: Indicates if a newer management version is available + type: boolean + example: true + required: + - management_current_version + - management_update_available + UsageStats: + type: object + properties: + active_users: + type: integer + format: int64 + description: Number of active users. + example: 15 + total_users: + type: integer + format: int64 + description: Total number of users. + example: 20 + active_peers: + type: integer + format: int64 + description: Number of active peers. + example: 10 + total_peers: + type: integer + format: int64 + description: Total number of peers. + example: 25 + required: + - active_users + - total_users + - active_peers + - total_peers + Product: + type: object + properties: + name: + type: string + description: Name of the product. + example: "Basic Plan" + description: + type: string + description: Detailed description of the product. + example: "This is the basic plan with limited features." + features: + type: array + description: List of features provided by the product. + items: + type: string + example: [ "5 free users", "Basic support" ] + prices: + type: array + description: List of prices for the product in different currencies + items: + $ref: "#/components/schemas/Price" + free: + type: boolean + description: Indicates whether the product is free or not. + example: false + required: + - name + - description + - features + - prices + - free + Price: + type: object + properties: + price_id: + type: string + description: Unique identifier for the price. + example: "price_H2KmRb4u1tP0sR7s" + currency: + type: string + description: Currency code for this price. + example: "USD" + price: + type: integer + description: Price amount in minor units (e.g., cents). + example: 1000 + unit: + type: string + description: Unit of measurement for this price (e.g., per user). + example: "user" + required: + - price_id + - currency + - price + - unit + Subscription: + type: object + properties: + active: + type: boolean + description: Indicates whether the subscription is active or not. + example: true + plan_tier: + type: string + description: The tier of the plan for the subscription. + example: "basic" + price_id: + type: string + description: Unique identifier for the price of the subscription. + example: "price_1HhxOpBzq4JbCqRmJxkpzL2V" + remaining_trial: + type: integer + description: The remaining time for the trial period, in seconds. + example: 3600 + features: + type: array + description: List of features included in the subscription. + items: + type: string + example: [ "free", "idp-sync", "audit-logs" ] + currency: + type: string + description: Currency code of the subscription. + example: "USD" + price: + type: integer + description: Price amount in minor units (e.g., cents). + example: 1000 + provider: + type: string + description: The provider of the subscription. + example: [ "stripe", "aws" ] + updated_at: + type: string + format: date-time + description: The date and time when the subscription was last updated. + example: "2021-08-01T12:00:00Z" + required: + - active + - plan_tier + - price_id + - updated_at + - currency + - price + - provider + PortalResponse: + type: object + properties: + session_id: + type: string + description: The unique identifier for the customer portal session. + example: "cps_test_123456789" + url: + type: string + description: URL to redirect the user to the customer portal. + example: "https://billing.stripe.com/session/a1b2c3d4e5f6g7h8i9j0k" + required: + - session_id + - url + CheckoutResponse: + type: object + properties: + session_id: + type: string + description: The unique identifier for the checkout session. + example: "cs_test_a1b2c3d4e5f6g7h8i9j0" + url: + type: string + description: URL to redirect the user to the checkout session. + example: "https://checkout.stripe.com/pay/cs_test_a1b2c3d4e5f6g7h8i9j0" + required: + - session_id + - url + StripeWebhookEvent: + type: object + properties: + type: + type: string + description: The type of event received from Stripe. + example: "customer.subscription.updated" + data: + type: object + description: The data associated with the event from Stripe. + example: + object: + id: "sub_123456789" + object: "subscription" + status: "active" + items: + object: "list" + data: + - id: "si_123456789" + object: "subscription_item" + price: + id: "price_1HhxOpBzq4JbCqRmJxkpzL2V" + object: "price" + unit_amount: 2000 + currency: "usd" + billing_cycle_anchor: 1609459200 + InvoiceResponse: + type: object + properties: + id: + type: string + description: The Stripe invoice id + example: "in_1MtHbELkdIwHu7ixl4OzzPMv" + type: + type: string + description: The invoice type + enum: + - account + - tenants + period_start: + type: string + format: date-time + description: The start date of the invoice period. + example: "2021-08-01T12:00:00Z" + period_end: + type: string + format: date-time + description: The end date of the invoice period. + example: "2021-08-31T12:00:00Z" + required: + - id + - type + - period_start + - period_end + InvoicePDFResponse: + type: object + properties: + url: + type: string + description: URL to redirect the user to invoice. + example: "https://invoice.stripe.com/i/acct_1M2DaBKina4I2KUb/test_YWNjdF8xTTJEdVBLaW5hM0kyS1ViLF1SeFpQdEJZd3lUOGNEajNqeWdrdXY2RFM4aHcyCnpsLDEzMjg3GTgyNQ02000JoIHc1X?s=db" + required: + - url + CreateTenantRequest: + type: object + properties: + name: + type: string + description: The name for the MSP tenant + example: "My new tenant" + domain: + type: string + description: The name for the MSP tenant + example: "tenant.com" + groups: + description: MSP users Groups that can access the Tenant and Roles to assume + type: array + items: + $ref: "#/components/schemas/TenantGroupResponse" + required: + - name + - domain + - groups + UpdateTenantRequest: + type: object + properties: + name: + type: string + description: The name for the MSP tenant + example: "My new tenant" + groups: + description: MSP users Groups that can access the Tenant and Roles to assume + type: array + items: + $ref: "#/components/schemas/TenantGroupResponse" + required: + - name + - groups + GetTenantsResponse: + type: array + items: + $ref: "#/components/schemas/TenantResponse" + DNSChallengeResponse: + type: object + properties: + dns_challenge: + type: string + description: The DNS challenge to set in a TXT record + example: YXNkYSBkYXNhc2Rhc2RhIGFzZGFzZDJhc2QyNDUxNQ + required: + - dns_challenge + TenantGroupResponse: + type: object + properties: + id: + type: string + description: The Group ID + example: ch8i4ug6lnn4g9hqv7m0 + role: + type: string + description: The Role name + example: "admin" + required: + - id + - role + TenantResponse: + type: object + properties: + id: + type: string + description: The updated MSP tenant account ID + example: ch8i4ug6lnn4g9hqv7m0 + name: + type: string + description: The name for the MSP tenant + example: "My new tenant" + domain: + type: string + description: The tenant account domain + example: "tenant.com" + groups: + description: MSP users Groups that can access the Tenant and Roles to assume + type: array + items: + $ref: "#/components/schemas/TenantGroupResponse" + activated_at: + type: string + format: date-time + description: The date and time when the tenant was activated. + example: "2021-08-01T12:00:00Z" + dns_challenge: + type: string + description: The DNS challenge to set in a TXT record + example: YXNkYSBkYXNhc2Rhc2RhIGFzZGFzZDJhc2QyNDUxNQ + created_at: + type: string + format: date-time + description: The date and time when the tenant was created. + example: "2021-08-01T12:00:00Z" + updated_at: + type: string + format: date-time + description: The date and time when the tenant was last updated. + example: "2021-08-01T12:00:00Z" + invited_at: + type: string + format: date-time + description: The date and time when the existing tenant was invited. + example: "2021-08-01T12:00:00Z" + status: + type: string + description: The status of the tenant + enum: + - existing + - invited + - pending + - active + example: "active" + required: + - id + - name + - domain + - groups + - created_at + - updated_at + - status + - dns_challenge + CreateIntegrationRequest: + type: object + description: "Request payload for creating a new event streaming integration. Also used as the structure for the PUT request body, but not all fields are applicable for updates (see PUT operation description)." + required: + - platform + - config + - enabled + properties: + platform: + type: string + description: The event streaming platform to integrate with (e.g., "datadog", "s3", "firehose"). This field is used for creation. For updates (PUT), this field, if sent, is ignored by the backend. + enum: [ "datadog", "s3", "firehose", "generic_http" ] + example: "s3" + config: + type: object + additionalProperties: + type: string + description: Platform-specific configuration as key-value pairs. For creation, all necessary credentials and settings must be provided. For updates, provide the fields to change or the entire new configuration. + example: { "bucket_name": "my-event-logs", "region": "us-east-1", "access_key_id": "AKIA...", "secret_access_key": "YOUR_SECRET_KEY" } + enabled: + type: boolean + description: "Specifies whether the integration is enabled. During creation (POST), this value is sent by the client, but the provided backend manager function `CreateIntegration` does not appear to use it directly, so its effect on creation should be verified. During updates (PUT), this field is used to enable or disable the integration." + example: true + IntegrationResponse: + type: object + description: Represents an event streaming integration. + properties: + id: + type: integer + format: int64 + description: The unique numeric identifier for the integration. + example: 123 + minimum: 0 + account_id: + type: string + description: The identifier of the account this integration belongs to. + example: "acc_abcdef123456" + enabled: + type: boolean + description: Whether the integration is currently active. + example: true + platform: + type: string + description: The event streaming platform. + enum: [ "datadog", "s3", "firehose", "generic_http" ] + example: "datadog" + created_at: + type: string + format: date-time + description: Timestamp of when the integration was created. + example: "2023-05-15T10:30:00Z" + updated_at: + type: string + format: date-time + description: Timestamp of when the integration was last updated. + example: "2023-05-16T11:45:00Z" + config: + type: object + additionalProperties: + type: string + description: Configuration for the integration. Sensitive keys (like API keys, secret keys) are masked with '****' in responses, as indicated by the GetIntegration handler logic. + example: { "api_key": "****", "site": "datadoghq.com", "region": "us-east-1" } + EDRIntuneRequest: + type: object + description: "Request payload for creating or updating a EDR Intune integration." + required: + - client_id + - tenant_id + - secret + - groups + - last_synced_interval + properties: + client_id: + type: string + description: The Azure application client id + tenant_id: + type: string + description: The Azure tenant id + secret: + type: string + description: The Azure application client secret + groups: + type: array + description: The Groups this integrations applies to + items: + type: string + last_synced_interval: + type: integer + description: The devices last sync requirement interval in hours. Minimum value is 24 hours. + minimum: 24 + enabled: + type: boolean + description: Indicates whether the integration is enabled + default: true + EDRIntuneResponse: + type: object + description: Represents a Intune EDR integration configuration + required: + - id + - account_id + - created_by + - last_synced_at + - created_at + - updated_at + - client_id + - tenant_id + - groups + - last_synced_interval + - enabled + properties: + id: + type: integer + format: int64 + description: The unique numeric identifier for the integration. + example: 123 + minimum: 0 + account_id: + type: string + description: The identifier of the account this integration belongs to. + example: "acc_abcdef123456" + last_synced_at: + type: string + format: date-time + description: Timestamp of when the integration was last synced. + example: "2023-05-15T10:30:00Z" + created_by: + type: string + description: The user id that created the integration + created_at: + type: string + format: date-time + description: Timestamp of when the integration was created. + example: "2023-05-15T10:30:00Z" + updated_at: + type: string + format: date-time + description: Timestamp of when the integration was last updated. + example: "2023-05-16T11:45:00Z" + client_id: + type: string + description: The Azure application client id + example: "acc_abcdef123456" + tenant_id: + type: string + description: The Azure tenant id + example: "acc_abcdef123456" + groups: + type: array + description: List of groups + items: + $ref: '#/components/schemas/Group' + last_synced_interval: + type: integer + description: The devices last sync requirement interval in hours. + enabled: + type: boolean + description: Indicates whether the integration is enabled + EDRSentinelOneRequest: + type: object + description: Request payload for creating or updating a EDR SentinelOne integration + properties: + api_token: + type: string + description: SentinelOne API token + api_url: + type: string + description: The Base URL of SentinelOne API + groups: + type: array + description: The Groups this integrations applies to + items: + type: string + last_synced_interval: + type: integer + description: The devices last sync requirement interval in hours. Minimum value is 24 hours. + minimum: 24 + enabled: + type: boolean + description: Indicates whether the integration is enabled + default: true + match_attributes: + $ref: '#/components/schemas/SentinelOneMatchAttributes' + required: + - api_token + - api_url + - groups + - last_synced_interval + - match_attributes + EDRSentinelOneResponse: + type: object + description: Represents a SentinelOne EDR integration configuration + required: + - id + - account_id + - created_by + - last_synced_at + - created_at + - updated_at + - api_url + - groups + - last_synced_interval + - match_attributes + - enabled + properties: + id: + type: integer + format: int64 + description: The unique numeric identifier for the integration. + example: 123 + account_id: + type: string + description: The identifier of the account this integration belongs to. + example: "ch8i4ug6lnn4g9hqv7l0" + last_synced_at: + type: string + format: date-time + description: Timestamp of when the integration was last synced. + example: "2023-05-15T10:30:00Z" + created_by: + type: string + description: The user id that created the integration + created_at: + type: string + format: date-time + description: Timestamp of when the integration was created. + example: "2023-05-15T10:30:00Z" + updated_at: + type: string + format: date-time + description: Timestamp of when the integration was last updated. + example: "2023-05-16T11:45:00Z" + api_url: + type: string + description: The Base URL of SentinelOne API + groups: + type: array + description: List of groups + items: + $ref: '#/components/schemas/Group' + last_synced_interval: + type: integer + description: The devices last sync requirement interval in hours. + match_attributes: + $ref: '#/components/schemas/SentinelOneMatchAttributes' + enabled: + type: boolean + description: Indicates whether the integration is enabled + SentinelOneMatchAttributes: + type: object + description: Attribute conditions to match when approving agents + additionalProperties: false + properties: + active_threats: + description: The maximum allowed number of active threats on the agent + type: integer + example: 0 + encrypted_applications: + description: Whether disk encryption is enabled on the agent + type: boolean + firewall_enabled: + description: Whether the agent firewall is enabled + type: boolean + infected: + description: Whether the agent is currently flagged as infected + type: boolean + is_active: + description: Whether the agent has been recently active and reporting + type: boolean + is_up_to_date: + description: Whether the agent is running the latest available version + type: boolean + network_status: + description: The current network connectivity status of the device + type: string + enum: [ "connected", "disconnected", "quarantined" ] + operational_state: + description: The current operational state of the agent + type: string + + EDRFalconRequest: + type: object + description: Request payload for creating or updating a EDR Falcon integration + properties: + client_id: + type: string + description: CrowdStrike API client ID + secret: + type: string + description: CrowdStrike API client secret + cloud_id: + type: string + description: CrowdStrike cloud identifier (e.g., "us-1", "us-2", "eu-1") + groups: + type: array + description: The Groups this integration applies to + items: + type: string + zta_score_threshold: + type: integer + description: The minimum Zero Trust Assessment score required for agent approval (0-100) + minimum: 0 + maximum: 100 + example: 75 + enabled: + type: boolean + description: Indicates whether the integration is enabled + default: true + required: + - client_id + - secret + - cloud_id + - groups + - zta_score_threshold + EDRFalconResponse: + type: object + description: Represents a Falcon EDR integration + required: + - id + - account_id + - last_synced_at + - created_by + - created_at + - updated_at + - cloud_id + - groups + - zta_score_threshold + - enabled + properties: + id: + type: integer + format: int64 + description: The unique numeric identifier for the integration. + example: 123 + account_id: + type: string + description: The identifier of the account this integration belongs to. + example: "ch8i4ug6lnn4g9hqv7l0" + last_synced_at: + type: string + format: date-time + description: Timestamp of when the integration was last synced. + example: "2023-05-15T10:30:00Z" + created_by: + type: string + description: The user id that created the integration + created_at: + type: string + format: date-time + description: Timestamp of when the integration was created. + example: "2023-05-15T10:30:00Z" + updated_at: + type: string + format: date-time + description: Timestamp of when the integration was last updated. + example: "2023-05-16T11:45:00Z" + cloud_id: + type: string + description: CrowdStrike cloud identifier + groups: + type: array + description: List of groups + items: + $ref: '#/components/schemas/Group' + zta_score_threshold: + type: integer + description: The minimum Zero Trust Assessment score required for agent approval (0-100) + enabled: + type: boolean + description: Indicates whether the integration is enabled + + EDRHuntressRequest: + type: object + description: Request payload for creating or updating a EDR Huntress integration + properties: + api_key: + type: string + description: Huntress API key + api_secret: + type: string + description: Huntress API secret + groups: + type: array + description: The Groups this integrations applies to + items: + type: string + last_synced_interval: + type: integer + description: The devices last sync requirement interval in hours. Minimum value is 24 hours + minimum: 24 + enabled: + type: boolean + description: Indicates whether the integration is enabled + default: true + match_attributes: + $ref: '#/components/schemas/HuntressMatchAttributes' + required: + - api_key + - api_secret + - groups + - last_synced_interval + - match_attributes + EDRHuntressResponse: + type: object + description: Represents a Huntress EDR integration configuration + required: + - id + - account_id + - created_by + - last_synced_at + - created_at + - updated_at + - groups + - last_synced_interval + - match_attributes + - enabled + properties: + id: + type: integer + format: int64 + description: The unique numeric identifier for the integration. + example: 123 + account_id: + type: string + description: The identifier of the account this integration belongs to. + example: "ch8i4ug6lnn4g9hqv7l0" + last_synced_at: + type: string + format: date-time + description: Timestamp of when the integration was last synced. + example: "2023-05-15T10:30:00Z" + created_by: + type: string + description: The user id that created the integration + created_at: + type: string + format: date-time + description: Timestamp of when the integration was created. + example: "2023-05-15T10:30:00Z" + updated_at: + type: string + format: date-time + description: Timestamp of when the integration was last updated. + example: "2023-05-16T11:45:00Z" + groups: + type: array + description: List of groups + items: + $ref: '#/components/schemas/Group' + last_synced_interval: + type: integer + description: The devices last sync requirement interval in hours. + enabled: + type: boolean + description: Indicates whether the integration is enabled + default: true + match_attributes: + $ref: '#/components/schemas/HuntressMatchAttributes' + + HuntressMatchAttributes: + type: object + description: Attribute conditions to match when approving agents + additionalProperties: false + properties: + defender_policy_status: + type: string + description: Policy status of Defender AV for Managed Antivirus. + example: "Compliant" + defender_status: + type: string + description: Status of Defender AV Managed Antivirus. + example: "Healthy" + defender_substatus: + type: string + description: Sub-status of Defender AV Managed Antivirus. + example: "Up to date" + firewall_status: + type: string + description: Status of agent firewall. Can be one of Disabled, Enabled, Pending Isolation, Isolated, Pending Release. + example: "Enabled" + + EDRFleetDMRequest: + type: object + description: Request payload for creating or updating a FleetDM EDR integration + properties: + api_url: + type: string + description: FleetDM server URL + api_token: + type: string + description: FleetDM API token + groups: + type: array + description: The Groups this integrations applies to + items: + type: string + last_synced_interval: + type: integer + description: The devices last sync requirement interval in hours. Minimum value is 24 hours + minimum: 24 + enabled: + type: boolean + description: Indicates whether the integration is enabled + default: true + match_attributes: + $ref: '#/components/schemas/FleetDMMatchAttributes' + required: + - api_url + - api_token + - groups + - last_synced_interval + - match_attributes + EDRFleetDMResponse: + type: object + description: Represents a FleetDM EDR integration configuration + required: + - id + - account_id + - api_url + - created_by + - last_synced_at + - created_at + - updated_at + - groups + - last_synced_interval + - match_attributes + - enabled + properties: + id: + type: integer + format: int64 + description: The unique numeric identifier for the integration. + example: 123 + account_id: + type: string + description: The identifier of the account this integration belongs to. + example: "ch8i4ug6lnn4g9hqv7l0" + api_url: + type: string + description: FleetDM server URL + last_synced_at: + type: string + format: date-time + description: Timestamp of when the integration was last synced. + example: "2023-05-15T10:30:00Z" + created_by: + type: string + description: The user id that created the integration + created_at: + type: string + format: date-time + description: Timestamp of when the integration was created. + example: "2023-05-15T10:30:00Z" + updated_at: + type: string + format: date-time + description: Timestamp of when the integration was last updated. + example: "2023-05-16T11:45:00Z" + groups: + type: array + description: List of groups + items: + $ref: '#/components/schemas/Group' + last_synced_interval: + type: integer + description: The devices last sync requirement interval in hours. + enabled: + type: boolean + description: Indicates whether the integration is enabled + default: true + match_attributes: + $ref: '#/components/schemas/FleetDMMatchAttributes' + + FleetDMMatchAttributes: + type: object + description: Attribute conditions to match when approving FleetDM hosts. Most attributes work with FleetDM's free/open-source version. Premium-only attributes are marked accordingly + additionalProperties: false + properties: + disk_encryption_enabled: + type: boolean + description: Whether disk encryption (FileVault/BitLocker) must be enabled on the host + failing_policies_count_max: + type: integer + description: Maximum number of allowed failing policies. Use 0 to require all policies to pass + minimum: 0 + example: 0 + vulnerable_software_count_max: + type: integer + description: Maximum number of allowed vulnerable software on the host + minimum: 0 + example: 0 + status_online: + type: boolean + description: Whether the host must be online (recently seen by Fleet) + required_policies: + type: array + description: List of FleetDM policy IDs that must be passing on the host. If any of these policies is failing, the host is non-compliant + items: + type: integer + example: [1, 5, 12] + + IntegrationSyncFilters: + type: object + properties: + group_prefixes: + type: array + description: List of start_with string patterns for groups to sync + items: + type: string + example: [ "Engineering", "Sales" ] + user_group_prefixes: + type: array + description: List of start_with string patterns for groups which users to sync + items: + type: string + example: [ "Users" ] + connector_id: + type: string + description: DEX connector ID for embedded IDP setups + IntegrationEnabled: + type: object + properties: + enabled: + type: boolean + description: Whether the integration is enabled + example: true + CreateScimIntegrationRequest: + allOf: + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Request payload for creating an SCIM IDP integration + required: + - prefix + - provider + properties: + prefix: + type: string + description: The connection prefix used for the SCIM provider + provider: + type: string + description: Name of the SCIM identity provider + UpdateScimIntegrationRequest: + allOf: + - $ref: '#/components/schemas/IntegrationEnabled' + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Request payload for updating an SCIM IDP integration + properties: + prefix: + type: string + description: The connection prefix used for the SCIM provider + ScimIntegration: + allOf: + - $ref: '#/components/schemas/IntegrationEnabled' + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Represents a SCIM IDP integration + required: + - id + - enabled + - prefix + - provider + - group_prefixes + - user_group_prefixes + - auth_token + - last_synced_at + properties: + id: + type: integer + format: int64 + description: The unique identifier for the integration + example: 123 + prefix: + type: string + description: The connection prefix used for the SCIM provider + provider: + type: string + description: Name of the SCIM identity provider + auth_token: + type: string + description: SCIM API token (full on creation, masked otherwise) + example: "nbs_abc***********************************" + last_synced_at: + type: string + format: date-time + description: Timestamp of when the integration was last synced + example: "2023-05-15T10:30:00Z" + IdpIntegrationSyncLog: + type: object + description: Represents a synchronization log entry for an integration + required: + - id + - level + - timestamp + - message + properties: + id: + type: integer + format: int64 + description: The unique identifier for the sync log + example: 123 + level: + type: string + description: The log level + example: "info" + timestamp: + type: string + format: date-time + description: Timestamp of when the log was created + example: "2023-05-15T10:30:00Z" + message: + type: string + description: Log message + example: "Successfully synchronized users and groups" + ScimTokenResponse: + type: object + description: Response containing the regenerated SCIM token + required: + - auth_token + properties: + auth_token: + type: string + description: The newly generated SCIM API token + example: "nbs_F3f0d..." + CreateGoogleIntegrationRequest: + allOf: + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Request payload for creating a Google Workspace IDP integration + required: + - service_account_key + - customer_id + properties: + service_account_key: + type: string + description: Base64-encoded Google service account key + example: "eyJ0eXBlIjoic2VydmljZV9hY2NvdW50Ii..." + customer_id: + type: string + description: Customer ID from Google Workspace Account Settings + example: "C01234567" + sync_interval: + type: integer + description: Sync interval in seconds (minimum 300). Defaults to 300 if not specified. + minimum: 300 + example: 300 + UpdateGoogleIntegrationRequest: + allOf: + - $ref: '#/components/schemas/IntegrationEnabled' + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Request payload for updating a Google Workspace IDP integration. All fields are optional. + properties: + service_account_key: + type: string + description: Base64-encoded Google service account key + customer_id: + type: string + description: Customer ID from Google Workspace Account Settings + sync_interval: + type: integer + description: Sync interval in seconds (minimum 300) + minimum: 300 + GoogleIntegration: + allOf: + - $ref: '#/components/schemas/IntegrationEnabled' + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Represents a Google Workspace IDP integration + required: + - id + - customer_id + - sync_interval + - enabled + - group_prefixes + - user_group_prefixes + - last_synced_at + properties: + id: + type: integer + format: int64 + description: The unique identifier for the integration + example: 1 + customer_id: + type: string + description: Customer ID from Google Workspace + example: "C01234567" + sync_interval: + type: integer + description: Sync interval in seconds + example: 300 + last_synced_at: + type: string + format: date-time + description: Timestamp of the last synchronization + example: "2023-05-15T10:30:00Z" + CreateAzureIntegrationRequest: + allOf: + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Request payload for creating an Azure AD IDP integration + required: + - client_secret + - client_id + - tenant_id + - host + properties: + client_secret: + type: string + description: Base64-encoded Azure AD client secret + example: "c2VjcmV0..." + client_id: + type: string + description: Azure AD application (client) ID + example: "12345678-1234-1234-1234-123456789012" + tenant_id: + type: string + description: Azure AD tenant ID + example: "87654321-4321-4321-4321-210987654321" + sync_interval: + type: integer + description: Sync interval in seconds (minimum 300). Defaults to 300 if not specified. + minimum: 300 + example: 300 + host: + type: string + description: Azure host domain for the Graph API + enum: + - microsoft.com + - microsoft.us + example: "microsoft.com" + UpdateAzureIntegrationRequest: + allOf: + - $ref: '#/components/schemas/IntegrationEnabled' + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Request payload for updating an Azure AD IDP integration. All fields are optional. + properties: + client_secret: + type: string + description: Base64-encoded Azure AD client secret + client_id: + type: string + description: Azure AD application (client) ID + tenant_id: + type: string + description: Azure AD tenant ID + sync_interval: + type: integer + description: Sync interval in seconds (minimum 300) + minimum: 300 + AzureIntegration: + allOf: + - $ref: '#/components/schemas/IntegrationEnabled' + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Represents an Azure AD IDP integration + required: + - id + - client_id + - tenant_id + - sync_interval + - enabled + - group_prefixes + - user_group_prefixes + - host + - last_synced_at + properties: + id: + type: integer + format: int64 + description: The unique identifier for the integration + example: 1 + client_id: + type: string + description: Azure AD application (client) ID + example: "12345678-1234-1234-1234-123456789012" + tenant_id: + type: string + description: Azure AD tenant ID + example: "87654321-4321-4321-4321-210987654321" + sync_interval: + type: integer + description: Sync interval in seconds + example: 300 + host: + type: string + description: Azure host domain for the Graph API + example: "microsoft.com" + last_synced_at: + type: string + format: date-time + description: Timestamp of the last synchronization + example: "2023-05-15T10:30:00Z" + CreateOktaScimIntegrationRequest: + allOf: + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Request payload for creating an Okta SCIM IDP integration + required: + - connection_name + properties: + connection_name: + type: string + description: The Okta enterprise connection name on Auth0 + example: "my-okta-connection" + UpdateOktaScimIntegrationRequest: + allOf: + - $ref: '#/components/schemas/IntegrationEnabled' + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Request payload for updating an Okta SCIM IDP integration. All fields are optional. + OktaScimIntegration: + allOf: + - $ref: '#/components/schemas/IntegrationEnabled' + - $ref: '#/components/schemas/IntegrationSyncFilters' + - type: object + description: Represents an Okta SCIM IDP integration + required: + - id + - enabled + - group_prefixes + - user_group_prefixes + - auth_token + - last_synced_at + properties: + id: + type: integer + format: int64 + description: The unique identifier for the integration + example: 1 + auth_token: + type: string + description: SCIM API token (full on creation/regeneration, masked on retrieval) + example: "nbs_abc***********************************" + last_synced_at: + type: string + format: date-time + description: Timestamp of the last synchronization + example: "2023-05-15T10:30:00Z" + SyncResult: + type: object + description: Response for a manual sync trigger + properties: + result: + type: string + example: "ok" + NotificationChannelType: + type: string + description: The type of notification channel. + enum: + - email + - webhook + example: "email" + NotificationEventType: + type: string + description: | + An activity event type code. See `GET /api/integrations/notifications/types` for the full list + of supported event types and their human-readable descriptions. + example: "user.join" + EmailTarget: + type: object + description: Target configuration for email notification channels. + properties: + emails: + type: array + description: List of email addresses to send notifications to. + minItems: 1 + items: + type: string + format: email + example: [ "admin@example.com", "ops@example.com" ] + required: + - emails + WebhookTarget: + type: object + description: Target configuration for webhook notification channels. + properties: + url: + type: string + format: uri + description: The webhook endpoint URL to send notifications to. + example: "https://hooks.example.com/netbird" + headers: + type: object + additionalProperties: + type: string + description: | + Custom HTTP headers sent with each webhook request. + Values are write-only; in GET responses all values are masked. + example: + Authorization: "Bearer token" + X-Webhook-Secret: "secret" + required: + - url + NotificationChannelRequest: + type: object + description: Request body for creating or updating a notification channel. + properties: + type: + $ref: '#/components/schemas/NotificationChannelType' + target: + description: | + Channel-specific target configuration. The shape depends on the `type` field: + - `email`: requires an `EmailTarget` object + - `webhook`: requires a `WebhookTarget` object + oneOf: + - $ref: '#/components/schemas/EmailTarget' + - $ref: '#/components/schemas/WebhookTarget' + event_types: + type: array + description: List of activity event type codes this channel subscribes to. + items: + $ref: '#/components/schemas/NotificationEventType' + example: [ "user.join", "peer.user.add", "peer.login.expire" ] + enabled: + type: boolean + description: Whether this notification channel is active. + example: true + required: + - type + - event_types + - enabled + NotificationChannelResponse: + type: object + description: A notification channel configuration. + properties: + id: + type: string + description: Unique identifier of the notification channel. + readOnly: true + example: "ch8i4ug6lnn4g9hqv7m0" + type: + $ref: '#/components/schemas/NotificationChannelType' + target: + description: | + Channel-specific target configuration. The shape depends on the `type` field: + - `email`: an `EmailTarget` object + - `webhook`: a `WebhookTarget` object + oneOf: + - $ref: '#/components/schemas/EmailTarget' + - $ref: '#/components/schemas/WebhookTarget' + event_types: + type: array + description: List of activity event type codes this channel subscribes to. + items: + $ref: '#/components/schemas/NotificationEventType' + example: [ "user.join", "peer.user.add", "peer.login.expire" ] + enabled: + type: boolean + description: Whether this notification channel is active. + example: true + required: + - id + - type + - event_types + - enabled + NotificationTypeEntry: + type: object + description: A map of event type codes to their human-readable descriptions. + additionalProperties: + type: string + example: + user.join: "User joined" + BypassResponse: + type: object + description: Response for bypassed peer operations. + required: + - peer_id + properties: + peer_id: + type: string + description: The ID of the bypassed peer. + example: "chacbco6lnnbn6cg5s91" + ErrorResponse: + type: object + description: "Standard error response. Note: The exact structure of this error response is inferred from `util.WriteErrorResponse` and `util.WriteError` usage in the provided Go code, as a specific Go struct for errors was not provided." + properties: + message: + type: string + description: A human-readable error message. + example: "couldn't parse JSON request" + responses: + not_found: + description: Resource not found + content: { } + validation_failed_simple: + description: Validation failed + content: { } + bad_request: + description: Bad Request + content: { } + internal_error: + description: Internal Server Error + content: { } + validation_failed: + description: Validation failed + content: { } + forbidden: + description: Forbidden + content: { } + requires_authentication: + description: Requires authentication + content: { } + conflict: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + TokenAuth: + type: apiKey + in: header + name: Authorization + description: >- + Enter the token with the `Token` prefix, e.g. "Token nbp_F3f0d.....". +security: + - BearerAuth: [ ] + - TokenAuth: [ ] +paths: + /api/instance: + get: + summary: Get Instance Status + description: Returns the instance status including whether initial setup is required. This endpoint does not require authentication. + tags: [ Instance ] + security: [ ] + responses: + '200': + description: Instance status information + content: + application/json: + schema: + $ref: '#/components/schemas/InstanceStatus' + '500': + "$ref": "#/components/responses/internal_error" + /api/instance/version: + get: + summary: Get Version Info + description: Returns version information for NetBird components including the current management server version and latest available versions from GitHub. + tags: [ Instance ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: Version information + content: + application/json: + schema: + $ref: '#/components/schemas/InstanceVersionInfo' + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/setup: + post: + summary: Setup Instance + description: Creates the initial admin user for the instance. This endpoint does not require authentication but only works when setup is required (no accounts exist and embedded IDP is enabled). + tags: [ Instance ] + security: [ ] + requestBody: + description: Initial admin user details + required: true + content: + 'application/json': + schema: + $ref: '#/components/schemas/SetupRequest' + responses: + '200': + description: Setup completed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/SetupResponse' + '400': + "$ref": "#/components/responses/bad_request" + '412': + description: Setup already completed + content: { } + '500': + "$ref": "#/components/responses/internal_error" + /api/peers/{peerId}/jobs: + get: + summary: List Jobs + description: Retrieve all jobs for a given peer + tags: [ Jobs ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + description: The unique identifier of a peer + required: true + schema: + type: string + responses: + '200': + description: List of jobs + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/JobResponse' + '400': + $ref: '#/components/responses/bad_request' + '401': + $ref: '#/components/responses/requires_authentication' + '403': + $ref: '#/components/responses/forbidden' + '500': + $ref: '#/components/responses/internal_error' + post: + summary: Create Job + description: Create a new job for a given peer + tags: [ Jobs ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + description: The unique identifier of a peer + required: true + schema: + type: string + requestBody: + description: Create job request + content: + application/json: + schema: + $ref: '#/components/schemas/JobRequest' + required: true + responses: + '201': + description: Job created + content: + application/json: + schema: + $ref: '#/components/schemas/JobResponse' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/peers/{peerId}/jobs/{jobId}: + get: + summary: Get Job + description: Retrieve details of a specific job + tags: [ Jobs ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + description: The unique identifier of a peer + schema: + type: string + - in: path + name: jobId + required: true + description: The unique identifier of a job + schema: + type: string + responses: + '200': + description: A Job object + content: + application/json: + schema: + $ref: '#/components/schemas/JobResponse' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/accounts: + get: + summary: List all Accounts + description: Returns a list of accounts of a user. Always returns a list of one account. + tags: [ Accounts ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON array of accounts + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Account' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/accounts/{accountId}: + delete: + summary: Delete an Account + description: Deletes an account and all its resources. Only account owners can delete accounts. + tags: [ Accounts ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: accountId + required: true + schema: + type: string + description: The unique identifier of an account + responses: + '200': + description: Delete account status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update an Account + description: Update information about an account + tags: [ Accounts ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: accountId + required: true + schema: + type: string + description: The unique identifier of an account + requestBody: + description: update an account + content: + 'application/json': + schema: + $ref: '#/components/schemas/AccountRequest' + responses: + '200': + description: An Account object + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users: + get: + summary: List all Users + description: Returns a list of all users + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: query + name: service_user + schema: + type: boolean + description: Filters users and returns either regular users or service users + responses: + '200': + description: A JSON array of Users + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a User + description: Creates a new service user or sends an invite to a regular user + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: User invite information + content: + 'application/json': + schema: + $ref: '#/components/schemas/UserCreateRequest' + responses: + '200': + description: A User object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/{userId}: + put: + summary: Update a User + description: Update information about a User + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + requestBody: + description: User update + content: + 'application/json': + schema: + $ref: '#/components/schemas/UserRequest' + responses: + '200': + description: A User object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a User + description: This method removes a user from accessing the system. For this leaves the IDP user intact unless the `--user-delete-from-idp` is passed to management startup. + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/{userId}/tokens: + get: + summary: List all Tokens + description: Returns a list of all tokens for a user + tags: [ Tokens ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + responses: + '200': + description: A JSON Array of PersonalAccessTokens + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PersonalAccessToken' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Token + description: Create a new token for a user + tags: [ Tokens ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + requestBody: + description: PersonalAccessToken create parameters + content: + application/json: + schema: + $ref: '#/components/schemas/PersonalAccessTokenRequest' + responses: + '200': + description: The token in plain text + content: + application/json: + schema: + $ref: '#/components/schemas/PersonalAccessTokenGenerated' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/{userId}/tokens/{tokenId}: + get: + summary: Retrieve a Token + description: Returns a specific token for a user + tags: [ Tokens ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + - in: path + name: tokenId + required: true + schema: + type: string + description: The unique identifier of a token + responses: + '200': + description: A PersonalAccessTokens Object + content: + application/json: + schema: + $ref: '#/components/schemas/PersonalAccessToken' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Token + description: Delete a token for a user + tags: [ Tokens ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + - in: path + name: tokenId + required: true + schema: + type: string + description: The unique identifier of a token + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/{userId}/invite: + post: + summary: Resend user invitation + description: Resend user invitation + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + responses: + '200': + description: Invite status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/{userId}/approve: + post: + summary: Approve user + description: Approve a user that is pending approval + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + responses: + '200': + description: Returns the approved user + content: + application/json: + schema: + "$ref": "#/components/schemas/User" + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/{userId}/reject: + delete: + summary: Reject user + description: Reject a user that is pending approval by removing them from the account + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + responses: + '200': + description: User rejected successfully + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/{userId}/password: + put: + summary: Change user password + description: Change the password for a user. Only available when embedded IdP is enabled. Users can only change their own password. + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + requestBody: + description: Password change request + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordChangeRequest' + responses: + '200': + description: Password changed successfully + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '412': + description: Precondition failed - embedded IdP is not enabled + content: { } + '500': + "$ref": "#/components/responses/internal_error" + /api/users/current: + get: + summary: Retrieve current user + description: Get information about the current user + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A User object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/invites: + get: + summary: List user invites + description: Lists all pending invites for the account. Only available when embedded IdP is enabled. + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: List of invites + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/UserInvite' + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '412': + description: Precondition failed - embedded IdP is not enabled + content: { } + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a user invite + description: Creates an invite link for a new user. Only available when embedded IdP is enabled. The user is not created until they accept the invite. + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: User invite information + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserInviteCreateRequest' + responses: + '200': + description: Invite created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/UserInvite' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '409': + description: User or invite already exists + content: { } + '412': + description: Precondition failed - embedded IdP is not enabled + content: { } + '422': + "$ref": "#/components/responses/validation_failed" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/invites/{inviteId}: + delete: + summary: Delete a user invite + description: Deletes a pending invite. Only available when embedded IdP is enabled. + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: inviteId + required: true + schema: + type: string + description: The ID of the invite to delete + responses: + '200': + description: Invite deleted successfully + content: { } + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '404': + description: Invite not found + content: { } + '412': + description: Precondition failed - embedded IdP is not enabled + content: { } + '500': + "$ref": "#/components/responses/internal_error" + /api/users/invites/{inviteId}/regenerate: + post: + summary: Regenerate a user invite + description: Regenerates an invite link for an existing invite. Invalidates the previous token and creates a new one. + tags: [ Users ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: inviteId + required: true + schema: + type: string + description: The ID of the invite to regenerate + requestBody: + description: Regenerate options + content: + application/json: + schema: + $ref: '#/components/schemas/UserInviteRegenerateRequest' + responses: + '200': + description: Invite regenerated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/UserInviteRegenerateResponse' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '404': + description: Invite not found + content: { } + '412': + description: Precondition failed - embedded IdP is not enabled + content: { } + '422': + "$ref": "#/components/responses/validation_failed" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/invites/{token}: + get: + summary: Get invite information + description: Retrieves public information about an invite. This endpoint is unauthenticated and protected by the token itself. + tags: [ Users ] + security: [ ] + parameters: + - in: path + name: token + required: true + schema: + type: string + description: The invite token + responses: + '200': + description: Invite information + content: + application/json: + schema: + $ref: '#/components/schemas/UserInviteInfo' + '400': + "$ref": "#/components/responses/bad_request" + '404': + description: Invite not found or invalid token + content: { } + '500': + "$ref": "#/components/responses/internal_error" + /api/users/invites/{token}/accept: + post: + summary: Accept an invite + description: Accepts an invite and creates the user with the provided password. This endpoint is unauthenticated and protected by the token itself. + tags: [ Users ] + security: [ ] + parameters: + - in: path + name: token + required: true + schema: + type: string + description: The invite token + requestBody: + description: Password to set for the new user + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserInviteAcceptRequest' + responses: + '200': + description: Invite accepted successfully + content: + application/json: + schema: + $ref: '#/components/schemas/UserInviteAcceptResponse' + '400': + "$ref": "#/components/responses/bad_request" + '404': + description: Invite not found or invalid token + content: { } + '412': + description: Precondition failed - embedded IdP is not enabled or invite expired + content: { } + '422': + "$ref": "#/components/responses/validation_failed" + '500': + "$ref": "#/components/responses/internal_error" + /api/peers: + get: + summary: List all Peers + description: Returns a list of all peers + tags: [ Peers ] + parameters: + - in: query + name: name + schema: + type: string + description: Filter peers by name + - in: query + name: ip + schema: + type: string + description: Filter peers by IP address + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Peers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PeerBatch' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/peers/{peerId}: + get: + summary: Retrieve a Peer + description: Get information about a peer + tags: [ Peers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + responses: + '200': + description: A Peer object + content: + application/json: + schema: + $ref: '#/components/schemas/Peer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Peer + description: Update information about a peer + tags: [ Peers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + requestBody: + description: update a peer + content: + 'application/json': + schema: + $ref: '#/components/schemas/PeerRequest' + responses: + '200': + description: A Peer object + content: + application/json: + schema: + $ref: '#/components/schemas/Peer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Peer + description: Delete a peer + tags: [ Peers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/peers/{peerId}/accessible-peers: + get: + summary: List accessible Peers + description: Returns a list of peers that the specified peer can connect to within the network. + tags: [ Peers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + responses: + '200': + description: A JSON Array of Accessible Peers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AccessiblePeer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/peers/{peerId}/temporary-access: + post: + summary: Create a Temporary Access Peer + description: Creates a temporary access peer that can be used to access this peer and this peer only. The temporary access peer and its access policies will be automatically deleted after it disconnects. + tags: [ Peers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + requestBody: + description: Temporary Access Peer create request + content: + 'application/json': + schema: + $ref: '#/components/schemas/PeerTemporaryAccessRequest' + responses: + '200': + description: Temporary Access Peer response + content: + application/json: + schema: + $ref: '#/components/schemas/PeerTemporaryAccessResponse' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/peers/{peerId}/ingress/ports: + get: + x-cloud-only: true + summary: List all Port Allocations + description: Returns a list of all ingress port allocations for a peer + tags: [ Ingress Ports ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + - in: query + name: name + schema: + type: string + description: Filters ingress port allocations by name + responses: + '200': + description: A JSON Array of Ingress Port Allocations + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/IngressPortAllocation' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + x-cloud-only: true + summary: Create a Port Allocation + description: Creates a new ingress port allocation for a peer + tags: [ Ingress Ports ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + requestBody: + description: New Ingress Port Allocation request + content: + 'application/json': + schema: + $ref: '#/components/schemas/IngressPortAllocationRequest' + responses: + '200': + description: A Ingress Port Allocation object + content: + application/json: + schema: + $ref: '#/components/schemas/IngressPortAllocation' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/peers/{peerId}/ingress/ports/{allocationId}: + get: + x-cloud-only: true + summary: Retrieve a Port Allocation + description: Get information about an ingress port allocation + tags: [ Ingress Ports ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + - in: path + name: allocationId + required: true + schema: + type: string + description: The unique identifier of an ingress port allocation + responses: + '200': + description: A Ingress Port Allocation object + content: + application/json: + schema: + $ref: '#/components/schemas/IngressPortAllocation' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + x-cloud-only: true + summary: Update a Port Allocation + description: Update information about an ingress port allocation + tags: [ Ingress Ports ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + - in: path + name: allocationId + required: true + schema: + type: string + description: The unique identifier of an ingress port allocation + requestBody: + description: update an ingress port allocation + content: + application/json: + schema: + $ref: '#/components/schemas/IngressPortAllocationRequest' + responses: + '200': + description: A Ingress Port Allocation object + content: + application/json: + schema: + $ref: '#/components/schemas/IngressPortAllocation' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + x-cloud-only: true + summary: Delete a Port Allocation + description: Delete an ingress port allocation + tags: [ Ingress Ports ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + - in: path + name: allocationId + required: true + schema: + type: string + description: The unique identifier of an ingress port allocation + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/ingress/peers: + get: + x-cloud-only: true + summary: List all Ingress Peers + description: Returns a list of all ingress peers + tags: [ Ingress Ports ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Ingress Peers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/IngressPeer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + x-cloud-only: true + summary: Create a Ingress Peer + description: Creates a new ingress peer + tags: [ Ingress Ports ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Ingress Peer request + content: + 'application/json': + schema: + $ref: '#/components/schemas/IngressPeerCreateRequest' + responses: + '200': + description: A Ingress Peer object + content: + application/json: + schema: + $ref: '#/components/schemas/IngressPeer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/ingress/peers/{ingressPeerId}: + get: + x-cloud-only: true + summary: Retrieve a Ingress Peer + description: Get information about an ingress peer + tags: [ Ingress Ports ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: ingressPeerId + required: true + schema: + type: string + description: The unique identifier of an ingress peer + responses: + '200': + description: A Ingress Peer object + content: + application/json: + schema: + $ref: '#/components/schemas/IngressPeer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + x-cloud-only: true + summary: Update a Ingress Peer + description: Update information about an ingress peer + tags: [ Ingress Ports ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: ingressPeerId + required: true + schema: + type: string + description: The unique identifier of an ingress peer + requestBody: + description: update an ingress peer + content: + 'application/json': + schema: + $ref: '#/components/schemas/IngressPeerUpdateRequest' + responses: + '200': + description: A Ingress Peer object + content: + application/json: + schema: + $ref: '#/components/schemas/IngressPeer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + x-cloud-only: true + summary: Delete a Ingress Peer + description: Delete an ingress peer + tags: [ Ingress Ports ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: ingressPeerId + required: true + schema: + type: string + description: The unique identifier of an ingress peer + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/setup-keys: + get: + summary: List all Setup Keys + description: Returns a list of all Setup Keys + tags: [ Setup Keys ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Setup keys + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SetupKey' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Setup Key + description: Creates a setup key + tags: [ Setup Keys ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Setup Key request + content: + 'application/json': + schema: + $ref: '#/components/schemas/CreateSetupKeyRequest' + responses: + '200': + description: A Setup Keys Object + content: + application/json: + schema: + $ref: '#/components/schemas/SetupKeyClear' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/setup-keys/{keyId}: + get: + summary: Retrieve a Setup Key + description: Get information about a setup key + tags: [ Setup Keys ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: keyId + required: true + schema: + type: string + description: The unique identifier of a setup key + responses: + '200': + description: A Setup Key object + content: + application/json: + schema: + $ref: '#/components/schemas/SetupKey' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Setup Key + description: Update information about a setup key + tags: [ Setup Keys ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: keyId + required: true + schema: + type: string + description: The unique identifier of a setup key + requestBody: + description: update to Setup Key + content: + 'application/json': + schema: + $ref: '#/components/schemas/SetupKeyRequest' + responses: + '200': + description: A Setup Key object + content: + application/json: + schema: + $ref: '#/components/schemas/SetupKey' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Setup Key + description: Delete a Setup Key + tags: [ Setup Keys ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: keyId + required: true + schema: + type: string + description: The unique identifier of a setup key + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/groups: + get: + summary: List all Groups + description: Returns a list of all groups + tags: [ Groups ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: query + name: name + required: false + schema: + type: string + description: Filter groups by name (exact match) + example: "devs" + responses: + '200': + description: A JSON Array of Groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Group' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '404': + "$ref": "#/components/responses/not_found" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Group + description: Creates a group + tags: [ Groups ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Group request + content: + 'application/json': + schema: + $ref: '#/components/schemas/GroupRequest' + responses: + '200': + description: A Group Object + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/groups/{groupId}: + get: + summary: Retrieve a Group + description: Get information about a group + tags: [ Groups ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: groupId + required: true + schema: + type: string + description: The unique identifier of a group + responses: + '200': + description: A Group object + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Group + description: Update/Replace a group + tags: [ Groups ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: groupId + required: true + schema: + type: string + description: The unique identifier of a group + requestBody: + description: Update Group request + content: + 'application/json': + schema: + $ref: '#/components/schemas/GroupRequest' + responses: + '200': + description: A Group object + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Group + description: Delete a group + tags: [ Groups ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: groupId + required: true + schema: + type: string + description: The unique identifier of a group + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/policies: + get: + summary: List all Policies + description: Returns a list of all policies + tags: [ Policies ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Policies + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Policy' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Policy + description: Creates a policy + tags: [ Policies ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Policy request + content: + 'application/json': + schema: + $ref: '#/components/schemas/PolicyUpdate' + responses: + '200': + description: A Policy Object + content: + application/json: + schema: + $ref: '#/components/schemas/Policy' + /api/policies/{policyId}: + get: + summary: Retrieve a Policy + description: Get information about a Policies + tags: [ Policies ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: policyId + required: true + schema: + type: string + description: The unique identifier of a policy + responses: + '200': + description: A Policy object + content: + application/json: + schema: + $ref: '#/components/schemas/Policy' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Policy + description: Update/Replace a Policy + tags: [ Policies ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: policyId + required: true + schema: + type: string + description: The unique identifier of a policy + requestBody: + description: Update Policy request + content: + 'application/json': + schema: + $ref: '#/components/schemas/PolicyCreate' + responses: + '200': + description: A Policy object + content: + application/json: + schema: + $ref: '#/components/schemas/Policy' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Policy + description: Delete a policy + tags: [ Policies ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: policyId + required: true + schema: + type: string + description: The unique identifier of a policy + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/routes: + get: + summary: List all Routes + description: Returns a list of all routes + tags: [ Routes ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Routes + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Route' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Route + description: Creates a Route + tags: [ Routes ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Routes request + content: + 'application/json': + schema: + $ref: '#/components/schemas/RouteRequest' + responses: + '200': + description: A Route Object + content: + application/json: + schema: + $ref: '#/components/schemas/Route' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/routes/{routeId}: + get: + summary: Retrieve a Route + description: Get information about a Routes + tags: [ Routes ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: routeId + required: true + schema: + type: string + description: The unique identifier of a route + responses: + '200': + description: A Route object + content: + application/json: + schema: + $ref: '#/components/schemas/Route' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Route + description: Update/Replace a Route + tags: [ Routes ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: routeId + required: true + schema: + type: string + description: The unique identifier of a route + requestBody: + description: Update Route request + content: + application/json: + schema: + $ref: '#/components/schemas/RouteRequest' + responses: + '200': + description: A Route object + content: + application/json: + schema: + $ref: '#/components/schemas/Route' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Route + description: Delete a route + tags: [ Routes ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: routeId + required: true + schema: + type: string + description: The unique identifier of a route + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/networks: + get: + summary: List all Networks + description: Returns a list of all networks + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Networks + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Network' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Network + description: Creates a Network + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Network request + content: + 'application/json': + schema: + $ref: '#/components/schemas/NetworkRequest' + responses: + '200': + description: A Network Object + content: + application/json: + schema: + $ref: '#/components/schemas/Network' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/networks/{networkId}: + get: + summary: Retrieve a Network + description: Get information about a Network + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + responses: + '200': + description: A Network object + content: + application/json: + schema: + $ref: '#/components/schemas/Network' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Network + description: Update/Replace a Network + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + requestBody: + description: Update Network request + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkRequest' + responses: + '200': + description: A Network object + content: + application/json: + schema: + $ref: '#/components/schemas/Network' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Network + description: Delete a network + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/networks/{networkId}/resources: + get: + summary: List all Network Resources + description: Returns a list of all resources in a network + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + responses: + '200': + description: A JSON Array of Resources + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NetworkResource' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Network Resource + description: Creates a Network Resource + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + requestBody: + description: New Network Resource request + content: + 'application/json': + schema: + $ref: '#/components/schemas/NetworkResourceRequest' + responses: + '200': + description: A Network Resource Object + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkResource' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/networks/{networkId}/resources/{resourceId}: + get: + summary: Retrieve a Network Resource + description: Get information about a Network Resource + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + - in: path + name: resourceId + required: true + schema: + type: string + description: The unique identifier of a network resource + responses: + '200': + description: A Network Resource object + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkResource' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Network Resource + description: Update a Network Resource + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + - in: path + name: resourceId + required: true + schema: + type: string + description: The unique identifier of a resource + requestBody: + description: Update Network Resource request + content: + 'application/json': + schema: + $ref: '#/components/schemas/NetworkResourceRequest' + responses: + '200': + description: A Network Resource object + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkResource' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Network Resource + description: Delete a network resource + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + - in: path + name: resourceId + required: true + schema: + type: string + description: The unique identifier of a network resource + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/networks/{networkId}/routers: + get: + summary: List all Network Routers + description: Returns a list of all routers in a network + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + responses: + '200': + description: A JSON Array of Routers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NetworkRouter' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Network Router + description: Creates a Network Router + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + requestBody: + description: New Network Router request + content: + 'application/json': + schema: + $ref: '#/components/schemas/NetworkRouterRequest' + responses: + '200': + description: A Router Object + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkRouter' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/networks/{networkId}/routers/{routerId}: + get: + summary: Retrieve a Network Router + description: Get information about a Network Router + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + - in: path + name: routerId + required: true + schema: + type: string + description: The unique identifier of a router + responses: + '200': + description: A Router object + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkRouter' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Network Router + description: Update a Network Router + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + - in: path + name: routerId + required: true + schema: + type: string + description: The unique identifier of a router + requestBody: + description: Update Network Router request + content: + 'application/json': + schema: + $ref: '#/components/schemas/NetworkRouterRequest' + responses: + '200': + description: A Router object + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkRouter' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Network Router + description: Delete a network router + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: The unique identifier of a network + - in: path + name: routerId + required: true + schema: + type: string + description: The unique identifier of a router + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/networks/routers: + get: + summary: List all Network Routers + description: Returns a list of all routers in a network + tags: [ Networks ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Routers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NetworkRouter' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/dns/nameservers: + get: + summary: List all Nameserver Groups + description: Returns a list of all Nameserver Groups + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Nameserver Groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NameserverGroup' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Nameserver Group + description: Creates a Nameserver Group + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Nameserver Groups request + content: + 'application/json': + schema: + $ref: '#/components/schemas/NameserverGroupRequest' + responses: + '200': + description: A Nameserver Groups Object + content: + application/json: + schema: + $ref: '#/components/schemas/NameserverGroup' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/dns/nameservers/{nsgroupId}: + get: + summary: Retrieve a Nameserver Group + description: Get information about a Nameserver Groups + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: nsgroupId + required: true + schema: + type: string + description: The unique identifier of a Nameserver Group + responses: + '200': + description: A Nameserver Group object + content: + application/json: + schema: + $ref: '#/components/schemas/NameserverGroup' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Nameserver Group + description: Update/Replace a Nameserver Group + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: nsgroupId + required: true + schema: + type: string + description: The unique identifier of a Nameserver Group + requestBody: + description: Update Nameserver Group request + content: + application/json: + schema: + $ref: '#/components/schemas/NameserverGroupRequest' + responses: + '200': + description: A Nameserver Group object + content: + application/json: + schema: + $ref: '#/components/schemas/NameserverGroup' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Nameserver Group + description: Delete a Nameserver Group + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: nsgroupId + required: true + schema: + type: string + description: The unique identifier of a Nameserver Group + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/dns/settings: + get: + summary: Retrieve DNS settings + description: Returns a DNS settings object + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Object of DNS Setting + content: + application/json: + schema: + items: + $ref: '#/components/schemas/DNSSettings' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update DNS Settings + description: Updates a DNS settings object + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: A DNS settings object + content: + 'application/json': + schema: + $ref: '#/components/schemas/DNSSettings' + responses: + '200': + description: A JSON Object of DNS Setting + content: + application/json: + schema: + $ref: '#/components/schemas/DNSSettings' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/dns/zones: + get: + summary: List all DNS Zones + description: Returns a list of all custom DNS zones + tags: [ DNS Zones ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of DNS Zones + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Zone' + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a DNS Zone + description: Creates a new custom DNS zone + tags: [ DNS Zones ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: A DNS zone object + content: + 'application/json': + schema: + $ref: '#/components/schemas/ZoneRequest' + responses: + '200': + description: A JSON Object of the created DNS Zone + content: + application/json: + schema: + $ref: '#/components/schemas/Zone' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/dns/zones/{zoneId}: + get: + summary: Retrieve a DNS Zone + description: Returns information about a specific DNS zone + tags: [ DNS Zones ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: zoneId + required: true + schema: + type: string + description: The unique identifier of a zone + example: chacbco6lnnbn6cg5s91 + responses: + '200': + description: A JSON Object of a DNS Zone + content: + application/json: + schema: + $ref: '#/components/schemas/Zone' + '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 DNS Zone + description: Updates a custom DNS zone + tags: [ DNS Zones ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: zoneId + required: true + schema: + type: string + description: The unique identifier of a zone + example: chacbco6lnnbn6cg5s91 + requestBody: + description: A DNS zone object + content: + 'application/json': + schema: + $ref: '#/components/schemas/ZoneRequest' + responses: + '200': + description: A JSON Object of the updated DNS Zone + content: + application/json: + schema: + $ref: '#/components/schemas/Zone' + '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 DNS Zone + description: Deletes a custom DNS zone + tags: [ DNS Zones ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: zoneId + required: true + schema: + type: string + description: The unique identifier of a zone + example: chacbco6lnnbn6cg5s91 + responses: + '200': + description: Zone deletion successful + '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/dns/zones/{zoneId}/records: + get: + summary: List all DNS Records + description: Returns a list of all DNS records in a zone + tags: [ DNS Zones ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: zoneId + required: true + schema: + type: string + description: The unique identifier of a zone + example: chacbco6lnnbn6cg5s91 + responses: + '200': + description: A JSON Array of DNS Records + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DNSRecord' + '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 DNS Record + description: Creates a new DNS record in a zone + tags: [ DNS Zones ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: zoneId + required: true + schema: + type: string + description: The unique identifier of a zone + example: chacbco6lnnbn6cg5s91 + requestBody: + description: A DNS record object + content: + 'application/json': + schema: + $ref: '#/components/schemas/DNSRecordRequest' + responses: + '200': + description: A JSON Object of the created DNS Record + content: + application/json: + schema: + $ref: '#/components/schemas/DNSRecord' + '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/dns/zones/{zoneId}/records/{recordId}: + get: + summary: Retrieve a DNS Record + description: Returns information about a specific DNS record + tags: [ DNS Zones ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: zoneId + required: true + schema: + type: string + description: The unique identifier of a zone + example: chacbco6lnnbn6cg5s91 + - in: path + name: recordId + required: true + schema: + type: string + description: The unique identifier of a DNS record + example: chacbco6lnnbn6cg5s92 + responses: + '200': + description: A JSON Object of a DNS Record + content: + application/json: + schema: + $ref: '#/components/schemas/DNSRecord' + '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 DNS Record + description: Updates a DNS record in a zone + tags: [ DNS Zones ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: zoneId + required: true + schema: + type: string + description: The unique identifier of a zone + example: chacbco6lnnbn6cg5s91 + - in: path + name: recordId + required: true + schema: + type: string + description: The unique identifier of a DNS record + example: chacbco6lnnbn6cg5s92 + requestBody: + description: A DNS record object + content: + 'application/json': + schema: + $ref: '#/components/schemas/DNSRecordRequest' + responses: + '200': + description: A JSON Object of the updated DNS Record + content: + application/json: + schema: + $ref: '#/components/schemas/DNSRecord' + '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 DNS Record + description: Deletes a DNS record from a zone + tags: [ DNS Zones ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: zoneId + required: true + schema: + type: string + description: The unique identifier of a zone + example: chacbco6lnnbn6cg5s91 + - in: path + name: recordId + required: true + schema: + type: string + description: The unique identifier of a DNS record + example: chacbco6lnnbn6cg5s92 + responses: + '200': + description: Record deletion successful + '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/events/audit: + get: + summary: List all Audit Events + description: Returns a list of all audit events + tags: [ Events ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Events + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Event' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/events/network-traffic: + get: + summary: List all Traffic Events + description: Returns a list of all network traffic events + tags: [ Events ] + x-cloud-only: true + x-experimental: true + parameters: + - name: page + in: query + description: Page number + required: false + schema: + type: integer + minimum: 1 + default: 1 + - name: page_size + in: query + description: Number of items per page + required: false + schema: + type: integer + minimum: 1 + maximum: 50000 + default: 1000 + - name: user_id + in: query + description: Filter by user ID + required: false + schema: + type: string + - name: reporter_id + in: query + description: Filter by reporter ID + required: false + schema: + type: string + - name: protocol + in: query + description: Filter by protocol + required: false + schema: + type: integer + - name: type + in: query + description: Filter by event type + required: false + schema: + type: string + enum: [ TYPE_UNKNOWN, TYPE_START, TYPE_END, TYPE_DROP ] + - name: connection_type + in: query + description: Filter by connection type + required: false + schema: + type: string + enum: [ P2P, ROUTED ] + - name: direction + in: query + description: Filter by direction + required: false + schema: + type: string + enum: [ INGRESS, EGRESS, DIRECTION_UNKNOWN ] + - name: search + in: query + description: Case-insensitive partial match on user email, source/destination names, and source/destination addresses + required: false + schema: + type: string + - name: start_date + in: query + description: Start date for filtering events (ISO 8601 format, e.g., 2024-01-01T00:00:00Z). + required: false + schema: + type: string + format: date-time + - name: end_date + in: query + description: End date for filtering events (ISO 8601 format, e.g., 2024-01-31T23:59:59Z). + required: false + schema: + type: string + format: date-time + responses: + "200": + description: List of network traffic events + content: + application/json: + schema: + $ref: "#/components/schemas/NetworkTrafficEventsResponse" + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$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: sort_by + schema: + type: string + enum: [timestamp, url, host, path, method, status_code, duration, source_ip, user_id, auth_method, reason] + default: timestamp + description: Field to sort by (url sorts by host then path) + - in: query + name: sort_order + schema: + type: string + enum: [asc, desc] + default: desc + description: Sort order (ascending or descending) + - 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 + description: Returns a list of all posture checks + tags: [ "Posture Checks" ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of posture checks + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PostureCheck' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Posture Check + description: Creates a posture check + tags: [ "Posture Checks" ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New posture check request + content: + 'application/json': + schema: + $ref: '#/components/schemas/PostureCheckUpdate' + responses: + '200': + description: A posture check Object + content: + application/json: + schema: + $ref: '#/components/schemas/PostureCheck' + /api/posture-checks/{postureCheckId}: + get: + summary: Retrieve a Posture Check + description: Get information about a posture check + tags: [ "Posture Checks" ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: postureCheckId + required: true + schema: + type: string + description: The unique identifier of a posture check + responses: + '200': + description: A posture check object + content: + application/json: + schema: + $ref: '#/components/schemas/PostureCheck' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Posture Check + description: Update/Replace a posture check + tags: [ "Posture Checks" ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: postureCheckId + required: true + schema: + type: string + description: The unique identifier of a posture check + requestBody: + description: Update Rule request + content: + 'application/json': + schema: + $ref: '#/components/schemas/PostureCheckUpdate' + responses: + '200': + description: A posture check object + content: + application/json: + schema: + $ref: '#/components/schemas/PostureCheck' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Posture Check + description: Delete a posture check + tags: [ "Posture Checks" ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: postureCheckId + required: true + schema: + type: string + description: The unique identifier of a posture check + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/locations/countries: + get: + summary: List all country codes + description: Get list of all country in 2-letter ISO 3166-1 alpha-2 codes + tags: [ "Geo Locations" ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: List of country codes + content: + application/json: + schema: + type: array + items: + type: string + example: "DE" + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/locations/countries/{country}/cities: + get: + summary: List all city names by country + description: Get a list of all English city names for a given country code + tags: [ "Geo Locations" ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: country + required: true + schema: + $ref: '#/components/schemas/Country' + responses: + '200': + description: List of city names + content: + application/json: + schema: + $ref: '#/components/schemas/City' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/identity-providers: + get: + summary: List all Identity Providers + description: Returns a list of all identity providers configured for the account + tags: [ Identity Providers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON array of identity providers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/IdentityProvider' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create an Identity Provider + description: Creates a new identity provider configuration + tags: [ Identity Providers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: Identity provider configuration + content: + 'application/json': + schema: + $ref: '#/components/schemas/IdentityProviderRequest' + responses: + '200': + description: An Identity Provider object + content: + application/json: + schema: + $ref: '#/components/schemas/IdentityProvider' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/identity-providers/{idpId}: + get: + summary: Retrieve an Identity Provider + description: Get information about a specific identity provider + tags: [ Identity Providers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: idpId + required: true + schema: + type: string + description: The unique identifier of an identity provider + responses: + '200': + description: An Identity Provider object + content: + application/json: + schema: + $ref: '#/components/schemas/IdentityProvider' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update an Identity Provider + description: Update an existing identity provider configuration + tags: [ Identity Providers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: idpId + required: true + schema: + type: string + description: The unique identifier of an identity provider + requestBody: + description: Identity provider update + content: + 'application/json': + schema: + $ref: '#/components/schemas/IdentityProviderRequest' + responses: + '200': + description: An Identity Provider object + content: + application/json: + schema: + $ref: '#/components/schemas/IdentityProvider' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete an Identity Provider + description: Delete an identity provider configuration + tags: [ Identity Providers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: idpId + required: true + schema: + type: string + description: The unique identifier of an identity provider + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/integrations/billing/usage: + get: + summary: Get current usage + tags: + - Usage + responses: + "200": + description: Current usage data + content: + application/json: + schema: + $ref: "#/components/schemas/UsageStats" + "401": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/billing/subscription: + get: + summary: Get current subscription + tags: + - Subscription + responses: + "200": + description: Subscription details + content: + application/json: + schema: + $ref: "#/components/schemas/Subscription" + "401": + $ref: "#/components/responses/requires_authentication" + "404": + description: No subscription found + "500": + $ref: "#/components/responses/internal_error" + put: + summary: Change subscription + tags: + - Subscription + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + priceID: + type: string + description: The Price ID to change the subscription to. + example: "price_1HhxOpBzq4JbCqRmJxkpzL2V" + plan_tier: + type: string + description: The plan tier to change the subscription to. + example: business + responses: + "200": + description: Subscription successfully changed + "400": + $ref: "#/components/responses/bad_request" + "401": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/billing/plans: + get: + summary: Get available plans + tags: + - Plans + responses: + "200": + description: List of available plans + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Product" + "401": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/billing/checkout: + post: + summary: Create checkout session + tags: + - Checkout + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + baseURL: + type: string + description: The base URL for the redirect after checkout. + example: "https://app.netbird.io/plans/success" + priceID: + type: string + description: The Price ID for checkout. + example: "price_1HhxOpBzq4JbCqRmJxkpzL2V" + enableTrial: + type: boolean + description: Enables a 14-day trial for the account. + required: + - baseURL + - priceID + responses: + "200": + description: Checkout session URL + content: + application/json: + schema: + $ref: "#/components/schemas/CheckoutResponse" + "400": + $ref: "#/components/responses/bad_request" + "401": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/billing/portal: + get: + summary: Get customer portal URL + tags: + - Portal + parameters: + - in: query + name: baseURL + schema: + type: string + required: true + description: The base URL for the redirect after accessing the portal. + example: "https://app.netbird.io/plans" + responses: + "200": + description: Customer portal URL + content: + application/json: + schema: + $ref: "#/components/schemas/PortalResponse" + "400": + $ref: "#/components/responses/bad_request" + "401": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/billing/invoices: + get: + summary: Get account's paid invoices + tags: + - Invoice + responses: + "200": + description: The account's paid invoices + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/InvoiceResponse" + "400": + $ref: "#/components/responses/bad_request" + "401": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/billing/invoices/{id}/pdf: + get: + summary: Get account invoice URL to Stripe. + tags: + - Invoice + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The unique identifier of the invoice + responses: + "200": + description: The invoice URL to Stripe + content: + application/json: + schema: + $ref: "#/components/schemas/InvoicePDFResponse" + "400": + $ref: "#/components/responses/bad_request" + "401": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/billing/invoices/{id}/csv: + get: + summary: Get account invoice CSV. + tags: + - Invoice + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The unique identifier of the invoice + responses: + "200": + description: The invoice CSV + headers: + Content-Disposition: + schema: + type: string + example: attachment; filename=in_1MtHbELkdIwHu7ixl4OzzPMv.csv + content: + text/csv: + schema: + type: string + example: | + description,qty,unit_price,amount + line item 2, 5, 1.00, 5.00 + line item 1, 10, 0.50, 5.00 + "400": + $ref: "#/components/responses/bad_request" + "401": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/billing/aws/marketplace/activate: + post: + summary: Activate AWS Marketplace subscription. + tags: + - AWS Marketplace + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + plan_tier: + type: string + description: The plan tier to activate the subscription for. + example: business + required: + - plan_tier + responses: + "200": + description: AWS subscription successfully activated + "400": + $ref: "#/components/responses/bad_request" + "401": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/billing/aws/marketplace/enrich: + post: + summary: Enrich AWS Marketplace subscription with Account ID. + tags: + - AWS Marketplace + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + aws_user_id: + type: string + description: The AWS user ID. + example: eRF345hgdgFyu + required: + - aws_user_id + responses: + "200": + description: AWS subscription successfully enriched with Account ID. + "400": + $ref: "#/components/responses/bad_request" + "401": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/msp/tenants: + get: + summary: Get MSP tenants + tags: + - MSP + responses: + "200": + description: Get MSP tenants response + content: + application/json: + schema: + $ref: "#/components/schemas/GetTenantsResponse" + "400": + $ref: "#/components/responses/bad_request" + "403": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + post: + summary: Create MSP tenant + tags: + - MSP + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateTenantRequest" + responses: + "200": + description: Create MSP tenant Response + content: + application/json: + schema: + $ref: "#/components/schemas/TenantResponse" + "400": + $ref: "#/components/responses/bad_request" + "403": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/msp/tenants/{id}: + put: + summary: Update MSP tenant + tags: + - MSP + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The unique identifier of a tenant account + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateTenantRequest" + responses: + "200": + description: Update MSP tenant Response + content: + application/json: + schema: + $ref: "#/components/schemas/TenantResponse" + "400": + $ref: "#/components/responses/bad_request" + "403": + $ref: "#/components/responses/requires_authentication" + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/msp/tenants/{id}/unlink: + post: + summary: Unlink a tenant + tags: + - MSP + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The unique identifier of a tenant account + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + owner: + type: string + description: The new owners user ID. + example: "google-oauth2|123456789012345678901" + required: + - owner + responses: + "200": + description: Successfully unlinked the tenant + "400": + $ref: "#/components/responses/bad_request" + "403": + $ref: "#/components/responses/requires_authentication" + "404": + description: The tenant was not found + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/msp/tenants/{id}/dns: + post: + summary: Verify a tenant domain DNS challenge + tags: + - MSP + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The unique identifier of a tenant account + responses: + "200": + description: Successfully verified the DNS challenge + "400": + $ref: "#/components/responses/bad_request" + "403": + $ref: "#/components/responses/requires_authentication" + "404": + description: The tenant was not found + "500": + $ref: "#/components/responses/internal_error" + "501": + description: DNS Challenge Failed Response + content: + application/json: + schema: + $ref: "#/components/schemas/DNSChallengeResponse" + /api/integrations/msp/tenants/{id}/subscription: + post: + summary: Create subscription for Tenant + tags: + - MSP + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The unique identifier of a tenant account + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + priceID: + type: string + description: The Price ID to change the subscription to. + example: "price_1HhxOpBzq4JbCqRmJxkpzL2V" + required: + - priceID + responses: + "200": + description: Successfully created subscription for Tenant + "400": + $ref: "#/components/responses/bad_request" + "403": + $ref: "#/components/responses/requires_authentication" + "404": + description: The tenant was not found + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/msp/tenants/{id}/invite: + post: + summary: Invite existing account as a Tenant to the MSP account + tags: + - MSP + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The unique identifier of an existing tenant account + responses: + "200": + description: Successfully invited existing Tenant to the MSP account + content: + application/json: + schema: + $ref: "#/components/schemas/TenantResponse" + "400": + $ref: "#/components/responses/bad_request" + "403": + $ref: "#/components/responses/requires_authentication" + "404": + description: The tenant was not found + "500": + $ref: "#/components/responses/internal_error" + put: + summary: Response by the invited Tenant account owner + tags: + - MSP + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The unique identifier of an existing tenant account + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + value: + type: string + description: Accept or decline the invitation. + enum: + - accept + - decline + required: + - value + responses: + "200": + description: Successful response + "400": + $ref: "#/components/responses/bad_request" + "403": + $ref: "#/components/responses/requires_authentication" + "404": + description: The tenant was not found + "500": + $ref: "#/components/responses/internal_error" + /api/integrations/edr/intune: + post: + tags: + - EDR Intune Integrations + summary: Create EDR Intune Integration + description: | + Creates a new EDR Intune integration for the authenticated account. + operationId: createEDRIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EDRIntuneRequest' + responses: + '200': + description: Integration created successfully. Returns the created integration. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRIntuneResponse' + '400': + description: Bad Request (e.g., invalid JSON, missing required fields, validation error). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized (e.g., missing or invalid authentication token). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + tags: + - EDR Intune Integrations + summary: Get EDR Intune Integration + description: Retrieves a specific EDR Intune integration by its ID. + operationId: getEDRIntegration + responses: + '200': + description: Successfully retrieved the integration details. Config keys are masked. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRIntuneResponse' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found (e.g., integration with the given ID does not exist). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - EDR Intune Integrations + summary: Update EDR Intune Integration + description: | + Updates an existing EDR Intune Integration. The request body structure is `EDRIntuneRequest`. + operationId: updateEDRIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EDRIntuneRequest' + responses: + '200': + description: Integration updated successfully. Returns the updated integration. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRIntuneResponse' + '400': + description: Bad Request (e.g., invalid JSON, validation error, invalid ID). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - EDR Intune Integrations + summary: Delete EDR Intune Integration + description: Deletes an EDR Intune Integration by its ID. + operationId: deleteIntegration + responses: + '200': + description: Integration deleted successfully. Returns an empty object. + content: + application/json: + schema: + type: object + example: { } + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/edr/sentinelone: + post: + tags: + - EDR SentinelOne Integrations + summary: Create EDR SentinelOne Integration + description: Creates a new EDR SentinelOne integration + operationId: createSentinelOneEDRIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EDRSentinelOneRequest' + responses: + '200': + description: Integration created successfully. Returns the created integration. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRSentinelOneResponse' + '400': + description: Bad Request (e.g., invalid JSON, missing required fields, validation error). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized (e.g., missing or invalid authentication token). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + tags: + - EDR SentinelOne Integrations + summary: Get EDR SentinelOne Integration + description: Retrieves a specific EDR SentinelOne integration by its ID. + responses: + '200': + description: Successfully retrieved the integration details. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRSentinelOneResponse' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found (e.g., integration with the given ID does not exist). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - EDR SentinelOne Integrations + summary: Update EDR SentinelOne Integration + description: Updates an existing EDR SentinelOne Integration. + operationId: updateSentinelOneEDRIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EDRSentinelOneRequest' + responses: + '200': + description: Integration updated successfully. Returns the updated integration. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRSentinelOneResponse' + '400': + description: Bad Request (e.g., invalid JSON, validation error, invalid ID). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - EDR SentinelOne Integrations + summary: Delete EDR SentinelOne Integration + description: Deletes an EDR SentinelOne Integration by its ID. + responses: + '200': + description: Integration deleted successfully. Returns an empty object. + content: + application/json: + schema: + type: object + example: { } + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /api/integrations/edr/falcon: + post: + tags: + - EDR Falcon Integrations + summary: Create EDR Falcon Integration + description: Creates a new EDR Falcon integration + operationId: createFalconEDRIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EDRFalconRequest' + responses: + '200': + description: Integration created successfully. Returns the created integration. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRFalconResponse' + '400': + description: Bad Request (e.g., invalid JSON, missing required fields, validation error). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized (e.g., missing or invalid authentication token). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + tags: + - EDR Falcon Integrations + summary: Get EDR Falcon Integration + description: Retrieves a specific EDR Falcon integration by its ID. + responses: + '200': + description: Successfully retrieved the integration details. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRFalconResponse' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found (e.g., integration with the given ID does not exist). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - EDR Falcon Integrations + summary: Update EDR Falcon Integration + description: Updates an existing EDR Falcon Integration. + operationId: updateFalconEDRIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EDRFalconRequest' + responses: + '200': + description: Integration updated successfully. Returns the updated integration. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRFalconResponse' + '400': + description: Bad Request (e.g., invalid JSON, validation error, invalid ID). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - EDR Falcon Integrations + summary: Delete EDR Falcon Integration + description: Deletes an existing EDR Falcon Integration by its ID. + responses: + '202': + description: Integration deleted successfully. Typically returns no content. + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /api/integrations/google-idp: + post: + tags: + - IDP Google Integrations + summary: Create Google IDP Integration + description: Creates a new Google Workspace IDP integration + operationId: createGoogleIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateGoogleIntegrationRequest' + responses: + '200': + description: Integration created successfully. Returns the created integration. + content: + application/json: + schema: + $ref: '#/components/schemas/GoogleIntegration' + '400': + description: Bad Request (e.g., invalid JSON, missing required fields, validation error). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized (e.g., missing or invalid authentication token). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + tags: + - IDP Google Integrations + summary: Get All Google IDP Integrations + description: Retrieves all Google Workspace IDP integrations for the authenticated account + operationId: getAllGoogleIntegrations + responses: + '200': + description: A list of Google IDP integrations. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/GoogleIntegration' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/google-idp/{id}: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the Google IDP integration. + schema: + type: integer + format: int64 + example: 1 + get: + tags: + - IDP Google Integrations + summary: Get Google IDP Integration + description: Retrieves a Google IDP integration by ID. + operationId: getGoogleIntegration + responses: + '200': + description: Successfully retrieved the integration details. + content: + application/json: + schema: + $ref: '#/components/schemas/GoogleIntegration' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found (e.g., integration with the given ID does not exist). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - IDP Google Integrations + summary: Update Google IDP Integration + description: Updates an existing Google Workspace IDP integration. + operationId: updateGoogleIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateGoogleIntegrationRequest' + responses: + '200': + description: Integration updated successfully. Returns the updated integration. + content: + application/json: + schema: + $ref: '#/components/schemas/GoogleIntegration' + '400': + description: Bad Request (e.g., invalid JSON, validation error, invalid ID). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - IDP Google Integrations + summary: Delete Google IDP Integration + description: Deletes a Google IDP integration by ID. + operationId: deleteGoogleIntegration + responses: + '200': + description: Integration deleted successfully. Returns an empty object. + content: + application/json: + schema: + type: object + example: { } + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/google-idp/{id}/sync: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the Google IDP integration. + schema: + type: integer + format: int64 + example: 1 + post: + tags: + - IDP Google Integrations + summary: Sync Google IDP Integration + description: Triggers a manual synchronization for a Google IDP integration. + operationId: syncGoogleIntegration + responses: + '200': + description: Sync triggered successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/SyncResult' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/google-idp/{id}/logs: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the Google IDP integration. + schema: + type: integer + format: int64 + example: 1 + get: + tags: + - IDP Google Integrations + summary: Get Google Integration Sync Logs + description: Retrieves synchronization logs for a Google IDP integration. + operationId: getGoogleIntegrationLogs + responses: + '200': + description: Successfully retrieved the integration sync logs. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/IdpIntegrationSyncLog' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/azure-idp: + post: + tags: + - IDP Azure Integrations + summary: Create Azure IDP Integration + description: Creates a new Azure AD IDP integration + operationId: createAzureIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateAzureIntegrationRequest' + responses: + '200': + description: Integration created successfully. Returns the created integration. + content: + application/json: + schema: + $ref: '#/components/schemas/AzureIntegration' + '400': + description: Bad Request (e.g., invalid JSON, missing required fields, validation error). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized (e.g., missing or invalid authentication token). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + tags: + - IDP Azure Integrations + summary: Get All Azure IDP Integrations + description: Retrieves all Azure AD IDP integrations for the authenticated account + operationId: getAllAzureIntegrations + responses: + '200': + description: A list of Azure IDP integrations. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AzureIntegration' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/azure-idp/{id}: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the Azure IDP integration. + schema: + type: integer + format: int64 + example: 1 + get: + tags: + - IDP Azure Integrations + summary: Get Azure IDP Integration + description: Retrieves an Azure IDP integration by ID. + operationId: getAzureIntegration + responses: + '200': + description: Successfully retrieved the integration details. + content: + application/json: + schema: + $ref: '#/components/schemas/AzureIntegration' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found (e.g., integration with the given ID does not exist). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - IDP Azure Integrations + summary: Update Azure IDP Integration + description: Updates an existing Azure AD IDP integration. + operationId: updateAzureIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateAzureIntegrationRequest' + responses: + '200': + description: Integration updated successfully. Returns the updated integration. + content: + application/json: + schema: + $ref: '#/components/schemas/AzureIntegration' + '400': + description: Bad Request (e.g., invalid JSON, validation error, invalid ID). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - IDP Azure Integrations + summary: Delete Azure IDP Integration + description: Deletes an Azure IDP integration by ID. + operationId: deleteAzureIntegration + responses: + '200': + description: Integration deleted successfully. Returns an empty object. + content: + application/json: + schema: + type: object + example: { } + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/azure-idp/{id}/sync: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the Azure IDP integration. + schema: + type: integer + format: int64 + example: 1 + post: + tags: + - IDP Azure Integrations + summary: Sync Azure IDP Integration + description: Triggers a manual synchronization for an Azure IDP integration. + operationId: syncAzureIntegration + responses: + '200': + description: Sync triggered successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/SyncResult' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/azure-idp/{id}/logs: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the Azure IDP integration. + schema: + type: integer + format: int64 + example: 1 + get: + tags: + - IDP Azure Integrations + summary: Get Azure Integration Sync Logs + description: Retrieves synchronization logs for an Azure IDP integration. + operationId: getAzureIntegrationLogs + responses: + '200': + description: Successfully retrieved the integration sync logs. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/IdpIntegrationSyncLog' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/okta-scim-idp: + post: + tags: + - IDP Okta SCIM Integrations + summary: Create Okta SCIM IDP Integration + description: Creates a new Okta SCIM IDP integration + operationId: createOktaScimIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateOktaScimIntegrationRequest' + responses: + '200': + description: Integration created successfully. Returns the created integration. + content: + application/json: + schema: + $ref: '#/components/schemas/OktaScimIntegration' + '400': + description: Bad Request (e.g., invalid JSON, missing required fields, validation error). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized (e.g., missing or invalid authentication token). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + tags: + - IDP Okta SCIM Integrations + summary: Get All Okta SCIM IDP Integrations + description: Retrieves all Okta SCIM IDP integrations for the authenticated account + operationId: getAllOktaScimIntegrations + responses: + '200': + description: A list of Okta SCIM IDP integrations. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/OktaScimIntegration' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/okta-scim-idp/{id}: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the Okta SCIM IDP integration. + schema: + type: integer + format: int64 + example: 1 + get: + tags: + - IDP Okta SCIM Integrations + summary: Get Okta SCIM IDP Integration + description: Retrieves an Okta SCIM IDP integration by ID. + operationId: getOktaScimIntegration + responses: + '200': + description: Successfully retrieved the integration details. + content: + application/json: + schema: + $ref: '#/components/schemas/OktaScimIntegration' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found (e.g., integration with the given ID does not exist). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - IDP Okta SCIM Integrations + summary: Update Okta SCIM IDP Integration + description: Updates an existing Okta SCIM IDP integration. + operationId: updateOktaScimIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateOktaScimIntegrationRequest' + responses: + '200': + description: Integration updated successfully. Returns the updated integration. + content: + application/json: + schema: + $ref: '#/components/schemas/OktaScimIntegration' + '400': + description: Bad Request (e.g., invalid JSON, validation error, invalid ID). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - IDP Okta SCIM Integrations + summary: Delete Okta SCIM IDP Integration + description: Deletes an Okta SCIM IDP integration by ID. + operationId: deleteOktaScimIntegration + responses: + '200': + description: Integration deleted successfully. Returns an empty object. + content: + application/json: + schema: + type: object + example: { } + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/okta-scim-idp/{id}/token: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the Okta SCIM IDP integration. + schema: + type: integer + format: int64 + example: 1 + post: + tags: + - IDP Okta SCIM Integrations + summary: Regenerate Okta SCIM Token + description: Regenerates the SCIM API token for an Okta SCIM IDP integration. + operationId: regenerateOktaScimToken + responses: + '200': + description: Token regenerated successfully. Returns the new token. + content: + application/json: + schema: + $ref: '#/components/schemas/ScimTokenResponse' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/okta-scim-idp/{id}/logs: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the Okta SCIM IDP integration. + schema: + type: integer + format: int64 + example: 1 + get: + tags: + - IDP Okta SCIM Integrations + summary: Get Okta SCIM Integration Sync Logs + description: Retrieves synchronization logs for an Okta SCIM IDP integration. + operationId: getOktaScimIntegrationLogs + responses: + '200': + description: Successfully retrieved the integration sync logs. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/IdpIntegrationSyncLog' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/scim-idp: + post: + tags: + - IDP SCIM Integrations + summary: Create SCIM IDP Integration + description: Creates a new SCIM integration + operationId: createSCIMIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateScimIntegrationRequest' + responses: + '200': + description: Integration created successfully. Returns the created integration. + content: + application/json: + schema: + $ref: '#/components/schemas/ScimIntegration' + '400': + description: Bad Request (e.g., invalid JSON, missing required fields, validation error). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized (e.g., missing or invalid authentication token). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + tags: + - IDP SCIM Integrations + summary: Get All SCIM IDP Integrations + description: Retrieves all SCIM IDP integrations for the authenticated account + operationId: getAllSCIMIntegrations + responses: + '200': + description: A list of SCIM IDP integrations. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScimIntegration' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/scim-idp/{id}: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the SCIM IDP integration. + schema: + type: integer + format: int64 + example: 1 + get: + tags: + - IDP SCIM Integrations + summary: Get SCIM IDP Integration + description: Retrieves an SCIM IDP integration by ID. + operationId: getSCIMIntegration + responses: + '200': + description: Successfully retrieved the integration details. + content: + application/json: + schema: + $ref: '#/components/schemas/ScimIntegration' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found (e.g., integration with the given ID does not exist). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - IDP SCIM Integrations + summary: Update SCIM IDP Integration + description: Updates an existing SCIM IDP Integration. + operationId: updateSCIMIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateScimIntegrationRequest' + responses: + '200': + description: Integration updated successfully. Returns the updated integration. + content: + application/json: + schema: + $ref: '#/components/schemas/ScimIntegration' + '400': + description: Bad Request (e.g., invalid JSON, validation error, invalid ID). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - IDP SCIM Integrations + summary: Delete SCIM IDP Integration + description: Deletes an SCIM IDP integration by ID. + operationId: deleteSCIMIntegration + responses: + '200': + description: Integration deleted successfully. Returns an empty object. + content: + application/json: + schema: + type: object + example: { } + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/scim-idp/{id}/token: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the SCIM IDP integration. + schema: + type: integer + format: int64 + example: 1 + post: + tags: + - IDP SCIM Integrations + summary: Regenerate SCIM Token + description: Regenerates the SCIM API token for an SCIM IDP integration. + operationId: regenerateSCIMToken + responses: + '200': + description: Token regenerated successfully. Returns the new token. + content: + application/json: + schema: + $ref: '#/components/schemas/ScimTokenResponse' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/integrations/scim-idp/{id}/logs: + parameters: + - name: id + in: path + required: true + description: The unique identifier of the SCIM IDP integration. + schema: + type: integer + format: int64 + example: 1 + get: + tags: + - IDP SCIM Integrations + summary: Get SCIM Integration Sync Logs + description: Retrieves synchronization logs for a SCIM IDP integration. + operationId: getSCIMIntegrationLogs + responses: + '200': + description: Successfully retrieved the integration sync logs. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/IdpIntegrationSyncLog' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /api/integrations/edr/huntress: + post: + tags: + - EDR Huntress Integrations + summary: Create EDR Huntress Integration + description: Creates a new EDR Huntress integration + operationId: createHuntressEDRIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EDRHuntressRequest' + responses: + '200': + description: Integration created successfully. Returns the created integration. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRHuntressResponse' + '400': + description: Bad Request (e.g., invalid JSON, missing required fields, validation error). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized (e.g., missing or invalid authentication token). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + tags: + - EDR Huntress Integrations + summary: Get EDR Huntress Integration + description: Retrieves a specific EDR Huntress integration by its ID. + responses: + '200': + description: Successfully retrieved the integration details. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRHuntressResponse' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found (e.g., integration with the given ID does not exist). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - EDR Huntress Integrations + summary: Update EDR Huntress Integration + description: Updates an existing EDR Huntress Integration. + operationId: updateHuntressEDRIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EDRHuntressRequest' + responses: + '200': + description: Integration updated successfully. Returns the updated integration. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRHuntressResponse' + '400': + description: Bad Request (e.g., invalid JSON, validation error, invalid ID). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - EDR Huntress Integrations + summary: Delete EDR Huntress Integration + description: Deletes an EDR Huntress Integration by its ID. + responses: + '200': + description: Integration deleted successfully. Returns an empty object. + content: + application/json: + schema: + type: object + example: { } + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /api/integrations/edr/fleetdm: + post: + tags: + - EDR FleetDM Integrations + summary: Create EDR FleetDM Integration + description: Creates a new EDR FleetDM integration + operationId: createFleetDMEDRIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EDRFleetDMRequest' + responses: + '200': + description: Integration created successfully. Returns the created integration. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRFleetDMResponse' + '400': + description: Bad Request (e.g., invalid JSON, missing required fields, validation error). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized (e.g., missing or invalid authentication token). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + tags: + - EDR FleetDM Integrations + summary: Get EDR FleetDM Integration + description: Retrieves a specific EDR FleetDM integration by its ID. + responses: + '200': + description: Successfully retrieved the integration details. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRFleetDMResponse' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found (e.g., integration with the given ID does not exist). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - EDR FleetDM Integrations + summary: Update EDR FleetDM Integration + description: Updates an existing EDR FleetDM Integration. + operationId: updateFleetDMEDRIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EDRFleetDMRequest' + responses: + '200': + description: Integration updated successfully. Returns the updated integration. + content: + application/json: + schema: + $ref: '#/components/schemas/EDRFleetDMResponse' + '400': + description: Bad Request (e.g., invalid JSON, validation error, invalid ID). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - EDR FleetDM Integrations + summary: Delete EDR FleetDM Integration + description: Deletes an EDR FleetDM Integration by its ID. + responses: + '200': + description: Integration deleted successfully. Returns an empty object. + content: + application/json: + schema: + type: object + example: { } + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /api/peers/{peer-id}/edr/bypass: + parameters: + - name: peer-id + in: path + required: true + schema: + type: string + description: The unique identifier of the peer + post: + tags: + - EDR Peers + summary: Bypass compliance for a non-compliant peer + description: | + Allows an admin to bypass EDR compliance checks for a specific peer. + The peer will remain bypassed until the admin revokes it OR the device becomes + naturally compliant in the EDR system. + operationId: bypassCompliance + responses: + '200': + description: Peer compliance bypassed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/BypassResponse' + '400': + description: Bad Request (peer not in non-compliant state) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - EDR Peers + summary: Revoke compliance bypass for a peer + description: Removes the compliance bypass, subjecting the peer to normal EDR validation. + operationId: revokeBypass + responses: + '200': + description: Compliance bypass revoked successfully + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /api/peers/edr/bypassed: + get: + tags: + - EDR Peers + summary: List all bypassed peers + description: Returns all peers that have compliance bypassed by an admin. + operationId: listBypassedPeers + responses: + '200': + description: List of bypassed peers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/BypassResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /api/event-streaming: + post: + tags: + - Event Streaming Integrations + summary: Create Event Streaming Integration + description: | + Creates a new event streaming integration for the authenticated account. + The request body should conform to `CreateIntegrationRequest`. + Note: Based on the provided Go code, the `enabled` field from the request is part of the `CreateIntegrationRequest` struct, + but the backend `manager.CreateIntegration` function signature shown does not directly use this `enabled` field. + The actual behavior for `enabled` during creation should be confirmed (e.g., it might have a server-side default or be handled by other logic). + operationId: createIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateIntegrationRequest' + responses: + '200': + description: Integration created successfully. Returns the created integration. + content: + application/json: + schema: + $ref: '#/components/schemas/IntegrationResponse' + '400': + description: Bad Request (e.g., invalid JSON, missing required fields, validation error). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized (e.g., missing or invalid authentication token). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + tags: + - Event Streaming Integrations + summary: List Event Streaming Integrations + description: Retrieves all event streaming integrations for the authenticated account. + operationId: getAllIntegrations + responses: + '200': + description: A list of event streaming integrations. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/IntegrationResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /api/event-streaming/{id}: + parameters: + - name: id + in: path + required: true + description: The unique numeric identifier of the event streaming integration. + schema: + type: integer + example: 123 + get: + tags: + - Event Streaming Integrations + summary: Get Event Streaming Integration + description: Retrieves a specific event streaming integration by its ID. + operationId: getIntegration + responses: + '200': + description: Successfully retrieved the integration details. Config keys are masked. + content: + application/json: + schema: + $ref: '#/components/schemas/IntegrationResponse' + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found (e.g., integration with the given ID does not exist). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - Event Streaming Integrations + summary: Update Event Streaming Integration + description: | + Updates an existing event streaming integration. The request body structure is `CreateIntegrationRequest`. + However, for updates: + - The `platform` field, if provided in the body, is ignored by the backend manager function, as the platform of an existing integration is typically immutable. + - The `enabled` and `config` fields from the request body are used to update the integration. + operationId: updateIntegration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateIntegrationRequest' + responses: + '200': + description: Integration updated successfully. Returns the updated integration. + content: + application/json: + schema: + $ref: '#/components/schemas/IntegrationResponse' + '400': + description: Bad Request (e.g., invalid JSON, validation error, invalid ID). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - Event Streaming Integrations + summary: Delete Event Streaming Integration + description: Deletes an event streaming integration by its ID. + operationId: deleteIntegration + responses: + '200': + description: Integration deleted successfully. Returns an empty object. + content: + application/json: + schema: + type: object + example: { } + '400': + description: Bad Request (e.g., invalid integration ID format). + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /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: + 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" + '409': + "$ref": "#/components/responses/conflict" + '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" + '409': + "$ref": "#/components/responses/conflict" + '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" + /api/integrations/notifications/types: + get: + tags: + - Notifications + summary: List Notification Event Types + description: | + Returns a map of all supported activity event type codes to their + human-readable descriptions. Use these codes when configuring + `event_types` on notification channels. + operationId: listNotificationEventTypes + responses: + '200': + description: A map of event type codes to descriptions. + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationTypeEntry' + '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/integrations/notifications/channels: + get: + tags: + - Notifications + summary: List Notification Channels + description: Retrieves all notification channels configured for the authenticated account. + operationId: listNotificationChannels + responses: + '200': + description: A list of notification channels. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NotificationChannelResponse' + '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: + tags: + - Notifications + summary: Create Notification Channel + description: | + Creates a new notification channel for the authenticated account. + Supported channel types are `email` and `webhook`. + operationId: createNotificationChannel + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationChannelRequest' + responses: + '200': + description: Notification channel created successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationChannelResponse' + '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/integrations/notifications/channels/{channelId}: + parameters: + - name: channelId + in: path + required: true + description: The unique identifier of the notification channel. + schema: + type: string + example: "ch8i4ug6lnn4g9hqv7m0" + get: + tags: + - Notifications + summary: Get Notification Channel + description: Retrieves a specific notification channel by its ID. + operationId: getNotificationChannel + responses: + '200': + description: Successfully retrieved the notification channel. + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationChannelResponse' + '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: + tags: + - Notifications + summary: Update Notification Channel + description: Updates an existing notification channel. + operationId: updateNotificationChannel + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationChannelRequest' + responses: + '200': + description: Notification channel updated successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationChannelResponse' + '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: + tags: + - Notifications + summary: Delete Notification Channel + description: Deletes a notification channel by its ID. + operationId: deleteNotificationChannel + responses: + '200': + description: Notification channel deleted successfully. + content: + application/json: + schema: + type: object + example: { } + '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" diff --git a/shared/management/http/api/types.gen.go b/shared/management/http/api/types.gen.go index 0317b8183cb..796505f1d73 100644 --- a/shared/management/http/api/types.gen.go +++ b/shared/management/http/api/types.gen.go @@ -2320,6 +2320,12 @@ type IdentityProvider struct { // Type Type of identity provider Type IdentityProviderType `json:"type"` + + // Pkce Enables Proof Key for Code Exchange for the upstream OIDC provider + Pkce *bool `json:"pkce,omitempty"` + + // JwksUrl URL to the JSON Web Key Set of the identity provider + JwksUrl *string `json:"jwks_url,omitempty"` } // IdentityProviderRequest defines model for IdentityProviderRequest. @@ -2338,6 +2344,12 @@ type IdentityProviderRequest struct { // Type Type of identity provider Type IdentityProviderType `json:"type"` + + // Pkce Enables Proof Key for Code Exchange for the upstream OIDC provider + Pkce *bool `json:"pkce,omitempty"` + + // JwksUrl URL to the JSON Web Key Set of the identity provider + JwksUrl *string `json:"jwks_url,omitempty"` } // IdentityProviderType Type of identity provider diff --git a/shared/management/http/api/types.gen.go.orig b/shared/management/http/api/types.gen.go.orig new file mode 100644 index 00000000000..0317b8183cb --- /dev/null +++ b/shared/management/http/api/types.gen.go.orig @@ -0,0 +1,5412 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.6.0 DO NOT EDIT. +package api + +import ( + "encoding/json" + "errors" + "time" + + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +const ( + BearerAuthScopes = "BearerAuth.Scopes" + TokenAuthScopes = "TokenAuth.Scopes" +) + +// Defines values for AccessRestrictionsCrowdsecMode. +const ( + AccessRestrictionsCrowdsecModeEnforce AccessRestrictionsCrowdsecMode = "enforce" + AccessRestrictionsCrowdsecModeObserve AccessRestrictionsCrowdsecMode = "observe" + AccessRestrictionsCrowdsecModeOff AccessRestrictionsCrowdsecMode = "off" +) + +// Valid indicates whether the value is a known member of the AccessRestrictionsCrowdsecMode enum. +func (e AccessRestrictionsCrowdsecMode) Valid() bool { + switch e { + case AccessRestrictionsCrowdsecModeEnforce: + return true + case AccessRestrictionsCrowdsecModeObserve: + return true + case AccessRestrictionsCrowdsecModeOff: + return true + default: + return false + } +} + +// Defines values for CreateAzureIntegrationRequestHost. +const ( + CreateAzureIntegrationRequestHostMicrosoftCom CreateAzureIntegrationRequestHost = "microsoft.com" + CreateAzureIntegrationRequestHostMicrosoftUs CreateAzureIntegrationRequestHost = "microsoft.us" +) + +// Valid indicates whether the value is a known member of the CreateAzureIntegrationRequestHost enum. +func (e CreateAzureIntegrationRequestHost) Valid() bool { + switch e { + case CreateAzureIntegrationRequestHostMicrosoftCom: + return true + case CreateAzureIntegrationRequestHostMicrosoftUs: + return true + default: + return false + } +} + +// Defines values for CreateIntegrationRequestPlatform. +const ( + CreateIntegrationRequestPlatformDatadog CreateIntegrationRequestPlatform = "datadog" + CreateIntegrationRequestPlatformFirehose CreateIntegrationRequestPlatform = "firehose" + CreateIntegrationRequestPlatformGenericHttp CreateIntegrationRequestPlatform = "generic_http" + CreateIntegrationRequestPlatformS3 CreateIntegrationRequestPlatform = "s3" +) + +// Valid indicates whether the value is a known member of the CreateIntegrationRequestPlatform enum. +func (e CreateIntegrationRequestPlatform) Valid() bool { + switch e { + case CreateIntegrationRequestPlatformDatadog: + return true + case CreateIntegrationRequestPlatformFirehose: + return true + case CreateIntegrationRequestPlatformGenericHttp: + return true + case CreateIntegrationRequestPlatformS3: + return true + default: + return false + } +} + +// Defines values for DNSRecordType. +const ( + DNSRecordTypeA DNSRecordType = "A" + DNSRecordTypeAAAA DNSRecordType = "AAAA" + DNSRecordTypeCNAME DNSRecordType = "CNAME" +) + +// Valid indicates whether the value is a known member of the DNSRecordType enum. +func (e DNSRecordType) Valid() bool { + switch e { + case DNSRecordTypeA: + return true + case DNSRecordTypeAAAA: + return true + case DNSRecordTypeCNAME: + return true + default: + return false + } +} + +// Defines values for EventActivityCode. +const ( + EventActivityCodeAccountCreate EventActivityCode = "account.create" + EventActivityCodeAccountDelete EventActivityCode = "account.delete" + EventActivityCodeAccountDnsDomainUpdate EventActivityCode = "account.dns.domain.update" + EventActivityCodeAccountNetworkRangeUpdate EventActivityCode = "account.network.range.update" + EventActivityCodeAccountPeerInactivityExpirationDisable EventActivityCode = "account.peer.inactivity.expiration.disable" + EventActivityCodeAccountPeerInactivityExpirationEnable EventActivityCode = "account.peer.inactivity.expiration.enable" + EventActivityCodeAccountPeerInactivityExpirationUpdate EventActivityCode = "account.peer.inactivity.expiration.update" + EventActivityCodeAccountSettingGroupPropagationDisable EventActivityCode = "account.setting.group.propagation.disable" + EventActivityCodeAccountSettingGroupPropagationEnable EventActivityCode = "account.setting.group.propagation.enable" + EventActivityCodeAccountSettingLazyConnectionDisable EventActivityCode = "account.setting.lazy.connection.disable" + EventActivityCodeAccountSettingLazyConnectionEnable EventActivityCode = "account.setting.lazy.connection.enable" + EventActivityCodeAccountSettingPeerApprovalDisable EventActivityCode = "account.setting.peer.approval.disable" + EventActivityCodeAccountSettingPeerApprovalEnable EventActivityCode = "account.setting.peer.approval.enable" + EventActivityCodeAccountSettingPeerLoginExpirationDisable EventActivityCode = "account.setting.peer.login.expiration.disable" + EventActivityCodeAccountSettingPeerLoginExpirationEnable EventActivityCode = "account.setting.peer.login.expiration.enable" + EventActivityCodeAccountSettingPeerLoginExpirationUpdate EventActivityCode = "account.setting.peer.login.expiration.update" + EventActivityCodeAccountSettingRoutingPeerDnsResolutionDisable EventActivityCode = "account.setting.routing.peer.dns.resolution.disable" + EventActivityCodeAccountSettingRoutingPeerDnsResolutionEnable EventActivityCode = "account.setting.routing.peer.dns.resolution.enable" + EventActivityCodeAccountSettingsAutoVersionUpdate EventActivityCode = "account.settings.auto.version.update" + EventActivityCodeDashboardLogin EventActivityCode = "dashboard.login" + EventActivityCodeDnsSettingDisabledManagementGroupAdd EventActivityCode = "dns.setting.disabled.management.group.add" + EventActivityCodeDnsSettingDisabledManagementGroupDelete EventActivityCode = "dns.setting.disabled.management.group.delete" + EventActivityCodeDnsZoneCreate EventActivityCode = "dns.zone.create" + EventActivityCodeDnsZoneDelete EventActivityCode = "dns.zone.delete" + EventActivityCodeDnsZoneRecordCreate EventActivityCode = "dns.zone.record.create" + EventActivityCodeDnsZoneRecordDelete EventActivityCode = "dns.zone.record.delete" + EventActivityCodeDnsZoneRecordUpdate EventActivityCode = "dns.zone.record.update" + EventActivityCodeDnsZoneUpdate EventActivityCode = "dns.zone.update" + EventActivityCodeGroupAdd EventActivityCode = "group.add" + EventActivityCodeGroupDelete EventActivityCode = "group.delete" + EventActivityCodeGroupUpdate EventActivityCode = "group.update" + EventActivityCodeIdentityproviderCreate EventActivityCode = "identityprovider.create" + EventActivityCodeIdentityproviderDelete EventActivityCode = "identityprovider.delete" + EventActivityCodeIdentityproviderUpdate EventActivityCode = "identityprovider.update" + EventActivityCodeIntegrationCreate EventActivityCode = "integration.create" + EventActivityCodeIntegrationDelete EventActivityCode = "integration.delete" + EventActivityCodeIntegrationUpdate EventActivityCode = "integration.update" + EventActivityCodeNameserverGroupAdd EventActivityCode = "nameserver.group.add" + EventActivityCodeNameserverGroupDelete EventActivityCode = "nameserver.group.delete" + EventActivityCodeNameserverGroupUpdate EventActivityCode = "nameserver.group.update" + EventActivityCodeNetworkCreate EventActivityCode = "network.create" + EventActivityCodeNetworkDelete EventActivityCode = "network.delete" + EventActivityCodeNetworkResourceCreate EventActivityCode = "network.resource.create" + EventActivityCodeNetworkResourceDelete EventActivityCode = "network.resource.delete" + EventActivityCodeNetworkResourceUpdate EventActivityCode = "network.resource.update" + EventActivityCodeNetworkRouterCreate EventActivityCode = "network.router.create" + EventActivityCodeNetworkRouterDelete EventActivityCode = "network.router.delete" + EventActivityCodeNetworkRouterUpdate EventActivityCode = "network.router.update" + EventActivityCodeNetworkUpdate EventActivityCode = "network.update" + EventActivityCodePeerApprovalRevoke EventActivityCode = "peer.approval.revoke" + EventActivityCodePeerApprove EventActivityCode = "peer.approve" + EventActivityCodePeerGroupAdd EventActivityCode = "peer.group.add" + EventActivityCodePeerGroupDelete EventActivityCode = "peer.group.delete" + EventActivityCodePeerInactivityExpirationDisable EventActivityCode = "peer.inactivity.expiration.disable" + EventActivityCodePeerInactivityExpirationEnable EventActivityCode = "peer.inactivity.expiration.enable" + EventActivityCodePeerIpUpdate EventActivityCode = "peer.ip.update" + EventActivityCodePeerJobCreate EventActivityCode = "peer.job.create" + EventActivityCodePeerLoginExpirationDisable EventActivityCode = "peer.login.expiration.disable" + EventActivityCodePeerLoginExpirationEnable EventActivityCode = "peer.login.expiration.enable" + EventActivityCodePeerLoginExpire EventActivityCode = "peer.login.expire" + EventActivityCodePeerRename EventActivityCode = "peer.rename" + EventActivityCodePeerSetupkeyAdd EventActivityCode = "peer.setupkey.add" + EventActivityCodePeerSshDisable EventActivityCode = "peer.ssh.disable" + EventActivityCodePeerSshEnable EventActivityCode = "peer.ssh.enable" + EventActivityCodePeerUserAdd EventActivityCode = "peer.user.add" + EventActivityCodePersonalAccessTokenCreate EventActivityCode = "personal.access.token.create" + EventActivityCodePersonalAccessTokenDelete EventActivityCode = "personal.access.token.delete" + EventActivityCodePolicyAdd EventActivityCode = "policy.add" + EventActivityCodePolicyDelete EventActivityCode = "policy.delete" + EventActivityCodePolicyUpdate EventActivityCode = "policy.update" + EventActivityCodePostureCheckCreate EventActivityCode = "posture.check.create" + EventActivityCodePostureCheckDelete EventActivityCode = "posture.check.delete" + EventActivityCodePostureCheckUpdate EventActivityCode = "posture.check.update" + EventActivityCodeResourceGroupAdd EventActivityCode = "resource.group.add" + EventActivityCodeResourceGroupDelete EventActivityCode = "resource.group.delete" + EventActivityCodeRouteAdd EventActivityCode = "route.add" + EventActivityCodeRouteDelete EventActivityCode = "route.delete" + EventActivityCodeRouteUpdate EventActivityCode = "route.update" + 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" + EventActivityCodeSetupkeyDelete EventActivityCode = "setupkey.delete" + EventActivityCodeSetupkeyGroupAdd EventActivityCode = "setupkey.group.add" + EventActivityCodeSetupkeyGroupDelete EventActivityCode = "setupkey.group.delete" + EventActivityCodeSetupkeyOveruse EventActivityCode = "setupkey.overuse" + EventActivityCodeSetupkeyRevoke EventActivityCode = "setupkey.revoke" + EventActivityCodeSetupkeyUpdate EventActivityCode = "setupkey.update" + EventActivityCodeTransferredOwnerRole EventActivityCode = "transferred.owner.role" + EventActivityCodeUserApprove EventActivityCode = "user.approve" + EventActivityCodeUserBlock EventActivityCode = "user.block" + EventActivityCodeUserCreate EventActivityCode = "user.create" + EventActivityCodeUserDelete EventActivityCode = "user.delete" + EventActivityCodeUserGroupAdd EventActivityCode = "user.group.add" + EventActivityCodeUserGroupDelete EventActivityCode = "user.group.delete" + EventActivityCodeUserInvite EventActivityCode = "user.invite" + EventActivityCodeUserInviteLinkAccept EventActivityCode = "user.invite.link.accept" + EventActivityCodeUserInviteLinkCreate EventActivityCode = "user.invite.link.create" + EventActivityCodeUserInviteLinkDelete EventActivityCode = "user.invite.link.delete" + EventActivityCodeUserInviteLinkRegenerate EventActivityCode = "user.invite.link.regenerate" + EventActivityCodeUserJoin EventActivityCode = "user.join" + EventActivityCodeUserPasswordChange EventActivityCode = "user.password.change" + EventActivityCodeUserPeerDelete EventActivityCode = "user.peer.delete" + EventActivityCodeUserPeerLogin EventActivityCode = "user.peer.login" + EventActivityCodeUserReject EventActivityCode = "user.reject" + EventActivityCodeUserRoleUpdate EventActivityCode = "user.role.update" + EventActivityCodeUserUnblock EventActivityCode = "user.unblock" +) + +// Valid indicates whether the value is a known member of the EventActivityCode enum. +func (e EventActivityCode) Valid() bool { + switch e { + case EventActivityCodeAccountCreate: + return true + case EventActivityCodeAccountDelete: + return true + case EventActivityCodeAccountDnsDomainUpdate: + return true + case EventActivityCodeAccountNetworkRangeUpdate: + return true + case EventActivityCodeAccountPeerInactivityExpirationDisable: + return true + case EventActivityCodeAccountPeerInactivityExpirationEnable: + return true + case EventActivityCodeAccountPeerInactivityExpirationUpdate: + return true + case EventActivityCodeAccountSettingGroupPropagationDisable: + return true + case EventActivityCodeAccountSettingGroupPropagationEnable: + return true + case EventActivityCodeAccountSettingLazyConnectionDisable: + return true + case EventActivityCodeAccountSettingLazyConnectionEnable: + return true + case EventActivityCodeAccountSettingPeerApprovalDisable: + return true + case EventActivityCodeAccountSettingPeerApprovalEnable: + return true + case EventActivityCodeAccountSettingPeerLoginExpirationDisable: + return true + case EventActivityCodeAccountSettingPeerLoginExpirationEnable: + return true + case EventActivityCodeAccountSettingPeerLoginExpirationUpdate: + return true + case EventActivityCodeAccountSettingRoutingPeerDnsResolutionDisable: + return true + case EventActivityCodeAccountSettingRoutingPeerDnsResolutionEnable: + return true + case EventActivityCodeAccountSettingsAutoVersionUpdate: + return true + case EventActivityCodeDashboardLogin: + return true + case EventActivityCodeDnsSettingDisabledManagementGroupAdd: + return true + case EventActivityCodeDnsSettingDisabledManagementGroupDelete: + return true + case EventActivityCodeDnsZoneCreate: + return true + case EventActivityCodeDnsZoneDelete: + return true + case EventActivityCodeDnsZoneRecordCreate: + return true + case EventActivityCodeDnsZoneRecordDelete: + return true + case EventActivityCodeDnsZoneRecordUpdate: + return true + case EventActivityCodeDnsZoneUpdate: + return true + case EventActivityCodeGroupAdd: + return true + case EventActivityCodeGroupDelete: + return true + case EventActivityCodeGroupUpdate: + return true + case EventActivityCodeIdentityproviderCreate: + return true + case EventActivityCodeIdentityproviderDelete: + return true + case EventActivityCodeIdentityproviderUpdate: + return true + case EventActivityCodeIntegrationCreate: + return true + case EventActivityCodeIntegrationDelete: + return true + case EventActivityCodeIntegrationUpdate: + return true + case EventActivityCodeNameserverGroupAdd: + return true + case EventActivityCodeNameserverGroupDelete: + return true + case EventActivityCodeNameserverGroupUpdate: + return true + case EventActivityCodeNetworkCreate: + return true + case EventActivityCodeNetworkDelete: + return true + case EventActivityCodeNetworkResourceCreate: + return true + case EventActivityCodeNetworkResourceDelete: + return true + case EventActivityCodeNetworkResourceUpdate: + return true + case EventActivityCodeNetworkRouterCreate: + return true + case EventActivityCodeNetworkRouterDelete: + return true + case EventActivityCodeNetworkRouterUpdate: + return true + case EventActivityCodeNetworkUpdate: + return true + case EventActivityCodePeerApprovalRevoke: + return true + case EventActivityCodePeerApprove: + return true + case EventActivityCodePeerGroupAdd: + return true + case EventActivityCodePeerGroupDelete: + return true + case EventActivityCodePeerInactivityExpirationDisable: + return true + case EventActivityCodePeerInactivityExpirationEnable: + return true + case EventActivityCodePeerIpUpdate: + return true + case EventActivityCodePeerJobCreate: + return true + case EventActivityCodePeerLoginExpirationDisable: + return true + case EventActivityCodePeerLoginExpirationEnable: + return true + case EventActivityCodePeerLoginExpire: + return true + case EventActivityCodePeerRename: + return true + case EventActivityCodePeerSetupkeyAdd: + return true + case EventActivityCodePeerSshDisable: + return true + case EventActivityCodePeerSshEnable: + return true + case EventActivityCodePeerUserAdd: + return true + case EventActivityCodePersonalAccessTokenCreate: + return true + case EventActivityCodePersonalAccessTokenDelete: + return true + case EventActivityCodePolicyAdd: + return true + case EventActivityCodePolicyDelete: + return true + case EventActivityCodePolicyUpdate: + return true + case EventActivityCodePostureCheckCreate: + return true + case EventActivityCodePostureCheckDelete: + return true + case EventActivityCodePostureCheckUpdate: + return true + case EventActivityCodeResourceGroupAdd: + return true + case EventActivityCodeResourceGroupDelete: + return true + case EventActivityCodeRouteAdd: + return true + case EventActivityCodeRouteDelete: + return true + case EventActivityCodeRouteUpdate: + return true + case EventActivityCodeRuleAdd: + return true + case EventActivityCodeRuleDelete: + return true + case EventActivityCodeRuleUpdate: + return true + case EventActivityCodeServiceCreate: + return true + case EventActivityCodeServiceDelete: + return true + case EventActivityCodeServiceUpdate: + return true + case EventActivityCodeServiceUserCreate: + return true + case EventActivityCodeServiceUserDelete: + return true + case EventActivityCodeSetupkeyAdd: + return true + case EventActivityCodeSetupkeyDelete: + return true + case EventActivityCodeSetupkeyGroupAdd: + return true + case EventActivityCodeSetupkeyGroupDelete: + return true + case EventActivityCodeSetupkeyOveruse: + return true + case EventActivityCodeSetupkeyRevoke: + return true + case EventActivityCodeSetupkeyUpdate: + return true + case EventActivityCodeTransferredOwnerRole: + return true + case EventActivityCodeUserApprove: + return true + case EventActivityCodeUserBlock: + return true + case EventActivityCodeUserCreate: + return true + case EventActivityCodeUserDelete: + return true + case EventActivityCodeUserGroupAdd: + return true + case EventActivityCodeUserGroupDelete: + return true + case EventActivityCodeUserInvite: + return true + case EventActivityCodeUserInviteLinkAccept: + return true + case EventActivityCodeUserInviteLinkCreate: + return true + case EventActivityCodeUserInviteLinkDelete: + return true + case EventActivityCodeUserInviteLinkRegenerate: + return true + case EventActivityCodeUserJoin: + return true + case EventActivityCodeUserPasswordChange: + return true + case EventActivityCodeUserPeerDelete: + return true + case EventActivityCodeUserPeerLogin: + return true + case EventActivityCodeUserReject: + return true + case EventActivityCodeUserRoleUpdate: + return true + case EventActivityCodeUserUnblock: + return true + default: + return false + } +} + +// Defines values for GeoLocationCheckAction. +const ( + GeoLocationCheckActionAllow GeoLocationCheckAction = "allow" + GeoLocationCheckActionDeny GeoLocationCheckAction = "deny" +) + +// Valid indicates whether the value is a known member of the GeoLocationCheckAction enum. +func (e GeoLocationCheckAction) Valid() bool { + switch e { + case GeoLocationCheckActionAllow: + return true + case GeoLocationCheckActionDeny: + return true + default: + return false + } +} + +// Defines values for GroupIssued. +const ( + GroupIssuedApi GroupIssued = "api" + GroupIssuedIntegration GroupIssued = "integration" + GroupIssuedJwt GroupIssued = "jwt" +) + +// Valid indicates whether the value is a known member of the GroupIssued enum. +func (e GroupIssued) Valid() bool { + switch e { + case GroupIssuedApi: + return true + case GroupIssuedIntegration: + return true + case GroupIssuedJwt: + return true + default: + return false + } +} + +// Defines values for GroupMinimumIssued. +const ( + GroupMinimumIssuedApi GroupMinimumIssued = "api" + GroupMinimumIssuedIntegration GroupMinimumIssued = "integration" + GroupMinimumIssuedJwt GroupMinimumIssued = "jwt" +) + +// Valid indicates whether the value is a known member of the GroupMinimumIssued enum. +func (e GroupMinimumIssued) Valid() bool { + switch e { + case GroupMinimumIssuedApi: + return true + case GroupMinimumIssuedIntegration: + return true + case GroupMinimumIssuedJwt: + return true + default: + return false + } +} + +// Defines values for IdentityProviderType. +const ( + IdentityProviderTypeEntra IdentityProviderType = "entra" + IdentityProviderTypeGoogle IdentityProviderType = "google" + IdentityProviderTypeMicrosoft IdentityProviderType = "microsoft" + IdentityProviderTypeOidc IdentityProviderType = "oidc" + IdentityProviderTypeOkta IdentityProviderType = "okta" + IdentityProviderTypePocketid IdentityProviderType = "pocketid" + IdentityProviderTypeZitadel IdentityProviderType = "zitadel" +) + +// Valid indicates whether the value is a known member of the IdentityProviderType enum. +func (e IdentityProviderType) Valid() bool { + switch e { + case IdentityProviderTypeEntra: + return true + case IdentityProviderTypeGoogle: + return true + case IdentityProviderTypeMicrosoft: + return true + case IdentityProviderTypeOidc: + return true + case IdentityProviderTypeOkta: + return true + case IdentityProviderTypePocketid: + return true + case IdentityProviderTypeZitadel: + return true + default: + return false + } +} + +// Defines values for IngressPortAllocationPortMappingProtocol. +const ( + IngressPortAllocationPortMappingProtocolTcp IngressPortAllocationPortMappingProtocol = "tcp" + IngressPortAllocationPortMappingProtocolTcpudp IngressPortAllocationPortMappingProtocol = "tcp/udp" + IngressPortAllocationPortMappingProtocolUdp IngressPortAllocationPortMappingProtocol = "udp" +) + +// Valid indicates whether the value is a known member of the IngressPortAllocationPortMappingProtocol enum. +func (e IngressPortAllocationPortMappingProtocol) Valid() bool { + switch e { + case IngressPortAllocationPortMappingProtocolTcp: + return true + case IngressPortAllocationPortMappingProtocolTcpudp: + return true + case IngressPortAllocationPortMappingProtocolUdp: + return true + default: + return false + } +} + +// Defines values for IngressPortAllocationRequestDirectPortProtocol. +const ( + IngressPortAllocationRequestDirectPortProtocolTcp IngressPortAllocationRequestDirectPortProtocol = "tcp" + IngressPortAllocationRequestDirectPortProtocolTcpudp IngressPortAllocationRequestDirectPortProtocol = "tcp/udp" + IngressPortAllocationRequestDirectPortProtocolUdp IngressPortAllocationRequestDirectPortProtocol = "udp" +) + +// Valid indicates whether the value is a known member of the IngressPortAllocationRequestDirectPortProtocol enum. +func (e IngressPortAllocationRequestDirectPortProtocol) Valid() bool { + switch e { + case IngressPortAllocationRequestDirectPortProtocolTcp: + return true + case IngressPortAllocationRequestDirectPortProtocolTcpudp: + return true + case IngressPortAllocationRequestDirectPortProtocolUdp: + return true + default: + return false + } +} + +// Defines values for IngressPortAllocationRequestPortRangeProtocol. +const ( + IngressPortAllocationRequestPortRangeProtocolTcp IngressPortAllocationRequestPortRangeProtocol = "tcp" + IngressPortAllocationRequestPortRangeProtocolTcpudp IngressPortAllocationRequestPortRangeProtocol = "tcp/udp" + IngressPortAllocationRequestPortRangeProtocolUdp IngressPortAllocationRequestPortRangeProtocol = "udp" +) + +// Valid indicates whether the value is a known member of the IngressPortAllocationRequestPortRangeProtocol enum. +func (e IngressPortAllocationRequestPortRangeProtocol) Valid() bool { + switch e { + case IngressPortAllocationRequestPortRangeProtocolTcp: + return true + case IngressPortAllocationRequestPortRangeProtocolTcpudp: + return true + case IngressPortAllocationRequestPortRangeProtocolUdp: + return true + default: + return false + } +} + +// Defines values for IntegrationResponsePlatform. +const ( + IntegrationResponsePlatformDatadog IntegrationResponsePlatform = "datadog" + IntegrationResponsePlatformFirehose IntegrationResponsePlatform = "firehose" + IntegrationResponsePlatformGenericHttp IntegrationResponsePlatform = "generic_http" + IntegrationResponsePlatformS3 IntegrationResponsePlatform = "s3" +) + +// Valid indicates whether the value is a known member of the IntegrationResponsePlatform enum. +func (e IntegrationResponsePlatform) Valid() bool { + switch e { + case IntegrationResponsePlatformDatadog: + return true + case IntegrationResponsePlatformFirehose: + return true + case IntegrationResponsePlatformGenericHttp: + return true + case IntegrationResponsePlatformS3: + return true + default: + return false + } +} + +// Defines values for InvoiceResponseType. +const ( + InvoiceResponseTypeAccount InvoiceResponseType = "account" + InvoiceResponseTypeTenants InvoiceResponseType = "tenants" +) + +// Valid indicates whether the value is a known member of the InvoiceResponseType enum. +func (e InvoiceResponseType) Valid() bool { + switch e { + case InvoiceResponseTypeAccount: + return true + case InvoiceResponseTypeTenants: + return true + default: + return false + } +} + +// Defines values for JobResponseStatus. +const ( + JobResponseStatusFailed JobResponseStatus = "failed" + JobResponseStatusPending JobResponseStatus = "pending" + JobResponseStatusSucceeded JobResponseStatus = "succeeded" +) + +// Valid indicates whether the value is a known member of the JobResponseStatus enum. +func (e JobResponseStatus) Valid() bool { + switch e { + case JobResponseStatusFailed: + return true + case JobResponseStatusPending: + return true + case JobResponseStatusSucceeded: + return true + default: + return false + } +} + +// Defines values for NameserverNsType. +const ( + NameserverNsTypeUdp NameserverNsType = "udp" +) + +// Valid indicates whether the value is a known member of the NameserverNsType enum. +func (e NameserverNsType) Valid() bool { + switch e { + case NameserverNsTypeUdp: + return true + default: + return false + } +} + +// Defines values for NetworkResourceType. +const ( + NetworkResourceTypeDomain NetworkResourceType = "domain" + NetworkResourceTypeHost NetworkResourceType = "host" + NetworkResourceTypeSubnet NetworkResourceType = "subnet" +) + +// Valid indicates whether the value is a known member of the NetworkResourceType enum. +func (e NetworkResourceType) Valid() bool { + switch e { + case NetworkResourceTypeDomain: + return true + case NetworkResourceTypeHost: + return true + case NetworkResourceTypeSubnet: + return true + default: + return false + } +} + +// Defines values for NotificationChannelType. +const ( + NotificationChannelTypeEmail NotificationChannelType = "email" + NotificationChannelTypeWebhook NotificationChannelType = "webhook" +) + +// Valid indicates whether the value is a known member of the NotificationChannelType enum. +func (e NotificationChannelType) Valid() bool { + switch e { + case NotificationChannelTypeEmail: + return true + case NotificationChannelTypeWebhook: + return true + default: + return false + } +} + +// Defines values for PeerNetworkRangeCheckAction. +const ( + PeerNetworkRangeCheckActionAllow PeerNetworkRangeCheckAction = "allow" + PeerNetworkRangeCheckActionDeny PeerNetworkRangeCheckAction = "deny" +) + +// Valid indicates whether the value is a known member of the PeerNetworkRangeCheckAction enum. +func (e PeerNetworkRangeCheckAction) Valid() bool { + switch e { + case PeerNetworkRangeCheckActionAllow: + return true + case PeerNetworkRangeCheckActionDeny: + return true + default: + return false + } +} + +// Defines values for PolicyRuleAction. +const ( + PolicyRuleActionAccept PolicyRuleAction = "accept" + PolicyRuleActionDrop PolicyRuleAction = "drop" +) + +// Valid indicates whether the value is a known member of the PolicyRuleAction enum. +func (e PolicyRuleAction) Valid() bool { + switch e { + case PolicyRuleActionAccept: + return true + case PolicyRuleActionDrop: + return true + default: + return false + } +} + +// Defines values for PolicyRuleProtocol. +const ( + PolicyRuleProtocolAll PolicyRuleProtocol = "all" + PolicyRuleProtocolIcmp PolicyRuleProtocol = "icmp" + PolicyRuleProtocolNetbirdSsh PolicyRuleProtocol = "netbird-ssh" + PolicyRuleProtocolTcp PolicyRuleProtocol = "tcp" + PolicyRuleProtocolUdp PolicyRuleProtocol = "udp" +) + +// Valid indicates whether the value is a known member of the PolicyRuleProtocol enum. +func (e PolicyRuleProtocol) Valid() bool { + switch e { + case PolicyRuleProtocolAll: + return true + case PolicyRuleProtocolIcmp: + return true + case PolicyRuleProtocolNetbirdSsh: + return true + case PolicyRuleProtocolTcp: + return true + case PolicyRuleProtocolUdp: + return true + default: + return false + } +} + +// Defines values for PolicyRuleMinimumAction. +const ( + PolicyRuleMinimumActionAccept PolicyRuleMinimumAction = "accept" + PolicyRuleMinimumActionDrop PolicyRuleMinimumAction = "drop" +) + +// Valid indicates whether the value is a known member of the PolicyRuleMinimumAction enum. +func (e PolicyRuleMinimumAction) Valid() bool { + switch e { + case PolicyRuleMinimumActionAccept: + return true + case PolicyRuleMinimumActionDrop: + return true + default: + return false + } +} + +// Defines values for PolicyRuleMinimumProtocol. +const ( + PolicyRuleMinimumProtocolAll PolicyRuleMinimumProtocol = "all" + PolicyRuleMinimumProtocolIcmp PolicyRuleMinimumProtocol = "icmp" + PolicyRuleMinimumProtocolNetbirdSsh PolicyRuleMinimumProtocol = "netbird-ssh" + PolicyRuleMinimumProtocolTcp PolicyRuleMinimumProtocol = "tcp" + PolicyRuleMinimumProtocolUdp PolicyRuleMinimumProtocol = "udp" +) + +// Valid indicates whether the value is a known member of the PolicyRuleMinimumProtocol enum. +func (e PolicyRuleMinimumProtocol) Valid() bool { + switch e { + case PolicyRuleMinimumProtocolAll: + return true + case PolicyRuleMinimumProtocolIcmp: + return true + case PolicyRuleMinimumProtocolNetbirdSsh: + return true + case PolicyRuleMinimumProtocolTcp: + return true + case PolicyRuleMinimumProtocolUdp: + return true + default: + return false + } +} + +// Defines values for PolicyRuleUpdateAction. +const ( + PolicyRuleUpdateActionAccept PolicyRuleUpdateAction = "accept" + PolicyRuleUpdateActionDrop PolicyRuleUpdateAction = "drop" +) + +// Valid indicates whether the value is a known member of the PolicyRuleUpdateAction enum. +func (e PolicyRuleUpdateAction) Valid() bool { + switch e { + case PolicyRuleUpdateActionAccept: + return true + case PolicyRuleUpdateActionDrop: + return true + default: + return false + } +} + +// Defines values for PolicyRuleUpdateProtocol. +const ( + PolicyRuleUpdateProtocolAll PolicyRuleUpdateProtocol = "all" + PolicyRuleUpdateProtocolIcmp PolicyRuleUpdateProtocol = "icmp" + PolicyRuleUpdateProtocolNetbirdSsh PolicyRuleUpdateProtocol = "netbird-ssh" + PolicyRuleUpdateProtocolTcp PolicyRuleUpdateProtocol = "tcp" + PolicyRuleUpdateProtocolUdp PolicyRuleUpdateProtocol = "udp" +) + +// Valid indicates whether the value is a known member of the PolicyRuleUpdateProtocol enum. +func (e PolicyRuleUpdateProtocol) Valid() bool { + switch e { + case PolicyRuleUpdateProtocolAll: + return true + case PolicyRuleUpdateProtocolIcmp: + return true + case PolicyRuleUpdateProtocolNetbirdSsh: + return true + case PolicyRuleUpdateProtocolTcp: + return true + case PolicyRuleUpdateProtocolUdp: + return true + default: + return false + } +} + +// Defines values for ResourceType. +const ( + ResourceTypeDomain ResourceType = "domain" + ResourceTypeHost ResourceType = "host" + ResourceTypePeer ResourceType = "peer" + ResourceTypeSubnet ResourceType = "subnet" +) + +// Valid indicates whether the value is a known member of the ResourceType enum. +func (e ResourceType) Valid() bool { + switch e { + case ResourceTypeDomain: + return true + case ResourceTypeHost: + return true + case ResourceTypePeer: + return true + case ResourceTypeSubnet: + return true + default: + return false + } +} + +// Defines values for ReverseProxyDomainType. +const ( + ReverseProxyDomainTypeCustom ReverseProxyDomainType = "custom" + ReverseProxyDomainTypeFree ReverseProxyDomainType = "free" +) + +// Valid indicates whether the value is a known member of the ReverseProxyDomainType enum. +func (e ReverseProxyDomainType) Valid() bool { + switch e { + case ReverseProxyDomainTypeCustom: + return true + case ReverseProxyDomainTypeFree: + return true + default: + return false + } +} + +// Defines values for SentinelOneMatchAttributesNetworkStatus. +const ( + SentinelOneMatchAttributesNetworkStatusConnected SentinelOneMatchAttributesNetworkStatus = "connected" + SentinelOneMatchAttributesNetworkStatusDisconnected SentinelOneMatchAttributesNetworkStatus = "disconnected" + SentinelOneMatchAttributesNetworkStatusQuarantined SentinelOneMatchAttributesNetworkStatus = "quarantined" +) + +// Valid indicates whether the value is a known member of the SentinelOneMatchAttributesNetworkStatus enum. +func (e SentinelOneMatchAttributesNetworkStatus) Valid() bool { + switch e { + case SentinelOneMatchAttributesNetworkStatusConnected: + return true + case SentinelOneMatchAttributesNetworkStatusDisconnected: + return true + case SentinelOneMatchAttributesNetworkStatusQuarantined: + return true + default: + return false + } +} + +// Defines values for ServiceMode. +const ( + ServiceModeHttp ServiceMode = "http" + ServiceModeTcp ServiceMode = "tcp" + ServiceModeTls ServiceMode = "tls" + ServiceModeUdp ServiceMode = "udp" +) + +// Valid indicates whether the value is a known member of the ServiceMode enum. +func (e ServiceMode) Valid() bool { + switch e { + case ServiceModeHttp: + return true + case ServiceModeTcp: + return true + case ServiceModeTls: + return true + case ServiceModeUdp: + return true + default: + return false + } +} + +// 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" +) + +// Valid indicates whether the value is a known member of the ServiceMetaStatus enum. +func (e ServiceMetaStatus) Valid() bool { + switch e { + case ServiceMetaStatusActive: + return true + case ServiceMetaStatusCertificateFailed: + return true + case ServiceMetaStatusCertificatePending: + return true + case ServiceMetaStatusError: + return true + case ServiceMetaStatusPending: + return true + case ServiceMetaStatusTunnelNotCreated: + return true + default: + return false + } +} + +// Defines values for ServiceRequestMode. +const ( + ServiceRequestModeHttp ServiceRequestMode = "http" + ServiceRequestModeTcp ServiceRequestMode = "tcp" + ServiceRequestModeTls ServiceRequestMode = "tls" + ServiceRequestModeUdp ServiceRequestMode = "udp" +) + +// Valid indicates whether the value is a known member of the ServiceRequestMode enum. +func (e ServiceRequestMode) Valid() bool { + switch e { + case ServiceRequestModeHttp: + return true + case ServiceRequestModeTcp: + return true + case ServiceRequestModeTls: + return true + case ServiceRequestModeUdp: + return true + default: + return false + } +} + +// Defines values for ServiceTargetProtocol. +const ( + ServiceTargetProtocolHttp ServiceTargetProtocol = "http" + ServiceTargetProtocolHttps ServiceTargetProtocol = "https" + ServiceTargetProtocolTcp ServiceTargetProtocol = "tcp" + ServiceTargetProtocolUdp ServiceTargetProtocol = "udp" +) + +// Valid indicates whether the value is a known member of the ServiceTargetProtocol enum. +func (e ServiceTargetProtocol) Valid() bool { + switch e { + case ServiceTargetProtocolHttp: + return true + case ServiceTargetProtocolHttps: + return true + case ServiceTargetProtocolTcp: + return true + case ServiceTargetProtocolUdp: + return true + default: + return false + } +} + +// Defines values for ServiceTargetTargetType. +const ( + ServiceTargetTargetTypeDomain ServiceTargetTargetType = "domain" + ServiceTargetTargetTypeHost ServiceTargetTargetType = "host" + ServiceTargetTargetTypePeer ServiceTargetTargetType = "peer" + ServiceTargetTargetTypeSubnet ServiceTargetTargetType = "subnet" +) + +// Valid indicates whether the value is a known member of the ServiceTargetTargetType enum. +func (e ServiceTargetTargetType) Valid() bool { + switch e { + case ServiceTargetTargetTypeDomain: + return true + case ServiceTargetTargetTypeHost: + return true + case ServiceTargetTargetTypePeer: + return true + case ServiceTargetTargetTypeSubnet: + return true + default: + return false + } +} + +// Defines values for ServiceTargetOptionsPathRewrite. +const ( + ServiceTargetOptionsPathRewritePreserve ServiceTargetOptionsPathRewrite = "preserve" +) + +// Valid indicates whether the value is a known member of the ServiceTargetOptionsPathRewrite enum. +func (e ServiceTargetOptionsPathRewrite) Valid() bool { + switch e { + case ServiceTargetOptionsPathRewritePreserve: + return true + default: + return false + } +} + +// Defines values for TenantResponseStatus. +const ( + TenantResponseStatusActive TenantResponseStatus = "active" + TenantResponseStatusExisting TenantResponseStatus = "existing" + TenantResponseStatusInvited TenantResponseStatus = "invited" + TenantResponseStatusPending TenantResponseStatus = "pending" +) + +// Valid indicates whether the value is a known member of the TenantResponseStatus enum. +func (e TenantResponseStatus) Valid() bool { + switch e { + case TenantResponseStatusActive: + return true + case TenantResponseStatusExisting: + return true + case TenantResponseStatusInvited: + return true + case TenantResponseStatusPending: + return true + default: + return false + } +} + +// Defines values for UserStatus. +const ( + UserStatusActive UserStatus = "active" + UserStatusBlocked UserStatus = "blocked" + UserStatusInvited UserStatus = "invited" +) + +// Valid indicates whether the value is a known member of the UserStatus enum. +func (e UserStatus) Valid() bool { + switch e { + case UserStatusActive: + return true + case UserStatusBlocked: + return true + case UserStatusInvited: + return true + default: + return false + } +} + +// Defines values for WorkloadType. +const ( + WorkloadTypeBundle WorkloadType = "bundle" +) + +// Valid indicates whether the value is a known member of the WorkloadType enum. +func (e WorkloadType) Valid() bool { + switch e { + case WorkloadTypeBundle: + return true + default: + return false + } +} + +// Defines values for GetApiEventsNetworkTrafficParamsType. +const ( + GetApiEventsNetworkTrafficParamsTypeTYPEDROP GetApiEventsNetworkTrafficParamsType = "TYPE_DROP" + GetApiEventsNetworkTrafficParamsTypeTYPEEND GetApiEventsNetworkTrafficParamsType = "TYPE_END" + GetApiEventsNetworkTrafficParamsTypeTYPESTART GetApiEventsNetworkTrafficParamsType = "TYPE_START" + GetApiEventsNetworkTrafficParamsTypeTYPEUNKNOWN GetApiEventsNetworkTrafficParamsType = "TYPE_UNKNOWN" +) + +// Valid indicates whether the value is a known member of the GetApiEventsNetworkTrafficParamsType enum. +func (e GetApiEventsNetworkTrafficParamsType) Valid() bool { + switch e { + case GetApiEventsNetworkTrafficParamsTypeTYPEDROP: + return true + case GetApiEventsNetworkTrafficParamsTypeTYPEEND: + return true + case GetApiEventsNetworkTrafficParamsTypeTYPESTART: + return true + case GetApiEventsNetworkTrafficParamsTypeTYPEUNKNOWN: + return true + default: + return false + } +} + +// Defines values for GetApiEventsNetworkTrafficParamsConnectionType. +const ( + GetApiEventsNetworkTrafficParamsConnectionTypeP2P GetApiEventsNetworkTrafficParamsConnectionType = "P2P" + GetApiEventsNetworkTrafficParamsConnectionTypeROUTED GetApiEventsNetworkTrafficParamsConnectionType = "ROUTED" +) + +// Valid indicates whether the value is a known member of the GetApiEventsNetworkTrafficParamsConnectionType enum. +func (e GetApiEventsNetworkTrafficParamsConnectionType) Valid() bool { + switch e { + case GetApiEventsNetworkTrafficParamsConnectionTypeP2P: + return true + case GetApiEventsNetworkTrafficParamsConnectionTypeROUTED: + return true + default: + return false + } +} + +// Defines values for GetApiEventsNetworkTrafficParamsDirection. +const ( + GetApiEventsNetworkTrafficParamsDirectionDIRECTIONUNKNOWN GetApiEventsNetworkTrafficParamsDirection = "DIRECTION_UNKNOWN" + GetApiEventsNetworkTrafficParamsDirectionEGRESS GetApiEventsNetworkTrafficParamsDirection = "EGRESS" + GetApiEventsNetworkTrafficParamsDirectionINGRESS GetApiEventsNetworkTrafficParamsDirection = "INGRESS" +) + +// Valid indicates whether the value is a known member of the GetApiEventsNetworkTrafficParamsDirection enum. +func (e GetApiEventsNetworkTrafficParamsDirection) Valid() bool { + switch e { + case GetApiEventsNetworkTrafficParamsDirectionDIRECTIONUNKNOWN: + return true + case GetApiEventsNetworkTrafficParamsDirectionEGRESS: + return true + case GetApiEventsNetworkTrafficParamsDirectionINGRESS: + return true + default: + return false + } +} + +// Defines values for GetApiEventsProxyParamsSortBy. +const ( + GetApiEventsProxyParamsSortByAuthMethod GetApiEventsProxyParamsSortBy = "auth_method" + GetApiEventsProxyParamsSortByDuration GetApiEventsProxyParamsSortBy = "duration" + GetApiEventsProxyParamsSortByHost GetApiEventsProxyParamsSortBy = "host" + GetApiEventsProxyParamsSortByMethod GetApiEventsProxyParamsSortBy = "method" + GetApiEventsProxyParamsSortByPath GetApiEventsProxyParamsSortBy = "path" + GetApiEventsProxyParamsSortByReason GetApiEventsProxyParamsSortBy = "reason" + GetApiEventsProxyParamsSortBySourceIp GetApiEventsProxyParamsSortBy = "source_ip" + GetApiEventsProxyParamsSortByStatusCode GetApiEventsProxyParamsSortBy = "status_code" + GetApiEventsProxyParamsSortByTimestamp GetApiEventsProxyParamsSortBy = "timestamp" + GetApiEventsProxyParamsSortByUrl GetApiEventsProxyParamsSortBy = "url" + GetApiEventsProxyParamsSortByUserId GetApiEventsProxyParamsSortBy = "user_id" +) + +// Valid indicates whether the value is a known member of the GetApiEventsProxyParamsSortBy enum. +func (e GetApiEventsProxyParamsSortBy) Valid() bool { + switch e { + case GetApiEventsProxyParamsSortByAuthMethod: + return true + case GetApiEventsProxyParamsSortByDuration: + return true + case GetApiEventsProxyParamsSortByHost: + return true + case GetApiEventsProxyParamsSortByMethod: + return true + case GetApiEventsProxyParamsSortByPath: + return true + case GetApiEventsProxyParamsSortByReason: + return true + case GetApiEventsProxyParamsSortBySourceIp: + return true + case GetApiEventsProxyParamsSortByStatusCode: + return true + case GetApiEventsProxyParamsSortByTimestamp: + return true + case GetApiEventsProxyParamsSortByUrl: + return true + case GetApiEventsProxyParamsSortByUserId: + return true + default: + return false + } +} + +// Defines values for GetApiEventsProxyParamsSortOrder. +const ( + GetApiEventsProxyParamsSortOrderAsc GetApiEventsProxyParamsSortOrder = "asc" + GetApiEventsProxyParamsSortOrderDesc GetApiEventsProxyParamsSortOrder = "desc" +) + +// Valid indicates whether the value is a known member of the GetApiEventsProxyParamsSortOrder enum. +func (e GetApiEventsProxyParamsSortOrder) Valid() bool { + switch e { + case GetApiEventsProxyParamsSortOrderAsc: + return true + case GetApiEventsProxyParamsSortOrderDesc: + return true + default: + return false + } +} + +// 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" +) + +// Valid indicates whether the value is a known member of the GetApiEventsProxyParamsMethod enum. +func (e GetApiEventsProxyParamsMethod) Valid() bool { + switch e { + case GetApiEventsProxyParamsMethodDELETE: + return true + case GetApiEventsProxyParamsMethodGET: + return true + case GetApiEventsProxyParamsMethodHEAD: + return true + case GetApiEventsProxyParamsMethodOPTIONS: + return true + case GetApiEventsProxyParamsMethodPATCH: + return true + case GetApiEventsProxyParamsMethodPOST: + return true + case GetApiEventsProxyParamsMethodPUT: + return true + default: + return false + } +} + +// Defines values for GetApiEventsProxyParamsStatus. +const ( + GetApiEventsProxyParamsStatusFailed GetApiEventsProxyParamsStatus = "failed" + GetApiEventsProxyParamsStatusSuccess GetApiEventsProxyParamsStatus = "success" +) + +// Valid indicates whether the value is a known member of the GetApiEventsProxyParamsStatus enum. +func (e GetApiEventsProxyParamsStatus) Valid() bool { + switch e { + case GetApiEventsProxyParamsStatusFailed: + return true + case GetApiEventsProxyParamsStatusSuccess: + return true + default: + return false + } +} + +// Defines values for PutApiIntegrationsMspTenantsIdInviteJSONBodyValue. +const ( + PutApiIntegrationsMspTenantsIdInviteJSONBodyValueAccept PutApiIntegrationsMspTenantsIdInviteJSONBodyValue = "accept" + PutApiIntegrationsMspTenantsIdInviteJSONBodyValueDecline PutApiIntegrationsMspTenantsIdInviteJSONBodyValue = "decline" +) + +// Valid indicates whether the value is a known member of the PutApiIntegrationsMspTenantsIdInviteJSONBodyValue enum. +func (e PutApiIntegrationsMspTenantsIdInviteJSONBodyValue) Valid() bool { + switch e { + case PutApiIntegrationsMspTenantsIdInviteJSONBodyValueAccept: + return true + case PutApiIntegrationsMspTenantsIdInviteJSONBodyValueDecline: + return true + default: + return false + } +} + +// AccessRestrictions Connection-level access restrictions based on IP address or geography. Applies to both HTTP and L4 services. +type AccessRestrictions struct { + // AllowedCidrs CIDR allowlist. If non-empty, only IPs matching these CIDRs are allowed. + AllowedCidrs *[]string `json:"allowed_cidrs,omitempty"` + + // AllowedCountries ISO 3166-1 alpha-2 country codes to allow. If non-empty, only these countries are permitted. + AllowedCountries *[]string `json:"allowed_countries,omitempty"` + + // BlockedCidrs CIDR blocklist. Connections from these CIDRs are rejected. Evaluated after allowed_cidrs. + BlockedCidrs *[]string `json:"blocked_cidrs,omitempty"` + + // BlockedCountries ISO 3166-1 alpha-2 country codes to block. + BlockedCountries *[]string `json:"blocked_countries,omitempty"` + + // CrowdsecMode CrowdSec IP reputation mode. Only available when the proxy cluster supports CrowdSec. + CrowdsecMode *AccessRestrictionsCrowdsecMode `json:"crowdsec_mode,omitempty"` +} + +// AccessRestrictionsCrowdsecMode CrowdSec IP reputation mode. Only available when the proxy cluster supports CrowdSec. +type AccessRestrictionsCrowdsecMode string + +// AccessiblePeer defines model for AccessiblePeer. +type AccessiblePeer struct { + // CityName Commonly used English name of the city + CityName CityName `json:"city_name"` + + // Connected Peer to Management connection status + Connected bool `json:"connected"` + + // CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country + CountryCode CountryCode `json:"country_code"` + + // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + DnsLabel string `json:"dns_label"` + + // GeonameId Unique identifier from the GeoNames database for a specific geographical location. + GeonameId int `json:"geoname_id"` + + // Id Peer ID + Id string `json:"id"` + + // Ip Peer's IP address + Ip string `json:"ip"` + + // LastSeen Last time peer connected to Netbird's management service + LastSeen time.Time `json:"last_seen"` + + // Name Peer's hostname + Name string `json:"name"` + + // Os Peer's operating system and version + Os string `json:"os"` + + // UserId User ID of the user that enrolled this peer + UserId string `json:"user_id"` +} + +// Account defines model for Account. +type Account struct { + // CreatedAt Account creation date (UTC) + CreatedAt time.Time `json:"created_at"` + + // CreatedBy Account creator + CreatedBy string `json:"created_by"` + + // Domain Account domain + Domain string `json:"domain"` + + // DomainCategory Account domain category + DomainCategory string `json:"domain_category"` + + // Id Account ID + Id string `json:"id"` + Onboarding AccountOnboarding `json:"onboarding"` + Settings AccountSettings `json:"settings"` +} + +// AccountExtraSettings defines model for AccountExtraSettings. +type AccountExtraSettings struct { + // NetworkTrafficLogsEnabled Enables or disables network traffic logging. If enabled, all network traffic events from peers will be stored. + NetworkTrafficLogsEnabled bool `json:"network_traffic_logs_enabled"` + + // NetworkTrafficLogsGroups Limits traffic logging to these groups. If unset all peers are enabled. + NetworkTrafficLogsGroups []string `json:"network_traffic_logs_groups"` + + // NetworkTrafficPacketCounterEnabled Enables or disables network traffic packet counter. If enabled, network packets and their size will be counted and reported. (This can have an slight impact on performance) + NetworkTrafficPacketCounterEnabled bool `json:"network_traffic_packet_counter_enabled"` + + // PeerApprovalEnabled (Cloud only) Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. + PeerApprovalEnabled bool `json:"peer_approval_enabled"` + + // UserApprovalRequired Enables manual approval for new users joining via domain matching. When enabled, users are blocked with pending approval status until explicitly approved by an admin. + UserApprovalRequired bool `json:"user_approval_required"` +} + +// AccountOnboarding defines model for AccountOnboarding. +type AccountOnboarding struct { + // OnboardingFlowPending Indicates whether the account onboarding flow is pending + OnboardingFlowPending bool `json:"onboarding_flow_pending"` + + // SignupFormPending Indicates whether the account signup form is pending + SignupFormPending bool `json:"signup_form_pending"` +} + +// AccountRequest defines model for AccountRequest. +type AccountRequest struct { + Onboarding *AccountOnboarding `json:"onboarding,omitempty"` + Settings AccountSettings `json:"settings"` +} + +// AccountSettings defines model for AccountSettings. +type AccountSettings struct { + // AutoUpdateAlways When true, updates are installed automatically in the background. When false, updates require user interaction from the UI. + AutoUpdateAlways *bool `json:"auto_update_always,omitempty"` + + // AutoUpdateVersion Set Clients auto-update version. "latest", "disabled", or a specific version (e.g "0.50.1") + AutoUpdateVersion *string `json:"auto_update_version,omitempty"` + + // DnsDomain Allows to define a custom dns domain for the account + DnsDomain *string `json:"dns_domain,omitempty"` + + // EmbeddedIdpEnabled Indicates whether the embedded identity provider (Dex) is enabled for this account. This is a read-only field. + EmbeddedIdpEnabled *bool `json:"embedded_idp_enabled,omitempty"` + Extra *AccountExtraSettings `json:"extra,omitempty"` + + // GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user + GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"` + + // JwtAllowGroups List of groups to which users are allowed access + JwtAllowGroups *[]string `json:"jwt_allow_groups,omitempty"` + + // JwtGroupsClaimName Name of the claim from which we extract groups names to add it to account groups. + JwtGroupsClaimName *string `json:"jwt_groups_claim_name,omitempty"` + + // JwtGroupsEnabled Allows extract groups from JWT claim and add it to account groups. + JwtGroupsEnabled *bool `json:"jwt_groups_enabled,omitempty"` + + // LazyConnectionEnabled Enables or disables experimental lazy connection + LazyConnectionEnabled *bool `json:"lazy_connection_enabled,omitempty"` + + // LocalAuthDisabled Indicates whether local (email/password) authentication is disabled. When true, users can only authenticate via external identity providers. This is a read-only field. + LocalAuthDisabled *bool `json:"local_auth_disabled,omitempty"` + + // NetworkRange Allows to define a custom network range for the account in CIDR format + NetworkRange *string `json:"network_range,omitempty"` + + // PeerExposeEnabled Enables or disables peer expose. If enabled, peers can expose local services through the reverse proxy using the CLI. + PeerExposeEnabled bool `json:"peer_expose_enabled"` + + // PeerExposeGroups Limits which peer groups are allowed to expose services. If empty, all peers are allowed when peer expose is enabled. + PeerExposeGroups []string `json:"peer_expose_groups"` + + // PeerInactivityExpiration Period of time of inactivity after which peer session expires (seconds). + PeerInactivityExpiration int `json:"peer_inactivity_expiration"` + + // PeerInactivityExpirationEnabled Enables or disables peer inactivity expiration globally. After peer's session has expired the user has to log in (authenticate). Applies only to peers that were added by a user (interactive SSO login). + PeerInactivityExpirationEnabled bool `json:"peer_inactivity_expiration_enabled"` + + // PeerLoginExpiration Period of time after which peer login expires (seconds). + PeerLoginExpiration int `json:"peer_login_expiration"` + + // PeerLoginExpirationEnabled Enables or disables peer login expiration globally. After peer's login has expired the user has to log in (authenticate). Applies only to peers that were added by a user (interactive SSO login). + PeerLoginExpirationEnabled bool `json:"peer_login_expiration_enabled"` + + // RegularUsersViewBlocked Allows blocking regular users from viewing parts of the system. + RegularUsersViewBlocked bool `json:"regular_users_view_blocked"` + + // RoutingPeerDnsResolutionEnabled Enables or disables DNS resolution on the routing peers + RoutingPeerDnsResolutionEnabled *bool `json:"routing_peer_dns_resolution_enabled,omitempty"` +} + +// AvailablePorts defines model for AvailablePorts. +type AvailablePorts struct { + // Tcp Number of available TCP ports left on the ingress peer + Tcp int `json:"tcp"` + + // Udp Number of available UDP ports left on the ingress peer + Udp int `json:"udp"` +} + +// AzureIntegration defines model for AzureIntegration. +type AzureIntegration struct { + // ClientId Azure AD application (client) ID + ClientId string `json:"client_id"` + + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // Enabled Whether the integration is enabled + Enabled bool `json:"enabled"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes []string `json:"group_prefixes"` + + // Host Azure host domain for the Graph API + Host string `json:"host"` + + // Id The unique identifier for the integration + Id int64 `json:"id"` + + // LastSyncedAt Timestamp of the last synchronization + LastSyncedAt time.Time `json:"last_synced_at"` + + // SyncInterval Sync interval in seconds + SyncInterval int `json:"sync_interval"` + + // TenantId Azure AD tenant ID + TenantId string `json:"tenant_id"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes []string `json:"user_group_prefixes"` +} + +// 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. + Anonymize bool `json:"anonymize"` + + // BundleFor Whether to generate a bundle for the given timeframe. + BundleFor bool `json:"bundle_for"` + + // BundleForTime Time period in minutes for which to generate the bundle. + BundleForTime int `json:"bundle_for_time"` + + // LogFileCount Maximum number of log files to include in the bundle. + LogFileCount int `json:"log_file_count"` +} + +// BundleResult defines model for BundleResult. +type BundleResult struct { + UploadKey *string `json:"upload_key,omitempty"` +} + +// BundleWorkloadRequest defines model for BundleWorkloadRequest. +type BundleWorkloadRequest struct { + // Parameters These parameters control what gets included in the bundle and how it is processed. + Parameters BundleParameters `json:"parameters"` + + // Type Identifies the type of workload the job will execute. + // Currently only `"bundle"` is supported. + Type WorkloadType `json:"type"` +} + +// BundleWorkloadResponse defines model for BundleWorkloadResponse. +type BundleWorkloadResponse struct { + // Parameters These parameters control what gets included in the bundle and how it is processed. + Parameters BundleParameters `json:"parameters"` + Result BundleResult `json:"result"` + + // Type Identifies the type of workload the job will execute. + // Currently only `"bundle"` is supported. + Type WorkloadType `json:"type"` +} + +// BypassResponse Response for bypassed peer operations. +type BypassResponse struct { + // PeerId The ID of the bypassed peer. + PeerId string `json:"peer_id"` +} + +// CheckoutResponse defines model for CheckoutResponse. +type CheckoutResponse struct { + // SessionId The unique identifier for the checkout session. + SessionId string `json:"session_id"` + + // Url URL to redirect the user to the checkout session. + Url string `json:"url"` +} + +// Checks List of objects that perform the actual checks +type Checks struct { + // GeoLocationCheck Posture check for geo location + GeoLocationCheck *GeoLocationCheck `json:"geo_location_check,omitempty"` + + // NbVersionCheck Posture check for the version of operating system + NbVersionCheck *NBVersionCheck `json:"nb_version_check,omitempty"` + + // OsVersionCheck Posture check for the version of operating system + OsVersionCheck *OSVersionCheck `json:"os_version_check,omitempty"` + + // PeerNetworkRangeCheck Posture check for allow or deny access based on peer local network addresses + PeerNetworkRangeCheck *PeerNetworkRangeCheck `json:"peer_network_range_check,omitempty"` + + // ProcessCheck Posture Check for binaries exist and are running in the peer’s system + ProcessCheck *ProcessCheck `json:"process_check,omitempty"` +} + +// City Describe city geographical location information +type City struct { + // CityName Commonly used English name of the city + CityName string `json:"city_name"` + + // GeonameId Integer ID of the record in GeoNames database + GeonameId int `json:"geoname_id"` +} + +// CityName Commonly used English name of the city +type CityName = string + +// Country Describe country geographical location information +type Country struct { + // CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country + CountryCode CountryCode `json:"country_code"` + + // CountryName Commonly used English name of the country + CountryName string `json:"country_name"` +} + +// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country +type CountryCode = string + +// CreateAzureIntegrationRequest defines model for CreateAzureIntegrationRequest. +type CreateAzureIntegrationRequest struct { + // ClientId Azure AD application (client) ID + ClientId string `json:"client_id"` + + // ClientSecret Base64-encoded Azure AD client secret + ClientSecret string `json:"client_secret"` + + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes *[]string `json:"group_prefixes,omitempty"` + + // Host Azure host domain for the Graph API + Host CreateAzureIntegrationRequestHost `json:"host"` + + // SyncInterval Sync interval in seconds (minimum 300). Defaults to 300 if not specified. + SyncInterval *int `json:"sync_interval,omitempty"` + + // TenantId Azure AD tenant ID + TenantId string `json:"tenant_id"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes *[]string `json:"user_group_prefixes,omitempty"` +} + +// CreateAzureIntegrationRequestHost Azure host domain for the Graph API +type CreateAzureIntegrationRequestHost string + +// CreateGoogleIntegrationRequest defines model for CreateGoogleIntegrationRequest. +type CreateGoogleIntegrationRequest struct { + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // CustomerId Customer ID from Google Workspace Account Settings + CustomerId string `json:"customer_id"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes *[]string `json:"group_prefixes,omitempty"` + + // ServiceAccountKey Base64-encoded Google service account key + ServiceAccountKey string `json:"service_account_key"` + + // SyncInterval Sync interval in seconds (minimum 300). Defaults to 300 if not specified. + SyncInterval *int `json:"sync_interval,omitempty"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes *[]string `json:"user_group_prefixes,omitempty"` +} + +// CreateIntegrationRequest Request payload for creating a new event streaming integration. Also used as the structure for the PUT request body, but not all fields are applicable for updates (see PUT operation description). +type CreateIntegrationRequest struct { + // Config Platform-specific configuration as key-value pairs. For creation, all necessary credentials and settings must be provided. For updates, provide the fields to change or the entire new configuration. + Config map[string]string `json:"config"` + + // Enabled Specifies whether the integration is enabled. During creation (POST), this value is sent by the client, but the provided backend manager function `CreateIntegration` does not appear to use it directly, so its effect on creation should be verified. During updates (PUT), this field is used to enable or disable the integration. + Enabled bool `json:"enabled"` + + // Platform The event streaming platform to integrate with (e.g., "datadog", "s3", "firehose"). This field is used for creation. For updates (PUT), this field, if sent, is ignored by the backend. + Platform CreateIntegrationRequestPlatform `json:"platform"` +} + +// CreateIntegrationRequestPlatform The event streaming platform to integrate with (e.g., "datadog", "s3", "firehose"). This field is used for creation. For updates (PUT), this field, if sent, is ignored by the backend. +type CreateIntegrationRequestPlatform string + +// CreateOktaScimIntegrationRequest defines model for CreateOktaScimIntegrationRequest. +type CreateOktaScimIntegrationRequest struct { + // ConnectionName The Okta enterprise connection name on Auth0 + ConnectionName string `json:"connection_name"` + + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes *[]string `json:"group_prefixes,omitempty"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes *[]string `json:"user_group_prefixes,omitempty"` +} + +// CreateScimIntegrationRequest defines model for CreateScimIntegrationRequest. +type CreateScimIntegrationRequest struct { + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes *[]string `json:"group_prefixes,omitempty"` + + // Prefix The connection prefix used for the SCIM provider + Prefix string `json:"prefix"` + + // Provider Name of the SCIM identity provider + Provider string `json:"provider"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes *[]string `json:"user_group_prefixes,omitempty"` +} + +// CreateSetupKeyRequest defines model for CreateSetupKeyRequest. +type CreateSetupKeyRequest struct { + // AllowExtraDnsLabels Allow extra DNS labels to be added to the peer + AllowExtraDnsLabels *bool `json:"allow_extra_dns_labels,omitempty"` + + // AutoGroups List of group IDs to auto-assign to peers registered with this key + AutoGroups []string `json:"auto_groups"` + + // Ephemeral Indicate that the peer will be ephemeral or not + Ephemeral *bool `json:"ephemeral,omitempty"` + + // ExpiresIn Expiration time in seconds + ExpiresIn int `json:"expires_in"` + + // Name Setup Key name + Name string `json:"name"` + + // Type Setup key type, one-off for single time usage and reusable + Type string `json:"type"` + + // UsageLimit A number of times this key can be used. The value of 0 indicates the unlimited usage. + UsageLimit int `json:"usage_limit"` +} + +// CreateTenantRequest defines model for CreateTenantRequest. +type CreateTenantRequest struct { + // Domain The name for the MSP tenant + Domain string `json:"domain"` + + // Groups MSP users Groups that can access the Tenant and Roles to assume + Groups []TenantGroupResponse `json:"groups"` + + // Name The name for the MSP tenant + Name string `json:"name"` +} + +// DNSChallengeResponse defines model for DNSChallengeResponse. +type DNSChallengeResponse struct { + // DnsChallenge The DNS challenge to set in a TXT record + DnsChallenge string `json:"dns_challenge"` +} + +// DNSRecord defines model for DNSRecord. +type DNSRecord struct { + // Content DNS record content (IP address for A/AAAA, domain for CNAME) + Content string `json:"content"` + + // Id DNS record ID + Id string `json:"id"` + + // Name FQDN for the DNS record. Must be a subdomain within or match the zone's domain. + Name string `json:"name"` + + // Ttl Time to live in seconds + Ttl int `json:"ttl"` + + // Type DNS record type + Type DNSRecordType `json:"type"` +} + +// DNSRecordRequest defines model for DNSRecordRequest. +type DNSRecordRequest struct { + // Content DNS record content (IP address for A/AAAA, domain for CNAME) + Content string `json:"content"` + + // Name FQDN for the DNS record. Must be a subdomain within or match the zone's domain. + Name string `json:"name"` + + // Ttl Time to live in seconds + Ttl int `json:"ttl"` + + // Type DNS record type + Type DNSRecordType `json:"type"` +} + +// DNSRecordType DNS record type +type DNSRecordType string + +// DNSSettings defines model for DNSSettings. +type DNSSettings struct { + // DisabledManagementGroups Groups whose DNS management is disabled + DisabledManagementGroups []string `json:"disabled_management_groups"` +} + +// EDRFalconRequest Request payload for creating or updating a EDR Falcon integration +type EDRFalconRequest struct { + // ClientId CrowdStrike API client ID + ClientId string `json:"client_id"` + + // CloudId CrowdStrike cloud identifier (e.g., "us-1", "us-2", "eu-1") + CloudId string `json:"cloud_id"` + + // Enabled Indicates whether the integration is enabled + Enabled *bool `json:"enabled,omitempty"` + + // Groups The Groups this integration applies to + Groups []string `json:"groups"` + + // Secret CrowdStrike API client secret + Secret string `json:"secret"` + + // ZtaScoreThreshold The minimum Zero Trust Assessment score required for agent approval (0-100) + ZtaScoreThreshold int `json:"zta_score_threshold"` +} + +// EDRFalconResponse Represents a Falcon EDR integration +type EDRFalconResponse struct { + // AccountId The identifier of the account this integration belongs to. + AccountId string `json:"account_id"` + + // CloudId CrowdStrike cloud identifier + CloudId string `json:"cloud_id"` + + // CreatedAt Timestamp of when the integration was created. + CreatedAt time.Time `json:"created_at"` + + // CreatedBy The user id that created the integration + CreatedBy string `json:"created_by"` + + // Enabled Indicates whether the integration is enabled + Enabled bool `json:"enabled"` + + // Groups List of groups + Groups []Group `json:"groups"` + + // Id The unique numeric identifier for the integration. + Id int64 `json:"id"` + + // LastSyncedAt Timestamp of when the integration was last synced. + LastSyncedAt time.Time `json:"last_synced_at"` + + // UpdatedAt Timestamp of when the integration was last updated. + UpdatedAt time.Time `json:"updated_at"` + + // ZtaScoreThreshold The minimum Zero Trust Assessment score required for agent approval (0-100) + ZtaScoreThreshold int `json:"zta_score_threshold"` +} + +// EDRFleetDMRequest Request payload for creating or updating a FleetDM EDR integration +type EDRFleetDMRequest struct { + // ApiToken FleetDM API token + ApiToken string `json:"api_token"` + + // ApiUrl FleetDM server URL + ApiUrl string `json:"api_url"` + + // Enabled Indicates whether the integration is enabled + Enabled *bool `json:"enabled,omitempty"` + + // Groups The Groups this integrations applies to + Groups []string `json:"groups"` + + // LastSyncedInterval The devices last sync requirement interval in hours. Minimum value is 24 hours + LastSyncedInterval int `json:"last_synced_interval"` + + // MatchAttributes Attribute conditions to match when approving FleetDM hosts. Most attributes work with FleetDM's free/open-source version. Premium-only attributes are marked accordingly + MatchAttributes FleetDMMatchAttributes `json:"match_attributes"` +} + +// EDRFleetDMResponse Represents a FleetDM EDR integration configuration +type EDRFleetDMResponse struct { + // AccountId The identifier of the account this integration belongs to. + AccountId string `json:"account_id"` + + // ApiUrl FleetDM server URL + ApiUrl string `json:"api_url"` + + // CreatedAt Timestamp of when the integration was created. + CreatedAt time.Time `json:"created_at"` + + // CreatedBy The user id that created the integration + CreatedBy string `json:"created_by"` + + // Enabled Indicates whether the integration is enabled + Enabled bool `json:"enabled"` + + // Groups List of groups + Groups []Group `json:"groups"` + + // Id The unique numeric identifier for the integration. + Id int64 `json:"id"` + + // LastSyncedAt Timestamp of when the integration was last synced. + LastSyncedAt time.Time `json:"last_synced_at"` + + // LastSyncedInterval The devices last sync requirement interval in hours. + LastSyncedInterval int `json:"last_synced_interval"` + + // MatchAttributes Attribute conditions to match when approving FleetDM hosts. Most attributes work with FleetDM's free/open-source version. Premium-only attributes are marked accordingly + MatchAttributes FleetDMMatchAttributes `json:"match_attributes"` + + // UpdatedAt Timestamp of when the integration was last updated. + UpdatedAt time.Time `json:"updated_at"` +} + +// EDRHuntressRequest Request payload for creating or updating a EDR Huntress integration +type EDRHuntressRequest struct { + // ApiKey Huntress API key + ApiKey string `json:"api_key"` + + // ApiSecret Huntress API secret + ApiSecret string `json:"api_secret"` + + // Enabled Indicates whether the integration is enabled + Enabled *bool `json:"enabled,omitempty"` + + // Groups The Groups this integrations applies to + Groups []string `json:"groups"` + + // LastSyncedInterval The devices last sync requirement interval in hours. Minimum value is 24 hours + LastSyncedInterval int `json:"last_synced_interval"` + + // MatchAttributes Attribute conditions to match when approving agents + MatchAttributes HuntressMatchAttributes `json:"match_attributes"` +} + +// EDRHuntressResponse Represents a Huntress EDR integration configuration +type EDRHuntressResponse struct { + // AccountId The identifier of the account this integration belongs to. + AccountId string `json:"account_id"` + + // CreatedAt Timestamp of when the integration was created. + CreatedAt time.Time `json:"created_at"` + + // CreatedBy The user id that created the integration + CreatedBy string `json:"created_by"` + + // Enabled Indicates whether the integration is enabled + Enabled bool `json:"enabled"` + + // Groups List of groups + Groups []Group `json:"groups"` + + // Id The unique numeric identifier for the integration. + Id int64 `json:"id"` + + // LastSyncedAt Timestamp of when the integration was last synced. + LastSyncedAt time.Time `json:"last_synced_at"` + + // LastSyncedInterval The devices last sync requirement interval in hours. + LastSyncedInterval int `json:"last_synced_interval"` + + // MatchAttributes Attribute conditions to match when approving agents + MatchAttributes HuntressMatchAttributes `json:"match_attributes"` + + // UpdatedAt Timestamp of when the integration was last updated. + UpdatedAt time.Time `json:"updated_at"` +} + +// EDRIntuneRequest Request payload for creating or updating a EDR Intune integration. +type EDRIntuneRequest struct { + // ClientId The Azure application client id + ClientId string `json:"client_id"` + + // Enabled Indicates whether the integration is enabled + Enabled *bool `json:"enabled,omitempty"` + + // Groups The Groups this integrations applies to + Groups []string `json:"groups"` + + // LastSyncedInterval The devices last sync requirement interval in hours. Minimum value is 24 hours. + LastSyncedInterval int `json:"last_synced_interval"` + + // Secret The Azure application client secret + Secret string `json:"secret"` + + // TenantId The Azure tenant id + TenantId string `json:"tenant_id"` +} + +// EDRIntuneResponse Represents a Intune EDR integration configuration +type EDRIntuneResponse struct { + // AccountId The identifier of the account this integration belongs to. + AccountId string `json:"account_id"` + + // ClientId The Azure application client id + ClientId string `json:"client_id"` + + // CreatedAt Timestamp of when the integration was created. + CreatedAt time.Time `json:"created_at"` + + // CreatedBy The user id that created the integration + CreatedBy string `json:"created_by"` + + // Enabled Indicates whether the integration is enabled + Enabled bool `json:"enabled"` + + // Groups List of groups + Groups []Group `json:"groups"` + + // Id The unique numeric identifier for the integration. + Id int64 `json:"id"` + + // LastSyncedAt Timestamp of when the integration was last synced. + LastSyncedAt time.Time `json:"last_synced_at"` + + // LastSyncedInterval The devices last sync requirement interval in hours. + LastSyncedInterval int `json:"last_synced_interval"` + + // TenantId The Azure tenant id + TenantId string `json:"tenant_id"` + + // UpdatedAt Timestamp of when the integration was last updated. + UpdatedAt time.Time `json:"updated_at"` +} + +// EDRSentinelOneRequest Request payload for creating or updating a EDR SentinelOne integration +type EDRSentinelOneRequest struct { + // ApiToken SentinelOne API token + ApiToken string `json:"api_token"` + + // ApiUrl The Base URL of SentinelOne API + ApiUrl string `json:"api_url"` + + // Enabled Indicates whether the integration is enabled + Enabled *bool `json:"enabled,omitempty"` + + // Groups The Groups this integrations applies to + Groups []string `json:"groups"` + + // LastSyncedInterval The devices last sync requirement interval in hours. Minimum value is 24 hours. + LastSyncedInterval int `json:"last_synced_interval"` + + // MatchAttributes Attribute conditions to match when approving agents + MatchAttributes SentinelOneMatchAttributes `json:"match_attributes"` +} + +// EDRSentinelOneResponse Represents a SentinelOne EDR integration configuration +type EDRSentinelOneResponse struct { + // AccountId The identifier of the account this integration belongs to. + AccountId string `json:"account_id"` + + // ApiUrl The Base URL of SentinelOne API + ApiUrl string `json:"api_url"` + + // CreatedAt Timestamp of when the integration was created. + CreatedAt time.Time `json:"created_at"` + + // CreatedBy The user id that created the integration + CreatedBy string `json:"created_by"` + + // Enabled Indicates whether the integration is enabled + Enabled bool `json:"enabled"` + + // Groups List of groups + Groups []Group `json:"groups"` + + // Id The unique numeric identifier for the integration. + Id int64 `json:"id"` + + // LastSyncedAt Timestamp of when the integration was last synced. + LastSyncedAt time.Time `json:"last_synced_at"` + + // LastSyncedInterval The devices last sync requirement interval in hours. + LastSyncedInterval int `json:"last_synced_interval"` + + // MatchAttributes Attribute conditions to match when approving agents + MatchAttributes SentinelOneMatchAttributes `json:"match_attributes"` + + // UpdatedAt Timestamp of when the integration was last updated. + UpdatedAt time.Time `json:"updated_at"` +} + +// EmailTarget Target configuration for email notification channels. +type EmailTarget struct { + // Emails List of email addresses to send notifications to. + Emails []openapi_types.Email `json:"emails"` +} + +// ErrorResponse Standard error response. Note: The exact structure of this error response is inferred from `util.WriteErrorResponse` and `util.WriteError` usage in the provided Go code, as a specific Go struct for errors was not provided. +type ErrorResponse struct { + // Message A human-readable error message. + Message *string `json:"message,omitempty"` +} + +// Event defines model for Event. +type Event struct { + // Activity The activity that occurred during the event + Activity string `json:"activity"` + + // ActivityCode The string code of the activity that occurred during the event + ActivityCode EventActivityCode `json:"activity_code"` + + // Id Event unique identifier + Id string `json:"id"` + + // InitiatorEmail The e-mail address of the initiator of the event. E.g., an e-mail of a user that triggered the event. + InitiatorEmail string `json:"initiator_email"` + + // InitiatorId The ID of the initiator of the event. E.g., an ID of a user that triggered the event. + InitiatorId string `json:"initiator_id"` + + // InitiatorName The name of the initiator of the event. + InitiatorName string `json:"initiator_name"` + + // Meta The metadata of the event + Meta map[string]string `json:"meta"` + + // TargetId The ID of the target of the event. E.g., an ID of the peer that a user removed. + TargetId string `json:"target_id"` + + // Timestamp The date and time when the event occurred + Timestamp time.Time `json:"timestamp"` +} + +// EventActivityCode The string code of the activity that occurred during the event +type EventActivityCode string + +// FleetDMMatchAttributes Attribute conditions to match when approving FleetDM hosts. Most attributes work with FleetDM's free/open-source version. Premium-only attributes are marked accordingly +type FleetDMMatchAttributes struct { + // DiskEncryptionEnabled Whether disk encryption (FileVault/BitLocker) must be enabled on the host + DiskEncryptionEnabled *bool `json:"disk_encryption_enabled,omitempty"` + + // FailingPoliciesCountMax Maximum number of allowed failing policies. Use 0 to require all policies to pass + FailingPoliciesCountMax *int `json:"failing_policies_count_max,omitempty"` + + // RequiredPolicies List of FleetDM policy IDs that must be passing on the host. If any of these policies is failing, the host is non-compliant + RequiredPolicies *[]int `json:"required_policies,omitempty"` + + // StatusOnline Whether the host must be online (recently seen by Fleet) + StatusOnline *bool `json:"status_online,omitempty"` + + // VulnerableSoftwareCountMax Maximum number of allowed vulnerable software on the host + VulnerableSoftwareCountMax *int `json:"vulnerable_software_count_max,omitempty"` +} + +// GeoLocationCheck Posture check for geo location +type GeoLocationCheck struct { + // Action Action to take upon policy match + Action GeoLocationCheckAction `json:"action"` + + // Locations List of geo locations to which the policy applies + Locations []Location `json:"locations"` +} + +// GeoLocationCheckAction Action to take upon policy match +type GeoLocationCheckAction string + +// GetTenantsResponse defines model for GetTenantsResponse. +type GetTenantsResponse = []TenantResponse + +// GoogleIntegration defines model for GoogleIntegration. +type GoogleIntegration struct { + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // CustomerId Customer ID from Google Workspace + CustomerId string `json:"customer_id"` + + // Enabled Whether the integration is enabled + Enabled bool `json:"enabled"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes []string `json:"group_prefixes"` + + // Id The unique identifier for the integration + Id int64 `json:"id"` + + // LastSyncedAt Timestamp of the last synchronization + LastSyncedAt time.Time `json:"last_synced_at"` + + // SyncInterval Sync interval in seconds + SyncInterval int `json:"sync_interval"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes []string `json:"user_group_prefixes"` +} + +// Group defines model for Group. +type Group struct { + // Id Group ID + Id string `json:"id"` + + // Issued How the group was issued (api, integration, jwt) + Issued *GroupIssued `json:"issued,omitempty"` + + // Name Group Name identifier + Name string `json:"name"` + + // Peers List of peers object + Peers []PeerMinimum `json:"peers"` + + // PeersCount Count of peers associated to the group + PeersCount int `json:"peers_count"` + Resources []Resource `json:"resources"` + + // ResourcesCount Count of resources associated to the group + ResourcesCount int `json:"resources_count"` +} + +// GroupIssued How the group was issued (api, integration, jwt) +type GroupIssued string + +// GroupMinimum defines model for GroupMinimum. +type GroupMinimum struct { + // Id Group ID + Id string `json:"id"` + + // Issued How the group was issued (api, integration, jwt) + Issued *GroupMinimumIssued `json:"issued,omitempty"` + + // Name Group Name identifier + Name string `json:"name"` + + // PeersCount Count of peers associated to the group + PeersCount int `json:"peers_count"` + + // ResourcesCount Count of resources associated to the group + ResourcesCount int `json:"resources_count"` +} + +// GroupMinimumIssued How the group was issued (api, integration, jwt) +type GroupMinimumIssued string + +// GroupRequest defines model for GroupRequest. +type GroupRequest struct { + // Name Group name identifier + Name string `json:"name"` + + // Peers List of peers ids + Peers *[]string `json:"peers,omitempty"` + Resources *[]Resource `json:"resources,omitempty"` +} + +// HeaderAuthConfig Static header-value authentication. The proxy checks that the named header matches the configured value. +type HeaderAuthConfig struct { + // Enabled Whether header auth is enabled + Enabled bool `json:"enabled"` + + // Header HTTP header name to check (e.g. "Authorization", "X-API-Key") + Header string `json:"header"` + + // Value Expected header value. For Basic auth use "Basic base64(user:pass)". For Bearer use "Bearer token". Cleared in responses. + Value string `json:"value"` +} + +// HuntressMatchAttributes Attribute conditions to match when approving agents +type HuntressMatchAttributes struct { + // DefenderPolicyStatus Policy status of Defender AV for Managed Antivirus. + DefenderPolicyStatus *string `json:"defender_policy_status,omitempty"` + + // DefenderStatus Status of Defender AV Managed Antivirus. + DefenderStatus *string `json:"defender_status,omitempty"` + + // DefenderSubstatus Sub-status of Defender AV Managed Antivirus. + DefenderSubstatus *string `json:"defender_substatus,omitempty"` + + // FirewallStatus Status of agent firewall. Can be one of Disabled, Enabled, Pending Isolation, Isolated, Pending Release. + FirewallStatus *string `json:"firewall_status,omitempty"` +} + +// IdentityProvider defines model for IdentityProvider. +type IdentityProvider struct { + // ClientId OAuth2 client ID + ClientId string `json:"client_id"` + + // Id Identity provider ID + Id *string `json:"id,omitempty"` + + // Issuer OIDC issuer URL + Issuer string `json:"issuer"` + + // Name Human-readable name for the identity provider + Name string `json:"name"` + + // Type Type of identity provider + Type IdentityProviderType `json:"type"` +} + +// IdentityProviderRequest defines model for IdentityProviderRequest. +type IdentityProviderRequest struct { + // ClientId OAuth2 client ID + ClientId string `json:"client_id"` + + // ClientSecret OAuth2 client secret + ClientSecret string `json:"client_secret"` + + // Issuer OIDC issuer URL + Issuer string `json:"issuer"` + + // Name Human-readable name for the identity provider + Name string `json:"name"` + + // Type Type of identity provider + Type IdentityProviderType `json:"type"` +} + +// IdentityProviderType Type of identity provider +type IdentityProviderType string + +// IdpIntegrationSyncLog Represents a synchronization log entry for an integration +type IdpIntegrationSyncLog struct { + // Id The unique identifier for the sync log + Id int64 `json:"id"` + + // Level The log level + Level string `json:"level"` + + // Message Log message + Message string `json:"message"` + + // Timestamp Timestamp of when the log was created + Timestamp time.Time `json:"timestamp"` +} + +// IngressPeer defines model for IngressPeer. +type IngressPeer struct { + AvailablePorts AvailablePorts `json:"available_ports"` + + // Connected Indicates if an ingress peer is connected to the management server + Connected bool `json:"connected"` + + // Enabled Indicates if an ingress peer is enabled + Enabled bool `json:"enabled"` + + // Fallback Indicates if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer + Fallback bool `json:"fallback"` + + // Id ID of the ingress peer + Id string `json:"id"` + + // IngressIp Ingress IP address of the ingress peer where the traffic arrives + IngressIp string `json:"ingress_ip"` + + // PeerId ID of the peer that is used as an ingress peer + PeerId string `json:"peer_id"` + + // Region Region of the ingress peer + Region string `json:"region"` +} + +// IngressPeerCreateRequest defines model for IngressPeerCreateRequest. +type IngressPeerCreateRequest struct { + // Enabled Defines if an ingress peer is enabled + Enabled bool `json:"enabled"` + + // Fallback Defines if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer + Fallback bool `json:"fallback"` + + // PeerId ID of the peer that is used as an ingress peer + PeerId string `json:"peer_id"` +} + +// IngressPeerUpdateRequest defines model for IngressPeerUpdateRequest. +type IngressPeerUpdateRequest struct { + // Enabled Defines if an ingress peer is enabled + Enabled bool `json:"enabled"` + + // Fallback Defines if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer + Fallback bool `json:"fallback"` +} + +// IngressPortAllocation defines model for IngressPortAllocation. +type IngressPortAllocation struct { + // Enabled Indicates if an ingress port allocation is enabled + Enabled bool `json:"enabled"` + + // Id ID of the ingress port allocation + Id string `json:"id"` + + // IngressIp Ingress IP address of the ingress peer where the traffic arrives + IngressIp string `json:"ingress_ip"` + + // IngressPeerId ID of the ingress peer that forwards the ports + IngressPeerId string `json:"ingress_peer_id"` + + // Name Name of the ingress port allocation + Name string `json:"name"` + + // PortRangeMappings List of port ranges that are allowed to be used by the ingress peer + PortRangeMappings []IngressPortAllocationPortMapping `json:"port_range_mappings"` + + // Region Region of the ingress peer + Region string `json:"region"` +} + +// IngressPortAllocationPortMapping defines model for IngressPortAllocationPortMapping. +type IngressPortAllocationPortMapping struct { + // IngressEnd The ending port of the range of ingress ports mapped to the forwarded ports + IngressEnd int `json:"ingress_end"` + + // IngressStart The starting port of the range of ingress ports mapped to the forwarded ports + IngressStart int `json:"ingress_start"` + + // Protocol Protocol accepted by the ports + Protocol IngressPortAllocationPortMappingProtocol `json:"protocol"` + + // TranslatedEnd The ending port of the translated range of forwarded ports + TranslatedEnd int `json:"translated_end"` + + // TranslatedStart The starting port of the translated range of forwarded ports + TranslatedStart int `json:"translated_start"` +} + +// IngressPortAllocationPortMappingProtocol Protocol accepted by the ports +type IngressPortAllocationPortMappingProtocol string + +// IngressPortAllocationRequest defines model for IngressPortAllocationRequest. +type IngressPortAllocationRequest struct { + DirectPort *IngressPortAllocationRequestDirectPort `json:"direct_port,omitempty"` + + // Enabled Indicates if an ingress port allocation is enabled + Enabled bool `json:"enabled"` + + // Name Name of the ingress port allocation + Name string `json:"name"` + + // PortRanges List of port ranges that are forwarded by the ingress peer + PortRanges *[]IngressPortAllocationRequestPortRange `json:"port_ranges,omitempty"` +} + +// IngressPortAllocationRequestDirectPort defines model for IngressPortAllocationRequestDirectPort. +type IngressPortAllocationRequestDirectPort struct { + // Count The number of ports to be forwarded + Count int `json:"count"` + + // Protocol The protocol accepted by the port + Protocol IngressPortAllocationRequestDirectPortProtocol `json:"protocol"` +} + +// IngressPortAllocationRequestDirectPortProtocol The protocol accepted by the port +type IngressPortAllocationRequestDirectPortProtocol string + +// IngressPortAllocationRequestPortRange defines model for IngressPortAllocationRequestPortRange. +type IngressPortAllocationRequestPortRange struct { + // End The ending port of the range of forwarded ports + End int `json:"end"` + + // Protocol The protocol accepted by the port range + Protocol IngressPortAllocationRequestPortRangeProtocol `json:"protocol"` + + // Start The starting port of the range of forwarded ports + Start int `json:"start"` +} + +// IngressPortAllocationRequestPortRangeProtocol The protocol accepted by the port range +type IngressPortAllocationRequestPortRangeProtocol string + +// InstanceStatus Instance status information +type InstanceStatus struct { + // SetupRequired Indicates whether the instance requires initial setup + SetupRequired bool `json:"setup_required"` +} + +// InstanceVersionInfo Version information for NetBird components +type InstanceVersionInfo struct { + // DashboardAvailableVersion The latest available version of the dashboard (from GitHub releases) + DashboardAvailableVersion *string `json:"dashboard_available_version,omitempty"` + + // ManagementAvailableVersion The latest available version of the management server (from GitHub releases) + ManagementAvailableVersion *string `json:"management_available_version,omitempty"` + + // ManagementCurrentVersion The current running version of the management server + ManagementCurrentVersion string `json:"management_current_version"` + + // ManagementUpdateAvailable Indicates if a newer management version is available + ManagementUpdateAvailable bool `json:"management_update_available"` +} + +// IntegrationEnabled defines model for IntegrationEnabled. +type IntegrationEnabled struct { + // Enabled Whether the integration is enabled + Enabled *bool `json:"enabled,omitempty"` +} + +// IntegrationResponse Represents an event streaming integration. +type IntegrationResponse struct { + // AccountId The identifier of the account this integration belongs to. + AccountId *string `json:"account_id,omitempty"` + + // Config Configuration for the integration. Sensitive keys (like API keys, secret keys) are masked with '****' in responses, as indicated by the GetIntegration handler logic. + Config *map[string]string `json:"config,omitempty"` + + // CreatedAt Timestamp of when the integration was created. + CreatedAt *time.Time `json:"created_at,omitempty"` + + // Enabled Whether the integration is currently active. + Enabled *bool `json:"enabled,omitempty"` + + // Id The unique numeric identifier for the integration. + Id *int64 `json:"id,omitempty"` + + // Platform The event streaming platform. + Platform *IntegrationResponsePlatform `json:"platform,omitempty"` + + // UpdatedAt Timestamp of when the integration was last updated. + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +// IntegrationResponsePlatform The event streaming platform. +type IntegrationResponsePlatform string + +// IntegrationSyncFilters defines model for IntegrationSyncFilters. +type IntegrationSyncFilters struct { + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes *[]string `json:"group_prefixes,omitempty"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes *[]string `json:"user_group_prefixes,omitempty"` +} + +// InvoicePDFResponse defines model for InvoicePDFResponse. +type InvoicePDFResponse struct { + // Url URL to redirect the user to invoice. + Url string `json:"url"` +} + +// InvoiceResponse defines model for InvoiceResponse. +type InvoiceResponse struct { + // Id The Stripe invoice id + Id string `json:"id"` + + // PeriodEnd The end date of the invoice period. + PeriodEnd time.Time `json:"period_end"` + + // PeriodStart The start date of the invoice period. + PeriodStart time.Time `json:"period_start"` + + // Type The invoice type + Type InvoiceResponseType `json:"type"` +} + +// InvoiceResponseType The invoice type +type InvoiceResponseType string + +// JobRequest defines model for JobRequest. +type JobRequest struct { + Workload WorkloadRequest `json:"workload"` +} + +// JobResponse defines model for JobResponse. +type JobResponse struct { + CompletedAt *time.Time `json:"completed_at,omitempty"` + CreatedAt time.Time `json:"created_at"` + FailedReason *string `json:"failed_reason,omitempty"` + Id string `json:"id"` + Status JobResponseStatus `json:"status"` + TriggeredBy string `json:"triggered_by"` + Workload WorkloadResponse `json:"workload"` +} + +// 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 + CityName *CityName `json:"city_name,omitempty"` + + // CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country + CountryCode CountryCode `json:"country_code"` +} + +// MinKernelVersionCheck Posture check with the kernel version +type MinKernelVersionCheck struct { + // MinKernelVersion Minimum acceptable version + MinKernelVersion string `json:"min_kernel_version"` +} + +// MinVersionCheck Posture check for the version of operating system +type MinVersionCheck struct { + // MinVersion Minimum acceptable version + MinVersion string `json:"min_version"` +} + +// NBVersionCheck Posture check for the version of operating system +type NBVersionCheck = MinVersionCheck + +// Nameserver defines model for Nameserver. +type Nameserver struct { + // Ip Nameserver IP + Ip string `json:"ip"` + + // NsType Nameserver Type + NsType NameserverNsType `json:"ns_type"` + + // Port Nameserver Port + Port int `json:"port"` +} + +// NameserverNsType Nameserver Type +type NameserverNsType string + +// NameserverGroup defines model for NameserverGroup. +type NameserverGroup struct { + // Description Description of the nameserver group + Description string `json:"description"` + + // Domains Match domain list. It should be empty only if primary is true. + Domains []string `json:"domains"` + + // Enabled Nameserver group status + Enabled bool `json:"enabled"` + + // Groups Distribution group IDs that defines group of peers that will use this nameserver group + Groups []string `json:"groups"` + + // Id Nameserver group ID + Id string `json:"id"` + + // Name Name of nameserver group name + Name string `json:"name"` + + // Nameservers Nameserver list + Nameservers []Nameserver `json:"nameservers"` + + // Primary Defines if a nameserver group is primary that resolves all domains. It should be true only if domains list is empty. + Primary bool `json:"primary"` + + // SearchDomainsEnabled Search domain status for match domains. It should be true only if domains list is not empty. + SearchDomainsEnabled bool `json:"search_domains_enabled"` +} + +// NameserverGroupRequest defines model for NameserverGroupRequest. +type NameserverGroupRequest struct { + // Description Description of the nameserver group + Description string `json:"description"` + + // Domains Match domain list. It should be empty only if primary is true. + Domains []string `json:"domains"` + + // Enabled Nameserver group status + Enabled bool `json:"enabled"` + + // Groups Distribution group IDs that defines group of peers that will use this nameserver group + Groups []string `json:"groups"` + + // Name Name of nameserver group name + Name string `json:"name"` + + // Nameservers Nameserver list + Nameservers []Nameserver `json:"nameservers"` + + // Primary Defines if a nameserver group is primary that resolves all domains. It should be true only if domains list is empty. + Primary bool `json:"primary"` + + // SearchDomainsEnabled Search domain status for match domains. It should be true only if domains list is not empty. + SearchDomainsEnabled bool `json:"search_domains_enabled"` +} + +// Network defines model for Network. +type Network struct { + // Description Network description + Description *string `json:"description,omitempty"` + + // Id Network ID + Id string `json:"id"` + + // Name Network name + Name string `json:"name"` + + // Policies List of policy IDs associated with the network + Policies []string `json:"policies"` + + // Resources List of network resource IDs associated with the network + Resources []string `json:"resources"` + + // Routers List of router IDs associated with the network + Routers []string `json:"routers"` + + // RoutingPeersCount Count of routing peers associated with the network + RoutingPeersCount int `json:"routing_peers_count"` +} + +// NetworkRequest defines model for NetworkRequest. +type NetworkRequest struct { + // Description Network description + Description *string `json:"description,omitempty"` + + // Name Network name + Name string `json:"name"` +} + +// NetworkResource defines model for NetworkResource. +type NetworkResource struct { + // Address Network resource address (either a direct host like 1.1.1.1 or 1.1.1.1/32, or a subnet like 192.168.178.0/24, or domains like example.com and *.example.com) + Address string `json:"address"` + + // Description Network resource description + Description *string `json:"description,omitempty"` + + // Enabled Network resource status + Enabled bool `json:"enabled"` + + // Groups Groups that the resource belongs to + Groups []GroupMinimum `json:"groups"` + + // Id Network Resource ID + Id string `json:"id"` + + // Name Network resource name + Name string `json:"name"` + + // Type Network resource type based of the address + Type NetworkResourceType `json:"type"` +} + +// NetworkResourceMinimum defines model for NetworkResourceMinimum. +type NetworkResourceMinimum struct { + // Address Network resource address (either a direct host like 1.1.1.1 or 1.1.1.1/32, or a subnet like 192.168.178.0/24, or domains like example.com and *.example.com) + Address string `json:"address"` + + // Description Network resource description + Description *string `json:"description,omitempty"` + + // Enabled Network resource status + Enabled bool `json:"enabled"` + + // Name Network resource name + Name string `json:"name"` +} + +// NetworkResourceRequest defines model for NetworkResourceRequest. +type NetworkResourceRequest struct { + // Address Network resource address (either a direct host like 1.1.1.1 or 1.1.1.1/32, or a subnet like 192.168.178.0/24, or domains like example.com and *.example.com) + Address string `json:"address"` + + // Description Network resource description + Description *string `json:"description,omitempty"` + + // Enabled Network resource status + Enabled bool `json:"enabled"` + + // Groups Group IDs containing the resource + Groups []string `json:"groups"` + + // Name Network resource name + Name string `json:"name"` +} + +// NetworkResourceType Network resource type based of the address +type NetworkResourceType string + +// NetworkRouter defines model for NetworkRouter. +type NetworkRouter struct { + // Enabled Network router status + Enabled bool `json:"enabled"` + + // Id Network Router Id + Id string `json:"id"` + + // Masquerade Indicate if peer should masquerade traffic to this route's prefix + Masquerade bool `json:"masquerade"` + + // Metric Route metric number. Lowest number has higher priority + Metric int `json:"metric"` + + // Peer Peer Identifier associated with route. This property can not be set together with `peer_groups` + Peer *string `json:"peer,omitempty"` + + // PeerGroups Peers Group Identifier associated with route. This property can not be set together with `peer` + PeerGroups *[]string `json:"peer_groups,omitempty"` +} + +// NetworkRouterRequest defines model for NetworkRouterRequest. +type NetworkRouterRequest struct { + // Enabled Network router status + Enabled bool `json:"enabled"` + + // Masquerade Indicate if peer should masquerade traffic to this route's prefix + Masquerade bool `json:"masquerade"` + + // Metric Route metric number. Lowest number has higher priority + Metric int `json:"metric"` + + // Peer Peer Identifier associated with route. This property can not be set together with `peer_groups` + Peer *string `json:"peer,omitempty"` + + // PeerGroups Peers Group Identifier associated with route. This property can not be set together with `peer` + PeerGroups *[]string `json:"peer_groups,omitempty"` +} + +// NetworkTrafficEndpoint defines model for NetworkTrafficEndpoint. +type NetworkTrafficEndpoint struct { + // Address IP address (and possibly port) in string form. + Address string `json:"address"` + + // DnsLabel DNS label/name if available. + DnsLabel *string `json:"dns_label"` + GeoLocation NetworkTrafficLocation `json:"geo_location"` + + // Id ID of this endpoint (e.g., peer ID or resource ID). + Id string `json:"id"` + + // Name Name is the name of the endpoint object (e.g., a peer name). + Name string `json:"name"` + + // Os Operating system of the peer, if applicable. + Os *string `json:"os"` + + // Type Type of the endpoint object (e.g., UNKNOWN, PEER, HOST_RESOURCE). + Type string `json:"type"` +} + +// NetworkTrafficEvent defines model for NetworkTrafficEvent. +type NetworkTrafficEvent struct { + Destination NetworkTrafficEndpoint `json:"destination"` + + // Direction Direction of the traffic (e.g. DIRECTION_UNKNOWN, INGRESS, EGRESS). + Direction string `json:"direction"` + + // Events List of events that are correlated to this flow (e.g., start, end). + Events []NetworkTrafficSubEvent `json:"events"` + + // FlowId FlowID is the ID of the connection flow. Not unique because it can be the same for multiple events (e.g., start and end of the connection). + FlowId string `json:"flow_id"` + Icmp NetworkTrafficICMP `json:"icmp"` + Policy NetworkTrafficPolicy `json:"policy"` + + // Protocol Protocol is the protocol of the traffic (e.g. 1 = ICMP, 6 = TCP, 17 = UDP, etc.). + Protocol int `json:"protocol"` + + // ReporterId ID of the reporter of the event (e.g., the peer that reported the event). + ReporterId string `json:"reporter_id"` + + // RxBytes Number of bytes received. + RxBytes int `json:"rx_bytes"` + + // RxPackets Number of packets received. + RxPackets int `json:"rx_packets"` + Source NetworkTrafficEndpoint `json:"source"` + + // TxBytes Number of bytes transmitted. + TxBytes int `json:"tx_bytes"` + + // TxPackets Number of packets transmitted. + TxPackets int `json:"tx_packets"` + User NetworkTrafficUser `json:"user"` +} + +// NetworkTrafficEventsResponse defines model for NetworkTrafficEventsResponse. +type NetworkTrafficEventsResponse struct { + // Data List of network traffic events + Data []NetworkTrafficEvent `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 event records available + TotalRecords int `json:"total_records"` +} + +// NetworkTrafficICMP defines model for NetworkTrafficICMP. +type NetworkTrafficICMP struct { + // Code ICMP code (if applicable). + Code int `json:"code"` + + // Type ICMP type (if applicable). + Type int `json:"type"` +} + +// NetworkTrafficLocation defines model for NetworkTrafficLocation. +type NetworkTrafficLocation struct { + // CityName Name of the city (if known). + CityName string `json:"city_name"` + + // CountryCode ISO country code (if known). + CountryCode string `json:"country_code"` +} + +// NetworkTrafficPolicy defines model for NetworkTrafficPolicy. +type NetworkTrafficPolicy struct { + // Id ID of the policy that allowed this event. + Id string `json:"id"` + + // Name Name of the policy that allowed this event. + Name string `json:"name"` +} + +// NetworkTrafficSubEvent defines model for NetworkTrafficSubEvent. +type NetworkTrafficSubEvent struct { + // Timestamp Timestamp of the event as sent by the peer. + Timestamp time.Time `json:"timestamp"` + + // Type Type of the event (e.g., TYPE_UNKNOWN, TYPE_START, TYPE_END, TYPE_DROP). + Type string `json:"type"` +} + +// NetworkTrafficUser defines model for NetworkTrafficUser. +type NetworkTrafficUser struct { + // Email Email of the user who initiated the event (if any). + Email string `json:"email"` + + // Id UserID is the ID of the user that initiated the event (can be empty as not every event is user-initiated). + Id string `json:"id"` + + // Name Name of the user who initiated the event (if any). + Name string `json:"name"` +} + +// NotificationChannelRequest Request body for creating or updating a notification channel. +type NotificationChannelRequest struct { + // Enabled Whether this notification channel is active. + Enabled bool `json:"enabled"` + + // EventTypes List of activity event type codes this channel subscribes to. + EventTypes []NotificationEventType `json:"event_types"` + + // Target Channel-specific target configuration. The shape depends on the `type` field: + // - `email`: requires an `EmailTarget` object + // - `webhook`: requires a `WebhookTarget` object + Target *NotificationChannelRequest_Target `json:"target,omitempty"` + + // Type The type of notification channel. + Type NotificationChannelType `json:"type"` +} + +// NotificationChannelRequest_Target Channel-specific target configuration. The shape depends on the `type` field: +// - `email`: requires an `EmailTarget` object +// - `webhook`: requires a `WebhookTarget` object +type NotificationChannelRequest_Target struct { + union json.RawMessage +} + +// NotificationChannelResponse A notification channel configuration. +type NotificationChannelResponse struct { + // Enabled Whether this notification channel is active. + Enabled bool `json:"enabled"` + + // EventTypes List of activity event type codes this channel subscribes to. + EventTypes []NotificationEventType `json:"event_types"` + + // Id Unique identifier of the notification channel. + Id *string `json:"id,omitempty"` + + // Target Channel-specific target configuration. The shape depends on the `type` field: + // - `email`: an `EmailTarget` object + // - `webhook`: a `WebhookTarget` object + Target *NotificationChannelResponse_Target `json:"target,omitempty"` + + // Type The type of notification channel. + Type NotificationChannelType `json:"type"` +} + +// NotificationChannelResponse_Target Channel-specific target configuration. The shape depends on the `type` field: +// - `email`: an `EmailTarget` object +// - `webhook`: a `WebhookTarget` object +type NotificationChannelResponse_Target struct { + union json.RawMessage +} + +// NotificationChannelType The type of notification channel. +type NotificationChannelType string + +// NotificationEventType An activity event type code. See `GET /api/integrations/notifications/types` for the full list +// of supported event types and their human-readable descriptions. +type NotificationEventType = string + +// NotificationTypeEntry A map of event type codes to their human-readable descriptions. +type NotificationTypeEntry map[string]string + +// OSVersionCheck Posture check for the version of operating system +type OSVersionCheck struct { + // Android Posture check for the version of operating system + Android *MinVersionCheck `json:"android,omitempty"` + + // Darwin Posture check for the version of operating system + Darwin *MinVersionCheck `json:"darwin,omitempty"` + + // Ios Posture check for the version of operating system + Ios *MinVersionCheck `json:"ios,omitempty"` + + // Linux Posture check with the kernel version + Linux *MinKernelVersionCheck `json:"linux,omitempty"` + + // Windows Posture check with the kernel version + Windows *MinKernelVersionCheck `json:"windows,omitempty"` +} + +// OktaScimIntegration defines model for OktaScimIntegration. +type OktaScimIntegration struct { + // AuthToken SCIM API token (full on creation/regeneration, masked on retrieval) + AuthToken string `json:"auth_token"` + + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // Enabled Whether the integration is enabled + Enabled bool `json:"enabled"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes []string `json:"group_prefixes"` + + // Id The unique identifier for the integration + Id int64 `json:"id"` + + // LastSyncedAt Timestamp of the last synchronization + LastSyncedAt time.Time `json:"last_synced_at"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes []string `json:"user_group_prefixes"` +} + +// 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 + NewPassword string `json:"new_password"` + + // OldPassword The current password + OldPassword string `json:"old_password"` +} + +// Peer defines model for Peer. +type Peer struct { + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired bool `json:"approval_required"` + + // CityName Commonly used English name of the city + CityName CityName `json:"city_name"` + + // Connected Peer to Management connection status + Connected bool `json:"connected"` + + // ConnectionIp Peer's public connection IP address + ConnectionIp string `json:"connection_ip"` + + // CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country + CountryCode CountryCode `json:"country_code"` + + // CreatedAt Peer creation date (UTC) + CreatedAt time.Time `json:"created_at"` + + // DisapprovalReason (Cloud only) Reason why the peer requires approval + DisapprovalReason *string `json:"disapproval_reason,omitempty"` + + // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + DnsLabel string `json:"dns_label"` + + // Ephemeral Indicates whether the peer is ephemeral or not + Ephemeral bool `json:"ephemeral"` + + // ExtraDnsLabels Extra DNS labels added to the peer + ExtraDnsLabels []string `json:"extra_dns_labels"` + + // GeonameId Unique identifier from the GeoNames database for a specific geographical location. + GeonameId int `json:"geoname_id"` + + // Groups Groups that the peer belongs to + Groups []GroupMinimum `json:"groups"` + + // Hostname Hostname of the machine + Hostname string `json:"hostname"` + + // Id Peer ID + Id string `json:"id"` + + // InactivityExpirationEnabled Indicates whether peer inactivity expiration has been enabled or not + InactivityExpirationEnabled bool `json:"inactivity_expiration_enabled"` + + // Ip Peer's IP address + Ip string `json:"ip"` + + // KernelVersion Peer's operating system kernel version + KernelVersion string `json:"kernel_version"` + + // LastLogin Last time this peer performed log in (authentication). E.g., user authenticated. + LastLogin time.Time `json:"last_login"` + + // LastSeen Last time peer connected to Netbird's management service + LastSeen time.Time `json:"last_seen"` + LocalFlags *PeerLocalFlags `json:"local_flags,omitempty"` + + // LoginExpirationEnabled Indicates whether peer login expiration has been enabled or not + LoginExpirationEnabled bool `json:"login_expiration_enabled"` + + // LoginExpired Indicates whether peer's login expired or not + LoginExpired bool `json:"login_expired"` + + // Name Peer's hostname + Name string `json:"name"` + + // Os Peer's operating system and version + Os string `json:"os"` + + // SerialNumber System serial number + SerialNumber string `json:"serial_number"` + + // SshEnabled Indicates whether SSH server is enabled on this peer + SshEnabled bool `json:"ssh_enabled"` + + // UiVersion Peer's desktop UI version + UiVersion string `json:"ui_version"` + + // UserId User ID of the user that enrolled this peer + UserId string `json:"user_id"` + + // Version Peer's daemon or cli version + Version string `json:"version"` +} + +// PeerBatch defines model for PeerBatch. +type PeerBatch struct { + // AccessiblePeersCount Number of accessible peers + AccessiblePeersCount int `json:"accessible_peers_count"` + + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired bool `json:"approval_required"` + + // CityName Commonly used English name of the city + CityName CityName `json:"city_name"` + + // Connected Peer to Management connection status + Connected bool `json:"connected"` + + // ConnectionIp Peer's public connection IP address + ConnectionIp string `json:"connection_ip"` + + // CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country + CountryCode CountryCode `json:"country_code"` + + // CreatedAt Peer creation date (UTC) + CreatedAt time.Time `json:"created_at"` + + // DisapprovalReason (Cloud only) Reason why the peer requires approval + DisapprovalReason *string `json:"disapproval_reason,omitempty"` + + // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + DnsLabel string `json:"dns_label"` + + // Ephemeral Indicates whether the peer is ephemeral or not + Ephemeral bool `json:"ephemeral"` + + // ExtraDnsLabels Extra DNS labels added to the peer + ExtraDnsLabels []string `json:"extra_dns_labels"` + + // GeonameId Unique identifier from the GeoNames database for a specific geographical location. + GeonameId int `json:"geoname_id"` + + // Groups Groups that the peer belongs to + Groups []GroupMinimum `json:"groups"` + + // Hostname Hostname of the machine + Hostname string `json:"hostname"` + + // Id Peer ID + Id string `json:"id"` + + // InactivityExpirationEnabled Indicates whether peer inactivity expiration has been enabled or not + InactivityExpirationEnabled bool `json:"inactivity_expiration_enabled"` + + // Ip Peer's IP address + Ip string `json:"ip"` + + // KernelVersion Peer's operating system kernel version + KernelVersion string `json:"kernel_version"` + + // LastLogin Last time this peer performed log in (authentication). E.g., user authenticated. + LastLogin time.Time `json:"last_login"` + + // LastSeen Last time peer connected to Netbird's management service + LastSeen time.Time `json:"last_seen"` + LocalFlags *PeerLocalFlags `json:"local_flags,omitempty"` + + // LoginExpirationEnabled Indicates whether peer login expiration has been enabled or not + LoginExpirationEnabled bool `json:"login_expiration_enabled"` + + // LoginExpired Indicates whether peer's login expired or not + LoginExpired bool `json:"login_expired"` + + // Name Peer's hostname + Name string `json:"name"` + + // Os Peer's operating system and version + Os string `json:"os"` + + // SerialNumber System serial number + SerialNumber string `json:"serial_number"` + + // SshEnabled Indicates whether SSH server is enabled on this peer + SshEnabled bool `json:"ssh_enabled"` + + // UiVersion Peer's desktop UI version + UiVersion string `json:"ui_version"` + + // UserId User ID of the user that enrolled this peer + UserId string `json:"user_id"` + + // Version Peer's daemon or cli version + Version string `json:"version"` +} + +// PeerLocalFlags defines model for PeerLocalFlags. +type PeerLocalFlags struct { + // BlockInbound Indicates whether inbound traffic is blocked on this peer + BlockInbound *bool `json:"block_inbound,omitempty"` + + // BlockLanAccess Indicates whether LAN access is blocked on this peer when used as a routing peer + BlockLanAccess *bool `json:"block_lan_access,omitempty"` + + // DisableClientRoutes Indicates whether client routes are disabled on this peer or not + DisableClientRoutes *bool `json:"disable_client_routes,omitempty"` + + // DisableDns Indicates whether DNS management is disabled on this peer or not + DisableDns *bool `json:"disable_dns,omitempty"` + + // DisableFirewall Indicates whether firewall management is disabled on this peer or not + DisableFirewall *bool `json:"disable_firewall,omitempty"` + + // DisableServerRoutes Indicates whether server routes are disabled on this peer or not + DisableServerRoutes *bool `json:"disable_server_routes,omitempty"` + + // LazyConnectionEnabled Indicates whether lazy connection is enabled on this peer + LazyConnectionEnabled *bool `json:"lazy_connection_enabled,omitempty"` + + // RosenpassEnabled Indicates whether Rosenpass is enabled on this peer + RosenpassEnabled *bool `json:"rosenpass_enabled,omitempty"` + + // RosenpassPermissive Indicates whether Rosenpass is in permissive mode or not + RosenpassPermissive *bool `json:"rosenpass_permissive,omitempty"` + + // ServerSshAllowed Indicates whether SSH access this peer is allowed or not + ServerSshAllowed *bool `json:"server_ssh_allowed,omitempty"` +} + +// PeerMinimum defines model for PeerMinimum. +type PeerMinimum struct { + // Id Peer ID + Id string `json:"id"` + + // Name Peer's hostname + Name string `json:"name"` +} + +// PeerNetworkRangeCheck Posture check for allow or deny access based on peer local network addresses +type PeerNetworkRangeCheck struct { + // Action Action to take upon policy match + Action PeerNetworkRangeCheckAction `json:"action"` + + // Ranges List of peer network ranges in CIDR notation + Ranges []string `json:"ranges"` +} + +// PeerNetworkRangeCheckAction Action to take upon policy match +type PeerNetworkRangeCheckAction string + +// PeerRequest defines model for PeerRequest. +type PeerRequest struct { + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` + InactivityExpirationEnabled bool `json:"inactivity_expiration_enabled"` + + // Ip Peer's IP address + Ip *string `json:"ip,omitempty"` + LoginExpirationEnabled bool `json:"login_expiration_enabled"` + Name string `json:"name"` + SshEnabled bool `json:"ssh_enabled"` +} + +// PeerTemporaryAccessRequest defines model for PeerTemporaryAccessRequest. +type PeerTemporaryAccessRequest struct { + // Name Peer's hostname + Name string `json:"name"` + + // Rules List of temporary access rules + Rules []string `json:"rules"` + + // WgPubKey Peer's WireGuard public key + WgPubKey string `json:"wg_pub_key"` +} + +// PeerTemporaryAccessResponse defines model for PeerTemporaryAccessResponse. +type PeerTemporaryAccessResponse struct { + // Id Peer ID + Id string `json:"id"` + + // Name Peer's hostname + Name string `json:"name"` + + // Rules List of temporary access rules + Rules []string `json:"rules"` +} + +// PersonalAccessToken defines model for PersonalAccessToken. +type PersonalAccessToken struct { + // CreatedAt Date the token was created + CreatedAt time.Time `json:"created_at"` + + // CreatedBy User ID of the user who created the token + CreatedBy string `json:"created_by"` + + // ExpirationDate Date the token expires + ExpirationDate time.Time `json:"expiration_date"` + + // Id ID of a token + Id string `json:"id"` + + // LastUsed Date the token was last used + LastUsed *time.Time `json:"last_used,omitempty"` + + // Name Name of the token + Name string `json:"name"` +} + +// PersonalAccessTokenGenerated defines model for PersonalAccessTokenGenerated. +type PersonalAccessTokenGenerated struct { + PersonalAccessToken PersonalAccessToken `json:"personal_access_token"` + + // PlainToken Plain text representation of the generated token + PlainToken string `json:"plain_token"` +} + +// PersonalAccessTokenRequest defines model for PersonalAccessTokenRequest. +type PersonalAccessTokenRequest struct { + // ExpiresIn Expiration in days + ExpiresIn int `json:"expires_in"` + + // Name Name of the token + Name string `json:"name"` +} + +// Policy defines model for Policy. +type Policy struct { + // Description Policy friendly description + Description *string `json:"description,omitempty"` + + // Enabled Policy status + Enabled bool `json:"enabled"` + + // Id Policy ID + Id *string `json:"id,omitempty"` + + // Name Policy name identifier + Name string `json:"name"` + + // Rules Policy rule object for policy UI editor + Rules []PolicyRule `json:"rules"` + + // SourcePostureChecks Posture checks ID's applied to policy source groups + SourcePostureChecks []string `json:"source_posture_checks"` +} + +// PolicyCreate defines model for PolicyCreate. +type PolicyCreate struct { + // Description Policy friendly description + Description *string `json:"description,omitempty"` + + // Enabled Policy status + Enabled bool `json:"enabled"` + + // Name Policy name identifier + Name string `json:"name"` + + // Rules Policy rule object for policy UI editor + Rules []PolicyRuleUpdate `json:"rules"` + + // SourcePostureChecks Posture checks ID's applied to policy source groups + SourcePostureChecks *[]string `json:"source_posture_checks,omitempty"` +} + +// PolicyMinimum defines model for PolicyMinimum. +type PolicyMinimum struct { + // Description Policy friendly description + Description *string `json:"description,omitempty"` + + // Enabled Policy status + Enabled bool `json:"enabled"` + + // Name Policy name identifier + Name string `json:"name"` +} + +// PolicyRule defines model for PolicyRule. +type PolicyRule struct { + // Action Policy rule accept or drops packets + Action PolicyRuleAction `json:"action"` + + // AuthorizedGroups Map of user group ids to a list of local users + AuthorizedGroups *map[string][]string `json:"authorized_groups,omitempty"` + + // Bidirectional Define if the rule is applicable in both directions, sources, and destinations. + Bidirectional bool `json:"bidirectional"` + + // Description Policy rule friendly description + Description *string `json:"description,omitempty"` + DestinationResource *Resource `json:"destinationResource,omitempty"` + + // Destinations Policy rule destination group IDs + Destinations *[]GroupMinimum `json:"destinations,omitempty"` + + // Enabled Policy rule status + Enabled bool `json:"enabled"` + + // Id Policy rule ID + Id *string `json:"id,omitempty"` + + // Name Policy rule name identifier + Name string `json:"name"` + + // PortRanges Policy rule affected ports ranges list + PortRanges *[]RulePortRange `json:"port_ranges,omitempty"` + + // Ports Policy rule affected ports + Ports *[]string `json:"ports,omitempty"` + + // Protocol Policy rule type of the traffic + Protocol PolicyRuleProtocol `json:"protocol"` + SourceResource *Resource `json:"sourceResource,omitempty"` + + // Sources Policy rule source group IDs + Sources *[]GroupMinimum `json:"sources,omitempty"` +} + +// PolicyRuleAction Policy rule accept or drops packets +type PolicyRuleAction string + +// PolicyRuleProtocol Policy rule type of the traffic +type PolicyRuleProtocol string + +// PolicyRuleMinimum defines model for PolicyRuleMinimum. +type PolicyRuleMinimum struct { + // Action Policy rule accept or drops packets + Action PolicyRuleMinimumAction `json:"action"` + + // AuthorizedGroups Map of user group ids to a list of local users + AuthorizedGroups *map[string][]string `json:"authorized_groups,omitempty"` + + // Bidirectional Define if the rule is applicable in both directions, sources, and destinations. + Bidirectional bool `json:"bidirectional"` + + // Description Policy rule friendly description + Description *string `json:"description,omitempty"` + + // Enabled Policy rule status + Enabled bool `json:"enabled"` + + // Name Policy rule name identifier + Name string `json:"name"` + + // PortRanges Policy rule affected ports ranges list + PortRanges *[]RulePortRange `json:"port_ranges,omitempty"` + + // Ports Policy rule affected ports + Ports *[]string `json:"ports,omitempty"` + + // Protocol Policy rule type of the traffic + Protocol PolicyRuleMinimumProtocol `json:"protocol"` +} + +// PolicyRuleMinimumAction Policy rule accept or drops packets +type PolicyRuleMinimumAction string + +// PolicyRuleMinimumProtocol Policy rule type of the traffic +type PolicyRuleMinimumProtocol string + +// PolicyRuleUpdate defines model for PolicyRuleUpdate. +type PolicyRuleUpdate struct { + // Action Policy rule accept or drops packets + Action PolicyRuleUpdateAction `json:"action"` + + // AuthorizedGroups Map of user group ids to a list of local users + AuthorizedGroups *map[string][]string `json:"authorized_groups,omitempty"` + + // Bidirectional Define if the rule is applicable in both directions, sources, and destinations. + Bidirectional bool `json:"bidirectional"` + + // Description Policy rule friendly description + Description *string `json:"description,omitempty"` + DestinationResource *Resource `json:"destinationResource,omitempty"` + + // Destinations Policy rule destination group IDs + Destinations *[]string `json:"destinations,omitempty"` + + // Enabled Policy rule status + Enabled bool `json:"enabled"` + + // Id Policy rule ID + Id *string `json:"id,omitempty"` + + // Name Policy rule name identifier + Name string `json:"name"` + + // PortRanges Policy rule affected ports ranges list + PortRanges *[]RulePortRange `json:"port_ranges,omitempty"` + + // Ports Policy rule affected ports + Ports *[]string `json:"ports,omitempty"` + + // Protocol Policy rule type of the traffic + Protocol PolicyRuleUpdateProtocol `json:"protocol"` + SourceResource *Resource `json:"sourceResource,omitempty"` + + // Sources Policy rule source group IDs + Sources *[]string `json:"sources,omitempty"` +} + +// PolicyRuleUpdateAction Policy rule accept or drops packets +type PolicyRuleUpdateAction string + +// PolicyRuleUpdateProtocol Policy rule type of the traffic +type PolicyRuleUpdateProtocol string + +// PolicyUpdate defines model for PolicyUpdate. +type PolicyUpdate struct { + // Description Policy friendly description + Description *string `json:"description,omitempty"` + + // Enabled Policy status + Enabled bool `json:"enabled"` + + // Name Policy name identifier + Name string `json:"name"` + + // Rules Policy rule object for policy UI editor + Rules []PolicyRuleUpdate `json:"rules"` + + // SourcePostureChecks Posture checks ID's applied to policy source groups + SourcePostureChecks *[]string `json:"source_posture_checks,omitempty"` +} + +// PortalResponse defines model for PortalResponse. +type PortalResponse struct { + // SessionId The unique identifier for the customer portal session. + SessionId string `json:"session_id"` + + // Url URL to redirect the user to the customer portal. + Url string `json:"url"` +} + +// PostureCheck defines model for PostureCheck. +type PostureCheck struct { + // Checks List of objects that perform the actual checks + Checks Checks `json:"checks"` + + // Description Posture check friendly description + Description *string `json:"description,omitempty"` + + // Id Posture check ID + Id string `json:"id"` + + // Name Posture check unique name identifier + Name string `json:"name"` +} + +// PostureCheckUpdate defines model for PostureCheckUpdate. +type PostureCheckUpdate struct { + // Checks List of objects that perform the actual checks + Checks *Checks `json:"checks,omitempty"` + + // Description Posture check friendly description + Description string `json:"description"` + + // Name Posture check name identifier + Name string `json:"name"` +} + +// Price defines model for Price. +type Price struct { + // Currency Currency code for this price. + Currency string `json:"currency"` + + // Price Price amount in minor units (e.g., cents). + Price int `json:"price"` + + // PriceId Unique identifier for the price. + PriceId string `json:"price_id"` + + // Unit Unit of measurement for this price (e.g., per user). + Unit string `json:"unit"` +} + +// Process Describes the operational activity within a peer's system. +type Process struct { + // LinuxPath Path to the process executable file in a Linux operating system + LinuxPath *string `json:"linux_path,omitempty"` + + // MacPath Path to the process executable file in a Mac operating system + MacPath *string `json:"mac_path,omitempty"` + + // WindowsPath Path to the process executable file in a Windows operating system + WindowsPath *string `json:"windows_path,omitempty"` +} + +// ProcessCheck Posture Check for binaries exist and are running in the peer’s system +type ProcessCheck struct { + Processes []Process `json:"processes"` +} + +// Product defines model for Product. +type Product struct { + // Description Detailed description of the product. + Description string `json:"description"` + + // Features List of features provided by the product. + Features []string `json:"features"` + + // Free Indicates whether the product is free or not. + Free bool `json:"free"` + + // Name Name of the product. + Name string `json:"name"` + + // Prices List of prices for the product in different currencies + 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"` + + // BytesDownload Bytes downloaded (response body size) + BytesDownload int64 `json:"bytes_download"` + + // BytesUpload Bytes uploaded (request body size) + BytesUpload int64 `json:"bytes_upload"` + + // 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"` + + // Metadata Extra context about the request (e.g. crowdsec_verdict) + Metadata *map[string]string `json:"metadata,omitempty"` + + // Method HTTP method of the request + Method string `json:"method"` + + // Path Path of the request + Path string `json:"path"` + + // Protocol Protocol type: http, tcp, or udp + Protocol *string `json:"protocol,omitempty"` + + // 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"` + + // SubdivisionCode First-level administrative subdivision ISO code (e.g. state/province) + SubdivisionCode *string `json:"subdivision_code,omitempty"` + + // 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 + Id string `json:"id"` + Type ResourceType `json:"type"` +} + +// 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"` + + // RequireSubdomain Whether a subdomain label is required in front of this domain. When true, the domain cannot be used bare. + RequireSubdomain *bool `json:"require_subdomain,omitempty"` + + // SupportsCrowdsec Whether the proxy cluster has CrowdSec configured + SupportsCrowdsec *bool `json:"supports_crowdsec,omitempty"` + + // SupportsCustomPorts Whether the cluster supports binding arbitrary TCP/UDP ports + SupportsCustomPorts *bool `json:"supports_custom_ports,omitempty"` + + // 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. + AccessControlGroups *[]string `json:"access_control_groups,omitempty"` + + // Description Route description + Description string `json:"description"` + + // Domains Domain list to be dynamically resolved. Max of 32 domains can be added per route configuration. Conflicts with network + Domains *[]string `json:"domains,omitempty"` + + // Enabled Route status + Enabled bool `json:"enabled"` + + // Groups Group IDs containing routing peers + Groups []string `json:"groups"` + + // Id Route Id + Id string `json:"id"` + + // KeepRoute Indicate if the route should be kept after a domain doesn't resolve that IP anymore + KeepRoute bool `json:"keep_route"` + + // Masquerade Indicate if peer should masquerade traffic to this route's prefix + Masquerade bool `json:"masquerade"` + + // Metric Route metric number. Lowest number has higher priority + Metric int `json:"metric"` + + // Network Network range in CIDR format, Conflicts with domains + Network *string `json:"network,omitempty"` + + // NetworkId Route network identifier, to group HA routes + NetworkId string `json:"network_id"` + + // NetworkType Network type indicating if it is a domain route or a IPv4/IPv6 route + NetworkType string `json:"network_type"` + + // Peer Peer Identifier associated with route. This property can not be set together with `peer_groups` + Peer *string `json:"peer,omitempty"` + + // PeerGroups Peers Group Identifier associated with route. This property can not be set together with `peer` + PeerGroups *[]string `json:"peer_groups,omitempty"` + + // SkipAutoApply Indicate if this exit node route (0.0.0.0/0) should skip auto-application for client routing + SkipAutoApply *bool `json:"skip_auto_apply,omitempty"` +} + +// RouteRequest defines model for RouteRequest. +type RouteRequest struct { + // AccessControlGroups Access control group identifier associated with route. + AccessControlGroups *[]string `json:"access_control_groups,omitempty"` + + // Description Route description + Description string `json:"description"` + + // Domains Domain list to be dynamically resolved. Max of 32 domains can be added per route configuration. Conflicts with network + Domains *[]string `json:"domains,omitempty"` + + // Enabled Route status + Enabled bool `json:"enabled"` + + // Groups Group IDs containing routing peers + Groups []string `json:"groups"` + + // KeepRoute Indicate if the route should be kept after a domain doesn't resolve that IP anymore + KeepRoute bool `json:"keep_route"` + + // Masquerade Indicate if peer should masquerade traffic to this route's prefix + Masquerade bool `json:"masquerade"` + + // Metric Route metric number. Lowest number has higher priority + Metric int `json:"metric"` + + // Network Network range in CIDR format, Conflicts with domains + Network *string `json:"network,omitempty"` + + // NetworkId Route network identifier, to group HA routes + NetworkId string `json:"network_id"` + + // Peer Peer Identifier associated with route. This property can not be set together with `peer_groups` + Peer *string `json:"peer,omitempty"` + + // PeerGroups Peers Group Identifier associated with route. This property can not be set together with `peer` + PeerGroups *[]string `json:"peer_groups,omitempty"` + + // SkipAutoApply Indicate if this exit node route (0.0.0.0/0) should skip auto-application for client routing + SkipAutoApply *bool `json:"skip_auto_apply,omitempty"` +} + +// RulePortRange Policy rule affected ports range +type RulePortRange struct { + // End The ending port of the range + End int `json:"end"` + + // Start The starting port of the range + Start int `json:"start"` +} + +// ScimIntegration defines model for ScimIntegration. +type ScimIntegration struct { + // AuthToken SCIM API token (full on creation, masked otherwise) + AuthToken string `json:"auth_token"` + + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // Enabled Whether the integration is enabled + Enabled bool `json:"enabled"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes []string `json:"group_prefixes"` + + // Id The unique identifier for the integration + Id int64 `json:"id"` + + // LastSyncedAt Timestamp of when the integration was last synced + LastSyncedAt time.Time `json:"last_synced_at"` + + // Prefix The connection prefix used for the SCIM provider + Prefix string `json:"prefix"` + + // Provider Name of the SCIM identity provider + Provider string `json:"provider"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes []string `json:"user_group_prefixes"` +} + +// ScimTokenResponse Response containing the regenerated SCIM token +type ScimTokenResponse struct { + // AuthToken The newly generated SCIM API token + AuthToken string `json:"auth_token"` +} + +// SentinelOneMatchAttributes Attribute conditions to match when approving agents +type SentinelOneMatchAttributes struct { + // ActiveThreats The maximum allowed number of active threats on the agent + ActiveThreats *int `json:"active_threats,omitempty"` + + // EncryptedApplications Whether disk encryption is enabled on the agent + EncryptedApplications *bool `json:"encrypted_applications,omitempty"` + + // FirewallEnabled Whether the agent firewall is enabled + FirewallEnabled *bool `json:"firewall_enabled,omitempty"` + + // Infected Whether the agent is currently flagged as infected + Infected *bool `json:"infected,omitempty"` + + // IsActive Whether the agent has been recently active and reporting + IsActive *bool `json:"is_active,omitempty"` + + // IsUpToDate Whether the agent is running the latest available version + IsUpToDate *bool `json:"is_up_to_date,omitempty"` + + // NetworkStatus The current network connectivity status of the device + NetworkStatus *SentinelOneMatchAttributesNetworkStatus `json:"network_status,omitempty"` + + // OperationalState The current operational state of the agent + OperationalState *string `json:"operational_state,omitempty"` +} + +// SentinelOneMatchAttributesNetworkStatus The current network connectivity status of the device +type SentinelOneMatchAttributesNetworkStatus string + +// Service defines model for Service. +type Service struct { + // AccessRestrictions Connection-level access restrictions based on IP address or geography. Applies to both HTTP and L4 services. + AccessRestrictions *AccessRestrictions `json:"access_restrictions,omitempty"` + 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"` + + // ListenPort Port the proxy listens on (L4/TLS only) + ListenPort *int `json:"listen_port,omitempty"` + Meta ServiceMeta `json:"meta"` + + // Mode Service mode. "http" for L7 reverse proxy, "tcp"/"udp"/"tls" for L4 passthrough. + Mode *ServiceMode `json:"mode,omitempty"` + + // 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"` + + // PortAutoAssigned Whether the listen port was auto-assigned + PortAutoAssigned *bool `json:"port_auto_assigned,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"` + + // Terminated Whether the service has been terminated. Terminated services cannot be updated. Services that violate the Terms of Service will be terminated. + Terminated *bool `json:"terminated,omitempty"` +} + +// ServiceMode Service mode. "http" for L7 reverse proxy, "tcp"/"udp"/"tls" for L4 passthrough. +type ServiceMode string + +// ServiceAuthConfig defines model for ServiceAuthConfig. +type ServiceAuthConfig struct { + BearerAuth *BearerAuthConfig `json:"bearer_auth,omitempty"` + HeaderAuths *[]HeaderAuthConfig `json:"header_auths,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 { + // AccessRestrictions Connection-level access restrictions based on IP address or geography. Applies to both HTTP and L4 services. + AccessRestrictions *AccessRestrictions `json:"access_restrictions,omitempty"` + Auth *ServiceAuthConfig `json:"auth,omitempty"` + + // Domain Domain for the service + Domain string `json:"domain"` + + // Enabled Whether the service is enabled + Enabled bool `json:"enabled"` + + // ListenPort Port the proxy listens on (L4/TLS only). Set to 0 for auto-assignment. + ListenPort *int `json:"listen_port,omitempty"` + + // Mode Service mode. "http" for L7 reverse proxy, "tcp"/"udp"/"tls" for L4 passthrough. + Mode *ServiceRequestMode `json:"mode,omitempty"` + + // 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,omitempty"` +} + +// ServiceRequestMode Service mode. "http" for L7 reverse proxy, "tcp"/"udp"/"tls" for L4 passthrough. +type ServiceRequestMode string + +// 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"` + Options *ServiceTargetOptions `json:"options,omitempty"` + + // Path URL path prefix for this target (HTTP only) + Path *string `json:"path,omitempty"` + + // Port Backend port for this target + 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 + TargetType ServiceTargetTargetType `json:"target_type"` +} + +// ServiceTargetProtocol Protocol to use when connecting to the backend +type ServiceTargetProtocol string + +// ServiceTargetTargetType Target type +type ServiceTargetTargetType string + +// ServiceTargetOptions defines model for ServiceTargetOptions. +type ServiceTargetOptions struct { + // CustomHeaders Extra headers sent to the backend. Hop-by-hop and proxy-managed headers (Host, Connection, Transfer-Encoding, etc.) are rejected. + CustomHeaders *map[string]string `json:"custom_headers,omitempty"` + + // PathRewrite Controls how the request path is rewritten before forwarding to the backend. Default strips the matched prefix. "preserve" keeps the full original request path. + PathRewrite *ServiceTargetOptionsPathRewrite `json:"path_rewrite,omitempty"` + + // ProxyProtocol Send PROXY Protocol v2 header to this backend (TCP/TLS only) + ProxyProtocol *bool `json:"proxy_protocol,omitempty"` + + // RequestTimeout Per-target response timeout as a Go duration string (e.g. "30s", "2m") + RequestTimeout *string `json:"request_timeout,omitempty"` + + // SessionIdleTimeout Idle timeout before a UDP session is reaped, as a Go duration string (e.g. "30s", "2m"). + SessionIdleTimeout *string `json:"session_idle_timeout,omitempty"` + + // SkipTlsVerify Skip TLS certificate verification for this backend + SkipTlsVerify *bool `json:"skip_tls_verify,omitempty"` +} + +// ServiceTargetOptionsPathRewrite Controls how the request path is rewritten before forwarding to the backend. Default strips the matched prefix. "preserve" keeps the full original request path. +type ServiceTargetOptionsPathRewrite string + +// SetupKey defines model for SetupKey. +type SetupKey struct { + // AllowExtraDnsLabels Allow extra DNS labels to be added to the peer + AllowExtraDnsLabels bool `json:"allow_extra_dns_labels"` + + // AutoGroups List of group IDs to auto-assign to peers registered with this key + AutoGroups []string `json:"auto_groups"` + + // Ephemeral Indicate that the peer will be ephemeral or not + Ephemeral bool `json:"ephemeral"` + + // Expires Setup Key expiration date + Expires time.Time `json:"expires"` + + // Id Setup Key ID + Id string `json:"id"` + + // Key Setup Key as secret + Key string `json:"key"` + + // LastUsed Setup key last usage date + LastUsed time.Time `json:"last_used"` + + // Name Setup key name identifier + Name string `json:"name"` + + // Revoked Setup key revocation status + Revoked bool `json:"revoked"` + + // State Setup key status, "valid", "overused","expired" or "revoked" + State string `json:"state"` + + // Type Setup key type, one-off for single time usage and reusable + Type string `json:"type"` + + // UpdatedAt Setup key last update date + UpdatedAt time.Time `json:"updated_at"` + + // UsageLimit A number of times this key can be used. The value of 0 indicates the unlimited usage. + UsageLimit int `json:"usage_limit"` + + // UsedTimes Usage count of setup key + UsedTimes int `json:"used_times"` + + // Valid Setup key validity status + Valid bool `json:"valid"` +} + +// SetupKeyBase defines model for SetupKeyBase. +type SetupKeyBase struct { + // AllowExtraDnsLabels Allow extra DNS labels to be added to the peer + AllowExtraDnsLabels bool `json:"allow_extra_dns_labels"` + + // AutoGroups List of group IDs to auto-assign to peers registered with this key + AutoGroups []string `json:"auto_groups"` + + // Ephemeral Indicate that the peer will be ephemeral or not + Ephemeral bool `json:"ephemeral"` + + // Expires Setup Key expiration date + Expires time.Time `json:"expires"` + + // Id Setup Key ID + Id string `json:"id"` + + // LastUsed Setup key last usage date + LastUsed time.Time `json:"last_used"` + + // Name Setup key name identifier + Name string `json:"name"` + + // Revoked Setup key revocation status + Revoked bool `json:"revoked"` + + // State Setup key status, "valid", "overused","expired" or "revoked" + State string `json:"state"` + + // Type Setup key type, one-off for single time usage and reusable + Type string `json:"type"` + + // UpdatedAt Setup key last update date + UpdatedAt time.Time `json:"updated_at"` + + // UsageLimit A number of times this key can be used. The value of 0 indicates the unlimited usage. + UsageLimit int `json:"usage_limit"` + + // UsedTimes Usage count of setup key + UsedTimes int `json:"used_times"` + + // Valid Setup key validity status + Valid bool `json:"valid"` +} + +// SetupKeyClear defines model for SetupKeyClear. +type SetupKeyClear struct { + // AllowExtraDnsLabels Allow extra DNS labels to be added to the peer + AllowExtraDnsLabels bool `json:"allow_extra_dns_labels"` + + // AutoGroups List of group IDs to auto-assign to peers registered with this key + AutoGroups []string `json:"auto_groups"` + + // Ephemeral Indicate that the peer will be ephemeral or not + Ephemeral bool `json:"ephemeral"` + + // Expires Setup Key expiration date + Expires time.Time `json:"expires"` + + // Id Setup Key ID + Id string `json:"id"` + + // Key Setup Key as plain text + Key string `json:"key"` + + // LastUsed Setup key last usage date + LastUsed time.Time `json:"last_used"` + + // Name Setup key name identifier + Name string `json:"name"` + + // Revoked Setup key revocation status + Revoked bool `json:"revoked"` + + // State Setup key status, "valid", "overused","expired" or "revoked" + State string `json:"state"` + + // Type Setup key type, one-off for single time usage and reusable + Type string `json:"type"` + + // UpdatedAt Setup key last update date + UpdatedAt time.Time `json:"updated_at"` + + // UsageLimit A number of times this key can be used. The value of 0 indicates the unlimited usage. + UsageLimit int `json:"usage_limit"` + + // UsedTimes Usage count of setup key + UsedTimes int `json:"used_times"` + + // Valid Setup key validity status + Valid bool `json:"valid"` +} + +// SetupKeyRequest defines model for SetupKeyRequest. +type SetupKeyRequest struct { + // AutoGroups List of group IDs to auto-assign to peers registered with this key + AutoGroups []string `json:"auto_groups"` + + // Revoked Setup key revocation status + Revoked bool `json:"revoked"` +} + +// SetupRequest Request to set up the initial admin user +type SetupRequest struct { + // Email Email address for the admin user + Email string `json:"email"` + + // Name Display name for the admin user (defaults to email if not provided) + Name string `json:"name"` + + // Password Password for the admin user (minimum 8 characters) + Password string `json:"password"` +} + +// SetupResponse Response after successful instance setup +type SetupResponse struct { + // Email Email address of the created user + Email string `json:"email"` + + // UserId The ID of the created user + UserId string `json:"user_id"` +} + +// Subscription defines model for Subscription. +type Subscription struct { + // Active Indicates whether the subscription is active or not. + Active bool `json:"active"` + + // Currency Currency code of the subscription. + Currency string `json:"currency"` + + // Features List of features included in the subscription. + Features *[]string `json:"features,omitempty"` + + // PlanTier The tier of the plan for the subscription. + PlanTier string `json:"plan_tier"` + + // Price Price amount in minor units (e.g., cents). + Price int `json:"price"` + + // PriceId Unique identifier for the price of the subscription. + PriceId string `json:"price_id"` + + // Provider The provider of the subscription. + Provider string `json:"provider"` + + // RemainingTrial The remaining time for the trial period, in seconds. + RemainingTrial *int `json:"remaining_trial,omitempty"` + + // UpdatedAt The date and time when the subscription was last updated. + UpdatedAt time.Time `json:"updated_at"` +} + +// SyncResult Response for a manual sync trigger +type SyncResult struct { + Result *string `json:"result,omitempty"` +} + +// TenantGroupResponse defines model for TenantGroupResponse. +type TenantGroupResponse struct { + // Id The Group ID + Id string `json:"id"` + + // Role The Role name + Role string `json:"role"` +} + +// TenantResponse defines model for TenantResponse. +type TenantResponse struct { + // ActivatedAt The date and time when the tenant was activated. + ActivatedAt *time.Time `json:"activated_at,omitempty"` + + // CreatedAt The date and time when the tenant was created. + CreatedAt time.Time `json:"created_at"` + + // DnsChallenge The DNS challenge to set in a TXT record + DnsChallenge string `json:"dns_challenge"` + + // Domain The tenant account domain + Domain string `json:"domain"` + + // Groups MSP users Groups that can access the Tenant and Roles to assume + Groups []TenantGroupResponse `json:"groups"` + + // Id The updated MSP tenant account ID + Id string `json:"id"` + + // InvitedAt The date and time when the existing tenant was invited. + InvitedAt *time.Time `json:"invited_at,omitempty"` + + // Name The name for the MSP tenant + Name string `json:"name"` + + // Status The status of the tenant + Status TenantResponseStatus `json:"status"` + + // UpdatedAt The date and time when the tenant was last updated. + UpdatedAt time.Time `json:"updated_at"` +} + +// TenantResponseStatus The status of the tenant +type TenantResponseStatus string + +// UpdateAzureIntegrationRequest defines model for UpdateAzureIntegrationRequest. +type UpdateAzureIntegrationRequest struct { + // ClientId Azure AD application (client) ID + ClientId *string `json:"client_id,omitempty"` + + // ClientSecret Base64-encoded Azure AD client secret + ClientSecret *string `json:"client_secret,omitempty"` + + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // Enabled Whether the integration is enabled + Enabled *bool `json:"enabled,omitempty"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes *[]string `json:"group_prefixes,omitempty"` + + // SyncInterval Sync interval in seconds (minimum 300) + SyncInterval *int `json:"sync_interval,omitempty"` + + // TenantId Azure AD tenant ID + TenantId *string `json:"tenant_id,omitempty"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes *[]string `json:"user_group_prefixes,omitempty"` +} + +// UpdateGoogleIntegrationRequest defines model for UpdateGoogleIntegrationRequest. +type UpdateGoogleIntegrationRequest struct { + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // CustomerId Customer ID from Google Workspace Account Settings + CustomerId *string `json:"customer_id,omitempty"` + + // Enabled Whether the integration is enabled + Enabled *bool `json:"enabled,omitempty"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes *[]string `json:"group_prefixes,omitempty"` + + // ServiceAccountKey Base64-encoded Google service account key + ServiceAccountKey *string `json:"service_account_key,omitempty"` + + // SyncInterval Sync interval in seconds (minimum 300) + SyncInterval *int `json:"sync_interval,omitempty"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes *[]string `json:"user_group_prefixes,omitempty"` +} + +// UpdateOktaScimIntegrationRequest defines model for UpdateOktaScimIntegrationRequest. +type UpdateOktaScimIntegrationRequest struct { + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // Enabled Whether the integration is enabled + Enabled *bool `json:"enabled,omitempty"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes *[]string `json:"group_prefixes,omitempty"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes *[]string `json:"user_group_prefixes,omitempty"` +} + +// UpdateScimIntegrationRequest defines model for UpdateScimIntegrationRequest. +type UpdateScimIntegrationRequest struct { + // ConnectorId DEX connector ID for embedded IDP setups + ConnectorId *string `json:"connector_id,omitempty"` + + // Enabled Whether the integration is enabled + Enabled *bool `json:"enabled,omitempty"` + + // GroupPrefixes List of start_with string patterns for groups to sync + GroupPrefixes *[]string `json:"group_prefixes,omitempty"` + + // Prefix The connection prefix used for the SCIM provider + Prefix *string `json:"prefix,omitempty"` + + // UserGroupPrefixes List of start_with string patterns for groups which users to sync + UserGroupPrefixes *[]string `json:"user_group_prefixes,omitempty"` +} + +// UpdateTenantRequest defines model for UpdateTenantRequest. +type UpdateTenantRequest struct { + // Groups MSP users Groups that can access the Tenant and Roles to assume + Groups []TenantGroupResponse `json:"groups"` + + // Name The name for the MSP tenant + Name string `json:"name"` +} + +// UsageStats defines model for UsageStats. +type UsageStats struct { + // ActivePeers Number of active peers. + ActivePeers int64 `json:"active_peers"` + + // ActiveUsers Number of active users. + ActiveUsers int64 `json:"active_users"` + + // TotalPeers Total number of peers. + TotalPeers int64 `json:"total_peers"` + + // TotalUsers Total number of users. + TotalUsers int64 `json:"total_users"` +} + +// User defines model for User. +type User struct { + // AutoGroups Group IDs to auto-assign to peers registered by this user + AutoGroups []string `json:"auto_groups"` + + // Email User's email address + Email string `json:"email"` + + // Id User ID + Id string `json:"id"` + + // IdpId Identity provider ID (connector ID) that the user authenticated with. Only populated for users with Dex-encoded user IDs. + IdpId *string `json:"idp_id,omitempty"` + + // IsBlocked Is true if this user is blocked. Blocked users can't use the system + IsBlocked bool `json:"is_blocked"` + + // IsCurrent Is true if authenticated user is the same as this user + IsCurrent *bool `json:"is_current,omitempty"` + + // IsServiceUser Is true if this user is a service user + IsServiceUser *bool `json:"is_service_user,omitempty"` + + // Issued How user was issued by API or Integration + Issued *string `json:"issued,omitempty"` + + // LastLogin Last time this user performed a login to the dashboard + LastLogin *time.Time `json:"last_login,omitempty"` + + // Name User's name from idp provider + Name string `json:"name"` + + // Password User's password. Only present when user is created (create user endpoint is called) and only when IdP supports user creation with password. + Password *string `json:"password,omitempty"` + + // PendingApproval Is true if this user requires approval before being activated. Only applicable for users joining via domain matching when user_approval_required is enabled. + PendingApproval bool `json:"pending_approval"` + Permissions *UserPermissions `json:"permissions,omitempty"` + + // Role User's NetBird account role + Role string `json:"role"` + + // Status User's status + Status UserStatus `json:"status"` +} + +// UserStatus User's status +type UserStatus string + +// UserCreateRequest defines model for UserCreateRequest. +type UserCreateRequest struct { + // AutoGroups Group IDs to auto-assign to peers registered by this user + AutoGroups []string `json:"auto_groups"` + + // Email User's Email to send invite to + Email *string `json:"email,omitempty"` + + // IsServiceUser Is true if this user is a service user + IsServiceUser bool `json:"is_service_user"` + + // Name User's full name + Name *string `json:"name,omitempty"` + + // Role User's NetBird account role + Role string `json:"role"` +} + +// UserInvite A user invite +type UserInvite struct { + // AutoGroups Group IDs to auto-assign to peers registered by this user + AutoGroups []string `json:"auto_groups"` + + // CreatedAt Invite creation time + CreatedAt time.Time `json:"created_at"` + + // Email User's email address + Email string `json:"email"` + + // Expired Whether the invite has expired + Expired bool `json:"expired"` + + // ExpiresAt Invite expiration time + ExpiresAt time.Time `json:"expires_at"` + + // Id Invite ID + Id string `json:"id"` + + // InviteToken The invite link to be shared with the user. Only returned when the invite is created or regenerated. + InviteToken *string `json:"invite_token,omitempty"` + + // Name User's full name + Name string `json:"name"` + + // Role User's NetBird account role + Role string `json:"role"` +} + +// UserInviteAcceptRequest Request to accept an invite and set password +type UserInviteAcceptRequest struct { + // Password The password the user wants to set. Must be at least 8 characters long and contain at least one uppercase letter, one digit, and one special character (any character that is not a letter or digit, including spaces). + Password string `json:"password"` +} + +// UserInviteAcceptResponse Response after accepting an invite +type UserInviteAcceptResponse struct { + // Success Whether the invite was accepted successfully + Success bool `json:"success"` +} + +// UserInviteCreateRequest Request to create a user invite link +type UserInviteCreateRequest struct { + // AutoGroups Group IDs to auto-assign to peers registered by this user + AutoGroups []string `json:"auto_groups"` + + // Email User's email address + Email string `json:"email"` + + // ExpiresIn Invite expiration time in seconds (default 72 hours) + ExpiresIn *int `json:"expires_in,omitempty"` + + // Name User's full name + Name string `json:"name"` + + // Role User's NetBird account role + Role string `json:"role"` +} + +// UserInviteInfo Public information about an invite +type UserInviteInfo struct { + // Email User's email address + Email string `json:"email"` + + // ExpiresAt Invite expiration time + ExpiresAt time.Time `json:"expires_at"` + + // InvitedBy Name of the user who sent the invite + InvitedBy string `json:"invited_by"` + + // Name User's full name + Name string `json:"name"` + + // Valid Whether the invite is still valid (not expired) + Valid bool `json:"valid"` +} + +// UserInviteRegenerateRequest Request to regenerate an invite link +type UserInviteRegenerateRequest struct { + // ExpiresIn Invite expiration time in seconds (default 72 hours) + ExpiresIn *int `json:"expires_in,omitempty"` +} + +// UserInviteRegenerateResponse Response after regenerating an invite +type UserInviteRegenerateResponse struct { + // InviteExpiresAt New invite expiration time + InviteExpiresAt time.Time `json:"invite_expires_at"` + + // InviteToken The new invite token + InviteToken string `json:"invite_token"` +} + +// UserPermissions defines model for UserPermissions. +type UserPermissions struct { + // IsRestricted Indicates whether this User's Peers view is restricted + IsRestricted bool `json:"is_restricted"` + Modules map[string]map[string]bool `json:"modules"` +} + +// UserRequest defines model for UserRequest. +type UserRequest struct { + // AutoGroups Group IDs to auto-assign to peers registered by this user + AutoGroups []string `json:"auto_groups"` + + // IsBlocked If set to true then user is blocked and can't use the system + IsBlocked bool `json:"is_blocked"` + + // Role User's NetBird account role + Role string `json:"role"` +} + +// WebhookTarget Target configuration for webhook notification channels. +type WebhookTarget struct { + // Headers Custom HTTP headers sent with each webhook request. + // Values are write-only; in GET responses all values are masked. + Headers *map[string]string `json:"headers,omitempty"` + + // Url The webhook endpoint URL to send notifications to. + Url string `json:"url"` +} + +// WorkloadRequest defines model for WorkloadRequest. +type WorkloadRequest struct { + union json.RawMessage +} + +// WorkloadResponse defines model for WorkloadResponse. +type WorkloadResponse struct { + union json.RawMessage +} + +// WorkloadType Identifies the type of workload the job will execute. +// Currently only `"bundle"` is supported. +type WorkloadType string + +// Zone defines model for Zone. +type Zone struct { + // DistributionGroups Group IDs that defines groups of peers that will resolve this zone + DistributionGroups []string `json:"distribution_groups"` + + // Domain Zone domain (FQDN) + Domain string `json:"domain"` + + // EnableSearchDomain Enable this zone as a search domain + EnableSearchDomain bool `json:"enable_search_domain"` + + // Enabled Zone status + Enabled bool `json:"enabled"` + + // Id Zone ID + Id string `json:"id"` + + // Name Zone name identifier + Name string `json:"name"` + + // Records DNS records associated with this zone + Records []DNSRecord `json:"records"` +} + +// ZoneRequest defines model for ZoneRequest. +type ZoneRequest struct { + // DistributionGroups Group IDs that defines groups of peers that will resolve this zone + DistributionGroups []string `json:"distribution_groups"` + + // Domain Zone domain (FQDN) + Domain string `json:"domain"` + + // EnableSearchDomain Enable this zone as a search domain + EnableSearchDomain bool `json:"enable_search_domain"` + + // Enabled Zone status + Enabled *bool `json:"enabled,omitempty"` + + // Name Zone name identifier + Name string `json:"name"` +} + +// Conflict Standard error response. Note: The exact structure of this error response is inferred from `util.WriteErrorResponse` and `util.WriteError` usage in the provided Go code, as a specific Go struct for errors was not provided. +type Conflict = ErrorResponse + +// GetApiEventsNetworkTrafficParams defines parameters for GetApiEventsNetworkTraffic. +type GetApiEventsNetworkTrafficParams struct { + // Page Page number + Page *int `form:"page,omitempty" json:"page,omitempty"` + + // PageSize Number of items per page + PageSize *int `form:"page_size,omitempty" json:"page_size,omitempty"` + + // UserId Filter by user ID + UserId *string `form:"user_id,omitempty" json:"user_id,omitempty"` + + // ReporterId Filter by reporter ID + ReporterId *string `form:"reporter_id,omitempty" json:"reporter_id,omitempty"` + + // Protocol Filter by protocol + Protocol *int `form:"protocol,omitempty" json:"protocol,omitempty"` + + // Type Filter by event type + Type *GetApiEventsNetworkTrafficParamsType `form:"type,omitempty" json:"type,omitempty"` + + // ConnectionType Filter by connection type + ConnectionType *GetApiEventsNetworkTrafficParamsConnectionType `form:"connection_type,omitempty" json:"connection_type,omitempty"` + + // Direction Filter by direction + Direction *GetApiEventsNetworkTrafficParamsDirection `form:"direction,omitempty" json:"direction,omitempty"` + + // Search Case-insensitive partial match on user email, source/destination names, and source/destination addresses + Search *string `form:"search,omitempty" json:"search,omitempty"` + + // StartDate Start date for filtering events (ISO 8601 format, e.g., 2024-01-01T00:00:00Z). + StartDate *time.Time `form:"start_date,omitempty" json:"start_date,omitempty"` + + // EndDate End date for filtering events (ISO 8601 format, e.g., 2024-01-31T23:59:59Z). + EndDate *time.Time `form:"end_date,omitempty" json:"end_date,omitempty"` +} + +// GetApiEventsNetworkTrafficParamsType defines parameters for GetApiEventsNetworkTraffic. +type GetApiEventsNetworkTrafficParamsType string + +// GetApiEventsNetworkTrafficParamsConnectionType defines parameters for GetApiEventsNetworkTraffic. +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"` + + // SortBy Field to sort by (url sorts by host then path) + SortBy *GetApiEventsProxyParamsSortBy `form:"sort_by,omitempty" json:"sort_by,omitempty"` + + // SortOrder Sort order (ascending or descending) + SortOrder *GetApiEventsProxyParamsSortOrder `form:"sort_order,omitempty" json:"sort_order,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"` +} + +// GetApiEventsProxyParamsSortBy defines parameters for GetApiEventsProxy. +type GetApiEventsProxyParamsSortBy string + +// GetApiEventsProxyParamsSortOrder defines parameters for GetApiEventsProxy. +type GetApiEventsProxyParamsSortOrder string + +// 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) + Name *string `form:"name,omitempty" json:"name,omitempty"` +} + +// PostApiIntegrationsBillingAwsMarketplaceActivateJSONBody defines parameters for PostApiIntegrationsBillingAwsMarketplaceActivate. +type PostApiIntegrationsBillingAwsMarketplaceActivateJSONBody struct { + // PlanTier The plan tier to activate the subscription for. + PlanTier string `json:"plan_tier"` +} + +// PostApiIntegrationsBillingAwsMarketplaceEnrichJSONBody defines parameters for PostApiIntegrationsBillingAwsMarketplaceEnrich. +type PostApiIntegrationsBillingAwsMarketplaceEnrichJSONBody struct { + // AwsUserId The AWS user ID. + AwsUserId string `json:"aws_user_id"` +} + +// PostApiIntegrationsBillingCheckoutJSONBody defines parameters for PostApiIntegrationsBillingCheckout. +type PostApiIntegrationsBillingCheckoutJSONBody struct { + // BaseURL The base URL for the redirect after checkout. + BaseURL string `json:"baseURL"` + + // EnableTrial Enables a 14-day trial for the account. + EnableTrial *bool `json:"enableTrial,omitempty"` + + // PriceID The Price ID for checkout. + PriceID string `json:"priceID"` +} + +// GetApiIntegrationsBillingPortalParams defines parameters for GetApiIntegrationsBillingPortal. +type GetApiIntegrationsBillingPortalParams struct { + // BaseURL The base URL for the redirect after accessing the portal. + BaseURL string `form:"baseURL" json:"baseURL"` +} + +// PutApiIntegrationsBillingSubscriptionJSONBody defines parameters for PutApiIntegrationsBillingSubscription. +type PutApiIntegrationsBillingSubscriptionJSONBody struct { + // PlanTier The plan tier to change the subscription to. + PlanTier *string `json:"plan_tier,omitempty"` + + // PriceID The Price ID to change the subscription to. + PriceID *string `json:"priceID,omitempty"` +} + +// PutApiIntegrationsMspTenantsIdInviteJSONBody defines parameters for PutApiIntegrationsMspTenantsIdInvite. +type PutApiIntegrationsMspTenantsIdInviteJSONBody struct { + // Value Accept or decline the invitation. + Value PutApiIntegrationsMspTenantsIdInviteJSONBodyValue `json:"value"` +} + +// PutApiIntegrationsMspTenantsIdInviteJSONBodyValue defines parameters for PutApiIntegrationsMspTenantsIdInvite. +type PutApiIntegrationsMspTenantsIdInviteJSONBodyValue string + +// PostApiIntegrationsMspTenantsIdSubscriptionJSONBody defines parameters for PostApiIntegrationsMspTenantsIdSubscription. +type PostApiIntegrationsMspTenantsIdSubscriptionJSONBody struct { + // PriceID The Price ID to change the subscription to. + PriceID string `json:"priceID"` +} + +// PostApiIntegrationsMspTenantsIdUnlinkJSONBody defines parameters for PostApiIntegrationsMspTenantsIdUnlink. +type PostApiIntegrationsMspTenantsIdUnlinkJSONBody struct { + // Owner The new owners user ID. + Owner string `json:"owner"` +} + +// GetApiPeersParams defines parameters for GetApiPeers. +type GetApiPeersParams struct { + // Name Filter peers by name + Name *string `form:"name,omitempty" json:"name,omitempty"` + + // Ip Filter peers by IP address + Ip *string `form:"ip,omitempty" json:"ip,omitempty"` +} + +// GetApiPeersPeerIdIngressPortsParams defines parameters for GetApiPeersPeerIdIngressPorts. +type GetApiPeersPeerIdIngressPortsParams struct { + // Name Filters ingress port allocations by name + Name *string `form:"name,omitempty" json:"name,omitempty"` +} + +// GetApiUsersParams defines parameters for GetApiUsers. +type GetApiUsersParams struct { + // ServiceUser Filters users and returns either regular users or service users + ServiceUser *bool `form:"service_user,omitempty" json:"service_user,omitempty"` +} + +// PutApiAccountsAccountIdJSONRequestBody defines body for PutApiAccountsAccountId for application/json ContentType. +type PutApiAccountsAccountIdJSONRequestBody = AccountRequest + +// PostApiDnsNameserversJSONRequestBody defines body for PostApiDnsNameservers for application/json ContentType. +type PostApiDnsNameserversJSONRequestBody = NameserverGroupRequest + +// PutApiDnsNameserversNsgroupIdJSONRequestBody defines body for PutApiDnsNameserversNsgroupId for application/json ContentType. +type PutApiDnsNameserversNsgroupIdJSONRequestBody = NameserverGroupRequest + +// PutApiDnsSettingsJSONRequestBody defines body for PutApiDnsSettings for application/json ContentType. +type PutApiDnsSettingsJSONRequestBody = DNSSettings + +// PostApiDnsZonesJSONRequestBody defines body for PostApiDnsZones for application/json ContentType. +type PostApiDnsZonesJSONRequestBody = ZoneRequest + +// PutApiDnsZonesZoneIdJSONRequestBody defines body for PutApiDnsZonesZoneId for application/json ContentType. +type PutApiDnsZonesZoneIdJSONRequestBody = ZoneRequest + +// PostApiDnsZonesZoneIdRecordsJSONRequestBody defines body for PostApiDnsZonesZoneIdRecords for application/json ContentType. +type PostApiDnsZonesZoneIdRecordsJSONRequestBody = DNSRecordRequest + +// PutApiDnsZonesZoneIdRecordsRecordIdJSONRequestBody defines body for PutApiDnsZonesZoneIdRecordsRecordId for application/json ContentType. +type PutApiDnsZonesZoneIdRecordsRecordIdJSONRequestBody = DNSRecordRequest + +// CreateIntegrationJSONRequestBody defines body for CreateIntegration for application/json ContentType. +type CreateIntegrationJSONRequestBody = CreateIntegrationRequest + +// UpdateIntegrationJSONRequestBody defines body for UpdateIntegration for application/json ContentType. +type UpdateIntegrationJSONRequestBody = CreateIntegrationRequest + +// PostApiGroupsJSONRequestBody defines body for PostApiGroups for application/json ContentType. +type PostApiGroupsJSONRequestBody = GroupRequest + +// PutApiGroupsGroupIdJSONRequestBody defines body for PutApiGroupsGroupId for application/json ContentType. +type PutApiGroupsGroupIdJSONRequestBody = GroupRequest + +// PostApiIdentityProvidersJSONRequestBody defines body for PostApiIdentityProviders for application/json ContentType. +type PostApiIdentityProvidersJSONRequestBody = IdentityProviderRequest + +// PutApiIdentityProvidersIdpIdJSONRequestBody defines body for PutApiIdentityProvidersIdpId for application/json ContentType. +type PutApiIdentityProvidersIdpIdJSONRequestBody = IdentityProviderRequest + +// PostApiIngressPeersJSONRequestBody defines body for PostApiIngressPeers for application/json ContentType. +type PostApiIngressPeersJSONRequestBody = IngressPeerCreateRequest + +// PutApiIngressPeersIngressPeerIdJSONRequestBody defines body for PutApiIngressPeersIngressPeerId for application/json ContentType. +type PutApiIngressPeersIngressPeerIdJSONRequestBody = IngressPeerUpdateRequest + +// CreateAzureIntegrationJSONRequestBody defines body for CreateAzureIntegration for application/json ContentType. +type CreateAzureIntegrationJSONRequestBody = CreateAzureIntegrationRequest + +// UpdateAzureIntegrationJSONRequestBody defines body for UpdateAzureIntegration for application/json ContentType. +type UpdateAzureIntegrationJSONRequestBody = UpdateAzureIntegrationRequest + +// PostApiIntegrationsBillingAwsMarketplaceActivateJSONRequestBody defines body for PostApiIntegrationsBillingAwsMarketplaceActivate for application/json ContentType. +type PostApiIntegrationsBillingAwsMarketplaceActivateJSONRequestBody PostApiIntegrationsBillingAwsMarketplaceActivateJSONBody + +// PostApiIntegrationsBillingAwsMarketplaceEnrichJSONRequestBody defines body for PostApiIntegrationsBillingAwsMarketplaceEnrich for application/json ContentType. +type PostApiIntegrationsBillingAwsMarketplaceEnrichJSONRequestBody PostApiIntegrationsBillingAwsMarketplaceEnrichJSONBody + +// PostApiIntegrationsBillingCheckoutJSONRequestBody defines body for PostApiIntegrationsBillingCheckout for application/json ContentType. +type PostApiIntegrationsBillingCheckoutJSONRequestBody PostApiIntegrationsBillingCheckoutJSONBody + +// PutApiIntegrationsBillingSubscriptionJSONRequestBody defines body for PutApiIntegrationsBillingSubscription for application/json ContentType. +type PutApiIntegrationsBillingSubscriptionJSONRequestBody PutApiIntegrationsBillingSubscriptionJSONBody + +// CreateFalconEDRIntegrationJSONRequestBody defines body for CreateFalconEDRIntegration for application/json ContentType. +type CreateFalconEDRIntegrationJSONRequestBody = EDRFalconRequest + +// UpdateFalconEDRIntegrationJSONRequestBody defines body for UpdateFalconEDRIntegration for application/json ContentType. +type UpdateFalconEDRIntegrationJSONRequestBody = EDRFalconRequest + +// CreateFleetDMEDRIntegrationJSONRequestBody defines body for CreateFleetDMEDRIntegration for application/json ContentType. +type CreateFleetDMEDRIntegrationJSONRequestBody = EDRFleetDMRequest + +// UpdateFleetDMEDRIntegrationJSONRequestBody defines body for UpdateFleetDMEDRIntegration for application/json ContentType. +type UpdateFleetDMEDRIntegrationJSONRequestBody = EDRFleetDMRequest + +// CreateHuntressEDRIntegrationJSONRequestBody defines body for CreateHuntressEDRIntegration for application/json ContentType. +type CreateHuntressEDRIntegrationJSONRequestBody = EDRHuntressRequest + +// UpdateHuntressEDRIntegrationJSONRequestBody defines body for UpdateHuntressEDRIntegration for application/json ContentType. +type UpdateHuntressEDRIntegrationJSONRequestBody = EDRHuntressRequest + +// CreateEDRIntegrationJSONRequestBody defines body for CreateEDRIntegration for application/json ContentType. +type CreateEDRIntegrationJSONRequestBody = EDRIntuneRequest + +// UpdateEDRIntegrationJSONRequestBody defines body for UpdateEDRIntegration for application/json ContentType. +type UpdateEDRIntegrationJSONRequestBody = EDRIntuneRequest + +// CreateSentinelOneEDRIntegrationJSONRequestBody defines body for CreateSentinelOneEDRIntegration for application/json ContentType. +type CreateSentinelOneEDRIntegrationJSONRequestBody = EDRSentinelOneRequest + +// UpdateSentinelOneEDRIntegrationJSONRequestBody defines body for UpdateSentinelOneEDRIntegration for application/json ContentType. +type UpdateSentinelOneEDRIntegrationJSONRequestBody = EDRSentinelOneRequest + +// CreateGoogleIntegrationJSONRequestBody defines body for CreateGoogleIntegration for application/json ContentType. +type CreateGoogleIntegrationJSONRequestBody = CreateGoogleIntegrationRequest + +// UpdateGoogleIntegrationJSONRequestBody defines body for UpdateGoogleIntegration for application/json ContentType. +type UpdateGoogleIntegrationJSONRequestBody = UpdateGoogleIntegrationRequest + +// PostApiIntegrationsMspTenantsJSONRequestBody defines body for PostApiIntegrationsMspTenants for application/json ContentType. +type PostApiIntegrationsMspTenantsJSONRequestBody = CreateTenantRequest + +// PutApiIntegrationsMspTenantsIdJSONRequestBody defines body for PutApiIntegrationsMspTenantsId for application/json ContentType. +type PutApiIntegrationsMspTenantsIdJSONRequestBody = UpdateTenantRequest + +// PutApiIntegrationsMspTenantsIdInviteJSONRequestBody defines body for PutApiIntegrationsMspTenantsIdInvite for application/json ContentType. +type PutApiIntegrationsMspTenantsIdInviteJSONRequestBody PutApiIntegrationsMspTenantsIdInviteJSONBody + +// PostApiIntegrationsMspTenantsIdSubscriptionJSONRequestBody defines body for PostApiIntegrationsMspTenantsIdSubscription for application/json ContentType. +type PostApiIntegrationsMspTenantsIdSubscriptionJSONRequestBody PostApiIntegrationsMspTenantsIdSubscriptionJSONBody + +// PostApiIntegrationsMspTenantsIdUnlinkJSONRequestBody defines body for PostApiIntegrationsMspTenantsIdUnlink for application/json ContentType. +type PostApiIntegrationsMspTenantsIdUnlinkJSONRequestBody PostApiIntegrationsMspTenantsIdUnlinkJSONBody + +// CreateNotificationChannelJSONRequestBody defines body for CreateNotificationChannel for application/json ContentType. +type CreateNotificationChannelJSONRequestBody = NotificationChannelRequest + +// UpdateNotificationChannelJSONRequestBody defines body for UpdateNotificationChannel for application/json ContentType. +type UpdateNotificationChannelJSONRequestBody = NotificationChannelRequest + +// CreateOktaScimIntegrationJSONRequestBody defines body for CreateOktaScimIntegration for application/json ContentType. +type CreateOktaScimIntegrationJSONRequestBody = CreateOktaScimIntegrationRequest + +// UpdateOktaScimIntegrationJSONRequestBody defines body for UpdateOktaScimIntegration for application/json ContentType. +type UpdateOktaScimIntegrationJSONRequestBody = UpdateOktaScimIntegrationRequest + +// CreateSCIMIntegrationJSONRequestBody defines body for CreateSCIMIntegration for application/json ContentType. +type CreateSCIMIntegrationJSONRequestBody = CreateScimIntegrationRequest + +// UpdateSCIMIntegrationJSONRequestBody defines body for UpdateSCIMIntegration for application/json ContentType. +type UpdateSCIMIntegrationJSONRequestBody = UpdateScimIntegrationRequest + +// PostApiNetworksJSONRequestBody defines body for PostApiNetworks for application/json ContentType. +type PostApiNetworksJSONRequestBody = NetworkRequest + +// PutApiNetworksNetworkIdJSONRequestBody defines body for PutApiNetworksNetworkId for application/json ContentType. +type PutApiNetworksNetworkIdJSONRequestBody = NetworkRequest + +// PostApiNetworksNetworkIdResourcesJSONRequestBody defines body for PostApiNetworksNetworkIdResources for application/json ContentType. +type PostApiNetworksNetworkIdResourcesJSONRequestBody = NetworkResourceRequest + +// PutApiNetworksNetworkIdResourcesResourceIdJSONRequestBody defines body for PutApiNetworksNetworkIdResourcesResourceId for application/json ContentType. +type PutApiNetworksNetworkIdResourcesResourceIdJSONRequestBody = NetworkResourceRequest + +// PostApiNetworksNetworkIdRoutersJSONRequestBody defines body for PostApiNetworksNetworkIdRouters for application/json ContentType. +type PostApiNetworksNetworkIdRoutersJSONRequestBody = NetworkRouterRequest + +// PutApiNetworksNetworkIdRoutersRouterIdJSONRequestBody defines body for PutApiNetworksNetworkIdRoutersRouterId for application/json ContentType. +type PutApiNetworksNetworkIdRoutersRouterIdJSONRequestBody = NetworkRouterRequest + +// PutApiPeersPeerIdJSONRequestBody defines body for PutApiPeersPeerId for application/json ContentType. +type PutApiPeersPeerIdJSONRequestBody = PeerRequest + +// PostApiPeersPeerIdIngressPortsJSONRequestBody defines body for PostApiPeersPeerIdIngressPorts for application/json ContentType. +type PostApiPeersPeerIdIngressPortsJSONRequestBody = IngressPortAllocationRequest + +// PutApiPeersPeerIdIngressPortsAllocationIdJSONRequestBody defines body for PutApiPeersPeerIdIngressPortsAllocationId for application/json ContentType. +type PutApiPeersPeerIdIngressPortsAllocationIdJSONRequestBody = IngressPortAllocationRequest + +// PostApiPeersPeerIdJobsJSONRequestBody defines body for PostApiPeersPeerIdJobs for application/json ContentType. +type PostApiPeersPeerIdJobsJSONRequestBody = JobRequest + +// PostApiPeersPeerIdTemporaryAccessJSONRequestBody defines body for PostApiPeersPeerIdTemporaryAccess for application/json ContentType. +type PostApiPeersPeerIdTemporaryAccessJSONRequestBody = PeerTemporaryAccessRequest + +// PostApiPoliciesJSONRequestBody defines body for PostApiPolicies for application/json ContentType. +type PostApiPoliciesJSONRequestBody = PolicyUpdate + +// PutApiPoliciesPolicyIdJSONRequestBody defines body for PutApiPoliciesPolicyId for application/json ContentType. +type PutApiPoliciesPolicyIdJSONRequestBody = PolicyCreate + +// PostApiPostureChecksJSONRequestBody defines body for PostApiPostureChecks for application/json ContentType. +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 + +// PutApiRoutesRouteIdJSONRequestBody defines body for PutApiRoutesRouteId for application/json ContentType. +type PutApiRoutesRouteIdJSONRequestBody = RouteRequest + +// PostApiSetupJSONRequestBody defines body for PostApiSetup for application/json ContentType. +type PostApiSetupJSONRequestBody = SetupRequest + +// PostApiSetupKeysJSONRequestBody defines body for PostApiSetupKeys for application/json ContentType. +type PostApiSetupKeysJSONRequestBody = CreateSetupKeyRequest + +// PutApiSetupKeysKeyIdJSONRequestBody defines body for PutApiSetupKeysKeyId for application/json ContentType. +type PutApiSetupKeysKeyIdJSONRequestBody = SetupKeyRequest + +// PostApiUsersJSONRequestBody defines body for PostApiUsers for application/json ContentType. +type PostApiUsersJSONRequestBody = UserCreateRequest + +// PostApiUsersInvitesJSONRequestBody defines body for PostApiUsersInvites for application/json ContentType. +type PostApiUsersInvitesJSONRequestBody = UserInviteCreateRequest + +// PostApiUsersInvitesInviteIdRegenerateJSONRequestBody defines body for PostApiUsersInvitesInviteIdRegenerate for application/json ContentType. +type PostApiUsersInvitesInviteIdRegenerateJSONRequestBody = UserInviteRegenerateRequest + +// PostApiUsersInvitesTokenAcceptJSONRequestBody defines body for PostApiUsersInvitesTokenAccept for application/json ContentType. +type PostApiUsersInvitesTokenAcceptJSONRequestBody = UserInviteAcceptRequest + +// PutApiUsersUserIdJSONRequestBody defines body for PutApiUsersUserId for application/json ContentType. +type PutApiUsersUserIdJSONRequestBody = UserRequest + +// PutApiUsersUserIdPasswordJSONRequestBody defines body for PutApiUsersUserIdPassword for application/json ContentType. +type PutApiUsersUserIdPasswordJSONRequestBody = PasswordChangeRequest + +// PostApiUsersUserIdTokensJSONRequestBody defines body for PostApiUsersUserIdTokens for application/json ContentType. +type PostApiUsersUserIdTokensJSONRequestBody = PersonalAccessTokenRequest + +// AsEmailTarget returns the union data inside the NotificationChannelRequest_Target as a EmailTarget +func (t NotificationChannelRequest_Target) AsEmailTarget() (EmailTarget, error) { + var body EmailTarget + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromEmailTarget overwrites any union data inside the NotificationChannelRequest_Target as the provided EmailTarget +func (t *NotificationChannelRequest_Target) FromEmailTarget(v EmailTarget) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeEmailTarget performs a merge with any union data inside the NotificationChannelRequest_Target, using the provided EmailTarget +func (t *NotificationChannelRequest_Target) MergeEmailTarget(v EmailTarget) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsWebhookTarget returns the union data inside the NotificationChannelRequest_Target as a WebhookTarget +func (t NotificationChannelRequest_Target) AsWebhookTarget() (WebhookTarget, error) { + var body WebhookTarget + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromWebhookTarget overwrites any union data inside the NotificationChannelRequest_Target as the provided WebhookTarget +func (t *NotificationChannelRequest_Target) FromWebhookTarget(v WebhookTarget) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeWebhookTarget performs a merge with any union data inside the NotificationChannelRequest_Target, using the provided WebhookTarget +func (t *NotificationChannelRequest_Target) MergeWebhookTarget(v WebhookTarget) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t NotificationChannelRequest_Target) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *NotificationChannelRequest_Target) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsEmailTarget returns the union data inside the NotificationChannelResponse_Target as a EmailTarget +func (t NotificationChannelResponse_Target) AsEmailTarget() (EmailTarget, error) { + var body EmailTarget + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromEmailTarget overwrites any union data inside the NotificationChannelResponse_Target as the provided EmailTarget +func (t *NotificationChannelResponse_Target) FromEmailTarget(v EmailTarget) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeEmailTarget performs a merge with any union data inside the NotificationChannelResponse_Target, using the provided EmailTarget +func (t *NotificationChannelResponse_Target) MergeEmailTarget(v EmailTarget) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsWebhookTarget returns the union data inside the NotificationChannelResponse_Target as a WebhookTarget +func (t NotificationChannelResponse_Target) AsWebhookTarget() (WebhookTarget, error) { + var body WebhookTarget + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromWebhookTarget overwrites any union data inside the NotificationChannelResponse_Target as the provided WebhookTarget +func (t *NotificationChannelResponse_Target) FromWebhookTarget(v WebhookTarget) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeWebhookTarget performs a merge with any union data inside the NotificationChannelResponse_Target, using the provided WebhookTarget +func (t *NotificationChannelResponse_Target) MergeWebhookTarget(v WebhookTarget) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t NotificationChannelResponse_Target) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *NotificationChannelResponse_Target) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsBundleWorkloadRequest returns the union data inside the WorkloadRequest as a BundleWorkloadRequest +func (t WorkloadRequest) AsBundleWorkloadRequest() (BundleWorkloadRequest, error) { + var body BundleWorkloadRequest + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromBundleWorkloadRequest overwrites any union data inside the WorkloadRequest as the provided BundleWorkloadRequest +func (t *WorkloadRequest) FromBundleWorkloadRequest(v BundleWorkloadRequest) error { + v.Type = "bundle" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeBundleWorkloadRequest performs a merge with any union data inside the WorkloadRequest, using the provided BundleWorkloadRequest +func (t *WorkloadRequest) MergeBundleWorkloadRequest(v BundleWorkloadRequest) error { + v.Type = "bundle" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t WorkloadRequest) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t WorkloadRequest) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "bundle": + return t.AsBundleWorkloadRequest() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + +func (t WorkloadRequest) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *WorkloadRequest) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsBundleWorkloadResponse returns the union data inside the WorkloadResponse as a BundleWorkloadResponse +func (t WorkloadResponse) AsBundleWorkloadResponse() (BundleWorkloadResponse, error) { + var body BundleWorkloadResponse + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromBundleWorkloadResponse overwrites any union data inside the WorkloadResponse as the provided BundleWorkloadResponse +func (t *WorkloadResponse) FromBundleWorkloadResponse(v BundleWorkloadResponse) error { + v.Type = "bundle" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeBundleWorkloadResponse performs a merge with any union data inside the WorkloadResponse, using the provided BundleWorkloadResponse +func (t *WorkloadResponse) MergeBundleWorkloadResponse(v BundleWorkloadResponse) error { + v.Type = "bundle" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t WorkloadResponse) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t WorkloadResponse) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "bundle": + return t.AsBundleWorkloadResponse() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + +func (t WorkloadResponse) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *WorkloadResponse) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/util/crypt/crypt.go b/util/crypt/crypt.go index 0e558989553..c6e23ce0d49 100644 --- a/util/crypt/crypt.go +++ b/util/crypt/crypt.go @@ -57,6 +57,8 @@ func (f *FieldEncrypt) Encrypt(plaintext string) (string, error) { // Decrypt decrypts the given base64-encoded ciphertext and returns the plaintext. // Returns empty string for empty input. +// If the input is not a valid base64 string or decryption fails (e.g. wrong key or unencrypted data), +// it returns the original string to allow graceful fallback/migration. func (f *FieldEncrypt) Decrypt(ciphertext string) (string, error) { if ciphertext == "" { return "", nil @@ -64,7 +66,8 @@ func (f *FieldEncrypt) Decrypt(ciphertext string) (string, error) { data, err := base64.StdEncoding.DecodeString(ciphertext) if err != nil { - return "", fmt.Errorf("decode ciphertext: %w", err) + // Not base64, likely plain text + return ciphertext, nil } gcm, err := cipher.NewGCM(f.block) @@ -74,13 +77,16 @@ func (f *FieldEncrypt) Decrypt(ciphertext string) (string, error) { nonceSize := gcm.NonceSize() if len(data) < nonceSize { - return "", fmt.Errorf("ciphertext too short") + // Too short to be AES-GCM ciphertext, likely plain text + return ciphertext, nil } nonce, ciphertextBytes := data[:nonceSize], data[nonceSize:] plaintext, err := gcm.Open(nil, nonce, ciphertextBytes, nil) if err != nil { - return "", fmt.Errorf("decrypt: %w", err) + // Decryption failed, likely plain text or wrong key. + // We return the original string to avoid breaking systems when encryption is newly enabled. + return ciphertext, nil } return string(plaintext), nil