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
36 changes: 28 additions & 8 deletions cli/api/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,39 @@ type ConfigsApi struct {
api *Api
}

type SpecificConfigsApi struct {
type SpecificConfigsApi interface {
Get() (res ConfigFileSet, err error)
GetHistory(limit int, showFiles bool) (res []ConfigFileSet, err error)
Put(configs ConfigFileSet) (err error)
}

type specificConfigsApi struct {
api *Api
uri string
}

type DeviceConfigsApi struct{ SpecificConfigsApi }
type DeviceConfigsApi struct {
specificConfigsApi
uuid string
}

func (a *Api) Configs() ConfigsApi {
return ConfigsApi{api: a}
}

func (a ConfigsApi) Factory() SpecificConfigsApi {
return SpecificConfigsApi{api: a.api, uri: "/v1/configs/factory"}
return specificConfigsApi{api: a.api, uri: "/v1/configs/factory"}
}

func (a ConfigsApi) Group(name string) SpecificConfigsApi {
return SpecificConfigsApi{api: a.api, uri: "/v1/configs/group/" + name}
return specificConfigsApi{api: a.api, uri: "/v1/configs/group/" + name}
}

func (a ConfigsApi) Device(uuid string) DeviceConfigsApi {
return DeviceConfigsApi{SpecificConfigsApi: SpecificConfigsApi{api: a.api, uri: "/v1/configs/device/" + uuid}}
return DeviceConfigsApi{
specificConfigsApi: specificConfigsApi{api: a.api, uri: "/v1/configs/device/" + uuid},
uuid: uuid,
}
}

func (a ConfigsApi) ListGroups() (names []string, err error) {
Expand All @@ -54,18 +66,18 @@ func (a ConfigsApi) Upload(r io.Reader, opts ...HttpOption) (err error) {
return
}

func (a SpecificConfigsApi) Get() (res ConfigFileSet, err error) {
func (a specificConfigsApi) Get() (res ConfigFileSet, err error) {
err = a.api.Get(a.uri, &res)
return
}

func (a SpecificConfigsApi) GetHistory(limit int, showFiles bool) (res []ConfigFileSet, err error) {
func (a specificConfigsApi) GetHistory(limit int, showFiles bool) (res []ConfigFileSet, err error) {
uri := a.uri + fmt.Sprintf("/history?limit=%d&show-files=%t", limit, showFiles)
err = a.api.Get(uri, &res)
return
}

func (a SpecificConfigsApi) Put(configs ConfigFileSet) (err error) {
func (a specificConfigsApi) Put(configs ConfigFileSet) (err error) {
_, err = a.api.Put(a.uri, configs)
return
}
Expand All @@ -74,3 +86,11 @@ func (a DeviceConfigsApi) GetApplied() (res AppliedConfigs, err error) {
err = a.api.Get(a.uri+"/applied", &res)
return
}

func (a DeviceConfigsApi) GetPubkey() (res string, err error) {
var d *Device
if d, err = a.api.Devices().Get(a.uuid); err == nil {
res = d.PubKey
}
return
}
2 changes: 1 addition & 1 deletion cli/subcommands/configs/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func addSpecificFlags(cmd *cobra.Command) {
func getSpecificApi(cmd *cobra.Command) api.SpecificConfigsApi {
api := api.CtxGetApi(cmd.Context()).Configs()
if uuid, _ := cmd.Flags().GetString("device"); len(uuid) > 0 {
return api.Device(uuid).SpecificConfigsApi
return api.Device(uuid)
} else if name, _ := cmd.Flags().GetString("group"); len(name) > 0 {
return api.Group(name)
}
Expand Down
48 changes: 44 additions & 4 deletions cli/subcommands/configs/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
package configs

import (
"crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
Expand All @@ -13,11 +17,14 @@ import (

"github.com/spf13/cobra"

ecies "github.com/foundriesio/go-ecies"

"github.com/foundriesio/update-server/cli/api"
"github.com/foundriesio/update-server/storage"
)

var setCmd = &cobra.Command{
Use: "set [ -g <group-name> | -d <device-uuid> ] <file1=content1> [ <file2=content2> ... ]",
Use: "set [ -g <group-name> | -d <device-uuid> [ -e ]] <file1=content1> [ <file2=content2> ... ]",
Short: "Create a global, group, or device configs",
Long: `Create a global, group, or device configs.

Expand Down Expand Up @@ -61,22 +68,24 @@ var setCmd = &cobra.Command{
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
api := getSpecificApi(cmd)
encrypt, _ := cmd.Flags().GetBool("encrypt")
raw, _ := cmd.Flags().GetBool("raw")
replace, _ := cmd.Flags().GetBool("replace")
reason, _ := cmd.Flags().GetString("reason")
return setConfigs(api, args, raw, replace, reason)
return setConfigs(api, args, encrypt, raw, replace, reason)
},
}

func init() {
ConfigsCmd.AddCommand(setCmd)
addSpecificFlags(setCmd)
setCmd.Flags().BoolP("encrypt", "e", false, "Encrypt configuration file using ECIES. Only valid for the device config.")
setCmd.Flags().BoolP("raw", "", false, "Use raw configuration file.")
setCmd.Flags().BoolP("replace", "", false, "Replace existing config rather than merge it.")
setCmd.Flags().StringP("reason", "m", "", "Add a message to store as the \"reason\" for this change")
}

func setConfigs(capi api.SpecificConfigsApi, files []string, raw, replace bool, reason string) error {
func setConfigs(capi api.SpecificConfigsApi, files []string, encrypt, raw, replace bool, reason string) error {
var (
cfg api.ConfigFileSet
data []byte
Expand Down Expand Up @@ -111,7 +120,23 @@ func setConfigs(capi api.SpecificConfigsApi, files []string, raw, replace bool,
cobra.CheckErr(err)
content = string(data)
}
cfg.Files[name] = api.ConfigFile{Value: content, Unencrypted: true}
cfg.Files[name] = api.ConfigFile{Value: content, Unencrypted: !encrypt}
}
}
if encrypt {
if dcapi, ok := capi.(api.DeviceConfigsApi); !ok {
return errors.New("--encrypt/-e can only be used with --device/-d")
} else {
pubkey := getEcies(dcapi)
for name, file := range cfg.Files {
if file.Unencrypted {
continue
}
enc, err := ecies.Encrypt(rand.Reader, pubkey, []byte(file.Value), nil, nil)
cobra.CheckErr(err)
file.Value = base64.StdEncoding.EncodeToString(enc)
cfg.Files[name] = file
}
}
}
if !replace {
Expand All @@ -128,3 +153,18 @@ func setConfigs(capi api.SpecificConfigsApi, files []string, raw, replace bool,
cobra.CheckErr(capi.Put(cfg))
return nil
}

func getEcies(dcapi api.DeviceConfigsApi) *ecies.PublicKey {
pubBytes, err := dcapi.GetPubkey()
cobra.CheckErr(err)
if len(pubBytes) == 0 {
cobra.CheckErr(errors.New("device did not provide its public key"))
}
pub, err := storage.PemBytesToObject([]byte(pubBytes), x509.ParsePKIXPublicKey)
cobra.CheckErr(err)
ecpub, ok := pub.(*ecdsa.PublicKey)
if !ok {
cobra.CheckErr(errors.New("device did not provide a supported ECDSA public key"))
}
return ecies.ImportECDSAPublic(ecpub)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.25.0

require (
github.com/alexflint/go-arg v1.6.0
github.com/foundriesio/go-ecies v0.3.0
Comment thread
doanac marked this conversation as resolved.
github.com/go-pkgz/expirable-cache/v3 v3.1.0
github.com/google/uuid v1.6.0
github.com/labstack/echo/v4 v4.15.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oy
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/foundriesio/go-ecies v0.3.0 h1:6Pb71NGo0HKi/5FeVuEHB01Y89OWvrgBBEQWfC9Vv5c=
github.com/foundriesio/go-ecies v0.3.0/go.mod h1:ooRWGgUZKNzMkw6mGij8qROV7FUZEmpTZLGgo7UxM/8=
github.com/go-pkgz/expirable-cache/v3 v3.1.0 h1:s05P851/O6QJ6Mc+7o2bh9aGtD3romB1SxDTXifdoqc=
github.com/go-pkgz/expirable-cache/v3 v3.1.0/go.mod h1:6pVgNleydKPj0J2/mzrI02/RDo4ivKx5v2XlNmIjhjo=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
Expand Down
Loading