diff --git a/cmd/provisioning-client/bluetooth.go b/cmd/provisioning-client/bluetooth.go index 17e50b6a..75b7be2e 100644 --- a/cmd/provisioning-client/bluetooth.go +++ b/cmd/provisioning-client/bluetooth.go @@ -6,6 +6,7 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" + "encoding/json" "errors" "fmt" "strings" @@ -27,6 +28,7 @@ const ( pskKey = "psk" robotPartIDKey = "id" robotPartSecretKey = "secret" + apiKeyCredsKey = "api_key" appAddressKey = "app_address" availableWiFiNetworksKey = "networks" statusKey = "status" @@ -296,6 +298,15 @@ func BTSetDeviceCreds(chars map[string]bluetooth.DeviceCharacteristic) error { return err } + apiKeyJSON, err := json.Marshal(opts.APIKey) + if err != nil { + return err + } + cryptAPIKey, err := encrypt(apiKeyJSON) + if err != nil { + return err + } + cryptAppAddr, err := encrypt([]byte(opts.AppAddr)) if err != nil { return err @@ -311,6 +322,11 @@ func BTSetDeviceCreds(chars map[string]bluetooth.DeviceCharacteristic) error { return errw.Wrap(err, "writing secret") } + _, err = chars[apiKeyCredsKey].WriteWithoutResponse(cryptAPIKey) + if err != nil { + return errw.Wrap(err, "writing api key") + } + _, err = chars[appAddressKey].WriteWithoutResponse(cryptAppAddr) if err != nil { return errw.Wrap(err, "writing app address") @@ -450,6 +466,9 @@ func getCharicteristicsMap(device *bluetooth.Device) (map[string]bluetooth.Devic case getUUID(robotPartSecretKey): key = robotPartSecretKey charMap[robotPartSecretKey] = char + case getUUID(apiKeyCredsKey): + key = apiKeyCredsKey + charMap[apiKeyCredsKey] = char case getUUID(cryptoKey): key = cryptoKey charMap[cryptoKey] = char diff --git a/cmd/provisioning-client/grpc.go b/cmd/provisioning-client/grpc.go index 053a0376..a5972448 100644 --- a/cmd/provisioning-client/grpc.go +++ b/cmd/provisioning-client/grpc.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/viamrobotics/agent/subsystems/networking" + "github.com/viamrobotics/agent/utils" pb "go.viam.com/api/provisioning/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -40,7 +41,7 @@ func grpcClient() error { } if opts.PartID != "" { - if err := SetDeviceCreds(ctx, client, opts.PartID, opts.Secret, opts.AppAddr); err != nil { + if err := SetDeviceCreds(ctx, client, opts.PartID, opts.Secret, opts.AppAddr, opts.APIKey); err != nil { return err } } @@ -91,13 +92,18 @@ func GetNetworks(ctx context.Context, client pb.ProvisioningServiceClient) error return nil } -func SetDeviceCreds(ctx context.Context, client pb.ProvisioningServiceClient, id, secret, appaddr string) error { +func SetDeviceCreds(ctx context.Context, client pb.ProvisioningServiceClient, id, secret, appaddr string, apiKey utils.APIKey) error { fmt.Println("Writing device credentials...") + req := &pb.SetSmartMachineCredentialsRequest{ Cloud: &pb.CloudConfig{ Id: id, Secret: secret, AppAddress: appaddr, + ApiKey: &pb.APIKey{ + Id: apiKey.ID, + Key: apiKey.Key, + }, }, } diff --git a/cmd/provisioning-client/opts.go b/cmd/provisioning-client/opts.go index ecf3343c..cd2a33b8 100644 --- a/cmd/provisioning-client/opts.go +++ b/cmd/provisioning-client/opts.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/jessevdk/go-flags" + "github.com/viamrobotics/agent/utils" ) var opts struct { @@ -21,9 +22,12 @@ var opts struct { PSK string `description:"psk for bluetooth security" long:"psk"` - AppAddr string `default:"https://app.viam.com:443" description:"Cloud address to set in viam.json" long:"app-addr"` - PartID string `description:"PartID to set in viam.json" long:"part-id"` - Secret string `description:"Device secret to set in viam.json" long:"secret"` + AppAddr string `default:"https://app.viam.com:443" description:"Cloud address to set in viam.json" long:"app-addr"` + PartID string `description:"PartID to set in viam.json" long:"part-id"` + Secret string `description:"Device secret to set in viam.json" long:"secret"` + APIKeyID string `description:"API Key ID" long:"api-key-id"` + APIKeyKey string `description:"API Key secret" long:"api-key-key"` + APIKey utils.APIKey Exit bool `description:"Tell the device to exit provisioning mode" long:"exit" short:"e"` @@ -33,6 +37,13 @@ var opts struct { Help bool `description:"Show this help message" long:"help" short:"h"` } +func SetAPIKey() { + opts.APIKey = utils.APIKey{ + ID: opts.APIKeyID, + Key: opts.APIKeyKey, + } +} + func parseOpts() bool { parser := flags.NewParser(&opts, flags.IgnoreUnknown) parser.Usage = "runs as a background service and manages updates and the process lifecycle for viam-server." @@ -55,9 +66,20 @@ func parseOpts() bool { return false } - if opts.PartID != "" || opts.Secret != "" { - if opts.PartID == "" || opts.Secret == "" || opts.AppAddr == "" { - fmt.Println("Error: Must set both Secret and PartID (and optionally AppAddr) at the same time!") + SetAPIKey() + if opts.PartID != "" || opts.Secret != "" || !opts.APIKey.IsEmpty() { + if opts.PartID == "" || opts.AppAddr == "" { + fmt.Println("Error: Must set PartID and AppAddr when configuring credentials!") + return false + } + + if opts.APIKey.IsPartiallySet() { + fmt.Println("Error: API Key must have both ID and Key set, or neither!") + return false + } + + if opts.Secret == "" && opts.APIKey.IsEmpty() { + fmt.Println("Error: Must provide either Secret or complete API Key!") return false } } diff --git a/cmd/test-client/main.go b/cmd/test-client/main.go index 713030d3..5c8904b1 100644 --- a/cmd/test-client/main.go +++ b/cmd/test-client/main.go @@ -10,6 +10,8 @@ import ( "os" "github.com/jessevdk/go-flags" + errw "github.com/pkg/errors" + "github.com/viamrobotics/agent/utils" pb "go.viam.com/api/app/agent/v1" "go.viam.com/rdk/logging" "go.viam.com/utils/rpc" @@ -91,28 +93,36 @@ func loadCredentials(path string) (*logging.CloudConfig, error) { return nil, err } - cfg := make(map[string]map[string]string) + var cfg map[string]interface{} err = json.Unmarshal(b, &cfg) if err != nil { return nil, err } - cloud, ok := cfg["cloud"] + cloud, ok := cfg["cloud"].(map[string]interface{}) if !ok { - return nil, fmt.Errorf("no cloud section in file %s", path) + return nil, errw.Errorf("no cloud section in file %s", path) } - for _, req := range []string{"app_address", "id", "secret"} { - field, ok := cloud[req] - if !ok { - return nil, fmt.Errorf("no cloud config field for %s", field) - } + appAddress, ok := cloud["app_address"].(string) + if !ok || appAddress == "" { + return nil, errw.New("field 'app_address' in cloud config must be a non-empty string") + } + + id, ok := cloud["id"].(string) + if !ok || id == "" { + return nil, errw.New("field 'id' in cloud config must be a non-empty string") + } + + cloudCreds, err := utils.ParseCloudCreds(cloud) + if err != nil { + return nil, err } cloudConfig := &logging.CloudConfig{ - AppAddress: cloud["app_address"], - ID: cloud["id"], - Secret: cloud["secret"], + AppAddress: appAddress, + ID: id, + CloudCred: cloudCreds, } return cloudConfig, nil @@ -125,14 +135,10 @@ func dial(ctx context.Context, logger logging.Logger, cloudConfig *logging.Cloud } dialOpts := make([]rpc.DialOption, 0, 2) - // Only add credentials when secret is set. - if cloudConfig.Secret != "" { - dialOpts = append(dialOpts, rpc.WithEntityCredentials(cloudConfig.ID, - rpc.Credentials{ - Type: "robot-secret", - Payload: cloudConfig.Secret, - }, - )) + + // Only add credentials when they are set. + if cloudConfig.CloudCred != nil { + dialOpts = append(dialOpts, cloudConfig.CloudCred) } if u.Scheme == "http" { diff --git a/go.mod b/go.mod index 47ed6ff5..3e040ad0 100644 --- a/go.mod +++ b/go.mod @@ -23,8 +23,8 @@ require ( github.com/ulikunitz/xz v0.5.15 github.com/viamrobotics/gonetworkmanager/v2 v2.2.3 go.uber.org/zap v1.27.0 - go.viam.com/api v0.1.503 - go.viam.com/rdk v0.108.0 + go.viam.com/api v0.1.508 + go.viam.com/rdk v0.109.0 go.viam.com/test v1.2.4 go.viam.com/utils v0.4.3 golang.org/x/sys v0.38.0 @@ -72,18 +72,18 @@ require ( github.com/muhlemmer/gu v0.3.1 // indirect github.com/pion/datachannel v1.5.10 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect - github.com/pion/interceptor v0.1.41 // indirect + github.com/pion/interceptor v0.1.42 // indirect github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns v0.0.12 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.16 // indirect - github.com/pion/rtp v1.8.25 // indirect - github.com/pion/sctp v1.8.40 // indirect + github.com/pion/rtp v1.8.26 // indirect + github.com/pion/sctp v1.8.41 // indirect github.com/pion/sdp/v3 v3.0.16 // indirect github.com/pion/srtp/v2 v2.0.20 // indirect github.com/pion/stun v0.6.1 // indirect github.com/pion/transport/v2 v2.2.10 // indirect - github.com/pion/transport/v3 v3.0.8 // indirect + github.com/pion/transport/v3 v3.1.1 // indirect github.com/pion/turn/v2 v2.1.6 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect @@ -120,6 +120,7 @@ require ( golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/tools v0.39.0 // indirect + gonum.org/v1/gonum v0.16.0 // indirect google.golang.org/api v0.246.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect diff --git a/go.sum b/go.sum index 5d718ae9..ba82dc76 100644 --- a/go.sum +++ b/go.sum @@ -16,7 +16,15 @@ cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenT cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= cloud.google.com/go/storage v1.53.0 h1:gg0ERZwL17pJ+Cz3cD2qS60w1WMDnwcm5YPAIQBHUAw= cloud.google.com/go/storage v1.53.0/go.mod h1:7/eO2a/srr9ImZW9k5uufcNahT2+fPb8w5it1i5boaA= +codeberg.org/go-fonts/liberation v0.5.0 h1:SsKoMO1v1OZmzkG2DY+7ZkCL9U+rrWI09niOLfQ5Bo0= +codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU= +codeberg.org/go-latex/latex v0.1.0 h1:hoGO86rIbWVyjtlDLzCqZPjNykpWQ9YuTZqAzPcfL3c= +codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw= +codeberg.org/go-pdf/fpdf v0.10.0 h1:u+w669foDDx5Ds43mpiiayp40Ov6sZalgcPMDBcZRd4= +codeberg.org/go-pdf/fpdf v0.10.0/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= +git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= @@ -33,6 +41,8 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -49,6 +59,8 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aybabtme/uniplot v0.0.0-20151203143629-039c559e5e7e h1:dSeuFcs4WAJJnswS8vXy7YY1+fdlbVPuEVmDAfqvFOQ= +github.com/aybabtme/uniplot v0.0.0-20151203143629-039c559e5e7e/go.mod h1:uh71c5Vc3VNIplXOFXsnDy21T1BepgT32c5X/YPrOyc= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -56,6 +68,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= +github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -68,6 +82,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= +github.com/chenzhekl/goply v0.0.0-20190930133256-258c2381defd h1:S0onsSZ3RawTrm4KrxPs7KoPT8R8aBZmbXvOUGkV5O8= +github.com/chenzhekl/goply v0.0.0-20190930133256-258c2381defd/go.mod h1:P2dOeu3SNXtjA5VOH7tF0AnGm/eYrst9YA89b36c35I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= @@ -96,12 +112,16 @@ github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFM github.com/dgottlieb/smarty-assertions v1.2.6 h1:YAXgSslRBbVtd54iTqM4yGT2k1a2qS6cffNQo0SDxDY= github.com/dgottlieb/smarty-assertions v1.2.6/go.mod h1:x1wpV/RTxYWtN+vgrcRuCF4hjUmonK5NR59ZzQSym2k= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edaniels/golog v0.0.0-20250821172758-0d08e67686a9 h1:/HeoZScYwEZburQ/HMRt8xM3RRsfyCvUdMhGsEQl8B8= github.com/edaniels/golog v0.0.0-20250821172758-0d08e67686a9/go.mod h1:66V//s+5fy74xUPs7VMhMST5AleWWK/s6bOwgbkiQik= +github.com/edaniels/lidario v0.0.0-20220607182921-5879aa7b96dd h1:W6Zlh2ja8A5vljn17Ix/gZhaUbYMFKAuBjc4t9nma7U= +github.com/edaniels/lidario v0.0.0-20220607182921-5879aa7b96dd/go.mod h1:CibiLtefSZOd0smxazen6pzvjNK3HrvgGojYOqC3/4Q= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -113,10 +133,14 @@ github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -128,6 +152,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68= +github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -164,6 +190,10 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I= +github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -191,6 +221,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gonuts/binary v0.2.0 h1:caITwMWAoQWlL0RNvv2lTU/AHqAJlVuu6nZmNgfbKW4= +github.com/gonuts/binary v0.2.0/go.mod h1:kM+CtBrCGDSKdv8WXTuCUsw+loiy8f/QEI8YCCC0M/E= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -276,6 +308,8 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kellydunn/golang-geo v0.7.0 h1:A5j0/BvNgGwY6Yb6inXQxzYwlPHc6WVZR+MrarZYNNg= +github.com/kellydunn/golang-geo v0.7.0/go.mod h1:YYlQPJ+DPEzrHx8kT3oPHC/NjyvCCXE+IuKGKdrjrcU= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -294,6 +328,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/go-gypsy v1.0.0 h1:7/wQ7A3UL1bnqRMnZ6T8cwCOArfZCxFmb1iTxaOOo1s= +github.com/kylelemons/go-gypsy v1.0.0/go.mod h1:chkXM0zjdpXOiqkCW1XcCHDfjfk14PH2KKkQWxfJUcU= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= @@ -308,8 +344,14 @@ github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtX github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lmittmann/ppm v1.0.2 h1:YW2FFG864rGdrzYu41XngKfptOQU2V+cOmi/hBbaUlI= +github.com/lmittmann/ppm v1.0.2/go.mod h1:GObNM/dbtplb87+9xClwI9bZ+AOPMg0Ujf4k3iLo23E= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -344,6 +386,10 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/muesli/clusters v0.0.0-20200529215643-2700303c1762 h1:p4A2Jx7Lm3NV98VRMKlyWd3nqf8obft8NfXlAUmqd3I= +github.com/muesli/clusters v0.0.0-20200529215643-2700303c1762/go.mod h1:mw5KDqUj0eLj/6DUNINLVJNoPTFkEuGMHtJsXLviLkY= +github.com/muesli/kmeans v0.3.1 h1:KshLQ8wAETfLWOJKMuDCVYHnafddSa1kwGh/IypGIzY= +github.com/muesli/kmeans v0.3.1/go.mod h1:8/OvJW7cHc1BpRf8URb43m+vR105DDe+Kj1WcFXYDqc= github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -386,8 +432,8 @@ github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oL github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= -github.com/pion/interceptor v0.1.41 h1:NpvX3HgWIukTf2yTBVjVGFXtpSpWgXjqz7IIpu7NsOw= -github.com/pion/interceptor v0.1.41/go.mod h1:nEt4187unvRXJFyjiw00GKo+kIuXMWQI9K89fsosDLY= +github.com/pion/interceptor v0.1.42 h1:0/4tvNtruXflBxLfApMVoMubUMik57VZ+94U0J7cmkQ= +github.com/pion/interceptor v0.1.42/go.mod h1:g6XYTChs9XyolIQFhRHOOUS+bGVGLRfgTCUzH29EfVU= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= @@ -399,10 +445,10 @@ github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9 github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= -github.com/pion/rtp v1.8.25 h1:b8+y44GNbwOJTYWuVan7SglX/hMlicVCAtL50ztyZHw= -github.com/pion/rtp v1.8.25/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= -github.com/pion/sctp v1.8.40 h1:bqbgWYOrUhsYItEnRObUYZuzvOMsVplS3oNgzedBlG8= -github.com/pion/sctp v1.8.40/go.mod h1:SPBBUENXE6ThkEksN5ZavfAhFYll+h+66ZiG6IZQuzo= +github.com/pion/rtp v1.8.26 h1:VB+ESQFQhBXFytD+Gk8cxB6dXeVf2WQzg4aORvAvAAc= +github.com/pion/rtp v1.8.26/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= +github.com/pion/sctp v1.8.41 h1:20R4OHAno4Vky3/iE4xccInAScAa83X6nWUfyc65MIs= +github.com/pion/sctp v1.8.41/go.mod h1:2wO6HBycUH7iCssuGyc2e9+0giXVW0pyCv3ZuL8LiyY= github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo= github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= github.com/pion/srtp/v2 v2.0.20 h1:HNNny4s+OUmG280ETrCdgFndp4ufx3/uy85EawYEhTk= @@ -414,8 +460,8 @@ github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLh github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= -github.com/pion/transport/v3 v3.0.8 h1:oI3myyYnTKUSTthu/NZZ8eu2I5sHbxbUNNFW62olaYc= -github.com/pion/transport/v3 v3.0.8/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= +github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= +github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc= github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -494,6 +540,8 @@ github.com/soypat/seqs v0.0.0-20240527012110-1201bab640ef h1:phH95I9wANjTYw6bSYL github.com/soypat/seqs v0.0.0-20240527012110-1201bab640ef/go.mod h1:oCVCNGCHMKoBj97Zp9znLbQ1nHxpkmOY9X+UAGzOxc8= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -554,6 +602,8 @@ github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xfmoulet/qoi v0.2.0 h1:+Smrwzy5ptRnPzGm/YHkZfyK9qGUSoOpiEPngGmFv+c= +github.com/xfmoulet/qoi v0.2.0/go.mod h1:uuPUygmV7o8qy7PhiaGAQX0iLiqoUvFEUKjwUFtlaTQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -562,10 +612,16 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zhuyie/golzf v0.0.0-20161112031142-8387b0307ade h1:bafvQukPrIYwYWcft4rl3WpHo3qO0/voaAgnCwgdhi0= +github.com/zhuyie/golzf v0.0.0-20161112031142-8387b0307ade/go.mod h1:juNhYdla04C276MyU4zR0BA7t90ziLKPwkjDgddGYV0= github.com/zitadel/oidc/v3 v3.37.0 h1:nYATWlnP7f18XiAbw6upUruBaqfB1kUrXrSTf1EYGO8= github.com/zitadel/oidc/v3 v3.37.0/go.mod h1:/xDan4OUQhguJ4Ur73OOJrtugvR164OMnidXP9xfVNw= github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU= github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go-hep.org/x/hep v0.32.1 h1:O96fOyMP+4ET8X+Uu38VFdegQb7rL0rjmFqXMCSm4VM= +go-hep.org/x/hep v0.32.1/go.mod h1:VX3IVUv0Ku5bgWhE+LxRQ1aT7BmWWxSxQu02hfsoeRI= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= @@ -613,10 +669,10 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.viam.com/api v0.1.503 h1:/AEnTDD09RQuB8ExQQct/C3xGYTMqClyraMM7IfVC/I= -go.viam.com/api v0.1.503/go.mod h1:qSrz3j4+QlXvw7ANs1G2fsX532vUQzLBbgaTxMu3lAw= -go.viam.com/rdk v0.108.0 h1:3archc1Rzqtr+pC58O9lqz5Ks1VwSbHl2X6ELeHFo3o= -go.viam.com/rdk v0.108.0/go.mod h1:3sVqj10U9+aSlfr88nnBsrxXED4+IKjVOgzxcHYVGlw= +go.viam.com/api v0.1.508 h1:X3JT5crkEHDq7767AeH3jNufFrZbAnD6twWDlynh7Oc= +go.viam.com/api v0.1.508/go.mod h1:qSrz3j4+QlXvw7ANs1G2fsX532vUQzLBbgaTxMu3lAw= +go.viam.com/rdk v0.109.0 h1:Gd02l9/T3Na72DcAC+iWWk5uMBF+gd6xPnOKMS8n9gM= +go.viam.com/rdk v0.109.0/go.mod h1:vw418fI0RtEq+g2xomoAMERq75et4Inp8zDaF0aneS4= go.viam.com/test v1.2.4 h1:JYgZhsuGAQ8sL9jWkziAXN9VJJiKbjoi9BsO33TW3ug= go.viam.com/test v1.2.4/go.mod h1:zI2xzosHdqXAJ/kFqcN+OIF78kQuTV2nIhGZ8EzvaJI= go.viam.com/utils v0.4.3 h1:jhx78k4MpVpg2kueBE/1L+ZRfsUs1FsUynYCQe6NV6Q= @@ -644,6 +700,8 @@ golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0c golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -809,6 +867,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/plot v0.15.2 h1:Tlfh/jBk2tqjLZ4/P8ZIwGrLEWQSPDLRm/SNWKNXiGI= +gonum.org/v1/plot v0.15.2/go.mod h1:DX+x+DWso3LTha+AdkJEv5Txvi+Tql3KAGkehP0/Ubg= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.246.0 h1:H0ODDs5PnMZVZAEtdLMn2Ul2eQi7QNjqM2DIFp8TlTM= google.golang.org/api v0.246.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8= diff --git a/manager.go b/manager.go index 95be4897..b0f1c3e0 100644 --- a/manager.go +++ b/manager.go @@ -97,28 +97,36 @@ func (m *Manager) LoadAppConfig() error { return errw.Wrap(err, "reading config file") } - cfg := make(map[string]map[string]string) + var cfg map[string]interface{} err = json.Unmarshal(jsonc.ToJSON(b), &cfg) if err != nil { return errw.Wrap(err, "parsing config file") } - cloud, ok := cfg["cloud"] + cloud, ok := cfg["cloud"].(map[string]interface{}) if !ok { return errw.New("no cloud section in local config file") } - for _, req := range []string{"app_address", "id", "secret"} { - field, ok := cloud[req] - if !ok { - return errw.Errorf("no cloud config field for %s", field) - } + appAddress, ok := cloud["app_address"].(string) + if !ok || appAddress == "" { + return errw.New("field 'app_address' in cloud config must be a non-empty string") + } + + id, ok := cloud["id"].(string) + if !ok || id == "" { + return errw.New("field 'id' in cloud config must be a non-empty string") + } + + cloudCreds, err := utils.ParseCloudCreds(cloud) + if err != nil { + return err } m.cloudConfig = &logging.CloudConfig{ - AppAddress: cloud["app_address"], - ID: cloud["id"], - Secret: cloud["secret"], + AppAddress: appAddress, + ID: id, + CloudCred: cloudCreds, } return nil @@ -640,14 +648,10 @@ func (m *Manager) dial(ctx context.Context) error { } dialOpts := make([]rpc.DialOption, 0, 2) - // Only add credentials when secret is set. - if m.cloudConfig.Secret != "" { - dialOpts = append(dialOpts, rpc.WithEntityCredentials(m.cloudConfig.ID, - rpc.Credentials{ - Type: "robot-secret", - Payload: m.cloudConfig.Secret, - }, - )) + + // Only add credentials when they are set. + if m.cloudConfig.CloudCred != nil { + dialOpts = append(dialOpts, m.cloudConfig.CloudCred) } if u.Scheme == "http" { diff --git a/subsystems/networking/bluetooth_characteristics_linux.go b/subsystems/networking/bluetooth_characteristics_linux.go index e825679a..3728a682 100644 --- a/subsystems/networking/bluetooth_characteristics_linux.go +++ b/subsystems/networking/bluetooth_characteristics_linux.go @@ -28,6 +28,7 @@ const ( pskKey = "psk" robotPartIDKey = "id" robotPartSecretKey = "secret" + apiKeyCredsKey = "api_key" appAddressKey = "app_address" availableWiFiNetworksKey = "networks" statusKey = "status" @@ -43,7 +44,7 @@ const ( var ( characteristicsWriteOnly = []string{ - ssidKey, pskKey, robotPartIDKey, robotPartSecretKey, + ssidKey, pskKey, robotPartIDKey, robotPartSecretKey, apiKeyCredsKey, appAddressKey, exitProvisioningKey, unlockPairingKey, } characteristicsReadOnly = []string{ @@ -274,6 +275,8 @@ func (b *btCharacteristics) recordInput(ctx context.Context, cName, value string b.userInputData.input.Secret = value case appAddressKey: b.userInputData.input.AppAddr = value + case apiKeyCredsKey: + b.userInputData.input.APIKey = utils.APIKeyFromString(value) case exitProvisioningKey: b.userInputData.sendInput(ctx) } diff --git a/subsystems/networking/definitions_linux.go b/subsystems/networking/definitions_linux.go index 7042363b..049fc4b4 100644 --- a/subsystems/networking/definitions_linux.go +++ b/subsystems/networking/definitions_linux.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/viamrobotics/agent/utils" gnm "github.com/viamrobotics/gonetworkmanager/v2" pb "go.viam.com/api/provisioning/v1" ) @@ -122,9 +123,23 @@ type MachineConfig struct { } type CloudConfig struct { - AppAddress string `json:"app_address"` - ID string `json:"id"` - Secret string `json:"secret"` + AppAddress string `json:"app_address"` + ID string `json:"id"` + Secret string `json:"secret,omitempty"` + APIKey *utils.APIKey `json:"api_key,omitempty"` +} + +func (cfg CloudConfig) IsValid() error { + if cfg.ID == "" || cfg.AppAddress == "" { + return errors.New("invalid cloud config: 'id' and 'app_address' must be provided") + } + if cfg.APIKey != nil && !cfg.APIKey.IsFullySet() { + return errors.New("invalid cloud config: 'api_key' must have both 'id' and 'key' set") + } + if cfg.Secret == "" && cfg.APIKey == nil { + return errors.New("invalid cloud config: at least one of 'secret' or 'api_key' must be provided") + } + return nil } func WriteDeviceConfig(file string, input userInput) error { @@ -137,11 +152,12 @@ func WriteDeviceConfig(file string, input userInput) error { AppAddress: input.AppAddr, ID: input.PartID, Secret: input.Secret, + APIKey: input.APIKey, }, } - if cfg.Cloud.AppAddress == "" || cfg.Cloud.ID == "" || cfg.Cloud.Secret == "" { - return errors.New("incomplete machine credentials received, please try again") + if err := cfg.Cloud.IsValid(); err != nil { + return err } jsonBytes, err := json.Marshal(cfg) @@ -199,6 +215,7 @@ type userInput struct { PartID string Secret string AppAddr string + APIKey *utils.APIKey // raw /etc/viam.json contents RawConfig string diff --git a/subsystems/networking/grpc_linux.go b/subsystems/networking/grpc_linux.go index 6c8b4060..a623a704 100644 --- a/subsystems/networking/grpc_linux.go +++ b/subsystems/networking/grpc_linux.go @@ -119,6 +119,16 @@ func (n *Networking) SetSmartMachineCredentials(ctx context.Context, n.portalData.input.Secret = cloud.GetSecret() n.portalData.input.AppAddr = cloud.GetAppAddress() + if apiKey := cloud.GetApiKey(); apiKey != nil && + apiKey.GetId() != "" && apiKey.GetKey() != "" { + n.portalData.input.APIKey = &utils.APIKey{ + ID: apiKey.GetId(), + Key: apiKey.GetKey(), + } + } else { + n.portalData.input.APIKey = nil + } + return &pb.SetSmartMachineCredentialsResponse{}, nil } diff --git a/subsystems/networking/portal_linux.go b/subsystems/networking/portal_linux.go index 4b429645..edb21e9c 100644 --- a/subsystems/networking/portal_linux.go +++ b/subsystems/networking/portal_linux.go @@ -178,10 +178,14 @@ func (n *Networking) portalSave(resp http.ResponseWriter, req *http.Request) { n.errors.Add(errw.Wrap(err, "invalid json config contents")) return } - if cfg.Cloud == nil || (cfg.Cloud.ID == "" || cfg.Cloud.Secret == "" || cfg.Cloud.AppAddress == "") { + if cfg.Cloud == nil { n.errors.Add(errors.New("incomplete cloud config provided")) return } + if err := cfg.Cloud.IsValid(); err != nil { + n.errors.Add(err) + return + } n.portalData.input.RawConfig = rawConfig n.logger.Debug("saving raw device config") n.banner.Set("Saving device config. ") diff --git a/utils/cloud_utils.go b/utils/cloud_utils.go new file mode 100644 index 00000000..f9152634 --- /dev/null +++ b/utils/cloud_utils.go @@ -0,0 +1,70 @@ +// Package utils contains helper functions shared between the main agent and subsystems +package utils + +import ( + "encoding/json" + "errors" + + rutils "go.viam.com/rdk/utils" + "go.viam.com/utils/rpc" +) + +type APIKey struct { + ID string `json:"id"` + Key string `json:"key"` +} + +func (a APIKey) IsEmpty() bool { + return a.ID == "" && a.Key == "" +} + +func (a APIKey) IsFullySet() bool { + return a.ID != "" && a.Key != "" +} + +func (a APIKey) IsPartiallySet() bool { + return !a.IsEmpty() && !a.IsFullySet() +} + +func APIKeyFromString(value string) *APIKey { + var apiKey APIKey + if err := json.Unmarshal([]byte(value), &apiKey); err != nil || !apiKey.IsFullySet() { + // If unmarshal fails or the result is not fully set, return nil. An empty APIKey won't be written to config. + return nil + } + return &apiKey +} + +func ParseCloudCreds(cloudCfg map[string]interface{}) (rpc.DialOption, error) { + if apiKeyInterface, hasApiKey := cloudCfg["api_key"]; hasApiKey { + apiKey, ok := apiKeyInterface.(map[string]interface{}) + if !ok { + return nil, errors.New(`"api_key" field is not a valid object`) + } + + keyID, ok := apiKey["id"].(string) + if !ok || keyID == "" { + return nil, errors.New("field 'id' in 'api_key' must be a non-empty string") + } + + keySecret, ok := apiKey["key"].(string) + if !ok || keySecret == "" { + return nil, errors.New("field 'key' in 'api_key' must be a non-empty string") + } + creds := rpc.WithEntityCredentials(keyID, rpc.Credentials{rutils.CredentialsTypeAPIKey, keySecret}) + return creds, nil + } + + // Fall back to secret-based auth + secret, ok := cloudCfg["secret"].(string) + if !ok || secret == "" { + return nil, errors.New("field 'secret' in cloud config must be a non-empty string") + } + + id, ok := cloudCfg["id"].(string) + if !ok || id == "" { + return nil, errors.New("field 'id' in cloud config must be a non-empty string") + } + creds := rpc.WithEntityCredentials(id, rpc.Credentials{rutils.CredentialsTypeRobotSecret, secret}) + return creds, nil +}