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 ARCH ?= arm64
ADDR ?= 0.0.0.0:8080 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 env GOOS=linux GOARCH=${ARCH} go build -o bin/rcond-${ARCH} ./cmd/rcond/main.go
run: run:
bin/rcond-${ARCH} ${ADDR} source .env && bin/rcond-${ARCH} ${ADDR}
dev: dev:
go run cmd/rcond/main.go ${ADDR} RCOND_API_TOKEN=1234567890 go run cmd/rcond/main.go
upload: upload:
scp rcond-${ARCH} pi@rpi-40ac:/home/pi/rcond scp rcond-${ARCH} pi@rpi-40ac:/home/pi/rcond

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
// Usage: rcond <address> // Usage: rcond <address> <api-token>
package main package main
@@ -20,12 +20,17 @@ func usage() {
} }
func main() { 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, apiToken)
srv := http.NewServer(addr)
srv.RegisterRoutes() srv.RegisterRoutes()
log.Printf("Starting server on %s", addr) log.Printf("Starting server on %s", addr)

View File

@@ -10,11 +10,12 @@ import (
) )
type Server struct { type Server struct {
router *mux.Router router *mux.Router
srv *http.Server srv *http.Server
apiToken string
} }
func NewServer(addr string) *Server { func NewServer(addr string, apiToken string) *Server {
router := mux.NewRouter() router := mux.NewRouter()
srv := &http.Server{ srv := &http.Server{
@@ -25,8 +26,9 @@ func NewServer(addr string) *Server {
} }
return &Server{ return &Server{
router: router, router: router,
srv: srv, srv: srv,
apiToken: apiToken,
} }
} }
@@ -38,13 +40,24 @@ func (s *Server) Shutdown(ctx context.Context) error {
return s.srv.Shutdown(ctx) 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() { func (s *Server) RegisterRoutes() {
s.router.HandleFunc("/health", s.healthHandler).Methods(http.MethodGet) s.router.HandleFunc("/health", s.healthHandler).Methods(http.MethodGet)
s.router.HandleFunc("/network/up", HandleNetworkUp).Methods(http.MethodPost) s.router.HandleFunc("/network/up", s.verifyToken(HandleNetworkUp)).Methods(http.MethodPost)
s.router.HandleFunc("/network/down", HandleNetworkDown).Methods(http.MethodPost) s.router.HandleFunc("/network/down", s.verifyToken(HandleNetworkDown)).Methods(http.MethodPost)
s.router.HandleFunc("/network/remove", HandleNetworkRemove).Methods(http.MethodPost) s.router.HandleFunc("/network/remove", s.verifyToken(HandleNetworkRemove)).Methods(http.MethodPost)
s.router.HandleFunc("/hostname", HandleGetHostname).Methods(http.MethodGet) s.router.HandleFunc("/hostname", s.verifyToken(HandleGetHostname)).Methods(http.MethodGet)
s.router.HandleFunc("/hostname", HandleSetHostname).Methods(http.MethodPost) s.router.HandleFunc("/hostname", s.verifyToken(HandleSetHostname)).Methods(http.MethodPost)
} }
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {