Skip to content

Commit 604fd86

Browse files
committed
cli: use workload owner key during set command
1 parent 850af9f commit 604fd86

File tree

1 file changed

+69
-4
lines changed

1 file changed

+69
-4
lines changed

cli/set.go

+69-4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ package main
22

33
import (
44
"context"
5+
"crypto/ecdsa"
6+
"crypto/sha256"
7+
"crypto/x509"
58
"encoding/hex"
69
"encoding/json"
10+
"encoding/pem"
711
"fmt"
812
"io"
13+
"log/slog"
914
"net"
1015
"os"
16+
"slices"
1117
"time"
1218

1319
"github.com/edgelesssys/nunki/internal/atls"
@@ -45,6 +51,7 @@ func newSetCmd() *cobra.Command {
4551
cmd.Flags().StringP("coordinator", "c", "", "endpoint the coordinator can be reached at")
4652
must(cobra.MarkFlagRequired(cmd.Flags(), "coordinator"))
4753
cmd.Flags().String("coordinator-policy-hash", DefaultCoordinatorPolicyHash, "expected policy hash of the coordinator, will not be checked if empty")
54+
cmd.Flags().String("workload-owner-key", workloadOwnerPEM, "path to workload owner key (.pem) file")
4855

4956
return cmd
5057
}
@@ -69,6 +76,11 @@ func runSet(cmd *cobra.Command, args []string) error {
6976
return fmt.Errorf("failed to unmarshal manifest: %w", err)
7077
}
7178

79+
workloadOwnerKey, err := loadWorkloadOwnerKey(flags.workloadOwnerKeyPath, m, log)
80+
if err != nil {
81+
return fmt.Errorf("loading workload owner key: %w", err)
82+
}
83+
7284
paths, err := findGenerateTargets(args, log)
7385
if err != nil {
7486
return fmt.Errorf("finding yaml files: %w", err)
@@ -92,7 +104,7 @@ func runSet(cmd *cobra.Command, args []string) error {
92104
kdsCache := fsstore.New(kdsDir, log.WithGroup("kds-cache"))
93105
kdsGetter := snp.NewCachedHTTPSGetter(kdsCache, snp.NeverGCTicker, log.WithGroup("kds-getter"))
94106
validator := snp.NewValidator(validateOptsGen, kdsGetter, log.WithGroup("snp-validator"))
95-
dialer := dialer.New(atls.NoIssuer, validator, &net.Dialer{})
107+
dialer := dialer.NewWithKey(atls.NoIssuer, validator, &net.Dialer{}, workloadOwnerKey)
96108

97109
conn, err := dialer.Dial(cmd.Context(), flags.coordinator)
98110
if err != nil {
@@ -107,6 +119,18 @@ func runSet(cmd *cobra.Command, args []string) error {
107119
}
108120
resp, err := setLoop(cmd.Context(), client, cmd.OutOrStdout(), req)
109121
if err != nil {
122+
grpcSt, ok := status.FromError(err)
123+
if ok {
124+
if grpcSt.Code() == codes.PermissionDenied {
125+
msg := "Permission denied."
126+
if workloadOwnerKey == nil {
127+
msg += " Specify a workload owner key with --workload-owner-key."
128+
} else {
129+
msg += " Ensure you are using a trusted workload owner key."
130+
}
131+
fmt.Fprintln(cmd.OutOrStdout(), msg)
132+
}
133+
}
110134
return fmt.Errorf("failed to set manifest: %w", err)
111135
}
112136

@@ -124,9 +148,10 @@ func runSet(cmd *cobra.Command, args []string) error {
124148
}
125149

126150
type setFlags struct {
127-
manifestPath string
128-
coordinator string
129-
policy []byte
151+
manifestPath string
152+
coordinator string
153+
policy []byte
154+
workloadOwnerKeyPath string
130155
}
131156

132157
func parseSetFlags(cmd *cobra.Command) (*setFlags, error) {
@@ -149,6 +174,10 @@ func parseSetFlags(cmd *cobra.Command) (*setFlags, error) {
149174
if err != nil {
150175
return nil, fmt.Errorf("hex-decoding coordinator-policy-hash flag: %w", err)
151176
}
177+
flags.workloadOwnerKeyPath, err = cmd.Flags().GetString("workload-owner-key")
178+
if err != nil {
179+
return nil, fmt.Errorf("getting workload-owner-key flag: %w", err)
180+
}
152181

153182
return flags, nil
154183
}
@@ -161,6 +190,42 @@ func policyMapToBytesList(m map[string]deployment) [][]byte {
161190
return policies
162191
}
163192

193+
func loadWorkloadOwnerKey(path string, manifst manifest.Manifest, log *slog.Logger) (*ecdsa.PrivateKey, error) {
194+
key, err := os.ReadFile(path)
195+
if os.IsNotExist(err) {
196+
return nil, nil
197+
}
198+
if err != nil {
199+
return nil, fmt.Errorf("reading workload owner key: %w", err)
200+
}
201+
pemBlock, _ := pem.Decode(key)
202+
if pemBlock == nil {
203+
return nil, fmt.Errorf("decoding workload owner key: %w", err)
204+
}
205+
if pemBlock.Type != "EC PRIVATE KEY" {
206+
return nil, fmt.Errorf("workload owner key is not an EC private key")
207+
}
208+
workloadOwnerKey, err := x509.ParseECPrivateKey(pemBlock.Bytes)
209+
if err != nil {
210+
return nil, fmt.Errorf("parsing workload owner key: %w", err)
211+
}
212+
pubKey, err := x509.MarshalPKIXPublicKey(&workloadOwnerKey.PublicKey)
213+
if err != nil {
214+
return nil, fmt.Errorf("marshaling public key: %w", err)
215+
}
216+
ownerKeyHash := sha256.Sum256(pubKey)
217+
ownerKeyHex := manifest.NewHexString(ownerKeyHash[:])
218+
if len(manifst.WorkloadOwnerKeyDigests) == 0 {
219+
log.Warn("No workload owner keys in manifest. Further manifest updates will be rejected by the coordinator")
220+
return workloadOwnerKey, nil
221+
}
222+
log.Debug("Workload owner keys in manifest", "keys", manifst.WorkloadOwnerKeyDigests)
223+
if !slices.Contains(manifst.WorkloadOwnerKeyDigests, ownerKeyHex) {
224+
log.Warn("Workload owner key not found in manifest. This may lock you out from further updates")
225+
}
226+
return workloadOwnerKey, nil
227+
}
228+
164229
func setLoop(
165230
ctx context.Context, client coordapi.CoordAPIClient, out io.Writer, req *coordapi.SetManifestRequest,
166231
) (resp *coordapi.SetManifestResponse, retErr error) {

0 commit comments

Comments
 (0)