Skip to content

Commit fe04d6c

Browse files
Merge pull request #705 from oasisprotocol/martin/feature/rofl-public-vars
Rofl public vars
2 parents 935888d + 17537ed commit fe04d6c

13 files changed

Lines changed: 520 additions & 2 deletions

build/rofl/artifacts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ var LatestContainerArtifacts = ArtifactsConfig{
2121
Kernel: "https://github.com/oasisprotocol/oasis-boot/releases/download/v0.6.2/stage1.bin#e5d4d654ca1fa2c388bf64b23fc6e67815893fc7cb8b7cfee253d87963f54973",
2222
Stage2: "https://github.com/oasisprotocol/oasis-boot/releases/download/v0.6.2/stage2-podman.tar.bz2#b2ea2a0ca769b6b2d64e3f0c577ee9c08f0bb81a6e33ed5b15b2a7e50ef9a09f",
2323
Container: ContainerArtifactsConfig{
24-
Runtime: "https://github.com/oasisprotocol/oasis-sdk/releases/download/rofl-containers%2Fv0.8.6/rofl-containers#5aa26a3c5a7e1d284e217959d89b7a620ea7b7fc5079909e25042124ffb384e8",
24+
Runtime: "https://github.com/oasisprotocol/oasis-sdk/releases/download/rofl-containers%2Fv0.9.0/rofl-containers#e2e074d03ab2fbacaacb01e6d63ce093fde04e4cc620dd8804e8afbb20390525",
2525
},
2626
}

cmd/rofl/build/tdx_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package build
1+
package build //revive:disable
22

