upload all
This commit is contained in:
7
backend/config/providers/autodns.json
Normal file
7
backend/config/providers/autodns.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"enabled": false,
|
||||
"settings": {
|
||||
"password": "test",
|
||||
"username": "test"
|
||||
}
|
||||
}
|
||||
4
backend/config/providers/dummy-ca.json
Normal file
4
backend/config/providers/dummy-ca.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"settings": {}
|
||||
}
|
||||
4
backend/config/providers/hetzner.json
Normal file
4
backend/config/providers/hetzner.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"enabled": false,
|
||||
"settings": {}
|
||||
}
|
||||
9
backend/go.mod
Normal file
9
backend/go.mod
Normal file
@@ -0,0 +1,9 @@
|
||||
module certigo-addon-backend
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/mattn/go-sqlite3 v1.14.18
|
||||
)
|
||||
6
backend/go.sum
Normal file
6
backend/go.sum
Normal file
@@ -0,0 +1,6 @@
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
2686
backend/main.go
Normal file
2686
backend/main.go
Normal file
File diff suppressed because it is too large
Load Diff
BIN
backend/myapp
Executable file
BIN
backend/myapp
Executable file
Binary file not shown.
586
backend/openapi.yaml
Normal file
586
backend/openapi.yaml
Normal file
@@ -0,0 +1,586 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Certigo Addon API
|
||||
description: API für die Verwaltung von Spaces, FQDNs und Certificate Signing Requests (CSRs)
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: Certigo Addon
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8080/api
|
||||
description: Local development server
|
||||
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
summary: System Health Check
|
||||
description: Prüft den Systemstatus des Backends
|
||||
tags:
|
||||
- System
|
||||
responses:
|
||||
'200':
|
||||
description: System ist erreichbar
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthResponse'
|
||||
|
||||
/stats:
|
||||
get:
|
||||
summary: Statistiken abrufen
|
||||
description: Ruft Statistiken über die Anzahl der Spaces, FQDNs und CSRs ab
|
||||
tags:
|
||||
- System
|
||||
responses:
|
||||
'200':
|
||||
description: Statistiken erfolgreich abgerufen
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StatsResponse'
|
||||
|
||||
/spaces:
|
||||
get:
|
||||
summary: Alle Spaces abrufen
|
||||
description: Ruft eine Liste aller Spaces ab
|
||||
tags:
|
||||
- Spaces
|
||||
responses:
|
||||
'200':
|
||||
description: Liste der Spaces
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Space'
|
||||
|
||||
post:
|
||||
summary: Space erstellen
|
||||
description: Erstellt einen neuen Space
|
||||
tags:
|
||||
- Spaces
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateSpaceRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Space erfolgreich erstellt
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Space'
|
||||
'400':
|
||||
description: Ungültige Anfrage
|
||||
|
||||
/spaces/{id}:
|
||||
delete:
|
||||
summary: Space löschen
|
||||
description: Löscht einen Space. Wenn der Space FQDNs enthält, muss der Parameter deleteFqdns=true gesetzt werden.
|
||||
tags:
|
||||
- Spaces
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: deleteFqdns
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Wenn true, werden alle FQDNs des Spaces mitgelöscht
|
||||
responses:
|
||||
'200':
|
||||
description: Space erfolgreich gelöscht
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/MessageResponse'
|
||||
'404':
|
||||
description: Space nicht gefunden
|
||||
'409':
|
||||
description: Space enthält noch FQDNs
|
||||
|
||||
/spaces/{id}/fqdns/count:
|
||||
get:
|
||||
summary: FQDN-Anzahl abrufen
|
||||
description: Ruft die Anzahl der FQDNs für einen Space ab
|
||||
tags:
|
||||
- FQDNs
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: Anzahl der FQDNs
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CountResponse'
|
||||
|
||||
/spaces/{id}/fqdns:
|
||||
get:
|
||||
summary: Alle FQDNs eines Spaces abrufen
|
||||
description: Ruft alle FQDNs für einen Space ab
|
||||
tags:
|
||||
- FQDNs
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: Liste der FQDNs
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/FQDN'
|
||||
'404':
|
||||
description: Space nicht gefunden
|
||||
|
||||
post:
|
||||
summary: FQDN erstellen
|
||||
description: Erstellt einen neuen FQDN innerhalb eines Spaces
|
||||
tags:
|
||||
- FQDNs
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateFQDNRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: FQDN erfolgreich erstellt
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FQDN'
|
||||
'400':
|
||||
description: Ungültige Anfrage
|
||||
'404':
|
||||
description: Space nicht gefunden
|
||||
'409':
|
||||
description: FQDN existiert bereits in diesem Space
|
||||
|
||||
delete:
|
||||
summary: Alle FQDNs eines Spaces löschen
|
||||
description: Löscht alle FQDNs eines Spaces
|
||||
tags:
|
||||
- FQDNs
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: Alle FQDNs erfolgreich gelöscht
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DeleteResponse'
|
||||
|
||||
/spaces/{id}/fqdns/{fqdnId}:
|
||||
delete:
|
||||
summary: FQDN löschen
|
||||
description: Löscht einen einzelnen FQDN
|
||||
tags:
|
||||
- FQDNs
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: fqdnId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: FQDN erfolgreich gelöscht
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/MessageResponse'
|
||||
'404':
|
||||
description: FQDN nicht gefunden
|
||||
|
||||
/fqdns:
|
||||
delete:
|
||||
summary: Alle FQDNs global löschen
|
||||
description: Löscht alle FQDNs aus allen Spaces. Erfordert confirm=true Query-Parameter.
|
||||
tags:
|
||||
- FQDNs
|
||||
parameters:
|
||||
- name: confirm
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: boolean
|
||||
description: Muss true sein, um die Operation auszuführen
|
||||
responses:
|
||||
'200':
|
||||
description: Alle FQDNs erfolgreich gelöscht
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DeleteResponse'
|
||||
'400':
|
||||
description: Bestätigung erforderlich
|
||||
|
||||
/csrs:
|
||||
delete:
|
||||
summary: Alle CSRs global löschen
|
||||
description: Löscht alle CSRs aus allen Spaces. Erfordert confirm=true Query-Parameter.
|
||||
tags:
|
||||
- CSRs
|
||||
parameters:
|
||||
- name: confirm
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Muss "true" sein, um die Operation auszuführen
|
||||
example: "true"
|
||||
responses:
|
||||
'200':
|
||||
description: Alle CSRs erfolgreich gelöscht
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DeleteResponse'
|
||||
'400':
|
||||
description: Bestätigung erforderlich
|
||||
|
||||
/spaces/{spaceId}/fqdns/{fqdnId}/csr:
|
||||
post:
|
||||
summary: CSR hochladen
|
||||
description: Lädt einen CSR (Certificate Signing Request) im PEM-Format hoch
|
||||
tags:
|
||||
- CSRs
|
||||
parameters:
|
||||
- name: spaceId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: fqdnId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- csr
|
||||
- spaceId
|
||||
- fqdn
|
||||
properties:
|
||||
csr:
|
||||
type: string
|
||||
format: binary
|
||||
description: CSR-Datei im PEM-Format
|
||||
spaceId:
|
||||
type: string
|
||||
description: ID des Spaces
|
||||
fqdn:
|
||||
type: string
|
||||
description: Name des FQDNs
|
||||
responses:
|
||||
'201':
|
||||
description: CSR erfolgreich hochgeladen
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CSR'
|
||||
'400':
|
||||
description: Ungültige Anfrage oder ungültiges CSR-Format
|
||||
'404':
|
||||
description: Space oder FQDN nicht gefunden
|
||||
|
||||
get:
|
||||
summary: CSR(s) abrufen
|
||||
description: Ruft CSR(s) für einen FQDN ab. Mit latest=true wird nur der neueste CSR zurückgegeben.
|
||||
tags:
|
||||
- CSRs
|
||||
parameters:
|
||||
- name: spaceId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: fqdnId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: latest
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Wenn true, wird nur der neueste CSR zurückgegeben
|
||||
responses:
|
||||
'200':
|
||||
description: CSR(s) erfolgreich abgerufen
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/CSR'
|
||||
- type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CSR'
|
||||
'404':
|
||||
description: FQDN nicht gefunden
|
||||
|
||||
components:
|
||||
schemas:
|
||||
HealthResponse:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: "ok"
|
||||
message:
|
||||
type: string
|
||||
example: "Backend ist erreichbar"
|
||||
time:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2024-01-15T10:30:00Z"
|
||||
|
||||
StatsResponse:
|
||||
type: object
|
||||
properties:
|
||||
spaces:
|
||||
type: integer
|
||||
example: 5
|
||||
fqdns:
|
||||
type: integer
|
||||
example: 12
|
||||
csrs:
|
||||
type: integer
|
||||
example: 7
|
||||
|
||||
Space:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
name:
|
||||
type: string
|
||||
example: "Mein Space"
|
||||
description:
|
||||
type: string
|
||||
example: "Beschreibung des Spaces"
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2024-01-15T10:30:00Z"
|
||||
|
||||
CreateSpaceRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: "Mein Space"
|
||||
description:
|
||||
type: string
|
||||
example: "Beschreibung des Spaces"
|
||||
|
||||
FQDN:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "660e8400-e29b-41d4-a716-446655440000"
|
||||
spaceId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
fqdn:
|
||||
type: string
|
||||
example: "example.com"
|
||||
description:
|
||||
type: string
|
||||
example: "Beschreibung des FQDN"
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2024-01-15T10:30:00Z"
|
||||
|
||||
CreateFQDNRequest:
|
||||
type: object
|
||||
required:
|
||||
- fqdn
|
||||
properties:
|
||||
fqdn:
|
||||
type: string
|
||||
example: "example.com"
|
||||
description:
|
||||
type: string
|
||||
example: "Beschreibung des FQDN"
|
||||
|
||||
Extension:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
example: "2.5.29.37"
|
||||
oid:
|
||||
type: string
|
||||
example: "2.5.29.37"
|
||||
name:
|
||||
type: string
|
||||
example: "X509v3 Extended Key Usage"
|
||||
critical:
|
||||
type: boolean
|
||||
example: false
|
||||
value:
|
||||
type: string
|
||||
example: "301406082b0601050507030106082b06010505070302"
|
||||
description:
|
||||
type: string
|
||||
example: "TLS Web Server Authentication\n TLS Web Client Authentication"
|
||||
purposes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["TLS Web Server Authentication", "TLS Web Client Authentication"]
|
||||
|
||||
CSR:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "770e8400-e29b-41d4-a716-446655440000"
|
||||
fqdnId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "660e8400-e29b-41d4-a716-446655440000"
|
||||
spaceId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
fqdn:
|
||||
type: string
|
||||
example: "example.com"
|
||||
csrPem:
|
||||
type: string
|
||||
example: "-----BEGIN CERTIFICATE REQUEST-----\n...\n-----END CERTIFICATE REQUEST-----"
|
||||
subject:
|
||||
type: string
|
||||
example: "CN=example.com"
|
||||
publicKeyAlgorithm:
|
||||
type: string
|
||||
example: "RSA"
|
||||
signatureAlgorithm:
|
||||
type: string
|
||||
example: "SHA256-RSA"
|
||||
keySize:
|
||||
type: integer
|
||||
example: 2048
|
||||
dnsNames:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["example.com", "www.example.com"]
|
||||
emailAddresses:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["admin@example.com"]
|
||||
ipAddresses:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["192.168.1.1"]
|
||||
uris:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["https://example.com"]
|
||||
extensions:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Extension'
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2024-01-15T10:30:00Z"
|
||||
|
||||
MessageResponse:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
example: "Operation erfolgreich"
|
||||
|
||||
CountResponse:
|
||||
type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
example: 5
|
||||
|
||||
DeleteResponse:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
example: "Alle FQDNs erfolgreich gelöscht"
|
||||
deletedCount:
|
||||
type: integer
|
||||
example: 5
|
||||
|
||||
securitySchemes:
|
||||
{}:
|
||||
type: http
|
||||
scheme: none
|
||||
|
||||
76
backend/providers/autodns.go
Normal file
76
backend/providers/autodns.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AutoDNSProvider ist der Provider für AutoDNS
|
||||
type AutoDNSProvider struct{}
|
||||
|
||||
func NewAutoDNSProvider() *AutoDNSProvider {
|
||||
return &AutoDNSProvider{}
|
||||
}
|
||||
|
||||
func (p *AutoDNSProvider) GetName() string {
|
||||
return "autodns"
|
||||
}
|
||||
|
||||
func (p *AutoDNSProvider) GetDisplayName() string {
|
||||
return "AutoDNS"
|
||||
}
|
||||
|
||||
func (p *AutoDNSProvider) GetDescription() string {
|
||||
return "AutoDNS SSL Certificate Provider"
|
||||
}
|
||||
|
||||
func (p *AutoDNSProvider) ValidateConfig(settings map[string]interface{}) error {
|
||||
username, ok := settings["username"].(string)
|
||||
if !ok || strings.TrimSpace(username) == "" {
|
||||
return fmt.Errorf("username ist erforderlich")
|
||||
}
|
||||
|
||||
password, ok := settings["password"].(string)
|
||||
if !ok || strings.TrimSpace(password) == "" {
|
||||
return fmt.Errorf("password ist erforderlich")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *AutoDNSProvider) TestConnection(settings map[string]interface{}) error {
|
||||
// Hier würde die tatsächliche Verbindung zu AutoDNS getestet werden
|
||||
// Für jetzt nur Validierung
|
||||
return p.ValidateConfig(settings)
|
||||
}
|
||||
|
||||
// GetRequiredSettings gibt die erforderlichen Einstellungen zurück
|
||||
func (p *AutoDNSProvider) GetRequiredSettings() []SettingField {
|
||||
return []SettingField{
|
||||
{
|
||||
Name: "username",
|
||||
Label: "Benutzername",
|
||||
Type: "text",
|
||||
Required: true,
|
||||
Description: "AutoDNS Benutzername",
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Label: "Passwort",
|
||||
Type: "password",
|
||||
Required: true,
|
||||
Description: "AutoDNS Passwort",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SignCSR signiert einen CSR (noch nicht implementiert)
|
||||
func (p *AutoDNSProvider) SignCSR(csrPEM string, settings map[string]interface{}) (*SignCSRResult, error) {
|
||||
return nil, fmt.Errorf("AutoDNS CSR-Signierung noch nicht implementiert")
|
||||
}
|
||||
|
||||
// GetCertificate ruft ein Zertifikat ab (noch nicht implementiert)
|
||||
func (p *AutoDNSProvider) GetCertificate(certificateID string, settings map[string]interface{}) (string, error) {
|
||||
return "", fmt.Errorf("AutoDNS Zertifikat-Abruf noch nicht implementiert")
|
||||
}
|
||||
|
||||
205
backend/providers/dummy.go
Normal file
205
backend/providers/dummy.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DummyCAProvider ist ein Dummy-Provider für Tests
|
||||
type DummyCAProvider struct {
|
||||
baseURL string
|
||||
}
|
||||
|
||||
func NewDummyCAProvider() *DummyCAProvider {
|
||||
return &DummyCAProvider{
|
||||
baseURL: "http://localhost:8088",
|
||||
}
|
||||
}
|
||||
|
||||
func (p *DummyCAProvider) GetName() string {
|
||||
return "dummy-ca"
|
||||
}
|
||||
|
||||
func (p *DummyCAProvider) GetDisplayName() string {
|
||||
return "Dummy CA"
|
||||
}
|
||||
|
||||
func (p *DummyCAProvider) GetDescription() string {
|
||||
return "Externe Dummy CA für Tests und Entwicklung (http://localhost:8088)"
|
||||
}
|
||||
|
||||
func (p *DummyCAProvider) ValidateConfig(settings map[string]interface{}) error {
|
||||
// Dummy-Provider benötigt keine Konfiguration
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DummyCAProvider) TestConnection(settings map[string]interface{}) error {
|
||||
// Teste Verbindung zur externen CA über Health Check
|
||||
url := fmt.Sprintf("%s/health", p.baseURL)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CA-Server nicht erreichbar: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("CA-Server antwortet mit Status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Prüfe Response Body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fehler beim Lesen der Health-Check-Response: %v", err)
|
||||
}
|
||||
|
||||
var healthResponse struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &healthResponse); err != nil {
|
||||
return fmt.Errorf("ungültige Health-Check-Response: %v", err)
|
||||
}
|
||||
|
||||
if healthResponse.Status != "ok" {
|
||||
return fmt.Errorf("CA-Server meldet Status: %s", healthResponse.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRequiredSettings gibt die erforderlichen Einstellungen zurück
|
||||
func (p *DummyCAProvider) GetRequiredSettings() []SettingField {
|
||||
return []SettingField{}
|
||||
}
|
||||
|
||||
// SignCSR signiert einen CSR über die externe Dummy CA API
|
||||
func (p *DummyCAProvider) SignCSR(csrPEM string, settings map[string]interface{}) (*SignCSRResult, error) {
|
||||
// Entferne mögliche Whitespace am Anfang/Ende
|
||||
csrPEM = strings.TrimSpace(csrPEM)
|
||||
|
||||
// Base64-kodiere den CSR
|
||||
csrB64 := base64.StdEncoding.EncodeToString([]byte(csrPEM))
|
||||
|
||||
// Erstelle Request Body
|
||||
requestBody := map[string]interface{}{
|
||||
"csr": csrB64,
|
||||
"action": "sign",
|
||||
"validity_days": 365,
|
||||
}
|
||||
|
||||
// Konvertiere zu JSON
|
||||
jsonData, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fehler beim Erstellen des Request-Body: %v", err)
|
||||
}
|
||||
|
||||
// Erstelle HTTP Request
|
||||
url := fmt.Sprintf("%s/csr", p.baseURL)
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fehler beim Erstellen des HTTP-Requests: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Führe Request aus
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fehler beim Senden des Requests an die CA: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Lese Response Body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fehler beim Lesen der Response: %v", err)
|
||||
}
|
||||
|
||||
// Prüfe Status Code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("CA-API Fehler (Status %d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// Parse Response
|
||||
var apiResponse struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Certificate string `json:"certificate"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
||||
return nil, fmt.Errorf("fehler beim Parsen der Response: %v", err)
|
||||
}
|
||||
|
||||
// Prüfe auf Fehler in der Response
|
||||
if apiResponse.Error != "" {
|
||||
return nil, fmt.Errorf("CA-API Fehler: %s", apiResponse.Error)
|
||||
}
|
||||
|
||||
// Prüfe Status
|
||||
if apiResponse.Status != "success" {
|
||||
return nil, fmt.Errorf("CSR-Signierung fehlgeschlagen: %s", apiResponse.Message)
|
||||
}
|
||||
|
||||
// Rückgabe des Ergebnisses
|
||||
return &SignCSRResult{
|
||||
CertificatePEM: apiResponse.Certificate,
|
||||
OrderID: apiResponse.ID,
|
||||
Status: "issued",
|
||||
Message: apiResponse.Message,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCertificate ruft ein Zertifikat über die externe Dummy CA API ab
|
||||
func (p *DummyCAProvider) GetCertificate(certificateID string, settings map[string]interface{}) (string, error) {
|
||||
if certificateID == "" {
|
||||
return "", fmt.Errorf("zertifikat-ID ist erforderlich")
|
||||
}
|
||||
|
||||
// Erstelle HTTP Request
|
||||
url := fmt.Sprintf("%s/certificate/%s", p.baseURL, certificateID)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fehler beim Abrufen des Zertifikats: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Lese Response Body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fehler beim Lesen der Response: %v", err)
|
||||
}
|
||||
|
||||
// Prüfe Status Code
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return "", fmt.Errorf("Zertifikat mit ID %s nicht gefunden", certificateID)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("CA-API Fehler (Status %d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// Parse Response
|
||||
var apiResponse struct {
|
||||
ID string `json:"id"`
|
||||
Certificate string `json:"certificate"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
||||
return "", fmt.Errorf("fehler beim Parsen der Response: %v", err)
|
||||
}
|
||||
|
||||
if apiResponse.Certificate == "" {
|
||||
return "", fmt.Errorf("zertifikat in Response nicht gefunden")
|
||||
}
|
||||
|
||||
return apiResponse.Certificate, nil
|
||||
}
|
||||
63
backend/providers/hetzner.go
Normal file
63
backend/providers/hetzner.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HetznerProvider ist der Provider für Hetzner
|
||||
type HetznerProvider struct{}
|
||||
|
||||
func NewHetznerProvider() *HetznerProvider {
|
||||
return &HetznerProvider{}
|
||||
}
|
||||
|
||||
func (p *HetznerProvider) GetName() string {
|
||||
return "hetzner"
|
||||
}
|
||||
|
||||
func (p *HetznerProvider) GetDisplayName() string {
|
||||
return "Hetzner"
|
||||
}
|
||||
|
||||
func (p *HetznerProvider) GetDescription() string {
|
||||
return "Hetzner SSL Certificate Provider"
|
||||
}
|
||||
|
||||
func (p *HetznerProvider) ValidateConfig(settings map[string]interface{}) error {
|
||||
apiKey, ok := settings["apiKey"].(string)
|
||||
if !ok || strings.TrimSpace(apiKey) == "" {
|
||||
return fmt.Errorf("apiKey ist erforderlich")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *HetznerProvider) TestConnection(settings map[string]interface{}) error {
|
||||
// Hier würde die tatsächliche Verbindung zu Hetzner getestet werden
|
||||
// Für jetzt nur Validierung
|
||||
return p.ValidateConfig(settings)
|
||||
}
|
||||
|
||||
// GetRequiredSettings gibt die erforderlichen Einstellungen zurück
|
||||
func (p *HetznerProvider) GetRequiredSettings() []SettingField {
|
||||
return []SettingField{
|
||||
{
|
||||
Name: "apiKey",
|
||||
Label: "API Key",
|
||||
Type: "password",
|
||||
Required: true,
|
||||
Description: "Hetzner API Key",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SignCSR signiert einen CSR (noch nicht implementiert)
|
||||
func (p *HetznerProvider) SignCSR(csrPEM string, settings map[string]interface{}) (*SignCSRResult, error) {
|
||||
return nil, fmt.Errorf("Hetzner CSR-Signierung noch nicht implementiert")
|
||||
}
|
||||
|
||||
// GetCertificate ruft ein Zertifikat ab (noch nicht implementiert)
|
||||
func (p *HetznerProvider) GetCertificate(certificateID string, settings map[string]interface{}) (string, error) {
|
||||
return "", fmt.Errorf("Hetzner Zertifikat-Abruf noch nicht implementiert")
|
||||
}
|
||||
214
backend/providers/provider.go
Normal file
214
backend/providers/provider.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ProviderConfig enthält die Konfiguration eines Providers
|
||||
type ProviderConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Settings map[string]interface{} `json:"settings"`
|
||||
}
|
||||
|
||||
// SignCSRResult enthält das Ergebnis einer CSR-Signierung
|
||||
type SignCSRResult struct {
|
||||
CertificatePEM string `json:"certificatePEM"`
|
||||
OrderID string `json:"orderId,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// Provider Interface für alle SSL Certificate Provider
|
||||
type Provider interface {
|
||||
// GetName gibt den Namen des Providers zurück
|
||||
GetName() string
|
||||
// GetDisplayName gibt den Anzeigenamen zurück
|
||||
GetDisplayName() string
|
||||
// GetDescription gibt eine Beschreibung zurück
|
||||
GetDescription() string
|
||||
// ValidateConfig validiert die Konfiguration
|
||||
ValidateConfig(settings map[string]interface{}) error
|
||||
// TestConnection testet die Verbindung zum Provider
|
||||
TestConnection(settings map[string]interface{}) error
|
||||
// GetRequiredSettings gibt die erforderlichen Einstellungen zurück
|
||||
GetRequiredSettings() []SettingField
|
||||
// SignCSR signiert einen CSR und gibt das Zertifikat zurück
|
||||
SignCSR(csrPEM string, settings map[string]interface{}) (*SignCSRResult, error)
|
||||
// GetCertificate ruft ein Zertifikat anhand der Zertifikat-ID ab
|
||||
GetCertificate(certificateID string, settings map[string]interface{}) (string, error)
|
||||
}
|
||||
|
||||
// ProviderManager verwaltet alle Provider
|
||||
type ProviderManager struct {
|
||||
providers map[string]Provider
|
||||
configs map[string]*ProviderConfig
|
||||
configDir string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var manager *ProviderManager
|
||||
var once sync.Once
|
||||
|
||||
// GetManager gibt die Singleton-Instanz des ProviderManagers zurück
|
||||
func GetManager() *ProviderManager {
|
||||
once.Do(func() {
|
||||
manager = &ProviderManager{
|
||||
providers: make(map[string]Provider),
|
||||
configs: make(map[string]*ProviderConfig),
|
||||
configDir: "./config/providers",
|
||||
}
|
||||
manager.loadAllConfigs()
|
||||
})
|
||||
return manager
|
||||
}
|
||||
|
||||
// RegisterProvider registriert einen neuen Provider
|
||||
func (pm *ProviderManager) RegisterProvider(provider Provider) {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
providerID := pm.getProviderID(provider.GetName())
|
||||
pm.providers[providerID] = provider
|
||||
|
||||
// Lade Konfiguration falls vorhanden
|
||||
if pm.configs[providerID] == nil {
|
||||
pm.configs[providerID] = &ProviderConfig{
|
||||
Enabled: false,
|
||||
Settings: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetProvider gibt einen Provider zurück
|
||||
func (pm *ProviderManager) GetProvider(id string) (Provider, bool) {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
provider, exists := pm.providers[id]
|
||||
return provider, exists
|
||||
}
|
||||
|
||||
// GetAllProviders gibt alle registrierten Provider zurück
|
||||
func (pm *ProviderManager) GetAllProviders() map[string]Provider {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
result := make(map[string]Provider)
|
||||
for id, provider := range pm.providers {
|
||||
result[id] = provider
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetProviderConfig gibt die Konfiguration eines Providers zurück
|
||||
func (pm *ProviderManager) GetProviderConfig(id string) (*ProviderConfig, error) {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
|
||||
config, exists := pm.configs[id]
|
||||
if !exists {
|
||||
return &ProviderConfig{
|
||||
Enabled: false,
|
||||
Settings: make(map[string]interface{}),
|
||||
}, nil
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// UpdateProviderConfig aktualisiert die Konfiguration eines Providers
|
||||
func (pm *ProviderManager) UpdateProviderConfig(id string, config *ProviderConfig) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
provider, exists := pm.providers[id]
|
||||
if !exists {
|
||||
return fmt.Errorf("provider %s nicht gefunden", id)
|
||||
}
|
||||
|
||||
// Validiere Konfiguration
|
||||
if err := provider.ValidateConfig(config.Settings); err != nil {
|
||||
return fmt.Errorf("ungültige Konfiguration: %v", err)
|
||||
}
|
||||
|
||||
pm.configs[id] = config
|
||||
|
||||
// Speichere Konfiguration in Datei
|
||||
return pm.saveConfig(id, config)
|
||||
}
|
||||
|
||||
// SetProviderEnabled aktiviert/deaktiviert einen Provider
|
||||
func (pm *ProviderManager) SetProviderEnabled(id string, enabled bool) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
if pm.configs[id] == nil {
|
||||
pm.configs[id] = &ProviderConfig{
|
||||
Enabled: enabled,
|
||||
Settings: make(map[string]interface{}),
|
||||
}
|
||||
} else {
|
||||
pm.configs[id].Enabled = enabled
|
||||
}
|
||||
|
||||
return pm.saveConfig(id, pm.configs[id])
|
||||
}
|
||||
|
||||
// getProviderID erstellt eine ID aus dem Provider-Namen
|
||||
func (pm *ProviderManager) getProviderID(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
// loadAllConfigs lädt alle Konfigurationsdateien
|
||||
func (pm *ProviderManager) loadAllConfigs() {
|
||||
// Stelle sicher, dass das Verzeichnis existiert
|
||||
os.MkdirAll(pm.configDir, 0755)
|
||||
|
||||
// Lade alle JSON-Dateien im Konfigurationsverzeichnis
|
||||
files, err := filepath.Glob(filepath.Join(pm.configDir, "*.json"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
id := filepath.Base(file[:len(file)-5]) // Entferne .json
|
||||
config, err := pm.loadConfig(id)
|
||||
if err == nil {
|
||||
pm.configs[id] = config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loadConfig lädt eine Konfigurationsdatei
|
||||
func (pm *ProviderManager) loadConfig(id string) (*ProviderConfig, error) {
|
||||
filePath := filepath.Join(pm.configDir, id+".json")
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config ProviderConfig
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// saveConfig speichert eine Konfiguration in eine Datei
|
||||
func (pm *ProviderManager) saveConfig(id string, config *ProviderConfig) error {
|
||||
// Stelle sicher, dass das Verzeichnis existiert
|
||||
os.MkdirAll(pm.configDir, 0755)
|
||||
|
||||
filePath := filepath.Join(pm.configDir, id+".json")
|
||||
|
||||
data, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(filePath, data, 0644)
|
||||
}
|
||||
|
||||
22
backend/providers/types.go
Normal file
22
backend/providers/types.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package providers
|
||||
|
||||
// SettingField beschreibt ein Konfigurationsfeld
|
||||
type SettingField struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type"` // text, password, number, email, url
|
||||
Required bool `json:"required"`
|
||||
Description string `json:"description"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
// ProviderInfo enthält Informationen über einen Provider
|
||||
type ProviderInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Description string `json:"description"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Settings []SettingField `json:"settings"`
|
||||
}
|
||||
|
||||
BIN
backend/spaces.db-shm
Normal file
BIN
backend/spaces.db-shm
Normal file
Binary file not shown.
BIN
backend/spaces.db-wal
Normal file
BIN
backend/spaces.db-wal
Normal file
Binary file not shown.
Reference in New Issue
Block a user