Skip to content

Commit

Permalink
Merge pull request from GHSA-j3rq-4xjw-xg63
Browse files Browse the repository at this point in the history
* Pin Coordinator certificate for all connections after "manifest set"

Signed-off-by: Daniel Weiße <[email protected]>

* Allow bypassing of cached cert using the insecure flag

Signed-off-by: Daniel Weiße <[email protected]>

* Reduce code duplication when setting up rest clients

Signed-off-by: Daniel Weiße <[email protected]>

* Fix comment

Signed-off-by: Daniel Weiße <[email protected]>

* Fixup rest client and tests

Signed-off-by: Daniel Weiße <[email protected]>

* Update docs for improved CLI verification

Signed-off-by: Daniel Weiße <[email protected]>

* Overwrite files by default [keeping old behavior]

Signed-off-by: Daniel Weiße <[email protected]>

* Dont mark flag as required [because it has a default value]

Signed-off-by: Daniel Weiße <[email protected]>

* Check CLI certificate command response against cached cert

Signed-off-by: Daniel Weiße <[email protected]>

* Fix test

Signed-off-by: Daniel Weiße <[email protected]>

* Allow setting K8s namespace for CLI commands

Signed-off-by: Daniel Weiße <[email protected]>

* Review suggestions

Signed-off-by: Daniel Weiße <[email protected]>

---------

Signed-off-by: Daniel Weiße <[email protected]>
  • Loading branch information
daniel-weisse authored Dec 4, 2023
1 parent 526f408 commit c3a26ec
Show file tree
Hide file tree
Showing 31 changed files with 779 additions and 320 deletions.
14 changes: 14 additions & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,22 @@ package cmd
import (
"context"
"os"
"path/filepath"

"github.com/edgelesssys/marblerun/cli/internal/cmd"
"github.com/edgelesssys/marblerun/cli/internal/helm"
"github.com/spf13/cobra"
)

// defaultCoordinatorCertCache is the default path to the Coordinator's certificate cache.
var defaultCoordinatorCertCache = func() string {
configDir, err := os.UserConfigDir()
if err != nil {
panic(err)
}
return filepath.Join(configDir, "marblerun", "coordinator-cert.pem")
}()

// Execute starts the CLI.
func Execute() error {
cobra.EnableCommandSorting = false
Expand Down Expand Up @@ -51,10 +62,13 @@ To install and configure MarbleRun, run:
rootCmd.AddCommand(cmd.NewPackageInfoCmd())
rootCmd.AddCommand(cmd.NewVersionCmd())

rootCmd.PersistentFlags().String("coordinator-cert", defaultCoordinatorCertCache, "Path to MarbleRun Coordinator's root certificate to use for TLS connections")
rootCmd.PersistentFlags().String("era-config", "", "Path to remote attestation config file in json format, if none provided the newest configuration will be loaded from github")
rootCmd.PersistentFlags().BoolP("insecure", "i", false, "Set to skip quote verification, needed when running in simulation mode")
rootCmd.PersistentFlags().StringSlice("accepted-tcb-statuses", []string{"UpToDate"}, "Comma-separated list of user accepted TCB statuses (e.g. ConfigurationNeeded,ConfigurationAndSWHardeningNeeded)")
rootCmd.PersistentFlags().StringP("namespace", "n", helm.Namespace, "Kubernetes namespace of the MarbleRun installation")

must(rootCmd.MarkPersistentFlagFilename("coordinator-cert", "pem", "crt"))
must(rootCmd.MarkPersistentFlagFilename("era-config", "json"))

return rootCmd
Expand Down
58 changes: 58 additions & 0 deletions cli/internal/cmd/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@
package cmd

import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"

"github.com/edgelesssys/marblerun/cli/internal/file"
"github.com/edgelesssys/marblerun/cli/internal/rest"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

Expand All @@ -27,6 +34,50 @@ func NewCertificateCmd() *cobra.Command {
return cmd
}

func runCertificate(saveCert func(io.Writer, *file.Handler, []*pem.Block) error,
) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
hostname := args[0]
fs := afero.NewOsFs()
flags, err := parseRestFlags(cmd.Flags())
if err != nil {
return err
}
output, err := cmd.Flags().GetString("output")
if err != nil {
return err
}

localCerts, err := rest.LoadCoordinatorCachedCert(cmd.Flags(), fs)
if err != nil {
return err
}
rootCert, err := getRootCertFromPEMChain(localCerts)
if err != nil {
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.insecure, flags.acceptedTCBStatuses,
)
if err != nil {
return fmt.Errorf("retrieving certificate from Coordinator: %w", err)
}

remoteRootCert, err := getRootCertFromPEMChain(certs)
if err != nil {
return fmt.Errorf("parsing root certificate from Coordinator: %w", err)
}

if !remoteRootCert.Equal(rootCert) {
return errors.New("root certificate of Coordinator changed. Run 'marblerun manifest verify' to verify the instance and update the local cache")
}

return saveCert(cmd.OutOrStdout(), file.New(output, fs), certs)
}
}

func outputFlagNotEmpty(cmd *cobra.Command, _ []string) error {
output, err := cmd.Flags().GetString("output")
if err != nil {
Expand All @@ -37,3 +88,10 @@ func outputFlagNotEmpty(cmd *cobra.Command, _ []string) error {
}
return nil
}

func getRootCertFromPEMChain(certs []*pem.Block) (*x509.Certificate, error) {
if len(certs) == 0 {
return nil, errors.New("no certificates received from Coordinator")
}
return x509.ParseCertificate(certs[len(certs)-1].Bytes)
}
33 changes: 5 additions & 28 deletions cli/internal/cmd/certificateChain.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import (
"io"

"github.com/edgelesssys/marblerun/cli/internal/file"
"github.com/edgelesssys/marblerun/cli/internal/rest"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

Expand All @@ -24,7 +22,7 @@ func newCertificateChain() *cobra.Command {
Short: "Returns the certificate chain of the MarbleRun Coordinator",
Long: `Returns the certificate chain of the MarbleRun Coordinator`,
Args: cobra.ExactArgs(1),
RunE: runCertificateChain,
RunE: runCertificate(saveCertChain),
PreRunE: outputFlagNotEmpty,
}

Expand All @@ -33,29 +31,8 @@ func newCertificateChain() *cobra.Command {
return cmd
}

func runCertificateChain(cmd *cobra.Command, args []string) error {
hostname := args[0]
flags, err := rest.ParseFlags(cmd)
if err != nil {
return err
}
output, err := cmd.Flags().GetString("output")
if err != nil {
return err
}

certs, err := rest.VerifyCoordinator(
cmd.Context(), cmd.OutOrStdout(), hostname,
flags.EraConfig, flags.Insecure, flags.AcceptedTCBStatuses,
)
if err != nil {
return fmt.Errorf("retrieving certificate chain from Coordinator: %w", err)
}
return cliCertificateChain(cmd.OutOrStdout(), file.New(output, afero.NewOsFs()), certs)
}

// cliCertificateChain gets the certificate chain of the MarbleRun Coordinator.
func cliCertificateChain(out io.Writer, file *file.Handler, certs []*pem.Block) error {
// saveCertChain saves the certificate chain of the MarbleRun Coordinator.
func saveCertChain(out io.Writer, certFile *file.Handler, certs []*pem.Block) error {
if len(certs) == 0 {
return errors.New("no certificates received from Coordinator")
}
Expand All @@ -68,10 +45,10 @@ func cliCertificateChain(out io.Writer, file *file.Handler, certs []*pem.Block)
chain = append(chain, pem.EncodeToMemory(cert)...)
}

if err := file.Write(chain); err != nil {
if err := certFile.Write(chain, file.OptOverwrite); err != nil {
return err
}
fmt.Fprintf(out, "Certificate chain written to %s\n", file.Name())
fmt.Fprintf(out, "Certificate chain written to %s\n", certFile.Name())

return nil
}
33 changes: 5 additions & 28 deletions cli/internal/cmd/certificateIntermediate.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import (
"io"

"github.com/edgelesssys/marblerun/cli/internal/file"
"github.com/edgelesssys/marblerun/cli/internal/rest"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

Expand All @@ -24,7 +22,7 @@ func newCertificateIntermediate() *cobra.Command {
Short: "Returns the intermediate certificate of the MarbleRun Coordinator",
Long: `Returns the intermediate certificate of the MarbleRun Coordinator`,
Args: cobra.ExactArgs(1),
RunE: runCertificateIntermediate,
RunE: runCertificate(saveIntermediateCert),
PreRunE: outputFlagNotEmpty,
}

Expand All @@ -33,36 +31,15 @@ func newCertificateIntermediate() *cobra.Command {
return cmd
}

func runCertificateIntermediate(cmd *cobra.Command, args []string) error {
hostname := args[0]
flags, err := rest.ParseFlags(cmd)
if err != nil {
return err
}
output, err := cmd.Flags().GetString("output")
if err != nil {
return err
}

certs, err := rest.VerifyCoordinator(
cmd.Context(), cmd.OutOrStdout(), hostname,
flags.EraConfig, flags.Insecure, flags.AcceptedTCBStatuses,
)
if err != nil {
return fmt.Errorf("retrieving intermediate certificate from Coordinator: %w", err)
}
return cliCertificateIntermediate(cmd.OutOrStdout(), file.New(output, afero.NewOsFs()), certs)
}

// cliCertificateIntermediate gets the intermediate certificate of the MarbleRun Coordinator.
func cliCertificateIntermediate(out io.Writer, file *file.Handler, certs []*pem.Block) error {
// cliCertificateIntermediate saves the intermediate certificate of the MarbleRun Coordinator.
func saveIntermediateCert(out io.Writer, certFile *file.Handler, certs []*pem.Block) error {
if len(certs) < 2 {
return errors.New("no intermediate certificate received from Coordinator")
}
if err := file.Write(pem.EncodeToMemory(certs[0])); err != nil {
if err := certFile.Write(pem.EncodeToMemory(certs[0]), file.OptOverwrite); err != nil {
return err
}
fmt.Fprintf(out, "Intermediate certificate written to %s\n", file.Name())
fmt.Fprintf(out, "Intermediate certificate written to %s\n", certFile.Name())

return nil
}
33 changes: 5 additions & 28 deletions cli/internal/cmd/certificateRoot.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import (
"io"

"github.com/edgelesssys/marblerun/cli/internal/file"
"github.com/edgelesssys/marblerun/cli/internal/rest"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

Expand All @@ -24,7 +22,7 @@ func newCertificateRoot() *cobra.Command {
Short: "Returns the root certificate of the MarbleRun Coordinator",
Long: `Returns the root certificate of the MarbleRun Coordinator`,
Args: cobra.ExactArgs(1),
RunE: runCertificateRoot,
RunE: runCertificate(saveRootCert),
PreRunE: outputFlagNotEmpty,
}

Expand All @@ -33,36 +31,15 @@ func newCertificateRoot() *cobra.Command {
return cmd
}

func runCertificateRoot(cmd *cobra.Command, args []string) error {
hostname := args[0]
flags, err := rest.ParseFlags(cmd)
if err != nil {
return err
}
output, err := cmd.Flags().GetString("output")
if err != nil {
return err
}

certs, err := rest.VerifyCoordinator(
cmd.Context(), cmd.OutOrStdout(), hostname,
flags.EraConfig, flags.Insecure, flags.AcceptedTCBStatuses,
)
if err != nil {
return fmt.Errorf("retrieving root certificate from Coordinator: %w", err)
}
return cliCertificateRoot(cmd.OutOrStdout(), file.New(output, afero.NewOsFs()), certs)
}

// cliCertificateRoot gets the root certificate of the MarbleRun Coordinator and saves it to a file.
func cliCertificateRoot(out io.Writer, file *file.Handler, certs []*pem.Block) error {
// saveRootCert saves the root certificate of the MarbleRun Coordinator to a file.
func saveRootCert(out io.Writer, certFile *file.Handler, certs []*pem.Block) error {
if len(certs) == 0 {
return errors.New("no certificates received from Coordinator")
}
if err := file.Write(pem.EncodeToMemory(certs[len(certs)-1])); err != nil {
if err := certFile.Write(pem.EncodeToMemory(certs[len(certs)-1]), file.OptOverwrite); err != nil {
return err
}
fmt.Fprintf(out, "Root certificate written to %s\n", file.Name())
fmt.Fprintf(out, "Root certificate written to %s\n", certFile.Name())

return nil
}
6 changes: 3 additions & 3 deletions cli/internal/cmd/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func TestCertificateRoot(t *testing.T) {

var out bytes.Buffer

err := cliCertificateRoot(&out, tc.file, tc.certs)
err := saveRootCert(&out, tc.file, tc.certs)
if tc.wantErr {
assert.Error(err)
return
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestCertificateIntermediate(t *testing.T) {

var out bytes.Buffer

err := cliCertificateIntermediate(&out, tc.file, tc.certs)
err := saveIntermediateCert(&out, tc.file, tc.certs)
if tc.wantErr {
assert.Error(err)
return
Expand Down Expand Up @@ -208,7 +208,7 @@ func TestCertificateChain(t *testing.T) {

var out bytes.Buffer

err := cliCertificateChain(&out, tc.file, tc.certs)
err := saveCertChain(&out, tc.file, tc.certs)
if tc.wantErr {
assert.Error(err)
return
Expand Down
1 change: 0 additions & 1 deletion cli/internal/cmd/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func NewCheckCmd() *cobra.Command {
}

cmd.Flags().Uint("timeout", 60, "Time to wait before aborting in seconds")
cmd.Flags().String("namespace", helm.Namespace, "Namespace MarbleRun is deployed to")
return cmd
}

Expand Down
Loading

0 comments on commit c3a26ec

Please sign in to comment.