mirror of
https://github.com/h44z/wg-portal
synced 2025-06-26 18:16:21 +00:00
provisioning endpoints
This commit is contained in:
parent
38838eb4ce
commit
fb19b6c4a8
@ -37,6 +37,7 @@
|
||||
"users": "Benutzer",
|
||||
"lang": "Sprache ändern",
|
||||
"profile": "Mein Profil",
|
||||
"settings": "Einstellungen",
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden"
|
||||
},
|
||||
@ -167,6 +168,26 @@
|
||||
"button-show-peer": "Show Peer",
|
||||
"button-edit-peer": "Edit Peer"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "Einstellungen",
|
||||
"abstract": "Hier finden Sie persönliche Einstellungen für WireGuard Portal.",
|
||||
"api": {
|
||||
"headline": "API Einstellungen",
|
||||
"abstract": "Hier können Sie die RESTful API verwalten.",
|
||||
"active-description": "Die API ist derzeit für Ihr Benutzerkonto aktiv. Alle API-Anfragen werden mit Basic Auth authentifiziert. Verwenden Sie zur Authentifizierung die folgenden Anmeldeinformationen.",
|
||||
"inactive-description": "Die API ist derzeit inaktiv. Klicken Sie auf die Schaltfläche unten, um sie zu aktivieren.",
|
||||
"user-label": "API Benutzername:",
|
||||
"user-placeholder": "API Benutzer",
|
||||
"token-label": "API Passwort:",
|
||||
"token-placeholder": "API Token",
|
||||
"token-created-label": "API-Zugriff gewährt seit: ",
|
||||
"button-disable-title": "Deaktivieren Sie die API. Dadurch wird der aktuelle Token ungültig.",
|
||||
"button-disable-text": "API deaktivieren",
|
||||
"button-enable-title": "Aktivieren Sie die API, dadurch wird ein neuer Token generiert.",
|
||||
"button-enable-text": "API aktivieren",
|
||||
"api-link": "API Dokumentation"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"user-view": {
|
||||
"headline": "User Account:",
|
||||
|
@ -674,6 +674,7 @@
|
||||
"/peer/config-qr/{id}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"image/png",
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
@ -694,7 +695,7 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"type": "file"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
@ -1964,6 +1965,9 @@
|
||||
"model.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ApiAdminOnly": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"MailLinkOnly": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -357,6 +357,8 @@ definitions:
|
||||
type: object
|
||||
model.Settings:
|
||||
properties:
|
||||
ApiAdminOnly:
|
||||
type: boolean
|
||||
MailLinkOnly:
|
||||
type: boolean
|
||||
PersistentConfigSupported:
|
||||
@ -943,12 +945,13 @@ paths:
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- image/png
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
type: file
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
@ -867,6 +867,73 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/provisioning/new-peer": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BasicAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Normal users can only create new peers if self provisioning is allowed. Admins can always add new peers.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Provisioning"
|
||||
],
|
||||
"summary": "Create a new peer for the given interface and user.",
|
||||
"operationId": "provisioning_handleNewPeerPost",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Provisioning request model.",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProvisioningRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Peer"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Error"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Error"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Error"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/all": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -1661,6 +1728,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProvisioningRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"InterfaceIdentifier"
|
||||
],
|
||||
"properties": {
|
||||
"InterfaceIdentifier": {
|
||||
"description": "InterfaceIdentifier is the identifier of the WireGuard interface the peer should be linked to.",
|
||||
"type": "string",
|
||||
"example": "wg0"
|
||||
},
|
||||
"PresharedKey": {
|
||||
"description": "PresharedKey is the optional pre-shared key of the peer. If no pre-shared key is set, a new key is generated.",
|
||||
"type": "string",
|
||||
"example": "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk="
|
||||
},
|
||||
"PublicKey": {
|
||||
"description": "PublicKey is the optional public key of the peer. If no public key is set, a new key pair is generated.",
|
||||
"type": "string",
|
||||
"example": "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="
|
||||
},
|
||||
"UserIdentifier": {
|
||||
"description": "UserIdentifier is the identifier of the user the peer should be linked to.\nIf no user identifier is set, the authenticated user is used.",
|
||||
"type": "string",
|
||||
"example": "uid-1234567"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.User": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -1775,16 +1870,19 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"PeerCount": {
|
||||
"description": "PeerCount is the number of peers linked to the user.",
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
},
|
||||
"Peers": {
|
||||
"description": "Peers is a list of peers linked to the user.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.UserInformationPeer"
|
||||
}
|
||||
},
|
||||
"UserIdentifier": {
|
||||
"description": "UserIdentifier is the unique identifier of the user.",
|
||||
"type": "string",
|
||||
"example": "uid-1234567"
|
||||
}
|
||||
|
@ -383,6 +383,32 @@ definitions:
|
||||
- InterfaceIdentifier
|
||||
- PrivateKey
|
||||
type: object
|
||||
models.ProvisioningRequest:
|
||||
properties:
|
||||
InterfaceIdentifier:
|
||||
description: InterfaceIdentifier is the identifier of the WireGuard interface
|
||||
the peer should be linked to.
|
||||
example: wg0
|
||||
type: string
|
||||
PresharedKey:
|
||||
description: PresharedKey is the optional pre-shared key of the peer. If no
|
||||
pre-shared key is set, a new key is generated.
|
||||
example: yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
|
||||
type: string
|
||||
PublicKey:
|
||||
description: PublicKey is the optional public key of the peer. If no public
|
||||
key is set, a new key pair is generated.
|
||||
example: xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
|
||||
type: string
|
||||
UserIdentifier:
|
||||
description: |-
|
||||
UserIdentifier is the identifier of the user the peer should be linked to.
|
||||
If no user identifier is set, the authenticated user is used.
|
||||
example: uid-1234567
|
||||
type: string
|
||||
required:
|
||||
- InterfaceIdentifier
|
||||
type: object
|
||||
models.User:
|
||||
properties:
|
||||
ApiEnabled:
|
||||
@ -478,13 +504,16 @@ definitions:
|
||||
models.UserInformation:
|
||||
properties:
|
||||
PeerCount:
|
||||
description: PeerCount is the number of peers linked to the user.
|
||||
example: 2
|
||||
type: integer
|
||||
Peers:
|
||||
description: Peers is a list of peers linked to the user.
|
||||
items:
|
||||
$ref: '#/definitions/models.UserInformationPeer'
|
||||
type: array
|
||||
UserIdentifier:
|
||||
description: UserIdentifier is the unique identifier of the user.
|
||||
example: uid-1234567
|
||||
type: string
|
||||
type: object
|
||||
@ -1087,6 +1116,50 @@ paths:
|
||||
summary: Get information about all peer records for a given user.
|
||||
tags:
|
||||
- Provisioning
|
||||
/provisioning/new-peer:
|
||||
post:
|
||||
description: Normal users can only create new peers if self provisioning is
|
||||
allowed. Admins can always add new peers.
|
||||
operationId: provisioning_handleNewPeerPost
|
||||
parameters:
|
||||
- description: Provisioning request model.
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProvisioningRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Peer'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
security:
|
||||
- BasicAuth: []
|
||||
summary: Create a new peer for the given interface and user.
|
||||
tags:
|
||||
- Provisioning
|
||||
/user/all:
|
||||
get:
|
||||
operationId: users_handleAllGet
|
||||
|
@ -345,7 +345,7 @@ func (e peerEndpoint) handleConfigGet() gin.HandlerFunc {
|
||||
// @Produce png
|
||||
// @Produce json
|
||||
// @Param id path string true "The peer identifier"
|
||||
// @Success 200 {object} file
|
||||
// @Success 200 {file} binary
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /peer/config-qr/{id} [get]
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/app/api/v1/models"
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
@ -17,6 +18,8 @@ type ProvisioningServiceUserManagerRepo interface {
|
||||
type ProvisioningServicePeerManagerRepo interface {
|
||||
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
||||
GetUserPeers(context.Context, domain.UserIdentifier) ([]domain.Peer, error)
|
||||
PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error)
|
||||
CreatePeer(ctx context.Context, p *domain.Peer) (*domain.Peer, error)
|
||||
}
|
||||
|
||||
type ProvisioningServiceConfigFileManagerRepo interface {
|
||||
@ -128,3 +131,44 @@ func (p ProvisioningService) GetPeerQrPng(ctx context.Context, peerId domain.Pee
|
||||
|
||||
return peerCfgQrData, nil
|
||||
}
|
||||
|
||||
func (p ProvisioningService) NewPeer(ctx context.Context, req models.ProvisioningRequest) (*domain.Peer, error) {
|
||||
if req.UserIdentifier == "" {
|
||||
req.UserIdentifier = string(domain.GetUserInfo(ctx).Id) // use authenticated user id if not set
|
||||
}
|
||||
|
||||
// check permissions
|
||||
if err := domain.ValidateUserAccessRights(ctx, domain.UserIdentifier(req.UserIdentifier)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !p.cfg.Core.SelfProvisioningAllowed {
|
||||
// only admins can create new peers if self-provisioning is disabled
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// prepare new peer
|
||||
peer, err := p.peers.PreparePeer(ctx, domain.InterfaceIdentifier(req.InterfaceIdentifier))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare new peer: %w", err)
|
||||
}
|
||||
peer.UserIdentifier = domain.UserIdentifier(req.UserIdentifier) // overwrite context user id with the one from the request
|
||||
if req.PublicKey != "" {
|
||||
peer.Identifier = domain.PeerIdentifier(req.PublicKey)
|
||||
peer.Interface.PublicKey = req.PublicKey
|
||||
peer.Interface.PrivateKey = "" // clear private key if public key is set, WireGuard Portal does not know the private key in that case
|
||||
}
|
||||
if req.PresharedKey != "" {
|
||||
peer.PresharedKey = domain.PreSharedKey(req.PresharedKey)
|
||||
}
|
||||
peer.GenerateDisplayName("API")
|
||||
|
||||
// save new peer
|
||||
peer, err = p.peers.CreatePeer(ctx, peer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new peer: %w", err)
|
||||
}
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ type ProvisioningEndpointProvisioningService interface {
|
||||
)
|
||||
GetPeerConfig(ctx context.Context, peerId domain.PeerIdentifier) ([]byte, error)
|
||||
GetPeerQrPng(ctx context.Context, peerId domain.PeerIdentifier) ([]byte, error)
|
||||
NewPeer(ctx context.Context, req models.ProvisioningRequest) (*domain.Peer, error)
|
||||
}
|
||||
|
||||
type ProvisioningEndpoint struct {
|
||||
@ -40,6 +41,8 @@ func (e ProvisioningEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *
|
||||
apiGroup.GET("/data/user-info", authenticator.LoggedIn(), e.handleUserInfoGet())
|
||||
apiGroup.GET("/data/peer-config", authenticator.LoggedIn(), e.handlePeerConfigGet())
|
||||
apiGroup.GET("/data/peer-qr", authenticator.LoggedIn(), e.handlePeerQrGet())
|
||||
|
||||
apiGroup.POST("/new-peer", authenticator.LoggedIn(), e.handleNewPeerPost())
|
||||
}
|
||||
|
||||
// handleUserInfoGet returns a gorm Handler function.
|
||||
@ -153,3 +156,40 @@ func (e ProvisioningEndpoint) handlePeerQrGet() gin.HandlerFunc {
|
||||
c.Data(http.StatusOK, "image/png", peerConfigQrCode)
|
||||
}
|
||||
}
|
||||
|
||||
// handleNewPeerPost returns a gorm Handler function.
|
||||
//
|
||||
// @ID provisioning_handleNewPeerPost
|
||||
// @Tags Provisioning
|
||||
// @Summary Create a new peer for the given interface and user.
|
||||
// @Description Normal users can only create new peers if self provisioning is allowed. Admins can always add new peers.
|
||||
// @Param request body models.ProvisioningRequest true "Provisioning request model."
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.Peer
|
||||
// @Failure 400 {object} models.Error
|
||||
// @Failure 401 {object} models.Error
|
||||
// @Failure 403 {object} models.Error
|
||||
// @Failure 404 {object} models.Error
|
||||
// @Failure 500 {object} models.Error
|
||||
// @Router /provisioning/new-peer [post]
|
||||
// @Security BasicAuth
|
||||
func (e ProvisioningEndpoint) handleNewPeerPost() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
var req models.ProvisioningRequest
|
||||
err := c.BindJSON(&req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
peer, err := e.provisioning.NewPeer(ctx, req)
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewPeer(peer))
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,12 @@ import "github.com/h44z/wg-portal/internal/domain"
|
||||
|
||||
// UserInformation represents the information about a user and its linked peers.
|
||||
type UserInformation struct {
|
||||
UserIdentifier string `json:"UserIdentifier" example:"uid-1234567"`
|
||||
PeerCount int `json:"PeerCount" example:"2"`
|
||||
Peers []UserInformationPeer `json:"Peers"`
|
||||
// UserIdentifier is the unique identifier of the user.
|
||||
UserIdentifier string `json:"UserIdentifier" example:"uid-1234567"`
|
||||
// PeerCount is the number of peers linked to the user.
|
||||
PeerCount int `json:"PeerCount" example:"2"`
|
||||
// Peers is a list of peers linked to the user.
|
||||
Peers []UserInformationPeer `json:"Peers"`
|
||||
}
|
||||
|
||||
// UserInformationPeer represents the information about a peer.
|
||||
@ -56,3 +59,17 @@ func NewUserInformationPeer(peer domain.Peer) UserInformationPeer {
|
||||
|
||||
return up
|
||||
}
|
||||
|
||||
// ProvisioningRequest represents a request to provision a new peer.
|
||||
type ProvisioningRequest struct {
|
||||
// InterfaceIdentifier is the identifier of the WireGuard interface the peer should be linked to.
|
||||
InterfaceIdentifier string `json:"InterfaceIdentifier" example:"wg0" binding:"required"`
|
||||
// UserIdentifier is the identifier of the user the peer should be linked to.
|
||||
// If no user identifier is set, the authenticated user is used.
|
||||
UserIdentifier string `json:"UserIdentifier" example:"uid-1234567"`
|
||||
|
||||
// PublicKey is the optional public key of the peer. If no public key is set, a new key pair is generated.
|
||||
PublicKey string `json:"PublicKey" example:"xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=" binding:"omitempty,len=44"`
|
||||
// PresharedKey is the optional pre-shared key of the peer. If no pre-shared key is set, a new key is generated.
|
||||
PresharedKey string `json:"PresharedKey" example:"yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=" binding:"omitempty,len=44"`
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/app"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -34,9 +33,9 @@ func (m Manager) CreateDefaultPeer(ctx context.Context, userId domain.UserIdenti
|
||||
}
|
||||
|
||||
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", userId)
|
||||
peer.AutomaticallyCreated = true
|
||||
peer.GenerateDisplayName("Default")
|
||||
|
||||
newPeers = append(newPeers, *peer)
|
||||
}
|
||||
@ -108,7 +107,6 @@ func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier)
|
||||
ExtraAllowedIPsStr: "",
|
||||
PresharedKey: pk,
|
||||
PersistentKeepalive: domain.NewConfigOption(iface.PeerDefPersistentKeepalive, true),
|
||||
DisplayName: fmt.Sprintf("Peer %s", internal.TruncateString(string(peerId), 8)),
|
||||
Identifier: peerId,
|
||||
UserIdentifier: currentUser.Id,
|
||||
InterfaceIdentifier: iface.Identifier,
|
||||
@ -132,6 +130,7 @@ func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier)
|
||||
PostDown: domain.NewConfigOption(iface.PeerDefPostDown, true),
|
||||
},
|
||||
}
|
||||
freshPeer.GenerateDisplayName("")
|
||||
|
||||
return freshPeer, nil
|
||||
}
|
||||
|
@ -120,6 +120,13 @@ func (p *Peer) ApplyInterfaceDefaults(in *Interface) {
|
||||
p.Interface.PostDown.TrySetValue(in.PeerDefPostDown)
|
||||
}
|
||||
|
||||
func (p *Peer) GenerateDisplayName(prefix string) {
|
||||
if prefix != "" {
|
||||
prefix = fmt.Sprintf("%s ", strings.TrimSpace(prefix)) // add a space after the prefix
|
||||
}
|
||||
p.DisplayName = fmt.Sprintf("%sPeer %s", prefix, internal.TruncateString(string(p.Identifier), 8))
|
||||
}
|
||||
|
||||
type PeerInterfaceConfig struct {
|
||||
KeyPair // private/public Key of the peer
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user