feat: add API token authentication

This commit is contained in:
2025-05-03 23:04:47 +02:00
parent e62bbf8ca3
commit 1a0499d81f
6 changed files with 73 additions and 23 deletions

2
.env Normal file
View File

@@ -0,0 +1,2 @@
RCOND_ADDR=0.0.0.0:8080
RCOND_API_TOKEN=your_api_token

View File

@@ -1,3 +1,4 @@
SHELL := bash
ARCH ?= arm64
ADDR ?= 0.0.0.0:8080
@@ -10,10 +11,10 @@ build:
env GOOS=linux GOARCH=${ARCH} go build -o bin/rcond-${ARCH} ./cmd/rcond/main.go
run:
bin/rcond-${ARCH} ${ADDR}
source .env && bin/rcond-${ARCH} ${ADDR}
dev:
go run cmd/rcond/main.go ${ADDR}
RCOND_API_TOKEN=1234567890 go run cmd/rcond/main.go
upload:
scp rcond-${ARCH} pi@rpi-40ac:/home/pi/rcond

View File

@@ -1,13 +1,14 @@
# rcond
A simple daemon to manage network connections through NetworkManager's D-Bus interface.
A simple daemon to manage
- network connections through NetworkManager's D-Bus interface
- system hostname through the hostname1 service
It provides a REST API to:
- Create and activate WiFi connections
- Deactivate WiFi connections
- Remove stored connection profiles
The daemon is designed to run on Linux systems with NetworkManager.
- Get and set the system hostname
## Build and Run
@@ -46,6 +47,7 @@ All endpoints use JSON for request and response payloads.
```bash
curl -v -X POST http://localhost:8080/network/up \
-H "Content-Type: application/json" \
-H "X-API-Token: 1234567890" \
-d '{
"interface": "wlan0",
"ssid": "MyNetworkSSID",
@@ -58,6 +60,7 @@ curl -v -X POST http://localhost:8080/network/up \
```bash
curl -v -X POST http://localhost:8080/network/down \
-H "Content-Type: application/json" \
-H "X-API-Token: 1234567890" \
-d '{
"interface": "wlan0"
}'
@@ -66,13 +69,18 @@ curl -v -X POST http://localhost:8080/network/down \
### Remove the stored connection
```bash
curl -v -X POST http://localhost:8080/network/remove
curl -v -X POST http://localhost:8080/network/remove \
-H "X-API-Token: 1234567890" \
-d '{
"interface": "wlan0"
}'
```
### Get the hostname
```bash
curl -v http://localhost:8080/hostname
curl -v http://localhost:8080/hostname \
-H "X-API-Token: 1234567890"
```
### Set the hostname
@@ -80,6 +88,7 @@ curl -v http://localhost:8080/hostname
```bash
curl -v -X POST http://localhost:8080/hostname \
-H "Content-Type: application/json" \
-H "X-API-Token: 1234567890" \
-d '{
"hostname": "MyHostname"
}'

View File

@@ -8,6 +8,17 @@ servers:
- url: http://localhost:8080
description: Local development server
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Token
description: API token for authentication
security:
- ApiKeyAuth: []
paths:
/health:
get:
@@ -57,6 +68,8 @@ paths:
description: Network interface brought up successfully
'400':
description: Invalid request payload
'401':
description: Unauthorized - invalid or missing API token
'500':
description: Internal server error
@@ -82,6 +95,8 @@ paths:
description: Network interface brought down successfully
'400':
description: Invalid request payload
'401':
description: Unauthorized - invalid or missing API token
'500':
description: Internal server error
@@ -92,6 +107,8 @@ paths:
responses:
'200':
description: Connection profile removed successfully
'401':
description: Unauthorized - invalid or missing API token
'500':
description: Internal server error
@@ -108,6 +125,8 @@ paths:
type: string
description: Current hostname
example: "MyHostname"
'401':
description: Unauthorized - invalid or missing API token
'500':
description: Internal server error
post:
@@ -131,6 +150,7 @@ paths:
description: Hostname set successfully
'400':
description: Invalid request payload
'401':
description: Unauthorized - invalid or missing API token
'500':
description: Internal server error

View File

@@ -1,4 +1,4 @@
// Usage: rcond <address>
// Usage: rcond <address> <api-token>
package main
@@ -20,12 +20,17 @@ func usage() {
}
func main() {
if len(os.Args) < 2 {
usage()
addr := "0.0.0.0:8080"
if len(os.Args) > 1 {
addr = os.Args[1]
}
apiToken := os.Getenv("RCOND_API_TOKEN")
if apiToken == "" {
log.Fatal("RCOND_API_TOKEN environment variable not set")
}
addr := os.Args[1]
srv := http.NewServer(addr)
srv := http.NewServer(addr, apiToken)
srv.RegisterRoutes()
log.Printf("Starting server on %s", addr)

View File

@@ -12,9 +12,10 @@ import (
type Server struct {
router *mux.Router
srv *http.Server
apiToken string
}
func NewServer(addr string) *Server {
func NewServer(addr string, apiToken string) *Server {
router := mux.NewRouter()
srv := &http.Server{
@@ -27,6 +28,7 @@ func NewServer(addr string) *Server {
return &Server{
router: router,
srv: srv,
apiToken: apiToken,
}
}
@@ -38,13 +40,24 @@ func (s *Server) Shutdown(ctx context.Context) error {
return s.srv.Shutdown(ctx)
}
func (s *Server) verifyToken(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("X-API-Token")
if token == "" || token != s.apiToken {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next(w, r)
}
}
func (s *Server) RegisterRoutes() {
s.router.HandleFunc("/health", s.healthHandler).Methods(http.MethodGet)
s.router.HandleFunc("/network/up", HandleNetworkUp).Methods(http.MethodPost)
s.router.HandleFunc("/network/down", HandleNetworkDown).Methods(http.MethodPost)
s.router.HandleFunc("/network/remove", HandleNetworkRemove).Methods(http.MethodPost)
s.router.HandleFunc("/hostname", HandleGetHostname).Methods(http.MethodGet)
s.router.HandleFunc("/hostname", HandleSetHostname).Methods(http.MethodPost)
s.router.HandleFunc("/network/up", s.verifyToken(HandleNetworkUp)).Methods(http.MethodPost)
s.router.HandleFunc("/network/down", s.verifyToken(HandleNetworkDown)).Methods(http.MethodPost)
s.router.HandleFunc("/network/remove", s.verifyToken(HandleNetworkRemove)).Methods(http.MethodPost)
s.router.HandleFunc("/hostname", s.verifyToken(HandleGetHostname)).Methods(http.MethodGet)
s.router.HandleFunc("/hostname", s.verifyToken(HandleSetHostname)).Methods(http.MethodPost)
}
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {