Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
41 changes: 24 additions & 17 deletions machinery/src/cloud/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,7 @@
if config.HubURI == "" ||
config.HubKey == "" ||
config.HubPrivateKey == "" ||
config.S3 == nil ||
config.S3.Region == "" {
msg := "cloud.VerifyPersistence(kerberoshub): Kerberos Hub not properly configured."
log.Error(msg)
Expand All @@ -1545,24 +1546,14 @@
})
} else {

// Open test-480p.mp4
file, err := os.Open(configDirectory + "/data/test-480p.mp4")
req, err := http.NewRequest(http.MethodHead, config.HubURI+"/storage/upload", nil)
if err != nil {
msg := "cloud.VerifyPersistence(kerberoshub): error reading test-480p.mp4: " + err.Error()
log.Error(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
defer file.Close()

req, err := http.NewRequest("POST", config.HubURI+"/storage/upload", file)
if err != nil {
msg := "cloud.VerifyPersistence(kerberoshub): error reading Kerberos Hub HEAD request, " + config.HubURI + "/storage: " + err.Error()
msg := "cloud.VerifyPersistence(kerberoshub): error creating Kerberos Hub upload authorization request: " + err.Error()
log.Error(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
return
}

timestamp := time.Now().Unix()
Expand All @@ -1580,12 +1571,12 @@
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client = &http.Client{Transport: tr}
client = &http.Client{Transport: tr, CheckRedirect: stripHubCredentialsOnCrossHostRedirect}
} else {
client = &http.Client{}
client = &http.Client{CheckRedirect: stripHubCredentialsOnCrossHostRedirect}
}

resp, err := client.Do(req)

Check failure

Code scanning / CodeQL

Uncontrolled data used in network request Critical

The
URL
of this request depends on a
user-provided value
.
if resp != nil {
defer resp.Body.Close()
}
Expand All @@ -1598,14 +1589,17 @@
Data: msg,
})
} else {
msg := "cloud.VerifyPersistence(kerberoshub): upload not allowed using the configured credentials"
msg := hubPersistenceVerificationError(resp.StatusCode)
log.Error(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
} else {
msg := "cloud.VerifyPersistence(kerberoshub): Error creating Kerberos Hub request"
msg := "cloud.VerifyPersistence(kerberoshub): Kerberos Hub upload authorization request failed"
if err != nil {
msg += ": " + err.Error()
}
log.Error(msg)
c.JSON(400, models.APIResponse{
Data: msg,
Expand Down Expand Up @@ -1774,6 +1768,19 @@
}
}

func hubPersistenceVerificationError(statusCode int) string {
reason := "Kerberos Hub rejected upload authorization"
switch statusCode {
case http.StatusBadRequest:
reason += ": the credentials or device/project assignment are invalid, or the storage limit was reached"
case http.StatusUnauthorized:
reason += ": the account has no active subscription or the subscription expired"
case http.StatusForbidden:
reason += ": the device is muted"
}
return "cloud.VerifyPersistence(kerberoshub): " + reason + " (HTTP " + strconv.Itoa(statusCode) + ")"
}

// VerifySecondaryPersistence godoc
// @Router /api/persistence/secondary/verify [post]
// @ID verify-secondary-persistence
Expand Down
77 changes: 77 additions & 0 deletions machinery/src/cloud/cloud_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package cloud

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/gin-gonic/gin"
"github.com/kerberos-io/agent/machinery/src/models"
agentonvif "github.com/kerberos-io/agent/machinery/src/onvif"
goonvif "github.com/kerberos-io/onvif"
Expand All @@ -18,6 +21,80 @@ import (
goonvifxsd "github.com/kerberos-io/onvif/xsd/onvif"
)

func TestVerifyPersistenceKerberosHubUsesAuthorizationHead(t *testing.T) {
var requestMethod string
var requestBody []byte
var requestHeaders http.Header
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestMethod = r.Method
requestHeaders = r.Header.Clone()
requestBody, _ = io.ReadAll(r.Body)
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

config := models.Config{
Cloud: "kerberoshub",
Key: "camera-1",
Name: "front-door",
HubURI: server.URL,
HubKey: "project-public",
HubPrivateKey: "project-private",
S3: &models.S3{Region: "eu"},
}
payload, err := json.Marshal(config)
if err != nil {
t.Fatalf("json.Marshal() error = %v", err)
}

gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/persistence/verify", bytes.NewReader(payload))
ctx.Request.Header.Set("Content-Type", "application/json")

VerifyPersistence(ctx, t.TempDir())

if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, body = %s", recorder.Code, recorder.Body.String())
}
if requestMethod != http.MethodHead {
t.Fatalf("Hub request method = %q, want HEAD", requestMethod)
}
if len(requestBody) != 0 {
t.Fatalf("Hub request body has %d bytes, want none", len(requestBody))
}
for header, want := range map[string]string{
"X-Kerberos-Storage-Device": "camera-1",
"X-Kerberos-Hub-PublicKey": "project-public",
"X-Kerberos-Hub-PrivateKey": "project-private",
"X-Kerberos-Hub-Region": "eu",
"X-Kerberos-Storage-Capture": "IPCamera",
} {
if got := requestHeaders.Get(header); got != want {
t.Errorf("%s = %q, want %q", header, got, want)
}
}
}
Comment thread
cedricve marked this conversation as resolved.

func TestHubPersistenceVerificationErrorExplainsStatus(t *testing.T) {
for _, test := range []struct {
status int
want string
}{
{status: http.StatusBadRequest, want: "credentials or device/project assignment"},
{status: http.StatusUnauthorized, want: "no active subscription"},
{status: http.StatusForbidden, want: "device is muted"},
{status: http.StatusBadGateway, want: "HTTP 502"},
} {
t.Run(http.StatusText(test.status), func(t *testing.T) {
if got := hubPersistenceVerificationError(test.status); !strings.Contains(got, test.want) {
t.Fatalf("hubPersistenceVerificationError(%d) = %q, want it to contain %q", test.status, got, test.want)
}
})
}
}

func TestHeartbeatFailureLogOmitsHubResponseBody(t *testing.T) {
response := &http.Response{
StatusCode: http.StatusBadRequest,
Expand Down
Loading