-
Notifications
You must be signed in to change notification settings - Fork 17
/
config_set.go
125 lines (111 loc) · 4.06 KB
/
config_set.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package devices
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/foundriesio/fioctl/client"
"github.com/foundriesio/fioctl/subcommands"
)
func init() {
setConfigCmd := &cobra.Command{
Use: "set <device> <file1=content> <file2=content ...>",
Short: "Create a secure configuration for the device",
Long: `Creates a secure configuration for device encrypting the contents each
file using the device's public key. The fioconfig daemon running
on each device will then be able to grab the latest version of the
device's configuration and apply it.`,
Example: `
# Basic use can be done with command line arguments:
fioctl device config set my-device npmtok="root" githubtok="1234" readme.md==./readme.md
There are several ways how to pass a file content into this command:
- with filename="filecontent" format, a file content is passed directly.
- with filename==/path/to/file format, a file content is read from a specified file path.
# The device configuration format also allows specifying what command
# to run after a configuration file is updated on the device. To take
# advantage of this, the "--raw" flag must be used.
cat >tmp.json <<EOF
{
"reason": "I want to use the on-changed attribute",
"files": [
{
"name": "npmtok",
"value": "root",
"on-changed": ["/usr/bin/touch", "/tmp/npmtok-changed"]
},
{
"name": "A-Readable-Value",
"value": "This won't be encrypted and will be visible from the API",
"unencrypted": true
},
{
"name": "githubtok",
"value": "1234"
}
]
}
> EOF
fioctl devices config set my-device --raw ./tmp.json
# fioctl will read in tmp.json, encrypt its contents, and upload it
# to the OTA server. Instead of using ./tmp.json, the command can take
# a "-" and will read the content from STDIN instead of a file.
`,
Run: doConfigSet,
Args: cobra.MinimumNArgs(2),
}
configCmd.AddCommand(setConfigCmd)
setConfigCmd.Flags().StringP("reason", "m", "", "Add a message to store as the \"reason\" for this change")
setConfigCmd.Flags().BoolP("raw", "", false, "Use raw configuration file")
setConfigCmd.Flags().BoolP("create", "", false, "Replace the whole config with these values. Default is to merge these values in with the existing config values")
}
func loadEciesPub(pubkey string) *ecies.PublicKey {
block, _ := pem.Decode([]byte(pubkey))
if block == nil {
subcommands.DieNotNil(fmt.Errorf("Failed to parse certificate PEM"))
return nil // return for go linter
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
subcommands.DieNotNil(err, "Failed to parse DER encoded public key:")
ecpub := pub.(*ecdsa.PublicKey)
return ecies.ImportECDSAPublic(ecpub)
}
func eciesEncrypt(content string, pubkey *ecies.PublicKey) string {
message := []byte(content)
enc, err := ecies.Encrypt(rand.Reader, pubkey, message, nil, nil)
subcommands.DieNotNil(err, "Failed to encrypt:")
return base64.StdEncoding.EncodeToString(enc)
}
func doConfigSet(cmd *cobra.Command, args []string) {
name := args[0]
reason, _ := cmd.Flags().GetString("reason")
isRaw, _ := cmd.Flags().GetBool("raw")
shouldCreate, _ := cmd.Flags().GetBool("create")
logrus.Debugf("Creating new device config for %s", name)
// Ensure the device has a public key we can encrypt with
device, err := api.DeviceGet(name)
subcommands.DieNotNil(err)
if len(device.PublicKey) == 0 {
subcommands.DieNotNil(fmt.Errorf("Device has no public key to encrypt with"))
}
pubkey := loadEciesPub(device.PublicKey)
subcommands.SetConfig(&subcommands.SetConfigOptions{
FileArgs: args[1:],
Reason: reason,
IsRawFile: isRaw,
SetFunc: func(cfg client.ConfigCreateRequest) error {
if shouldCreate {
return api.DeviceCreateConfig(device.Name, cfg)
} else {
return api.DevicePatchConfig(device.Name, cfg, false)
}
},
EncryptFunc: func(value string) string {
return eciesEncrypt(value, pubkey)
},
})
}