Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,24 @@ export AUTH_API_TOKEN="your-api-token-here"

**HTTP Basic Auth:**
```bash
curl -u admin:password http://localhost:7777/api/health
curl -u admin:password http://localhost:7777/api/keys
```

**Bearer Token:**
```bash
curl -H "Authorization: Bearer your-token" http://localhost:7777/api/health
curl -H "Authorization: Bearer your-token" http://localhost:7777/api/keys
```

### Unauthenticated Endpoints

The `/api/health` endpoint is exempt from authentication to allow Docker health checks, Kubernetes probes, and load balancer monitoring to work without credentials.

### Security Features
- Constant-time credential comparison (prevents timing attacks)
- Supports both Basic Auth and Bearer token simultaneously
- Bearer token takes precedence if both are provided
- bcrypt password hashing for stored credentials
- Health endpoint excluded from auth for container orchestration compatibility

**Note**: In production environments, always enable authentication and use HTTPS.

Expand All @@ -135,10 +140,12 @@ curl -H "Authorization: Bearer your-token" http://localhost:7777/api/health

### Get Server Health Status

Check if the server is running and responsive.
Check if the server is running and responsive. This endpoint does **not** require authentication, making it suitable for Docker health checks, Kubernetes probes, and load balancer health monitoring.

**Endpoint**: `GET /health`

**Authentication**: None required

**Response**: `200 OK`

```json
Expand All @@ -150,7 +157,11 @@ Check if the server is running and responsive.
**Example**:

```bash
# No authentication needed
curl http://localhost:7777/api/health

# Also works with HTTPS when TLS is enabled
curl -k https://localhost:7777/api/health
```

---
Expand Down
10 changes: 7 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -94,24 +94,28 @@
# Copy binary from builder
COPY --from=go-builder /app/web-cli /app/web-cli

# Copy healthcheck script
COPY scripts/healthcheck.sh /app/healthcheck.sh

# Set ownership
RUN chown webcli:webcli /app/web-cli
RUN chown webcli:webcli /app/web-cli /app/healthcheck.sh && \
chmod +x /app/healthcheck.sh

# Switch to non-root user
USER webcli

# Expose default port
EXPOSE 7777

# Health check using curl
# Health check using script (auto-detects http/https based on TLS config)
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD curl -sf http://localhost:7777/api/health || exit 1
CMD ["/app/healthcheck.sh"]

# Environment variables with defaults
# Note: WEBCLI_ENCRYPTION_KEY_PATH is a file path to the key file, not the secret itself
# TMPDIR is set to user's home for proper temp file creation in non-root context
# hadolint ignore=DL3044
ENV WEBCLI_PORT=7777 \

Check warning on line 118 in Dockerfile

View workflow job for this annotation

GitHub Actions / Build Docker Image

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "WEBCLI_ENCRYPTION_KEY_PATH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 118 in Dockerfile

View workflow job for this annotation

GitHub Actions / Build Docker Image

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "WEBCLI_ENCRYPTION_KEY_PATH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
WEBCLI_HOST=0.0.0.0 \
WEBCLI_DATABASE_PATH=/data/web-cli.db \
WEBCLI_ENCRYPTION_KEY_PATH=/data/.encryption_key \
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ services:
# CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}

healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:7777/api/health"]
test: ["CMD", "/app/healthcheck.sh"]
interval: 30s
timeout: 5s
retries: 3
Expand Down
28 changes: 21 additions & 7 deletions docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,41 @@ export AUTH_API_TOKEN="your-api-token-here"
- Supports both methods simultaneously (token takes precedence)
- **Startup validation**: Server fails fast if auth is enabled but credentials are missing

### Unauthenticated Endpoints

The following endpoints are exempt from authentication to support container health checks and orchestration probes:

| Endpoint | Purpose |
|----------|---------|
| `/api/health` | Health check for Docker/Kubernetes probes |

### Usage Examples

```bash
# Basic Auth
curl -u admin:password http://localhost:7777/api/health
# Health check (no auth required)
curl http://localhost:7777/api/health

