From 288b7794cab4ca264b6b93c9cd2d858be2c93690 Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Tue, 2 Apr 2024 22:29:10 +0200 Subject: [PATCH] fix default peer creation on login (#189) --- README.md | 179 +++++++++++----------- config.yml.sample | 2 + internal/app/repos.go | 2 +- internal/app/wireguard/wireguard.go | 44 +++++- internal/app/wireguard/wireguard_peers.go | 9 +- internal/config/config.go | 13 +- internal/domain/peer.go | 17 +- 7 files changed, 150 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 2f4ec1f..6912c98 100644 --- a/README.md +++ b/README.md @@ -53,95 +53,96 @@ By default, WireGuard Portal uses a SQLite database. The database is stored in * ### Configuration Options The following configuration options are available: -| configuration key | parent key | default_value | description | -|---------------------------|------------|--------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| admin_user | core | admin@wgportal.local | The administrator user. This user will be created as default admin if it does not yet exist. | -| admin_password | core | wgportal | The administrator password. If unchanged, a random password will be set on first startup. | -| editable_keys | core | true | Allow to edit key-pairs in the UI. | -| create_default_peer | core | false | If an LDAP user logs in for the first time, a new WireGuard peer will be created on the WG_DEFAULT_DEVICE if this option is enabled. | -| self_provisioning_allowed | core | false | Allow registered users to automatically create peers via their profile page. | -| import_existing | core | true | Import existing WireGuard interfaces and peers into WireGuard Portal. | -| restore_state | core | true | Restore the WireGuard interface state after WireGuard Portal has started. | -| log_level | advanced | warn | The loglevel, can be one of: trace, debug, info, warn, error. | -| log_pretty | advanced | false | Uses pretty, colorized log messages. | -| log_json | advanced | false | Logs in JSON format. | -| ldap_sync_interval | advanced | 15m | The time interval after which users will be synchronized from LDAP. | -| start_listen_port | advanced | 51820 | The first port number that will be used as listening port for new interfaces. | -| start_cidr_v4 | advanced | 10.11.12.0/24 | The first IPv4 subnet that will be used for new interfaces. | -| start_cidr_v6 | advanced | fdfd:d3ad:c0de:1234::0/64 | The first IPv6 subnet that will be used for new interfaces. | -| use_ip_v6 | advanced | true | Enable IPv6 support. | -| config_storage_path | advanced | | If a wg-quick style configuration should be stored to the filesystem, specify a storage directory. | -| expiry_check_interval | advanced | 15m | The interval after which existing peers will be checked if they expired. | -| rule_prio_offset | advanced | 20000 | The default offset for ip route rule priorities. | -| route_table_offset | advanced | 20000 | The default offset for ip route table id's. | -| use_ping_checks | statistics | true | If enabled, peers will be pinged periodically to check if they are still connected. | -| ping_check_workers | statistics | 10 | Number of parallel ping checks that will be executed. | -| ping_unprivileged | statistics | false | If set to false, the ping checks will run without root permissions (BETA). | -| ping_check_interval | statistics | 1m | The interval time between two ping check runs. | -| data_collection_interval | statistics | 10m | The interval between the data collection cycles. | -| collect_interface_data | statistics | true | A flag to enable interface data collection like bytes sent and received. | -| collect_peer_data | statistics | true | A flag to enable peer data collection like bytes sent and received, last handshake and remote endpoint address. | -| collect_audit_data | statistics | true | If enabled, some events, like portal logins, will be logged to the database. | -| host | mail | 127.0.0.1 | The mail-server address. | -| port | mail | 25 | The mail-server SMTP port. | -| encryption | mail | none | SMTP encryption type, allowed values: none, tls, starttls. | -| cert_validation | mail | false | Validate the mail server certificate (if encryption tls is used). | -| username | mail | | The SMTP user name. | -| password | mail | | The SMTP password. | -| auth_type | mail | plain | SMTP authentication type, allowed values: plain, login, crammd5. | -| from | mail | Wireguard Portal | The address that is used to send mails. | -| link_only | mail | false | Only send links to WireGuard Portal instead of the full configuration. | -| callback_url_prefix | auth | /api/v0 | OAuth callback URL prefix. The full callback URL will look like: https://wg.portal.local/callback_url_prefix/provider_name/callback | -| oidc | auth | Empty Array - no providers configured | A list of OpenID Connect providers. See auth/oidc properties to setup a new provider. | -| oauth | auth | Empty Array - no providers configured | A list of plain OAuth providers. See auth/oauth properties to setup a new provider. | -| ldap | auth | Empty Array - no providers configured | A list of LDAP providers. See auth/ldap properties to setup a new provider. | -| provider_name | auth/oidc | | A unique provider name. This name must be unique throughout all authentication providers (even other types). | -| display_name | auth/oidc | | The display name is shown at the login page (the login button). | -| base_url | auth/oidc | | The base_url is the URL identifier for the service. For example: "https://accounts.google.com". | -| client_id | auth/oidc | | The OAuth client id. | -| client_secret | auth/oidc | | The OAuth client secret. | -| extra_scopes | auth/oidc | | Extra scopes that should be used in the OpenID Connect authentication flow. | -| field_map | auth/oidc | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and is_admin. | -| registration_enabled | auth/oidc | | If registration is enabled, new user accounts will created in WireGuard Portal. | -| provider_name | auth/oauth | | A unique provider name. This name must be unique throughout all authentication providers (even other types). | -| display_name | auth/oauth | | The display name is shown at the login page (the login button). | -| base_url | auth/oauth | | The base_url is the URL identifier for the service. For example: "https://accounts.google.com". | -| client_id | auth/oauth | | The OAuth client id. | -| client_secret | auth/oauth | | The OAuth client secret. | -| auth_url | auth/oauth | | The URL for the authentication endpoint. | -| token_url | auth/oauth | | The URL for the token endpoint. | -| redirect_url | auth/oauth | | The redirect URL. | -| user_info_url | auth/oauth | | The URL for the user information endpoint. | -| scopes | auth/oauth | | OAuth scopes. | -| field_map | auth/oauth | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and is_admin. | -| registration_enabled | auth/oauth | | If registration is enabled, new user accounts will created in WireGuard Portal. | -| url | auth/ldap | | The LDAP server url. For example: ldap://srv-ad01.company.local:389 | -| start_tls | auth/ldap | | Use STARTTLS to encrypt LDAP requests. | -| cert_validation | auth/ldap | | Validate the LDAP server certificate. | -| tls_certificate_path | auth/ldap | | A path to the TLS certificate. | -| tls_key_path | auth/ldap | | A path to the TLS key. | -| base_dn | auth/ldap | | The base DN for searching users. For example: DC=COMPANY,DC=LOCAL | -| bind_user | auth/ldap | | The bind user. For example: company\\ldap_wireguard | -| bind_pass | auth/ldap | | The bind password. | -| field_map | auth/ldap | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and memberof. | -| login_filter | auth/ldap | | LDAP filters for users that should be allowed to log in. {{login_identifier}} will be replaced with the login username. | -| admin_group | auth/ldap | | Users in this group are marked as administrators. | -| synchronize | auth/ldap | | Periodically synchronize users (name, department, phone, status, ...) to the WireGuard Portal database. | -| disable_missing | auth/ldap | | If synchronization is enabled, missing LDAP users will be disabled in WireGuard Portal. | -| sync_filter | auth/ldap | | LDAP filters for users that should be synchronized to WireGuard Portal. | -| registration_enabled | auth/ldap | | If registration is enabled, new user accounts will created in WireGuard Portal. | -| debug | database | false | Debug database statements (log each statement). | -| slow_query_threshold | database | | A threshold for slow database queries. If the threshold is exceeded, a warning message will be logged. | -| type | database | sqlite | The database type. Allowed values: sqlite, mssql, mysql or postgres. | -| dsn | database | data/sqlite.db | The database DSN. For example: user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local | -| request_logging | web | false | Log all HTTP requests. | -| external_url | web | http://localhost:8888 | The URL where a client can access WireGuard Portal. | -| listening_address | web | :8888 | The listening port of the web server. | -| session_identifier | web | wgPortalSession | The session identifier for the web frontend. | -| session_secret | web | very_secret | The session secret for the web frontend. | -| csrf_secret | web | extremely_secret | The CSRF secret. | -| site_title | web | WireGuard Portal | The title that is shown in the web frontend. | -| site_company_name | web | WireGuard Portal | The company name that is shown at the bottom of the web frontend. | +| configuration key | parent key | default_value | description | +|---------------------------------|------------|--------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| admin_user | core | admin@wgportal.local | The administrator user. This user will be created as default admin if it does not yet exist. | +| admin_password | core | wgportal | The administrator password. If unchanged, a random password will be set on first startup. | +| editable_keys | core | true | Allow to edit key-pairs in the UI. | +| create_default_peer | core | false | If an LDAP user logs in for the first time and has no peers associated, a new WireGuard peer will be created for all server interfaces. | +| create_default_peer_on_creation | core | false | If an LDAP user is created (e.g. through LDAP sync), a new WireGuard peer will be created for all server interfaces. | +| self_provisioning_allowed | core | false | Allow registered users to automatically create peers via their profile page. | +| import_existing | core | true | Import existing WireGuard interfaces and peers into WireGuard Portal. | +| restore_state | core | true | Restore the WireGuard interface state after WireGuard Portal has started. | +| log_level | advanced | warn | The loglevel, can be one of: trace, debug, info, warn, error. | +| log_pretty | advanced | false | Uses pretty, colorized log messages. | +| log_json | advanced | false | Logs in JSON format. | +| ldap_sync_interval | advanced | 15m | The time interval after which users will be synchronized from LDAP. | +| start_listen_port | advanced | 51820 | The first port number that will be used as listening port for new interfaces. | +| start_cidr_v4 | advanced | 10.11.12.0/24 | The first IPv4 subnet that will be used for new interfaces. | +| start_cidr_v6 | advanced | fdfd:d3ad:c0de:1234::0/64 | The first IPv6 subnet that will be used for new interfaces. | +| use_ip_v6 | advanced | true | Enable IPv6 support. | +| config_storage_path | advanced | | If a wg-quick style configuration should be stored to the filesystem, specify a storage directory. | +| expiry_check_interval | advanced | 15m | The interval after which existing peers will be checked if they expired. | +| rule_prio_offset | advanced | 20000 | The default offset for ip route rule priorities. | +| route_table_offset | advanced | 20000 | The default offset for ip route table id's. | +| use_ping_checks | statistics | true | If enabled, peers will be pinged periodically to check if they are still connected. | +| ping_check_workers | statistics | 10 | Number of parallel ping checks that will be executed. | +| ping_unprivileged | statistics | false | If set to false, the ping checks will run without root permissions (BETA). | +| ping_check_interval | statistics | 1m | The interval time between two ping check runs. | +| data_collection_interval | statistics | 10m | The interval between the data collection cycles. | +| collect_interface_data | statistics | true | A flag to enable interface data collection like bytes sent and received. | +| collect_peer_data | statistics | true | A flag to enable peer data collection like bytes sent and received, last handshake and remote endpoint address. | +| collect_audit_data | statistics | true | If enabled, some events, like portal logins, will be logged to the database. | +| host | mail | 127.0.0.1 | The mail-server address. | +| port | mail | 25 | The mail-server SMTP port. | +| encryption | mail | none | SMTP encryption type, allowed values: none, tls, starttls. | +| cert_validation | mail | false | Validate the mail server certificate (if encryption tls is used). | +| username | mail | | The SMTP user name. | +| password | mail | | The SMTP password. | +| auth_type | mail | plain | SMTP authentication type, allowed values: plain, login, crammd5. | +| from | mail | Wireguard Portal | The address that is used to send mails. | +| link_only | mail | false | Only send links to WireGuard Portal instead of the full configuration. | +| callback_url_prefix | auth | /api/v0 | OAuth callback URL prefix. The full callback URL will look like: https://wg.portal.local/callback_url_prefix/provider_name/callback | +| oidc | auth | Empty Array - no providers configured | A list of OpenID Connect providers. See auth/oidc properties to setup a new provider. | +| oauth | auth | Empty Array - no providers configured | A list of plain OAuth providers. See auth/oauth properties to setup a new provider. | +| ldap | auth | Empty Array - no providers configured | A list of LDAP providers. See auth/ldap properties to setup a new provider. | +| provider_name | auth/oidc | | A unique provider name. This name must be unique throughout all authentication providers (even other types). | +| display_name | auth/oidc | | The display name is shown at the login page (the login button). | +| base_url | auth/oidc | | The base_url is the URL identifier for the service. For example: "https://accounts.google.com". | +| client_id | auth/oidc | | The OAuth client id. | +| client_secret | auth/oidc | | The OAuth client secret. | +| extra_scopes | auth/oidc | | Extra scopes that should be used in the OpenID Connect authentication flow. | +| field_map | auth/oidc | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and is_admin. | +| registration_enabled | auth/oidc | | If registration is enabled, new user accounts will created in WireGuard Portal. | +| provider_name | auth/oauth | | A unique provider name. This name must be unique throughout all authentication providers (even other types). | +| display_name | auth/oauth | | The display name is shown at the login page (the login button). | +| base_url | auth/oauth | | The base_url is the URL identifier for the service. For example: "https://accounts.google.com". | +| client_id | auth/oauth | | The OAuth client id. | +| client_secret | auth/oauth | | The OAuth client secret. | +| auth_url | auth/oauth | | The URL for the authentication endpoint. | +| token_url | auth/oauth | | The URL for the token endpoint. | +| redirect_url | auth/oauth | | The redirect URL. | +| user_info_url | auth/oauth | | The URL for the user information endpoint. | +| scopes | auth/oauth | | OAuth scopes. | +| field_map | auth/oauth | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and is_admin. | +| registration_enabled | auth/oauth | | If registration is enabled, new user accounts will created in WireGuard Portal. | +| url | auth/ldap | | The LDAP server url. For example: ldap://srv-ad01.company.local:389 | +| start_tls | auth/ldap | | Use STARTTLS to encrypt LDAP requests. | +| cert_validation | auth/ldap | | Validate the LDAP server certificate. | +| tls_certificate_path | auth/ldap | | A path to the TLS certificate. | +| tls_key_path | auth/ldap | | A path to the TLS key. | +| base_dn | auth/ldap | | The base DN for searching users. For example: DC=COMPANY,DC=LOCAL | +| bind_user | auth/ldap | | The bind user. For example: company\\ldap_wireguard | +| bind_pass | auth/ldap | | The bind password. | +| field_map | auth/ldap | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and memberof. | +| login_filter | auth/ldap | | LDAP filters for users that should be allowed to log in. {{login_identifier}} will be replaced with the login username. | +| admin_group | auth/ldap | | Users in this group are marked as administrators. | +| synchronize | auth/ldap | | Periodically synchronize users (name, department, phone, status, ...) to the WireGuard Portal database. | +| disable_missing | auth/ldap | | If synchronization is enabled, missing LDAP users will be disabled in WireGuard Portal. | +| sync_filter | auth/ldap | | LDAP filters for users that should be synchronized to WireGuard Portal. | +| registration_enabled | auth/ldap | | If registration is enabled, new user accounts will created in WireGuard Portal. | +| debug | database | false | Debug database statements (log each statement). | +| slow_query_threshold | database | | A threshold for slow database queries. If the threshold is exceeded, a warning message will be logged. | +| type | database | sqlite | The database type. Allowed values: sqlite, mssql, mysql or postgres. | +| dsn | database | data/sqlite.db | The database DSN. For example: user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local | +| request_logging | web | false | Log all HTTP requests. | +| external_url | web | http://localhost:8888 | The URL where a client can access WireGuard Portal. | +| listening_address | web | :8888 | The listening port of the web server. | +| session_identifier | web | wgPortalSession | The session identifier for the web frontend. | +| session_secret | web | very_secret | The session secret for the web frontend. | +| csrf_secret | web | extremely_secret | The CSRF secret. | +| site_title | web | WireGuard Portal | The title that is shown in the web frontend. | +| site_company_name | web | WireGuard Portal | The company name that is shown at the bottom of the web frontend. | ## Upgrading from V1 diff --git a/config.yml.sample b/config.yml.sample index 1303274..a1e5dc4 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -4,6 +4,8 @@ advanced: core: admin_user: test@test.de admin_password: secret + create_default_peer: true + create_default_peer_on_creation: false web: external_url: http://localhost:8888 diff --git a/internal/app/repos.go b/internal/app/repos.go index 4f2ce2b..1e754a6 100644 --- a/internal/app/repos.go +++ b/internal/app/repos.go @@ -30,7 +30,7 @@ type WireGuardManager interface { GetImportableInterfaces(ctx context.Context) ([]domain.PhysicalInterface, error) ImportNewInterfaces(ctx context.Context, filter ...domain.InterfaceIdentifier) (int, error) RestoreInterfaceState(ctx context.Context, updateDbOnError bool, filter ...domain.InterfaceIdentifier) error - CreateDefaultPeer(ctx context.Context, user *domain.User) error + CreateDefaultPeer(ctx context.Context, userId domain.UserIdentifier) error GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error) GetPeerStats(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.PeerStatus, error) GetUserPeerStats(ctx context.Context, id domain.UserIdentifier) ([]domain.PeerStatus, error) diff --git a/internal/app/wireguard/wireguard.go b/internal/app/wireguard/wireguard.go index d720470..96f9f52 100644 --- a/internal/app/wireguard/wireguard.go +++ b/internal/app/wireguard/wireguard.go @@ -41,18 +41,46 @@ func (m Manager) StartBackgroundJobs(ctx context.Context) { func (m Manager) connectToMessageBus() { _ = m.bus.Subscribe(app.TopicUserCreated, m.handleUserCreationEvent) + _ = m.bus.Subscribe(app.TopicAuthLogin, m.handleUserLoginEvent) } func (m Manager) handleUserCreationEvent(user *domain.User) { - logrus.Errorf("handling new user event for %s", user.Identifier) + if !m.cfg.Core.CreateDefaultPeerOnCreation { + return + } - if m.cfg.Core.CreateDefaultPeer { - ctx := domain.SetUserInfo(context.Background(), domain.SystemAdminContextUserInfo()) - err := m.CreateDefaultPeer(ctx, user) - if err != nil { - logrus.Errorf("failed to create default peer for %s: %v", user.Identifier, err) - return - } + logrus.Tracef("handling new user event for %s", user.Identifier) + + ctx := domain.SetUserInfo(context.Background(), domain.SystemAdminContextUserInfo()) + err := m.CreateDefaultPeer(ctx, user.Identifier) + if err != nil { + logrus.Errorf("failed to create default peer for %s: %v", user.Identifier, err) + return + } +} + +func (m Manager) handleUserLoginEvent(userId domain.UserIdentifier) { + if !m.cfg.Core.CreateDefaultPeer { + return + } + + userPeers, err := m.db.GetUserPeers(context.Background(), userId) + if err != nil { + logrus.Errorf("failed to retrieve existing peers for %s prior to default peer creation: %v", userId, err) + return + } + + if len(userPeers) > 0 { + return // user already has peers, skip creation + } + + logrus.Tracef("handling new user login for %s", userId) + + ctx := domain.SetUserInfo(context.Background(), domain.SystemAdminContextUserInfo()) + err = m.CreateDefaultPeer(ctx, userId) + if err != nil { + logrus.Errorf("failed to create default peer for %s: %v", userId, err) + return } } diff --git a/internal/app/wireguard/wireguard_peers.go b/internal/app/wireguard/wireguard_peers.go index d8c55ca..1e07252 100644 --- a/internal/app/wireguard/wireguard_peers.go +++ b/internal/app/wireguard/wireguard_peers.go @@ -11,7 +11,7 @@ import ( "time" ) -func (m Manager) CreateDefaultPeer(ctx context.Context, user *domain.User) error { +func (m Manager) CreateDefaultPeer(ctx context.Context, userId domain.UserIdentifier) error { if err := domain.ValidateAdminAccessRights(ctx); err != nil { return err } @@ -32,9 +32,10 @@ func (m Manager) CreateDefaultPeer(ctx context.Context, user *domain.User) error return fmt.Errorf("failed to create default peer for interface %s: %w", iface.Identifier, err) } - peer.UserIdentifier = user.Identifier + peer.UserIdentifier = userId peer.DisplayName = fmt.Sprintf("Default Peer %s", internal.TruncateString(string(peer.Identifier), 8)) - peer.Notes = fmt.Sprintf("Default peer created for user %s", user.Identifier) + peer.Notes = fmt.Sprintf("Default peer created for user %s", userId) + peer.AutomaticallyCreated = true newPeers = append(newPeers, *peer) } @@ -47,7 +48,7 @@ func (m Manager) CreateDefaultPeer(ctx context.Context, user *domain.User) error } } - logrus.Infof("created %d default peers for user %s", len(newPeers), user.Identifier) + logrus.Infof("created %d default peers for user %s", len(newPeers), userId) return nil } diff --git a/internal/config/config.go b/internal/config/config.go index 1feb690..fd5bf89 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -15,11 +15,12 @@ type Config struct { AdminUser string `yaml:"admin_user"` AdminPassword string `yaml:"admin_password"` - EditableKeys bool `yaml:"editable_keys"` - CreateDefaultPeer bool `yaml:"create_default_peer"` - SelfProvisioningAllowed bool `yaml:"self_provisioning_allowed"` - ImportExisting bool `yaml:"import_existing"` - RestoreState bool `yaml:"restore_state"` + EditableKeys bool `yaml:"editable_keys"` + CreateDefaultPeer bool `yaml:"create_default_peer"` + CreateDefaultPeerOnCreation bool `yaml:"create_default_peer_on_creation"` + SelfProvisioningAllowed bool `yaml:"self_provisioning_allowed"` + ImportExisting bool `yaml:"import_existing"` + RestoreState bool `yaml:"restore_state"` } `yaml:"core"` Advanced struct { @@ -60,7 +61,7 @@ type Config struct { func (c *Config) LogStartupValues() { logrus.Debug("WireGuard Portal Features:") logrus.Debugf(" - EditableKeys: %t", c.Core.EditableKeys) - logrus.Debugf(" - CreateDefaultPeer: %t", c.Core.CreateDefaultPeer) + logrus.Debugf(" - CreateDefaultPeerOnCreation: %t", c.Core.CreateDefaultPeerOnCreation) logrus.Debugf(" - SelfProvisioningAllowed: %t", c.Core.SelfProvisioningAllowed) logrus.Debugf(" - ImportExisting: %t", c.Core.ImportExisting) logrus.Debugf(" - RestoreState: %t", c.Core.RestoreState) diff --git a/internal/domain/peer.go b/internal/domain/peer.go index 565c865..7bf988e 100644 --- a/internal/domain/peer.go +++ b/internal/domain/peer.go @@ -40,14 +40,15 @@ type Peer struct { // WG Portal specific - DisplayName string // a nice display name/ description for the peer - Identifier PeerIdentifier `gorm:"primaryKey;column:identifier"` // peer unique identifier - UserIdentifier UserIdentifier `gorm:"index;column:user_identifier"` // the owner - InterfaceIdentifier InterfaceIdentifier `gorm:"index;column:interface_identifier"` // the interface id - Disabled *time.Time `gorm:"column:disabled"` // if this field is set, the peer is disabled - DisabledReason string // the reason why the peer has been disabled - ExpiresAt *time.Time `gorm:"column:expires_at"` // expiry dates for peers - Notes string `form:"notes" binding:"omitempty"` // a note field for peers + DisplayName string // a nice display name/ description for the peer + Identifier PeerIdentifier `gorm:"primaryKey;column:identifier"` // peer unique identifier + UserIdentifier UserIdentifier `gorm:"index;column:user_identifier"` // the owner + InterfaceIdentifier InterfaceIdentifier `gorm:"index;column:interface_identifier"` // the interface id + Disabled *time.Time `gorm:"column:disabled"` // if this field is set, the peer is disabled + DisabledReason string // the reason why the peer has been disabled + ExpiresAt *time.Time `gorm:"column:expires_at"` // expiry dates for peers + Notes string `form:"notes" binding:"omitempty"` // a note field for peers + AutomaticallyCreated bool `gorm:"column:auto_created"` // specifies if the peer was automatically created // Interface settings for the peer, used to generate the [interface] section in the peer config file Interface PeerInterfaceConfig `gorm:"embedded"`