Skip to content

Commit

Permalink
cli: add --save-sgx-quote flag to save a Coordinator's SGX quote to…
Browse files Browse the repository at this point in the history
… disk (#647)

* Allow saving Coordinator SGX quote to disk
* Update CLI reference
* Refactor options parsing for rest.VerifyCoordinator

---------

Signed-off-by: Daniel Weiße <[email protected]>
Co-authored-by: Thomas Tendyck <[email protected]>
  • Loading branch information
daniel-weisse and thomasten authored May 7, 2024
1 parent 0d3b015 commit 6c46cbc
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 69 deletions.
1 change: 1 addition & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ To install and configure MarbleRun, run:
rootCmd.PersistentFlags().StringSlice("accepted-tcb-statuses", []string{"UpToDate", "SWHardeningNeeded"}, "Comma-separated list of user accepted TCB statuses")
rootCmd.PersistentFlags().StringP("namespace", "n", helm.Namespace, "Kubernetes namespace of the MarbleRun installation")
rootCmd.PersistentFlags().String("nonce", "", "(Optional) nonce to use for quote verification. If set, the Coordinator will generate a quote over sha256(CoordinatorCert + nonce)")
rootCmd.PersistentFlags().String("save-sgx-quote", "", "If set, save the Coordinator's SGX quote to the specified file")

must(rootCmd.MarkPersistentFlagFilename("coordinator-cert", "pem", "crt"))
must(rootCmd.MarkPersistentFlagFilename("era-config", "json"))
Expand Down
7 changes: 2 additions & 5 deletions cli/internal/cmd/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func runCertificate(saveCert func(io.Writer, *file.Handler, []*pem.Block) error,
return func(cmd *cobra.Command, args []string) error {
hostname := args[0]
fs := afero.NewOsFs()
flags, err := parseRestFlags(cmd.Flags())
verifyOpts, err := parseRestFlags(cmd.Flags())
if err != nil {
return err
}
Expand All @@ -57,10 +57,7 @@ func runCertificate(saveCert func(io.Writer, *file.Handler, []*pem.Block) error,
return fmt.Errorf("parsing root certificate from local cache: %w", err)
}

certs, err := rest.VerifyCoordinator(
cmd.Context(), cmd.OutOrStdout(), hostname,
flags.eraConfig, flags.k8sNamespace, flags.nonce, flags.insecure, flags.acceptedTCBStatuses,
)
certs, err := rest.VerifyCoordinator(cmd.Context(), cmd.OutOrStdout(), hostname, verifyOpts)
if err != nil {
return fmt.Errorf("retrieving certificate from Coordinator: %w", err)
}
Expand Down
45 changes: 17 additions & 28 deletions cli/internal/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,51 +30,40 @@ type poster interface {
Post(ctx context.Context, path, contentType string, body io.Reader) ([]byte, error)
}

// restFlags are command line flags used to configure the REST client to talk to the Coordinator.
type restFlags struct {
// k8sNamespace is the namespace of the MarbleRun installation.
// We use this to try to find the Coordinator when retrieving the era config.
k8sNamespace string
// eraConfig is the path to the era config file.
eraConfig string
// insecure is a flag to disable TLS verification.
insecure bool
// acceptedTCBStatuses is a list of TCB statuses that are accepted by the CLI.
// This can be used to allow connections to Coordinator instances running on outdated hardware or firmware.
acceptedTCBStatuses []string
// nonce is a user supplied nonce to be used in the attestation process.
nonce []byte
}

// parseRestFlags parses the command line flags used to configure the REST client.
func parseRestFlags(flags *pflag.FlagSet) (restFlags, error) {
func parseRestFlags(flags *pflag.FlagSet) (rest.VerifyCoordinatorOptions, error) {
eraConfig, err := flags.GetString("era-config")
if err != nil {
return restFlags{}, err
return rest.VerifyCoordinatorOptions{}, err
}
insecure, err := flags.GetBool("insecure")
if err != nil {
return restFlags{}, err
return rest.VerifyCoordinatorOptions{}, err
}
acceptedTCBStatuses, err := flags.GetStringSlice("accepted-tcb-statuses")
if err != nil {
return restFlags{}, err
return rest.VerifyCoordinatorOptions{}, err
}
k8snamespace, err := flags.GetString("namespace")
if err != nil {
return restFlags{}, err
return rest.VerifyCoordinatorOptions{}, err
}
nonce, err := flags.GetString("nonce")
if err != nil {
return restFlags{}, err
return rest.VerifyCoordinatorOptions{}, err
}
sgxQuotePath, err := flags.GetString("save-sgx-quote")
if err != nil {
return rest.VerifyCoordinatorOptions{}, err
}

return restFlags{
k8sNamespace: k8snamespace,
eraConfig: eraConfig,
insecure: insecure,
acceptedTCBStatuses: acceptedTCBStatuses,
nonce: []byte(nonce),
return rest.VerifyCoordinatorOptions{
K8sNamespace: k8snamespace,
ConfigFilename: eraConfig,
Insecure: insecure,
AcceptedTCBStatuses: acceptedTCBStatuses,
Nonce: []byte(nonce),
SGXQuotePath: sgxQuotePath,
}, nil
}

Expand Down
9 changes: 3 additions & 6 deletions cli/internal/cmd/manifestGet.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,16 @@ func runManifestGet(cmd *cobra.Command, args []string) error {
hostname := args[0]
fs := afero.NewOsFs()

restFlags, err := parseRestFlags(cmd.Flags())
verifyOpts, err := parseRestFlags(cmd.Flags())
if err != nil {
return err
}
caCert, err := rest.VerifyCoordinator(
cmd.Context(), cmd.OutOrStdout(), hostname,
restFlags.eraConfig, restFlags.k8sNamespace, restFlags.nonce, restFlags.insecure, restFlags.acceptedTCBStatuses,
)
caCert, err := rest.VerifyCoordinator(cmd.Context(), cmd.OutOrStdout(), hostname, verifyOpts)
if err != nil {
return err
}

client, err := rest.NewClient(hostname, caCert, nil, restFlags.insecure)
client, err := rest.NewClient(hostname, caCert, nil, verifyOpts.Insecure)
if err != nil {
return err
}
Expand Down
9 changes: 3 additions & 6 deletions cli/internal/cmd/manifestSet.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,17 @@ func runManifestSet(cmd *cobra.Command, args []string) (retErr error) {
return err
}

restFlags, err := parseRestFlags(cmd.Flags())
verifyOpts, err := parseRestFlags(cmd.Flags())
if err != nil {
return err
}

caCert, err := rest.VerifyCoordinator(
cmd.Context(), cmd.OutOrStdout(), hostname,
restFlags.eraConfig, restFlags.k8sNamespace, restFlags.nonce, restFlags.insecure, restFlags.acceptedTCBStatuses,
)
caCert, err := rest.VerifyCoordinator(cmd.Context(), cmd.OutOrStdout(), hostname, verifyOpts)
if err != nil {
return err
}

client, err := rest.NewClient(hostname, caCert, nil, restFlags.insecure)
client, err := rest.NewClient(hostname, caCert, nil, verifyOpts.Insecure)
if err != nil {
return err
}
Expand Down
9 changes: 3 additions & 6 deletions cli/internal/cmd/manifestVerify.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,16 @@ func runManifestVerify(cmd *cobra.Command, args []string) error {
return err
}

restFlags, err := parseRestFlags(cmd.Flags())
verifyOpts, err := parseRestFlags(cmd.Flags())
if err != nil {
return err
}
caCert, err := rest.VerifyCoordinator(
cmd.Context(), cmd.OutOrStdout(), hostname,
restFlags.eraConfig, restFlags.k8sNamespace, restFlags.nonce, restFlags.insecure, restFlags.acceptedTCBStatuses,
)
caCert, err := rest.VerifyCoordinator(cmd.Context(), cmd.OutOrStdout(), hostname, verifyOpts)
if err != nil {
return err
}

client, err := rest.NewClient(hostname, caCert, nil, restFlags.insecure)
client, err := rest.NewClient(hostname, caCert, nil, verifyOpts.Insecure)
if err != nil {
return err
}
Expand Down
9 changes: 3 additions & 6 deletions cli/internal/cmd/recover.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,20 @@ func runRecover(cmd *cobra.Command, args []string) error {
return err
}

flags, err := parseRestFlags(cmd.Flags())
verifyOpts, err := parseRestFlags(cmd.Flags())
if err != nil {
return err
}

// A Coordinator in recovery mode will have a different certificate than what is cached
// Only unsealing the Coordinator will allow it to use the original certificate again
// Therefore we need to verify the Coordinator is running in the expected enclave instead
caCert, err := rest.VerifyCoordinator(
cmd.Context(), cmd.OutOrStdout(), hostname,
flags.eraConfig, flags.k8sNamespace, flags.nonce, flags.insecure, flags.acceptedTCBStatuses,
)
caCert, err := rest.VerifyCoordinator(cmd.Context(), cmd.OutOrStdout(), hostname, verifyOpts)
if err != nil {
return err
}

client, err := rest.NewClient(hostname, caCert, nil, flags.insecure)
client, err := rest.NewClient(hostname, caCert, nil, verifyOpts.Insecure)
if err != nil {
return err
}
Expand Down
46 changes: 34 additions & 12 deletions cli/internal/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ const (
dataField = "data"
)

// VerifyCoordinatorOptions defines the options for verifying the connection to the MarbleRun Coordinator.
type VerifyCoordinatorOptions struct {
// ConfigFilename is the path to the era config file.
ConfigFilename string
// K8sNamespace is the namespace of the MarbleRun installation.
// We use this to try to find the Coordinator when retrieving the era config.
K8sNamespace string
// Insecure is a flag to disable TLS verification.
Insecure bool
// AcceptedTCBStatuses is a list of TCB statuses that are accepted by the CLI.
// This can be used to allow connections to Coordinator instances running on outdated hardware or firmware.
AcceptedTCBStatuses []string
// Nonce is a user supplied nonce to be used in the attestation process.
Nonce []byte
// SGXQuotePath is the path to save SGX quote file.
SGXQuotePath string
}

// Client is a REST client for the MarbleRun Coordinator.
type Client struct {
client *http.Client
Expand Down Expand Up @@ -155,30 +173,27 @@ func (c *Client) do(req *http.Request) ([]byte, error) {
}

// VerifyCoordinator verifies the connection to the MarbleRun Coordinator.
func VerifyCoordinator(
ctx context.Context, out io.Writer, host, configFilename, k8sNamespace string,
nonce []byte, insecure bool, acceptedTCBStatuses []string,
) ([]*pem.Block, error) {
func VerifyCoordinator(ctx context.Context, out io.Writer, host string, opts VerifyCoordinatorOptions) ([]*pem.Block, error) {
// skip verification if specified
if insecure {
if opts.Insecure {
fmt.Fprintln(out, "Warning: skipping quote verification")
certs, _, err := attestation.InsecureGetCertificate(ctx, host)
return certs, err
}

if configFilename == "" {
configFilename = eraDefaultConfig
if opts.ConfigFilename == "" {
opts.ConfigFilename = eraDefaultConfig

// reuse existing config from current working directory if none specified
// or try to get latest config from github if it does not exist
if _, err := os.Stat(configFilename); err == nil {
if _, err := os.Stat(opts.ConfigFilename); err == nil {
fmt.Fprintln(out, "Reusing existing config file")
} else if err := fetchLatestCoordinatorConfiguration(ctx, out, k8sNamespace); err != nil {
} else if err := fetchLatestCoordinatorConfiguration(ctx, out, opts.K8sNamespace); err != nil {
return nil, err
}
}

eraCfgRaw, err := os.ReadFile(configFilename)
eraCfgRaw, err := os.ReadFile(opts.ConfigFilename)
if err != nil {
return nil, fmt.Errorf("reading era config file: %w", err)
}
Expand All @@ -188,8 +203,8 @@ func VerifyCoordinator(
return nil, fmt.Errorf("unmarshalling era config: %w", err)
}

pemBlock, tcbStatus, _, err := attestation.GetCertificate(ctx, host, nonce, eraCfg)
validity, err := tcb.CheckStatus(tcbStatus, err, acceptedTCBStatuses)
pemBlock, tcbStatus, rawQuote, err := attestation.GetCertificate(ctx, host, opts.Nonce, eraCfg)
validity, err := tcb.CheckStatus(tcbStatus, err, opts.AcceptedTCBStatuses)
if err != nil {
return nil, err
}
Expand All @@ -200,6 +215,13 @@ func VerifyCoordinator(
default:
fmt.Fprintln(out, "Warning: TCB level invalid, but accepted by configuration:", tcbStatus)
}

if opts.SGXQuotePath != "" {
if err := os.WriteFile(opts.SGXQuotePath, rawQuote, 0o644); err != nil {
return nil, fmt.Errorf("saving SGX quote to disk: %w", err)
}
}

return pemBlock, nil
}

Expand Down
Loading

0 comments on commit 6c46cbc

Please sign in to comment.