Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: support for authenticating with private keys and certificates stored in PKCS #11 backend #771

Merged
merged 8 commits into from
Dec 10, 2024
Merged
42 changes: 36 additions & 6 deletions cli/internal/certcache/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"errors"

"github.com/edgelesssys/marblerun/cli/internal/file"
"github.com/edgelesssys/marblerun/cli/internal/pkcs11"
"github.com/edgelesssys/marblerun/util"
"github.com/spf13/afero"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -43,21 +44,50 @@ func LoadCoordinatorCachedCert(flags *pflag.FlagSet, fs afero.Fs) (root, interme
}

// LoadClientCert parses the command line flags to load a TLS client certificate.
func LoadClientCert(flags *pflag.FlagSet) (*tls.Certificate, error) {
// The returned cancel function must be called only after the certificate is no longer needed.
func LoadClientCert(flags *pflag.FlagSet) (crt *tls.Certificate, cancel func() error, err error) {
certFile, err := flags.GetString("cert")
if err != nil {
return nil, err
return nil, nil, err
}
keyFile, err := flags.GetString("key")
if err != nil {
return nil, err
return nil, nil, err
}

pkcs11ConfigFile, err := flags.GetString("pkcs11-config")
if err != nil {
return nil, nil, err
}
pkcs11KeyID, err := flags.GetString("pkcs11-key-id")
if err != nil {
return nil, nil, err
}
pkcs11KeyLabel, err := flags.GetString("pkcs11-key-label")
if err != nil {
return nil, nil, err
}
clientCert, err := tls.LoadX509KeyPair(certFile, keyFile)
pkcs11CertID, err := flags.GetString("pkcs11-cert-id")
if err != nil {
return nil, err
return nil, nil, err
}
pkcs11CertLabel, err := flags.GetString("pkcs11-cert-label")
if err != nil {
return nil, nil, err
}

var clientCert tls.Certificate
switch {
case pkcs11ConfigFile != "":
clientCert, cancel, err = pkcs11.LoadX509KeyPair(pkcs11ConfigFile, pkcs11KeyID, pkcs11KeyLabel, pkcs11CertID, pkcs11CertLabel)
case certFile != "" && keyFile != "":
clientCert, err = tls.LoadX509KeyPair(certFile, keyFile)
cancel = func() error { return nil }
default:
err = errors.New("neither PKCS#11 nor file-based client certificate can be loaded with the provided flags")
}

return &clientCert, nil
return &clientCert, cancel, err
}

func saveCert(fh *file.Handler, root, intermediate *x509.Certificate) error {
Expand Down
87 changes: 69 additions & 18 deletions cli/internal/cmd/manifestUpdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package cmd

import (
"context"
"errors"
"fmt"
"io"

Expand Down Expand Up @@ -45,10 +46,23 @@ An admin certificate specified in the original manifest is needed to verify the
RunE: runUpdateApply,
}

cmd.Flags().StringP("cert", "c", "", "PEM encoded admin certificate file (required)")
must(cmd.MarkFlagRequired("cert"))
cmd.Flags().StringP("key", "k", "", "PEM encoded admin key file (required)")
must(cmd.MarkFlagRequired("key"))
cmd.Flags().StringP("cert", "c", "", "PEM encoded admin certificate file")
cmd.Flags().StringP("key", "k", "", "PEM encoded admin key file")
cmd.MarkFlagsRequiredTogether("key", "cert")

cmd.Flags().String("pkcs11-config", "", "Path to a PKCS#11 configuration file to load the client certificate with")
cmd.Flags().String("pkcs11-key-id", "", "ID of the private key in the PKCS#11 token")
cmd.Flags().String("pkcs11-key-label", "", "Label of the private key in the PKCS#11 token")
cmd.Flags().String("pkcs11-cert-id", "", "ID of the certificate in the PKCS#11 token")
cmd.Flags().String("pkcs11-cert-label", "", "Label of the certificate in the PKCS#11 token")
must(cmd.MarkFlagFilename("pkcs11-config", "json"))
cmd.MarkFlagsOneRequired("pkcs11-key-id", "pkcs11-key-label", "cert")
cmd.MarkFlagsOneRequired("pkcs11-cert-id", "pkcs11-cert-label", "cert")

cmd.MarkFlagsMutuallyExclusive("pkcs11-config", "cert")
cmd.MarkFlagsMutuallyExclusive("pkcs11-config", "key")
cmd.MarkFlagsOneRequired("pkcs11-config", "cert")
cmd.MarkFlagsOneRequired("pkcs11-config", "key")
daniel-weisse marked this conversation as resolved.
Show resolved Hide resolved

return cmd
}
Expand All @@ -67,10 +81,24 @@ All participants must use the same manifest to acknowledge the pending update.
RunE: runUpdateAcknowledge,
}