# Basic Auth (required for all other endpoints)
curl -u admin:password http://localhost:7777/api/keys

# Bearer Token
curl -H "Authorization: Bearer your-token" http://localhost:7777/api/health
# Bearer Token (required for all other endpoints)
curl -H "Authorization: Bearer your-token" http://localhost:7777/api/keys
```

### Testing Authentication

```bash
# Should fail (401 Unauthorized)
# Health endpoint - always succeeds (no auth required)
curl http://localhost:7777/api/health

# Other endpoints - should fail without auth (401 Unauthorized)
curl http://localhost:7777/api/keys

# Should succeed with Basic Auth
curl -u admin:password http://localhost:7777/api/health
curl -u admin:password http://localhost:7777/api/keys

# Should succeed with Bearer token
curl -H "Authorization: Bearer your-token" http://localhost:7777/api/health
curl -H "Authorization: Bearer your-token" http://localhost:7777/api/keys
```

---
Expand Down
30 changes: 30 additions & 0 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,26 @@ const docTemplate = `{
}
}
},
"/health": {
"get": {
"description": "Check if the server is running and responsive. This endpoint does not require authentication.",
"produces": [
"application/json"
],
"tags": [
"System"
],
"summary": "Health check",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/internal_server.HealthResponse"
}
}
}
}
},
"/history": {
"get": {
"security": [
Expand Down Expand Up @@ -3840,6 +3860,16 @@ const docTemplate = `{
}
}
},
"internal_server.HealthResponse": {
"description": "Health check response",
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "ok"
}
}
},
"internal_server.ShellInfo": {
"description": "Information about an available shell",
"type": "object",
Expand Down
30 changes: 30 additions & 0 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,26 @@
}
}
},
"/health": {
"get": {
"description": "Check if the server is running and responsive. This endpoint does not require authentication.",
"produces": [
"application/json"
],
"tags": [
"System"
],
"summary": "Health check",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/internal_server.HealthResponse"
}
}
}
}
},
"/history": {
"get": {
"security": [
Expand Down Expand Up @@ -3834,6 +3854,16 @@
}
}
},
"internal_server.HealthResponse": {
"description": "Health check response",
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "ok"
}
}
},
"internal_server.ShellInfo": {
"description": "Information about an available shell",
"type": "object",
Expand Down
21 changes: 21 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,13 @@ definitions:
example: Invalid request body
type: string
type: object
internal_server.HealthResponse:
description: Health check response
properties:
status:
example: ok
type: string
type: object
internal_server.ShellInfo:
description: Information about an available shell
properties:
Expand Down Expand Up @@ -1206,6 +1213,20 @@ paths:
summary: List all environment variable groups
tags:
- Environment Variables
/health:
get:
description: Check if the server is running and responsive. This endpoint does
not require authentication.
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/internal_server.HealthResponse'
summary: Health check
tags:
- System
/history:
get:
consumes:
Expand Down
17 changes: 13 additions & 4 deletions internal/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ var ErrAuthMisconfigured = errors.New("authentication is enabled but no credenti

// AuthConfig holds authentication configuration
type AuthConfig struct {
Enabled bool
Username string
Password string
APIToken string
Enabled bool
Username string
Password string
APIToken string
ExcludePaths []string // Paths exempt from authentication (e.g., /api/health)
}

// LoadAuthConfig loads authentication configuration from environment
Expand Down Expand Up @@ -61,6 +62,14 @@ func BasicAuth(config *AuthConfig) func(http.Handler) http.Handler {
return
}

// Skip auth for excluded paths (e.g., health checks)
for _, path := range config.ExcludePaths {
if r.URL.Path == path {
next.ServeHTTP(w, r)
return
}
}

// Check for API token first (Bearer token in Authorization header)
authHeader := r.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
Expand Down
Loading