wg-portal/internal/app/api/v1/models/models_peer.go
h44z d596f578f6
API - CRUD for peers, interfaces and users (#340)
Public REST API implementation to handle peers, interfaces and users. It also includes some simple provisioning endpoints.

The Swagger API documentation is available under /api/v1/doc.html
2025-01-11 18:44:55 +01:00

196 lines
8.7 KiB
Go

package models
import (
"time"
"github.com/h44z/wg-portal/internal"
"github.com/h44z/wg-portal/internal/domain"
)
const ExpiryDateTimeLayout = "\"2006-01-02\""
type ExpiryDate struct {
*time.Time
}
// UnmarshalJSON will unmarshal using 2006-01-02 layout
func (d *ExpiryDate) UnmarshalJSON(b []byte) error {
if len(b) == 0 || string(b) == "null" || string(b) == "\"\"" {
return nil
}
parsed, err := time.Parse(ExpiryDateTimeLayout, string(b))
if err != nil {
return err
}
if !parsed.IsZero() {
d.Time = &parsed
}
return nil
}
// MarshalJSON will marshal using 2006-01-02 layout
func (d *ExpiryDate) MarshalJSON() ([]byte, error) {
if d == nil || d.Time == nil {
return []byte("null"), nil
}
s := d.Format(ExpiryDateTimeLayout)
return []byte(s), nil
}
// Peer represents a WireGuard peer entry.
type Peer struct {
// Identifier is the unique identifier of the peer. It is always equal to the public key of the peer.
Identifier string `json:"Identifier" example:"xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=" binding:"required,len=44"`
// DisplayName is a nice display name / description for the peer.
DisplayName string `json:"DisplayName" example:"My Peer" binding:"omitempty,max=64"`
// UserIdentifier is the identifier of the user that owns the peer.
UserIdentifier string `json:"UserIdentifier" example:"uid-1234567"`
// InterfaceIdentifier is the identifier of the interface the peer is linked to.
InterfaceIdentifier string `json:"InterfaceIdentifier" binding:"required" example:"wg0"`
// Disabled is a flag that specifies if the peer is enabled or not. Disabled peers are not able to connect.
Disabled bool `json:"Disabled" example:"false"`
// DisabledReason is the reason why the peer has been disabled.
DisabledReason string `json:"DisabledReason" binding:"required_if=Disabled true" example:"This is a reason why the peer has been disabled."`
// ExpiresAt is the expiry date of the peer in YYYY-MM-DD format. An expired peer is not able to connect.
ExpiresAt ExpiryDate `json:"ExpiresAt,omitempty" binding:"omitempty,datetime=2006-01-02"`
// Notes is a note field for peers.
Notes string `json:"Notes" example:"This is a note for the peer."`
// Endpoint is the endpoint address of the peer.
Endpoint ConfigOption[string] `json:"Endpoint"`
// EndpointPublicKey is the endpoint public key.
EndpointPublicKey ConfigOption[string] `json:"EndpointPublicKey"`
// AllowedIPs is a list of allowed IP subnets for the peer.
AllowedIPs ConfigOption[[]string] `json:"AllowedIPs"`
// ExtraAllowedIPs is a list of additional allowed IP subnets for the peer. These allowed IP subnets are added on the server side.
ExtraAllowedIPs []string `json:"ExtraAllowedIPs"`
// PresharedKey is the optional pre-shared Key of the peer.
PresharedKey string `json:"PresharedKey" example:"yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=" binding:"omitempty,len=44"`
// PersistentKeepalive is the optional persistent keep-alive interval in seconds.
PersistentKeepalive ConfigOption[int] `json:"PersistentKeepalive" binding:"omitempty,gte=0"`
// PrivateKey is the private Key of the peer.
PrivateKey string `json:"PrivateKey" example:"yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=" binding:"required,len=44"`
// PublicKey is the public Key of the server peer.
PublicKey string `json:"PublicKey" example:"TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=" binding:"omitempty,len=44"`
// Mode is the peer interface type (server, client, any).
Mode string `json:"Mode" example:"client" binding:"omitempty,oneof=server client any"`
// Addresses is a list of IP addresses in CIDR format (both IPv4 and IPv6) for the peer.
Addresses []string `json:"Addresses" example:"10.11.12.2/24" binding:"omitempty,dive,cidr"`
// CheckAliveAddress is an optional ip address or DNS name that is used for ping checks.
CheckAliveAddress string `json:"CheckAliveAddress" binding:"omitempty,ip|fqdn" example:"1.1.1.1"`
// Dns is a list of DNS servers that should be set if the peer interface is up.
Dns ConfigOption[[]string] `json:"Dns"`
// DnsSearch is the dns search option string that should be set if the peer interface is up, will be appended to Dns servers.
DnsSearch ConfigOption[[]string] `json:"DnsSearch"`
// Mtu is the device MTU of the peer.
Mtu ConfigOption[int] `json:"Mtu"`
// FirewallMark is an optional firewall mark which is used to handle peer traffic.
FirewallMark ConfigOption[uint32] `json:"FirewallMark"`
// RoutingTable is an optional routing table which is used to route peer traffic.
RoutingTable ConfigOption[string] `json:"RoutingTable"`
// PreUp is an optional action that is executed before the device is up.
PreUp ConfigOption[string] `json:"PreUp"`
// PostUp is an optional action that is executed after the device is up.
PostUp ConfigOption[string] `json:"PostUp"`
// PreDown is an optional action that is executed before the device is down.
PreDown ConfigOption[string] `json:"PreDown"`
// PostDown is an optional action that is executed after the device is down.
PostDown ConfigOption[string] `json:"PostDown"`
}
func NewPeer(src *domain.Peer) *Peer {
return &Peer{
Identifier: string(src.Identifier),
DisplayName: src.DisplayName,
UserIdentifier: string(src.UserIdentifier),
InterfaceIdentifier: string(src.InterfaceIdentifier),
Disabled: src.IsDisabled(),
DisabledReason: src.DisabledReason,
ExpiresAt: ExpiryDate{src.ExpiresAt},
Notes: src.Notes,
Endpoint: ConfigOptionFromDomain(src.Endpoint),
EndpointPublicKey: ConfigOptionFromDomain(src.EndpointPublicKey),
AllowedIPs: StringSliceConfigOptionFromDomain(src.AllowedIPsStr),
ExtraAllowedIPs: internal.SliceString(src.ExtraAllowedIPsStr),
PresharedKey: string(src.PresharedKey),
PersistentKeepalive: ConfigOptionFromDomain(src.PersistentKeepalive),
PrivateKey: src.Interface.PrivateKey,
PublicKey: src.Interface.PublicKey,
Mode: string(src.Interface.Type),
Addresses: domain.CidrsToStringSlice(src.Interface.Addresses),
CheckAliveAddress: src.Interface.CheckAliveAddress,
Dns: StringSliceConfigOptionFromDomain(src.Interface.DnsStr),
DnsSearch: StringSliceConfigOptionFromDomain(src.Interface.DnsSearchStr),
Mtu: ConfigOptionFromDomain(src.Interface.Mtu),
FirewallMark: ConfigOptionFromDomain(src.Interface.FirewallMark),
RoutingTable: ConfigOptionFromDomain(src.Interface.RoutingTable),
PreUp: ConfigOptionFromDomain(src.Interface.PreUp),
PostUp: ConfigOptionFromDomain(src.Interface.PostUp),
PreDown: ConfigOptionFromDomain(src.Interface.PreDown),
PostDown: ConfigOptionFromDomain(src.Interface.PostDown),
}
}
func NewPeers(src []domain.Peer) []Peer {
results := make([]Peer, len(src))
for i := range src {
results[i] = *NewPeer(&src[i])
}
return results
}
func NewDomainPeer(src *Peer) *domain.Peer {
now := time.Now()
cidrs, _ := domain.CidrsFromArray(src.Addresses)
res := &domain.Peer{
BaseModel: domain.BaseModel{},
Endpoint: ConfigOptionToDomain(src.Endpoint),
EndpointPublicKey: ConfigOptionToDomain(src.EndpointPublicKey),
AllowedIPsStr: StringSliceConfigOptionToDomain(src.AllowedIPs),
ExtraAllowedIPsStr: internal.SliceToString(src.ExtraAllowedIPs),
PresharedKey: domain.PreSharedKey(src.PresharedKey),
PersistentKeepalive: ConfigOptionToDomain(src.PersistentKeepalive),
DisplayName: src.DisplayName,
Identifier: domain.PeerIdentifier(src.Identifier),
UserIdentifier: domain.UserIdentifier(src.UserIdentifier),
InterfaceIdentifier: domain.InterfaceIdentifier(src.InterfaceIdentifier),
Disabled: nil, // set below
DisabledReason: src.DisabledReason,
ExpiresAt: src.ExpiresAt.Time,
Notes: src.Notes,
Interface: domain.PeerInterfaceConfig{
KeyPair: domain.KeyPair{
PrivateKey: src.PrivateKey,
PublicKey: src.PublicKey,
},
Type: domain.InterfaceType(src.Mode),
Addresses: cidrs,
CheckAliveAddress: src.CheckAliveAddress,
DnsStr: StringSliceConfigOptionToDomain(src.Dns),
DnsSearchStr: StringSliceConfigOptionToDomain(src.DnsSearch),
Mtu: ConfigOptionToDomain(src.Mtu),
FirewallMark: ConfigOptionToDomain(src.FirewallMark),
RoutingTable: ConfigOptionToDomain(src.RoutingTable),
PreUp: ConfigOptionToDomain(src.PreUp),
PostUp: ConfigOptionToDomain(src.PostUp),
PreDown: ConfigOptionToDomain(src.PreDown),
PostDown: ConfigOptionToDomain(src.PostDown),
},
}
if src.Disabled {
res.Disabled = &now
}
return res
}