cmd.Flags().StringP("cert", "c", "", "PEM encoded admin certificate file (required)")
must(cmd.MarkFlagRequired("cert"))
cmd.Flags().StringP("key", "k", "", "PEM encoded admin key file (required)")
must(cmd.MarkFlagRequired("key"))
cmd.Flags().StringP("cert", "c", "", "PEM encoded admin certificate file")
cmd.Flags().StringP("key", "k", "", "PEM encoded admin key file")
cmd.MarkFlagsRequiredTogether("key", "cert")

cmd.Flags().String("pkcs11-config", "", "Path to a PKCS#11 configuration file to load the client certificate with")
cmd.Flags().String("pkcs11-key-id", "", "ID of the private key in the PKCS#11 token")
cmd.Flags().String("pkcs11-key-label", "", "Label of the private key in the PKCS#11 token")
cmd.Flags().String("pkcs11-cert-id", "", "ID of the certificate in the PKCS#11 token")
cmd.Flags().String("pkcs11-cert-label", "", "Label of the certificate in the PKCS#11 token")
must(cmd.MarkFlagFilename("pkcs11-config", "json"))
cmd.MarkFlagsOneRequired("pkcs11-key-id", "pkcs11-key-label", "cert")
cmd.MarkFlagsOneRequired("pkcs11-cert-id", "pkcs11-cert-label", "cert")

cmd.MarkFlagsMutuallyExclusive("pkcs11-config", "cert")
cmd.MarkFlagsMutuallyExclusive("pkcs11-config", "key")
cmd.MarkFlagsOneRequired("pkcs11-config", "cert")
cmd.MarkFlagsOneRequired("pkcs11-config", "key")

return cmd
}

Expand All @@ -84,10 +112,24 @@ func newUpdateCancel() *cobra.Command {
RunE: runUpdateCancel,
}

cmd.Flags().StringP("cert", "c", "", "PEM encoded admin certificate file (required)")
must(cmd.MarkFlagRequired("cert"))
cmd.Flags().StringP("key", "k", "", "PEM encoded admin key file (required)")
must(cmd.MarkFlagRequired("key"))
cmd.Flags().StringP("cert", "c", "", "PEM encoded admin certificate file")
cmd.Flags().StringP("key", "k", "", "PEM encoded admin key file")
cmd.MarkFlagsRequiredTogether("key", "cert")

cmd.Flags().String("pkcs11-config", "", "Path to a PKCS#11 configuration file to load the client certificate with")
cmd.Flags().String("pkcs11-key-id", "", "ID of the private key in the PKCS#11 token")
cmd.Flags().String("pkcs11-key-label", "", "Label of the private key in the PKCS#11 token")
cmd.Flags().String("pkcs11-cert-id", "", "ID of the certificate in the PKCS#11 token")
cmd.Flags().String("pkcs11-cert-label", "", "Label of the certificate in the PKCS#11 token")
must(cmd.MarkFlagFilename("pkcs11-config", "json"))
cmd.MarkFlagsOneRequired("pkcs11-key-id", "pkcs11-key-label", "cert")
cmd.MarkFlagsOneRequired("pkcs11-cert-id", "pkcs11-cert-label", "cert")

cmd.MarkFlagsMutuallyExclusive("pkcs11-config", "cert")
cmd.MarkFlagsMutuallyExclusive("pkcs11-config", "key")
cmd.MarkFlagsOneRequired("pkcs11-config", "cert")
cmd.MarkFlagsOneRequired("pkcs11-config", "key")

return cmd
}

Expand All @@ -107,7 +149,7 @@ func newUpdateGet() *cobra.Command {
return cmd
}