33
import (
44
"os"

cmd/rofl/mgmt.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ var (
652652
if err := appID.UnmarshalText([]byte(deployment.AppID)); err != nil {
653653
cobra.CheckErr(fmt.Errorf("malformed ROFL app ID: %w", err))
654654
}
655+
cobra.CheckErr(checkNoPublicVarNamed(deployment.Metadata, secretName))
655656

656657
// Establish connection with the target network.
657658
ctx := context.Background()
@@ -769,6 +770,8 @@ var (
769770

770771
var imported, updated int
771772
for _, name := range names {
773+
cobra.CheckErr(checkNoPublicVarNamed(deployment.Metadata, name))
774+
772775
value := []byte(entries[name])
773776

774777
encValue, err := buildRofl.EncryptSecret(name, value, appCfg.SEK)

cmd/rofl/public_var.go

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
package rofl
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"sort"
8+
"strings"
9+
"unicode/utf8"
10+
11+
"github.com/spf13/cobra"
12+
13+
buildDotenv "github.com/oasisprotocol/cli/build/dotenv"
14+
buildRofl "github.com/oasisprotocol/cli/build/rofl"
15+
"github.com/oasisprotocol/cli/cmd/common"
16+
roflCommon "github.com/oasisprotocol/cli/cmd/rofl/common"
17+
)
18+
19+
const publicVarMetadataPrefix = "env."
20+
21+
var (
22+
publicVarCmd = &cobra.Command{
23+
Use: "public-var",
24+
Short: "Public variable management commands",
25+
Long: "Public variable management commands.\n\n" +
26+
"Values are stored unencrypted in ROFL app metadata and exposed to containers as\n" +
27+
"environment variables. Use `oasis rofl secret` for confidential values.",
28+
}
29+
30+
publicVarSetCmd = &cobra.Command{
31+
Use: "set <name> <file>|-",
32+
Short: "Set a public variable in the manifest, reading the value from file or stdin",
33+
Args: cobra.ExactArgs(2),
34+
Run: func(_ *cobra.Command, args []string) {
35+
publicVarName := args[0]
36+
publicVarFn := args[1]
37+
38+
manifest, deployment, _ := roflCommon.LoadManifestAndSetNPA(&roflCommon.ManifestOptions{
39+
NeedAppID: false,
40+
NeedAdmin: false,
41+
})
42+
43+
key, err := publicVarMetadataKey(publicVarName)
44+
if err != nil {
45+
cobra.CheckErr(err)
46+
}
47+
cobra.CheckErr(checkNoSecretNamed(deployment.Secrets, publicVarName))
48+
49+
// Read public variable.
50+
var publicVarValueRaw []byte
51+
if publicVarFn == "-" {
52+
publicVarValueRaw, err = io.ReadAll(os.Stdin)
53+
if err != nil {
54+
cobra.CheckErr(fmt.Errorf("failed to read public variable from standard input: %w", err))
55+
}
56+
} else {
57+
publicVarValueRaw, err = os.ReadFile(publicVarFn)
58+
if err != nil {
59+
cobra.CheckErr(fmt.Errorf("failed to read public variable from file: %w", err))
60+
}
61+
}
62+
publicVarValue, err := parsePublicVarValue(publicVarValueRaw)
63+
if err != nil {
64+
cobra.CheckErr(err)
65+
}
66+
67+
if existing, ok := deployment.Metadata[key]; ok {
68+
if existing == publicVarValue {
69+
fmt.Printf("Public variable '%s' did not change; no update needed.\n", publicVarName)
70+
return
71+
}
72+
common.CheckForceErr(fmt.Errorf("the public variable named '%s' for deployment '%s' already exists", publicVarName, roflCommon.DeploymentName))
73+
}
74+
if deployment.Metadata == nil {
75+
deployment.Metadata = make(map[string]string)
76+
}
77+
deployment.Metadata[key] = publicVarValue
78+
79+
// Update manifest.
80+
if err = manifest.Save(); err != nil {
81+
cobra.CheckErr(fmt.Errorf("failed to update manifest: %w", err))
82+
}
83+
84+
fmt.Printf("Run `oasis rofl update` to update your ROFL app's on-chain configuration.\n")
85+
},
86+
}
87+
88+
// publicVarImportCmd bulk-imports public variables from a .env file (key=value with # comments).
89+
// Supports '-' to read from stdin. Existing public variables are replaced only with --force.
90+
publicVarImportCmd = &cobra.Command{
91+
Use: "import <dot-env-file>|-",
92+
Short: "Import multiple public variables from a .env file",
93+
Args: cobra.ExactArgs(1),
94+
Run: func(_ *cobra.Command, args []string) {
95+
envFn := args[0]
96+
97+
manifest, deployment, _ := roflCommon.LoadManifestAndSetNPA(&roflCommon.ManifestOptions{
98+
NeedAppID: false,
99+
NeedAdmin: false,
100+
})
101+
102+
// Read .env data (supports stdin via "-").
103+
var (
104+
raw []byte
105+
err error
106+
)
107+
if envFn == "-" {
108+
raw, err = io.ReadAll(os.Stdin)
109+
if err != nil {
110+
cobra.CheckErr(fmt.Errorf("failed to read .env from standard input: %w", err))
111+
}
112+
} else {
113+
raw, err = os.ReadFile(envFn)
114+
if err != nil {
115+
cobra.CheckErr(fmt.Errorf("failed to read .env file: %w", err))
116+
}
117+
}
118+
119+
entries, err := buildDotenv.Parse(string(raw))
120+
if err != nil {
121+
cobra.CheckErr(fmt.Errorf("failed to parse .env: %w", err))
122+
}
123+
if len(entries) == 0 {
124+
fmt.Println("No key=value pairs found in the provided .env input.")
125+
return
126+
}
127+
128+
// Deterministic iteration for stable updates.
129+
names := make([]string, 0, len(entries))
130+
for k := range entries {
131+
names = append(names, k)
132+
}
133+
sort.Strings(names)
134+
135+
if deployment.Metadata == nil {
136+
deployment.Metadata = make(map[string]string)
137+
}
138+
139+
var imported, updated int
140+
for _, name := range names {
141+
key, err := publicVarMetadataKey(name)
142+
if err != nil {
143+
cobra.CheckErr(err)
144+
}
145+
cobra.CheckErr(checkNoSecretNamed(deployment.Secrets, name))
146+
147+
if existing, ok := deployment.Metadata[key]; ok {
148+
if existing == entries[name] {
149+
continue
150+
}
151+
common.CheckForceErr(fmt.Errorf("the public variable named '%s' for deployment '%s' already exists", name, roflCommon.DeploymentName))
152+
updated++
153+
} else {
154+
imported++
155+
}
156+
deployment.Metadata[key] = entries[name]
157+
}
158+
159+
src := envFn
160+
if envFn == "-" {
161+
src = "stdin"
162+
}
163+
if imported == 0 && updated == 0 {
164+
fmt.Printf("No public variables were imported or updated using '%s'.\n", src)
165+
return
166+
}
167+
168+
// Update manifest.
169+
if err = manifest.Save(); err != nil {
170+
cobra.CheckErr(fmt.Errorf("failed to update manifest: %w", err))
171+
}
172+
173+
fmt.Printf("Imported %d public variables, updated %d existing from '%s'.\n", imported, updated, src)
174+
fmt.Printf("Run `oasis rofl update` to update your ROFL app's on-chain configuration.\n")
175+
},
176+
}
177+
178+
publicVarGetCmd = &cobra.Command{
179+
Use: "get <name>",
180+
Short: "Show the given public variable",
181+
Args: cobra.ExactArgs(1),
182+
Run: func(_ *cobra.Command, args []string) {
183+
publicVarName := args[0]
184+
185+
_, deployment, _ := roflCommon.LoadManifestAndSetNPA(&roflCommon.ManifestOptions{
186+
NeedAppID: false,
187+
NeedAdmin: false,
188+
})
189+
190+
key, err := publicVarMetadataKey(publicVarName)
191+
if err != nil {
192+
cobra.CheckErr(err)
193+
}
194+
value, ok := deployment.Metadata[key]
195+
if !ok {
196+
cobra.CheckErr(fmt.Errorf("public variable named '%s' does not exist for deployment '%s'", publicVarName, roflCommon.DeploymentName))
197+
return // Lint doesn't know that cobra.CheckErr never returns.
198+
}
199+
200+
fmt.Printf("Name: %s\n", publicVarName)
201+
fmt.Printf("Value: %s\n", value)
202+
fmt.Printf("Size: %d bytes\n", len(value))
203+
},
204+
}
205+
206+
publicVarRmCmd = &cobra.Command{
207+
Use: "rm <name>",
208+
Short: "Remove the given public variable from the manifest",
209+
Args: cobra.ExactArgs(1),
210+
Run: func(_ *cobra.Command, args []string) {
211+
publicVarName := args[0]
212+
213+
manifest, deployment, _ := roflCommon.LoadManifestAndSetNPA(&roflCommon.ManifestOptions{
214+
NeedAppID: false,
215+
NeedAdmin: false,
216+
})
217+
218+
key, err := publicVarMetadataKey(publicVarName)
219+
if err != nil {
220+
cobra.CheckErr(err)
221+
}
222+
if _, ok := deployment.Metadata[key]; !ok {
223+
cobra.CheckErr(fmt.Errorf("public variable named '%s' does not exist for deployment '%s'", publicVarName, roflCommon.DeploymentName))
224+
}
225+
delete(deployment.Metadata, key)
226+
if len(deployment.Metadata) == 0 {
227+
deployment.Metadata = nil
228+
}
229+
230+
// Update manifest.
231+
if err := manifest.Save(); err != nil {
232+
cobra.CheckErr(fmt.Errorf("failed to update manifest: %w", err))
233+
}
234+
235+
fmt.Printf("Run `oasis rofl update` to update your ROFL app's on-chain configuration.\n")
236+
},
237+
}
238+
)
239+
240+
func publicVarMetadataKey(name string) (string, error) {
241+
if err := validatePublicVarName(name); err != nil {
242+
return "", err
243+
}
244+
return publicVarMetadataPrefix + name, nil
245+
}
246+
247+
// checkNoSecretNamed returns an error if a secret exposed under the given environment variable
248+
// name exists among the secrets.
249+
func checkNoSecretNamed(secrets []*buildRofl.SecretConfig, name string) error {
250+
for _, sc := range secrets {
251+
if secretEnvVarName(sc.Name) == name {
252+
return fmt.Errorf("the secret named '%s' for deployment '%s' already exists", sc.Name, roflCommon.DeploymentName)
253+
}
254+
}
255+
return nil
256+
}
257+
258+
// checkNoPublicVarNamed returns an error if a public variable exposed under the same environment
259+
// variable name as a secret with the given name exists in the deployment metadata.
260+
func checkNoPublicVarNamed(metadata map[string]string, name string) error {
261+
envName := secretEnvVarName(name)
262+
if _, ok := metadata[publicVarMetadataPrefix+envName]; ok {
263+
return fmt.Errorf("the public variable named '%s' for deployment '%s' already exists", envName, roflCommon.DeploymentName)
264+
}
265+
return nil
266+
}
267+
268+
// secretEnvVarName returns the environment variable name under which rofl-containers exposes a
269+
// secret with the given name.
270+
func secretEnvVarName(name string) string {
271+
return strings.ToUpper(strings.ReplaceAll(name, " ", "_"))
272+
}
273+
274+
func validatePublicVarName(name string) error {
275+
if name == "" {
276+
return fmt.Errorf("public variable name cannot be empty")
277+
}
278+
for _, ch := range name {
279+
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' {
280+
continue
281+
}
282+
return fmt.Errorf("public variable name '%s' is invalid, only ASCII letters, digits and '_' are allowed", name)
283+
}
284+
return nil
285+
}
286+
287+
func parsePublicVarValue(raw []byte) (string, error) {
288+
if !utf8.Valid(raw) {
289+
return "", fmt.Errorf("public variable values must be valid UTF-8")
290+
}
291+
return string(raw), nil
292+
}
293+
294+
func init() {
295+
publicVarSetCmd.Flags().AddFlagSet(roflCommon.DeploymentFlags)
296+
publicVarSetCmd.Flags().AddFlagSet(common.ForceFlag)
297+
publicVarCmd.AddCommand(publicVarSetCmd)
298+
299+
publicVarImportCmd.Flags().AddFlagSet(roflCommon.DeploymentFlags)
300+
publicVarImportCmd.Flags().AddFlagSet(common.ForceFlag)
301+
publicVarCmd.AddCommand(publicVarImportCmd)
302+
303+
publicVarGetCmd.Flags().AddFlagSet(roflCommon.DeploymentFlags)
304+
publicVarCmd.AddCommand(publicVarGetCmd)
305+
306+
publicVarRmCmd.Flags().AddFlagSet(roflCommon.DeploymentFlags)
307+
publicVarCmd.AddCommand(publicVarRmCmd)
308+
}

0 commit comments

Comments
 (0)