Compare commits

...

11 Commits

Author SHA1 Message Date
Cedric Verstraeten
4cbf0323f1 Reference separate balena repositories 2023-06-25 20:21:44 +02:00
Cedric Verstraeten
1f5cb8ca88 merge directories 2023-06-25 20:05:03 +02:00
Cedric Verstraeten
8be0a04502 add balena deployment (app + block) 2023-06-25 20:03:37 +02:00
Cedric Verstraeten
bdc0039a24 fix: might be empty if not set, so will never fire motion alert 2023-06-24 20:22:51 +02:00
Cedric Verstraeten
756b893ecd reference latest tags 2023-06-24 12:58:16 +02:00
Cedric Verstraeten
36323b076f Fix for Kerberos Vault persistence check 2023-06-23 21:13:13 +02:00
Cedric Verstraeten
95f43b6444 Fix for empty vault settings, throw error 2023-06-23 20:20:38 +02:00
Cedric Verstraeten
5c23a62ac3 New function to validate Kerberos Hub connectivity and subscription 2023-06-23 19:01:04 +02:00
Cedric Verstraeten
2b425a2ddd add test video for verification 2023-06-23 17:16:13 +02:00
Cedric Verstraeten
abeeb95204 make region editable through ENV + add new upload function to upload to Kerberos Hub 2023-06-23 16:14:01 +02:00
Cédric Verstraeten
6aed20c466 Align to correct region 2023-06-22 15:53:46 +02:00
11 changed files with 251 additions and 147 deletions

View File