func runUpdateApply(cmd *cobra.Command, args []string) error {
func runUpdateApply(cmd *cobra.Command, args []string) (err error) {
manifestFile := args[0]
hostname := args[1]
fs := afero.NewOsFs()
Expand All @@ -116,10 +158,13 @@ func runUpdateApply(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
keyPair, err := certcache.LoadClientCert(cmd.Flags())
keyPair, cancel, err := certcache.LoadClientCert(cmd.Flags())
if err != nil {
return err
}
defer func() {
err = errors.Join(err, cancel())
daniel-weisse marked this conversation as resolved.
Show resolved Hide resolved
}()

manifest, err := loadManifestFile(file.New(manifestFile, fs))
if err != nil {
Expand All @@ -133,7 +178,7 @@ func runUpdateApply(cmd *cobra.Command, args []string) error {
return nil
}

func runUpdateAcknowledge(cmd *cobra.Command, args []string) error {
func runUpdateAcknowledge(cmd *cobra.Command, args []string) (err error) {
manifestFile := args[0]
hostname := args[1]
fs := afero.NewOsFs()
Expand All @@ -142,10 +187,13 @@ func runUpdateAcknowledge(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
keyPair, err := certcache.LoadClientCert(cmd.Flags())
keyPair, cancel, err := certcache.LoadClientCert(cmd.Flags())
if err != nil {
return err
}
defer func() {
err = errors.Join(err, cancel())
}()

manifest, err := loadManifestFile(file.New(manifestFile, fs))
if err != nil {
Expand All @@ -169,18 +217,21 @@ func runUpdateAcknowledge(cmd *cobra.Command, args []string) error {
return nil
}

func runUpdateCancel(cmd *cobra.Command, args []string) error {
func runUpdateCancel(cmd *cobra.Command, args []string) (err error) {
hostname := args[0]
fs := afero.NewOsFs()

root, _, err := certcache.LoadCoordinatorCachedCert(cmd.Flags(), fs)
if err != nil {
return err
}
keyPair, err := certcache.LoadClientCert(cmd.Flags())
keyPair, cancel, err := certcache.LoadClientCert(cmd.Flags())
if err != nil {
return err
}
defer func() {
err = errors.Join(err, cancel())
}()

if err := api.ManifestUpdateCancel(cmd.Context(), hostname, root, keyPair); err != nil {
return fmt.Errorf("canceling update: %w", err)
Expand Down
21 changes: 17 additions & 4 deletions cli/internal/cmd/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,23 @@ Manage secrets for the MarbleRun Coordinator.
Set or retrieve a secret defined in the manifest.`,
}

cmd.PersistentFlags().StringP("cert", "c", "", "PEM encoded MarbleRun user certificate file (required)")
cmd.PersistentFlags().StringP("key", "k", "", "PEM encoded MarbleRun user key file (required)")
must(cmd.MarkPersistentFlagRequired("key"))
must(cmd.MarkPersistentFlagRequired("cert"))
cmd.PersistentFlags().StringP("cert", "c", "", "PEM encoded MarbleRun user certificate file")
cmd.PersistentFlags().StringP("key", "k", "", "PEM encoded MarbleRun user key file")
cmd.MarkFlagsRequiredTogether("key", "cert")

cmd.PersistentFlags().String("pkcs11-config", "", "Path to a PKCS#11 configuration file to load the client certificate with")
cmd.PersistentFlags().String("pkcs11-key-id", "", "ID of the private key in the PKCS#11 token")
cmd.PersistentFlags().String("pkcs11-key-label", "", "Label of the private key in the PKCS#11 token")
cmd.PersistentFlags().String("pkcs11-cert-id", "", "ID of the certificate in the PKCS#11 token")
cmd.PersistentFlags().String("pkcs11-cert-label", "", "Label of the certificate in the PKCS#11 token")
must(cmd.MarkPersistentFlagFilename("pkcs11-config", "json"))
cmd.MarkFlagsOneRequired("pkcs11-key-id", "pkcs11-key-label", "cert")
cmd.MarkFlagsOneRequired("pkcs11-cert-id", "pkcs11-cert-label", "cert")

cmd.MarkFlagsMutuallyExclusive("pkcs11-config", "cert")
cmd.MarkFlagsMutuallyExclusive("pkcs11-config", "key")
cmd.MarkFlagsOneRequired("pkcs11-config", "cert")
cmd.MarkFlagsOneRequired("pkcs11-config", "key")

cmd.AddCommand(newSecretSet())
cmd.AddCommand(newSecretGet())
Expand Down
8 changes: 6 additions & 2 deletions cli/internal/cmd/secretGet.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package cmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"

Expand Down Expand Up @@ -39,7 +40,7 @@ and need permissions in the manifest to read the requested secrets.
return cmd
}

func runSecretGet(cmd *cobra.Command, args []string) error {
func runSecretGet(cmd *cobra.Command, args []string) (err error) {
hostname := args[len(args)-1]
secretIDs := args[0 : len(args)-1]
fs := afero.NewOsFs()
Expand All @@ -53,10 +54,13 @@ func runSecretGet(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
keyPair, err := certcache.LoadClientCert(cmd.Flags())
keyPair, cancel, err := certcache.LoadClientCert(cmd.Flags())
if err != nil {
return err
}
defer func() {
err = errors.Join(err, cancel())
}()

getSecrets := func(ctx context.Context) (map[string]manifest.Secret, error) {
return api.SecretGet(ctx, hostname, root, keyPair, secretIDs)
Expand Down
8 changes: 6 additions & 2 deletions cli/internal/cmd/secretSet.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"strings"

Expand Down Expand Up @@ -47,7 +48,7 @@ marblerun secret set certificate.pem $MARBLERUN -c admin.crt -k admin.key --from
return cmd
}

func runSecretSet(cmd *cobra.Command, args []string) error {
func runSecretSet(cmd *cobra.Command, args []string) (err error) {
secretFile := args[0]
hostname := args[1]
fs := afero.NewOsFs()
Expand Down Expand Up @@ -78,10 +79,13 @@ func runSecretSet(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
keyPair, err := certcache.LoadClientCert(cmd.Flags())
keyPair, cancel, err := certcache.LoadClientCert(cmd.Flags())
if err != nil {
return err
}
defer func() {
err = errors.Join(err, cancel())
}()

if err := api.SecretSet(cmd.Context(), hostname, root, keyPair, newSecrets); err != nil {
return err
Expand Down
Loading