Fixed issue with shelly self signed certificate.
This commit is contained in:
@@ -8,6 +8,7 @@ Prometheus exporter for Shelly Gen1 Wi-Fi devices that expose monitoring data th
|
||||
- Correct Prometheus energy counters for both Gen1 `meters` and EM/3EM `emeters`.
|
||||
- Concurrent collection from all configured devices.
|
||||
- Per-device availability, request duration, and error metrics.
|
||||
- HTTPS support for devices with self-signed or otherwise invalid certificates.
|
||||
- Strict YAML validation, graceful shutdown, and a health endpoint.
|
||||
- Generic `gen1` product type for compatible devices not explicitly listed.
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ The exporter sends an `Authorization: Basic` header when either `username` or `p
|
||||
| `http://192.168.0.30/` | `http://192.168.0.30/status` |
|
||||
| `https://shelly.example/device` | `https://shelly.example/device/status` |
|
||||
|
||||
HTTPS uses the operating system trust store and normal certificate verification. There is no option to disable TLS verification.
|
||||
For HTTPS device URLs, the exporter deliberately skips certificate verification. Self-signed, expired, and hostname-mismatched certificates are accepted without additional configuration.
|
||||
|
||||
## Product identifiers
|
||||
|
||||
|
||||
+3
-3
@@ -157,10 +157,10 @@ groups:
|
||||
## Security
|
||||
|
||||
- Store `config.yaml` outside source control and restrict it to the exporter account, for example with mode `0600` on Linux.
|
||||
- Prefer a trusted management network or HTTPS because Basic Authentication does not encrypt credentials over plain HTTP.
|
||||
- Prefer a trusted management network. HTTPS encrypts credentials in transit, but the exporter does not verify device certificates and therefore does not authenticate the remote endpoint.
|
||||
- The exporter endpoints themselves do not require authentication. Restrict port 9090 with firewall, reverse proxy, or network policy when necessary.
|
||||
- Device passwords are not metric labels, but request failures can include the target URL in logs. Do not put credentials in URL user-info; use `username` and `password` fields.
|
||||
- HTTPS certificate verification is enabled and cannot be bypassed by configuration.
|
||||
- HTTPS device certificates are not verified. Restrict device traffic to a trusted network to reduce man-in-the-middle risk.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -193,7 +193,7 @@ Confirm that the configured username and password match the device. The exporter
|
||||
|
||||
### TLS certificate error
|
||||
|
||||
The device certificate must be trusted by the exporter operating system or container. Use a certificate issued by a trusted internal CA and add that CA to the runtime trust store.
|
||||
The exporter deliberately ignores certificate validity for HTTPS device connections, including trust, expiry, and hostname checks. A certificate validation error therefore indicates that an older exporter build may still be running; update and restart it. Other TLS errors, such as unsupported protocol versions or cipher suites, are not certificate validation errors and can still fail the scrape.
|
||||
|
||||
### Device is up but an expected metric is absent
|
||||
|
||||
|
||||
+13
-1
@@ -3,6 +3,7 @@ package shelly
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -39,11 +40,22 @@ func NewClient(rawURL, username, password string, timeout time.Duration) (*Clien
|
||||
username: username,
|
||||
password: password,
|
||||
httpClient: &http.Client{
|
||||
Timeout: timeout,
|
||||
Timeout: timeout,
|
||||
Transport: insecureTLSTransport(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// insecureTLSTransport retains the standard HTTP transport behavior but accepts
|
||||
// certificates that are expired, self-signed, or issued for another hostname.
|
||||
func insecureTLSTransport() *http.Transport {
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true, // #nosec G402 -- Shelly device certificates are intentionally not verified.
|
||||
}
|
||||
return transport
|
||||
}
|
||||
|
||||
// GetStatus decodes the /status response into destination.
|
||||
func (c *Client) GetStatus(ctx context.Context, destination any) error {
|
||||
statusURL := *c.baseURL
|
||||
|
||||
@@ -38,6 +38,28 @@ func TestClientGetStatusUsesStatusEndpointAndBasicAuth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientGetStatusAcceptsUntrustedTLSCertificate(t *testing.T) {
|
||||
server := httptest.NewTLSServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
_, _ = response.Write([]byte(`{"serial":42}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL, "", "", time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var status struct {
|
||||
Serial int `json:"serial"`
|
||||
}
|
||||
if err := client.GetStatus(context.Background(), &status); err != nil {
|
||||
t.Fatalf("GetStatus() returned an unexpected TLS certificate error: %v", err)
|
||||
}
|
||||
if status.Serial != 42 {
|
||||
t.Fatalf("Serial = %d, want 42", status.Serial)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientGetStatusReportsHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
|
||||
http.Error(response, "not authorized", http.StatusUnauthorized)
|
||||
|
||||
Reference in New Issue
Block a user