@@ -81,7 +81,7 @@ If you want to connect to an USB or Raspberry Pi camera, [you'll need to run our
Run Kerberos Agent with Balena super powers. Monitor your agent with seamless remote access, and an encrypted https endpoint.
Checkout our fleet on [Balena Hub](https://hub.balena.io/fleets?0%5B0%5D%5Bn%5D=any&0%5B0%5D%5Bo%5D=full_text_search&0%5B0%5D%5Bv%5D=agent), and add your agent.
[![balena deploy button](https://www.balena.io/deploy.svg)](https://dashboard.balena-cloud.com/deploy?repoUrl=https://github.com/kerberos-io/agent)
[![deploy with balena](https://balena.io/deploy.svg)](https://dashboard.balena-cloud.com/deploy?repoUrl=https://github.com/kerberos-io/agent)
**_Work In Progress_** - Currently we only support IP and USB Cameras, we have [an approach for leveraging the Raspberry Pi camera](https://github.com/kerberos-io/camera-to-rtsp), but this isn't working as expected with Balena. If you require this, you'll need to use the traditional Docker deployment with sidecar as mentioned above.
@@ -203,7 +203,7 @@ Next to attaching the configuration file, it is also possible to override the co
| `AGENT_HUB_URI` | The Kerberos Hub API, defaults to our Kerberos Hub SAAS. | "https://api.hub.domain.com" |
| `AGENT_HUB_KEY` | The access key linked to your account in Kerberos Hub. | "" |
| `AGENT_HUB_PRIVATE_KEY` | The secret access key linked to your account in Kerberos Hub. | "" |
| `AGENT_HUB_USERNAME` | Your Kerberos Hub username, which owns the above access and secret keys. | "" |
| `AGENT_HUB_REGION` | The Kerberos Hub region, to which you want to upload. | "" |
| `AGENT_HUB_SITE` | The site ID of a site you've created in your Kerberos Hub account. | "" |
| `AGENT_KERBEROSVAULT_URI` | The Kerberos Vault API url. | "https://vault.domain.com/api" |
| `AGENT_KERBEROSVAULT_ACCESS_KEY` | The access key of a Kerberos Vault account. | "" |

View File

@@ -82,7 +82,7 @@
initContainers:
- name: download-config
image: kerberos/agent:1b96d01
image: kerberos/agent:latest
volumeMounts:
- name: kerberos-data
mountPath: /home/agent/data/config
@@ -96,7 +96,7 @@
containers:
- name: agent
image: kerberos/agent:1b96d01
image: kerberos/agent:latest
volumeMounts:
- name: kerberos-data
mountPath: /home/agent/data/config

View File

@@ -0,0 +1,8 @@
# Deployment with Balena
If you're looking for a way to quickly and easily get up and running with an Kerberos Agent for your home network, this is the project for you.
We provide two mechanisms to deploy Kerberos Agent to your Balena Cloud fleet:
1. Use Kerberos Agent as [a block part of your larger application](https://github.com/kerberos-io/balena-agent-block).
2. Use Kerberos Agent as [a stand-a-lone application](https://github.com/kerberos-io/balena-agent).

View File

@@ -21,7 +21,7 @@ spec:
initContainers:
- name: download-config
image: kerberos/agent:1b96d01
image: kerberos/agent:latest
volumeMounts:
- name: kerberos-data
mountPath: /home/agent/data/config

View File

@@ -95,7 +95,7 @@
"s3": {
"proxyuri": "http://proxy.kerberos.io",
"bucket": "kerberosaccept",
"region": "eu-west1"
"region": "eu-west-1"
},
"kstorage": {},
"dropbox": {},
@@ -112,4 +112,4 @@
"hub_private_key": "",
"hub_site": "",
"condition_uri": ""
}
}

Binary file not shown.

View File

@@ -2,7 +2,6 @@ package cloud
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
@@ -15,14 +14,12 @@ import (
"github.com/gin-gonic/gin"
"github.com/golang-module/carbon/v2"
"github.com/kerberos-io/joy4/av/pubsub"
"github.com/minio/minio-go/v6"
mqtt "github.com/eclipse/paho.mqtt.golang"
av "github.com/kerberos-io/joy4/av"
"github.com/kerberos-io/joy4/cgo/ffmpeg"
"net/http"
"net/url"
"strconv"
"time"
@@ -85,9 +82,9 @@ func HandleUpload(configuration *models.Configuration, communication *models.Com
uploaded := false
configured := false
err = nil
if config.Cloud == "s3" {
uploaded, configured, err = UploadS3(configuration, fileName)
} else if config.Cloud == "kstorage" {
if config.Cloud == "s3" || config.Cloud == "kerberoshub" {
uploaded, configured, err = UploadKerberosHub(configuration, fileName)
} else if config.Cloud == "kstorage" || config.Cloud == "kerberosvault" {
uploaded, configured, err = UploadKerberosVault(configuration, fileName)
} else if config.Cloud == "dropbox" {
uploaded, configured, err = UploadDropbox(configuration, fileName)
@@ -103,6 +100,13 @@ func HandleUpload(configuration *models.Configuration, communication *models.Com
// Todo: implement ftp upload
} else if config.Cloud == "sftp" {
// Todo: implement sftp upload
} else if config.Cloud == "aws" {
// Todo: need to be updated, was previously used for hub.
uploaded, configured, err = UploadS3(configuration, fileName)
} else if config.Cloud == "azure" {
// Todo: implement azure upload
} else if config.Cloud == "google" {
// Todo: implement google upload
}
// And so on... (have a look here -> https://github.com/kerberos-io/agent/issues/95)
@@ -357,7 +361,9 @@ loop:
communication.CloudTimestamp.Store(time.Now().Unix())
log.Log.Info("HandleHeartBeat: (200) Heartbeat received by Kerberos Hub.")
} else {
communication.CloudTimestamp.Store(0)
if communication.CloudTimestamp != nil && communication.CloudTimestamp.Load() != nil {
communication.CloudTimestamp.Store(int64(0))
}
log.Log.Error("HandleHeartBeat: (400) Something went wrong while sending to Kerberos Hub.")
}
@@ -536,14 +542,14 @@ func VerifyHub(c *gin.Context) {
err := c.BindJSON(&config)
if err == nil {
hubKey := config.HubKey
hubURI := config.HubURI
publicKey := config.HubKey
privateKey := config.HubPrivateKey
content := []byte(`{"message": "fake-message"}`)
body := bytes.NewReader(content)
req, err := http.NewRequest("POST", hubURI+"/queue/test", body)
req, err := http.NewRequest("POST", hubURI+"/subscription/verify", nil)
if err == nil {
req.Header.Set("X-Kerberos-Cloud-Key", hubKey)
req.Header.Set("X-Kerberos-Hub-PublicKey", publicKey)
req.Header.Set("X-Kerberos-Hub-PrivateKey", privateKey)
client := &http.Client{}
resp, err := client.Do(req)
@@ -600,88 +606,80 @@ func VerifyPersistence(c *gin.Context) {
if config.Cloud == "dropbox" {
VerifyDropbox(config, c)
} else if config.Cloud == "s3" {
} else if config.Cloud == "s3" || config.Cloud == "kerberoshub" {
// timestamp_microseconds_instanceName_regionCoordinates_numberOfChanges_token
// 1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4
// - Timestamp
// - Size + - + microseconds
// - device
// - Region
// - Number of changes
// - Token
aws_access_key_id := config.S3.Publickey
aws_secret_access_key := config.S3.Secretkey
aws_region := config.S3.Region
// This is the new way ;)
if config.HubKey != "" {
aws_access_key_id = config.HubKey
}
if config.HubPrivateKey != "" {
aws_secret_access_key = config.HubPrivateKey
}
s3Client, err := minio.NewWithRegion("s3.amazonaws.com", aws_access_key_id, aws_secret_access_key, true, aws_region)
if err != nil {
if config.HubURI == "" ||
config.HubKey == "" ||
config.HubPrivateKey == "" ||
config.S3.Region == "" {
msg := "VerifyPersistence: Kerberos Hub not properly configured."
log.Log.Info(msg)
c.JSON(400, models.APIResponse{
Data: "Creation of Kerberos Hub connection failed: " + err.Error(),
Data: msg,
})
} else {
// Check if we need to use the proxy.
if config.S3.ProxyURI != "" {
var transport http.RoundTripper = &http.Transport{
Proxy: func(*http.Request) (*url.URL, error) {
return url.Parse(config.S3.ProxyURI)
},
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
s3Client.SetCustomTransport(transport)
// Open test-480p.mp4
file, err := os.Open("./data/test-480p.mp4")
if err != nil {
msg := "VerifyPersistence: error reading test-480p.mp4: " + err.Error()
log.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 := "VerifyPersistence: error reading Kerberos Hub HEAD request, " + config.HubURI + "/storage: " + err.Error()
log.Log.Error(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
deviceKey := "fake-key"
devicename := "justatest"
coordinates := "200-200-400-400"
eventToken := "769"
timestamp := time.Now().Unix()
fileName := strconv.FormatInt(timestamp, 10) + "_6-967003_justatest_200-200-400-400_24_769.mp4"
content := []byte("test-file")
body := bytes.NewReader(content)
fileName := strconv.FormatInt(timestamp, 10) +
"_6-967003_" + config.Name + "_200-200-400-400_24_769.mp4"
req.Header.Set("X-Kerberos-Storage-FileName", fileName)
req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera")
req.Header.Set("X-Kerberos-Storage-Device", config.Key)
req.Header.Set("X-Kerberos-Hub-PublicKey", config.HubKey)
req.Header.Set("X-Kerberos-Hub-PrivateKey", config.HubPrivateKey)
req.Header.Set("X-Kerberos-Hub-Region", config.S3.Region)
n, err := s3Client.PutObject(config.S3.Bucket,
config.S3.Username+"/"+fileName,
body,
body.Size(),
minio.PutObjectOptions{
ContentType: "video/mp4",
StorageClass: "ONEZONE_IA",
UserMetadata: map[string]string{
"event-timestamp": strconv.FormatInt(timestamp, 10),
"event-microseconds": deviceKey,
"event-instancename": devicename,
"event-regioncoordinates": coordinates,
"event-numberofchanges": deviceKey,
"event-token": eventToken,
"productid": deviceKey,
"publickey": aws_access_key_id,
"uploadtime": "now",
},
})
client := &http.Client{}
if err != nil {
c.JSON(400, models.APIResponse{
Data: "Upload of fake recording failed: " + err.Error(),
})
resp, err := client.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err == nil && resp != nil {
if resp.StatusCode == 200 {
msg := "VerifyPersistence: Upload allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")"
log.Log.Info(msg)
c.JSON(200, models.APIResponse{
Data: msg,
})
} else {
msg := "VerifyPersistence: Upload NOT allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")"
log.Log.Info(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
} else {
c.JSON(200, models.APIResponse{
Data: "Upload Finished: file has been uploaded to bucket: " + strconv.FormatInt(n, 10),
msg := "VerifyPersistence: Error creating Kerberos Hub request"
log.Log.Info(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
}
} else if config.Cloud == "kstorage" {
} else if config.Cloud == "kstorage" || config.Cloud == "kerberosvault" {
uri := config.KStorage.URI
accessKey := config.KStorage.AccessKey
@@ -690,10 +688,9 @@ func VerifyPersistence(c *gin.Context) {
provider := config.KStorage.Provider
if err == nil && uri != "" && accessKey != "" && secretAccessKey != "" {
var postData = []byte(`{"title":"Buy cheese and bread for breakfast."}`)
client := &http.Client{}
req, err := http.NewRequest("POST", uri+"/ping", bytes.NewReader(postData))
client := &http.Client{}
req, err := http.NewRequest("POST", uri+"/ping", nil)
req.Header.Add("X-Kerberos-Storage-AccessKey", accessKey)
req.Header.Add("X-Kerberos-Storage-SecretAccessKey", secretAccessKey)
resp, err := client.Do(req)
@@ -705,32 +702,35 @@ func VerifyPersistence(c *gin.Context) {
if provider != "" || directory != "" {
hubKey := config.KStorage.CloudKey
// This is the new way ;)
if config.HubKey != "" {
hubKey = config.HubKey
}
// Generate a random name.
timestamp := time.Now().Unix()
fileName := strconv.FormatInt(timestamp, 10) +
"_6-967003_justatest_200-200-400-400_24_769.mp4"
content := []byte("test-file")
body := bytes.NewReader(content)
//fileSize := int64(len(content))
"_6-967003_" + config.Name + "_200-200-400-400_24_769.mp4"
req, err := http.NewRequest("POST", uri+"/storage", body)
// Open test-480p.mp4
file, err := os.Open("./data/test-480p.mp4")
if err != nil {
msg := "VerifyPersistence: error reading test-480p.mp4: " + err.Error()
log.Log.Error(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
defer file.Close()
req, err := http.NewRequest("POST", uri+"/storage", file)
if err == nil {
req.Header.Set("Content-Type", "video/mp4")
req.Header.Set("X-Kerberos-Storage-CloudKey", hubKey)
req.Header.Set("X-Kerberos-Storage-CloudKey", config.HubKey)
req.Header.Set("X-Kerberos-Storage-AccessKey", accessKey)
req.Header.Set("X-Kerberos-Storage-SecretAccessKey", secretAccessKey)
req.Header.Set("X-Kerberos-Storage-Provider", provider)
req.Header.Set("X-Kerberos-Storage-FileName", fileName)
req.Header.Set("X-Kerberos-Storage-Device", "test")
req.Header.Set("X-Kerberos-Storage-Device", config.Key)
req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera")
req.Header.Set("X-Kerberos-Storage-Directory", directory)
client := &http.Client{}
resp, err := client.Do(req)
@@ -744,41 +744,45 @@ func VerifyPersistence(c *gin.Context) {
c.JSON(200, body)
} else {
c.JSON(400, models.APIResponse{
Data: "Something went wrong while verifying your persistence settings. Make sure your provider is the same as the storage provider in your Kerberos Vault, and the relevant storage provider is configured properly.",
Data: "VerifyPersistence: Something went wrong while verifying your persistence settings. Make sure your provider is the same as the storage provider in your Kerberos Vault, and the relevant storage provider is configured properly.",
})
}
}
}
} else {
c.JSON(400, models.APIResponse{
Data: "Upload of fake recording failed: " + err.Error(),
Data: "VerifyPersistence: Upload of fake recording failed: " + err.Error(),
})
}
} else {
c.JSON(400, models.APIResponse{
Data: "Something went wrong while creating /storage POST request." + err.Error(),
Data: "VerifyPersistence: Something went wrong while creating /storage POST request." + err.Error(),
})
}
} else {
c.JSON(400, models.APIResponse{
Data: "Provider and/or directory is missing from the request.",
Data: "VerifyPersistence: Provider and/or directory is missing from the request.",
})
}
} else {
c.JSON(400, models.APIResponse{
Data: "Something went wrong while verifying storage credentials: " + string(body),
Data: "VerifyPersistence: Something went wrong while verifying storage credentials: " + string(body),
})
}
} else {
c.JSON(400, models.APIResponse{
Data: "Something went wrong while verifying storage credentials:" + err.Error(),
Data: "VerifyPersistence: Something went wrong while verifying storage credentials:" + err.Error(),
})
}
} else {
c.JSON(400, models.APIResponse{
Data: "VerifyPersistence: please fill-in the required Kerberos Vault credentials.",
})
}
}
} else {
c.JSON(400, models.APIResponse{
Data: "No persistence was specified, so do not know what to verify:" + err.Error(),
Data: "VerifyPersistence: No persistence was specified, so do not know what to verify:" + err.Error(),
})
}
}

View File

@@ -0,0 +1,125 @@
package cloud
import (
"errors"
"io/ioutil"
"net/http"
"os"
"github.com/kerberos-io/agent/machinery/src/log"
"github.com/kerberos-io/agent/machinery/src/models"
)
func UploadKerberosHub(configuration *models.Configuration, fileName string) (bool, bool, error) {
config := configuration.Config
if config.HubURI == "" ||
config.HubKey == "" ||
config.HubPrivateKey == "" ||
config.S3.Region == "" {
err := "UploadKerberosHub: Kerberos Hub not properly configured."
log.Log.Info(err)
return false, false, errors.New(err)
}
// timestamp_microseconds_instanceName_regionCoordinates_numberOfChanges_token
// 1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4
// - Timestamp
// - Size + - + microseconds
// - device
// - Region
// - Number of changes
// - Token
log.Log.Info("UploadKerberosHub: Uploading to Kerberos Hub (" + config.HubURI + ")")
log.Log.Info("UploadKerberosHub: Upload started for " + fileName)
fullname := "data/recordings/" + fileName
// Check if we still have the file otherwise we abort the request.
file, err := os.OpenFile(fullname, os.O_RDWR, 0755)
if file != nil {
defer file.Close()
}
if err != nil {
err := "UploadKerberosHub: Upload Failed, file doesn't exists anymore."
log.Log.Info(err)
return false, true, errors.New(err)
}
// Check if we are allowed to upload to the hub with these credentials.
// There might be different reasons like (muted, read-only..)
req, err := http.NewRequest("HEAD", config.HubURI+"/storage/upload", nil)
if err != nil {
errorMessage := "UploadKerberosHub: error reading HEAD request, " + config.HubURI + "/storage: " + err.Error()
log.Log.Error(errorMessage)
return false, true, errors.New(errorMessage)
}
req.Header.Set("X-Kerberos-Storage-FileName", fileName)
req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera")
req.Header.Set("X-Kerberos-Storage-Device", config.Key)
req.Header.Set("X-Kerberos-Hub-PublicKey", config.HubKey)
req.Header.Set("X-Kerberos-Hub-PrivateKey", config.HubPrivateKey)
req.Header.Set("X-Kerberos-Hub-Region", config.S3.Region)
client := &http.Client{}
resp, err := client.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err == nil {
if resp != nil {
if err == nil {
if resp.StatusCode == 200 {
log.Log.Info("UploadKerberosHub: Upload allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")")
} else {
log.Log.Info("UploadKerberosHub: Upload NOT allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")")
return false, true, nil
}
}
}
}
// Now we know we are allowed to upload to the hub, we can start uploading.
req, err = http.NewRequest("POST", config.HubURI+"/storage/upload", file)
if err != nil {
errorMessage := "UploadKerberosHub: error reading POST request, " + config.KStorage.URI + "/storage/upload: " + err.Error()
log.Log.Error(errorMessage)
return false, true, errors.New(errorMessage)
}
req.Header.Set("Content-Type", "video/mp4")
req.Header.Set("X-Kerberos-Storage-FileName", fileName)
req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera")
req.Header.Set("X-Kerberos-Storage-Device", config.Key)
req.Header.Set("X-Kerberos-Hub-PublicKey", config.HubKey)
req.Header.Set("X-Kerberos-Hub-PrivateKey", config.HubPrivateKey)
req.Header.Set("X-Kerberos-Hub-Region", config.S3.Region)
client = &http.Client{}
resp, err = client.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err == nil {
if resp != nil {
body, err := ioutil.ReadAll(resp.Body)
if err == nil {
if resp.StatusCode == 200 {
log.Log.Info("UploadKerberosHub: Upload Finished, " + resp.Status + ".")
return true, true, nil
} else {
log.Log.Info("UploadKerberosHub: Upload Failed, " + resp.Status + ", " + string(body))
return false, true, nil
}
}
}
}
errorMessage := "UploadKerberosHub: Upload Failed, " + err.Error()
log.Log.Info(errorMessage)
return false, true, errors.New(errorMessage)
}

View File

@@ -401,12 +401,12 @@ func OverrideWithEnvironmentVariables(configuration *models.Configuration) {
case "AGENT_HUB_PRIVATE_KEY":
configuration.Config.HubPrivateKey = value
break
case "AGENT_HUB_USERNAME":
configuration.Config.S3.Username = value
break
case "AGENT_HUB_SITE":
configuration.Config.HubSite = value
break
case "AGENT_HUB_REGION":
configuration.Config.S3.Region = value
break
/* When storing in a Kerberos Vault */
case "AGENT_KERBEROSVAULT_URI":

View File

@@ -165,7 +165,7 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi
if detectMotion && isPixelChangeThresholdReached {
// If offline mode is disabled, send a message to the hub
if config.Offline == "false" {
if config.Offline != "true" {
if mqttClient != nil {
if key != "" {
mqttClient.Publish("kerberos/"+key+"/device/"+config.Key+"/motion", 2, false, "motion")

View File

@@ -2114,17 +2114,6 @@ class Settings extends React.Component {
/>
{config.cloud === this.KERBEROS_HUB && (
<>
<Input
noPadding
label={t('settings.persistence.kerberoshub_proxyurl')}
placeholder={t(
'settings.persistence.kerberoshub_description_proxyurl'
)}
value={config.s3 ? config.s3.proxyuri : ''}
onChange={(value) =>
this.onUpdateField('s3', 'proxyuri', value, config.s3)
}
/>
<Input
noPadding
label={t('settings.persistence.kerberoshub_region')}
@@ -2136,28 +2125,6 @@ class Settings extends React.Component {
this.onUpdateField('s3', 'region', value, config.s3)
}
/>
<Input
noPadding
label={t('settings.persistence.kerberoshub_bucket')}
placeholder={t(
'settings.persistence.kerberoshub_description_bucket'
)}
value={config.s3 ? config.s3.bucket : ''}
onChange={(value) =>
this.onUpdateField('s3', 'bucket', value, config.s3)
}
/>
<Input
noPadding
label={t('settings.persistence.kerberoshub_username')}
placeholder={t(
'settings.persistence.kerberoshub_description_username'
)}
value={config.s3 ? config.s3.username : ''}
onChange={(value) =>
this.onUpdateField('s3', 'username', value, config.s3)
}
/>
</>
)}
{config.cloud === this.KERBEROS_VAULT && (