Skip to content

Commit

Permalink
scion-pki: enable kms support (#4617)
Browse files Browse the repository at this point in the history
Enable the scion-pki tool to interact with various cloud KMS and HSMs
through the step-kms-plugin. The step-kms-plugin must be installed and
available in the PATH.

For more information about step-kms-plugin, please refer to the
documentation at https://github.com/smallstep/step-kms-plugin.

To see example usage of step-kms-plugin, please
refer to
https://smallstep.com/docs/step-ca/cryptographic-protection
  • Loading branch information
oncilla authored Sep 24, 2024
1 parent f0f7162 commit 5550a98
Show file tree
Hide file tree
Showing 32 changed files with 495 additions and 56 deletions.
1 change: 1 addition & 0 deletions doc/command/scion-pki/scion-pki.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ SEE ALSO
* :ref:`scion-pki certificate <scion-pki_certificate>` - Manage certificates for the SCION control plane PKI.
* :ref:`scion-pki completion <scion-pki_completion>` - Generate the autocompletion script for the specified shell
* :ref:`scion-pki key <scion-pki_key>` - Manage private and public keys
* :ref:`scion-pki kms <scion-pki_kms>` - Run the step-kms-plugin
* :ref:`scion-pki trc <scion-pki_trc>` - Manage TRCs for the SCION control plane PKI
* :ref:`scion-pki version <scion-pki_version>` - Show the scion-pki version information

2 changes: 2 additions & 0 deletions doc/command/scion-pki/scion-pki_certificate_create.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,14 @@ Options
--bundle Bundle the certificate with the issuer certificate as a certificate chain
--ca string The path to the issuer certificate
--ca-key string The path to the issuer private key used to sign the new certificate
--ca-kms string The uri to configure a Cloud KMS or an HSM used for signing the certificate.
--common-name string The common name that replaces the common name in the subject template
--csr Generate a certificate signign request instead of a certificate
--curve string The elliptic curve to use (P-256|P-384|P-521) (default "P-256")
--force Force overwritting existing files
-h, --help help for create
--key string The path to the existing private key to use instead of creating a new one
--kms string The uri to configure a Cloud KMS or an HSM.
--not-after time The NotAfter time of the certificate. Can either be a timestamp or an offset.
If the value is a timestamp, it is expected to either be an RFC 3339 formatted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Options
::

-h, --help help for private
--kms string The uri to configure a Cloud KMS or an HSM.
--separator string The separator between file names (default "\n")

SEE ALSO
Expand Down
1 change: 1 addition & 0 deletions doc/command/scion-pki/scion-pki_certificate_sign.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Options
--bundle Bundle the certificate with the issuer certificate as a certificate chain
--ca string The path to the issuer certificate
--ca-key string The path to the issuer private key used to sign the new certificate
--ca-kms string The uri to configure a Cloud KMS or an HSM used for signing the certificate.
-h, --help help for sign
--not-after time The NotAfter time of the certificate. Can either be a timestamp or an offset.
Expand Down
1 change: 1 addition & 0 deletions doc/command/scion-pki/scion-pki_key_match_certificate.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Options
::

-h, --help help for certificate
--kms string The uri to configure a Cloud KMS or an HSM.
--separator string The separator between file names (default "\n")

SEE ALSO
Expand Down
1 change: 1 addition & 0 deletions doc/command/scion-pki/scion-pki_key_public.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Options

--force Force overwritting existing public key
-h, --help help for public
--kms string The uri to configure a Cloud KMS or an HSM.
--out string Path to write public key

SEE ALSO
Expand Down
45 changes: 45 additions & 0 deletions doc/command/scion-pki/scion-pki_kms.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
:orphan:

.. _scion-pki_kms:

scion-pki kms
-------------

Run the step-kms-plugin

Synopsis
~~~~~~~~


This command leverages the step-kms-plugin to interact with cloud Key Management
Systems (KMS) and Hardware Security Modules (HSM).

The commands are passed directly to the step-kms-plugin. For more information on
the available commands and their usage, please refer to the step-kms-plugin
documentation at https://github.com/smallstep/step-kms-plugin. In order to enable
KMS support, the step-kms-plugin must be installed and available in the PATH.

Various commands of the scion-pki tool allow the use of KMS. In all cases, the
private key needs to already exist in the KMS. To instruct the scion-pki tool to
use the key in the KMS, the --kms flag must be set.

For more information about supported KMSs and uri pattern, please consult
https://smallstep.com/docs/step-ca/cryptographic-protection.


::

scion-pki kms [command] [flags]

Options
~~~~~~~

::

-h, --help help for kms

SEE ALSO
~~~~~~~~

* :ref:`scion-pki <scion-pki>` - SCION Control Plane PKI Management Tool

1 change: 1 addition & 0 deletions doc/command/scion-pki/scion-pki_trc_sign.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Options
::

-h, --help help for sign
--kms string The uri to configure a Cloud KMS or an HSM.
-o, --out string Output file path. If --out is set, --out-dir is ignored.
--out-dir string Output directory. If --out is set, --out-dir is ignored. (default ".")

Expand Down
12 changes: 12 additions & 0 deletions scion-pki/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
load("//tools/lint:go.bzl", "go_library")

go_library(
name = "go_default_library",
srcs = [
"flags.go",
"plugin.go",
],
importpath = "github.com/scionproto/scion/scion-pki",
visibility = ["//visibility:public"],
deps = ["@com_github_spf13_pflag//:go_default_library"],
)
1 change: 1 addition & 0 deletions scion-pki/certs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ go_library(
"//private/svc:go_default_library",
"//private/tracing:go_default_library",
"//private/trust:go_default_library",
"//scion-pki:go_default_library",
"//scion-pki/encoding:go_default_library",
"//scion-pki/file:go_default_library",
"//scion-pki/key:go_default_library",
Expand Down
30 changes: 24 additions & 6 deletions scion-pki/certs/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/scionproto/scion/pkg/scrypto/cppki"
"github.com/scionproto/scion/private/app/command"
"github.com/scionproto/scion/private/app/flag"
scionpki "github.com/scionproto/scion/scion-pki"
"github.com/scionproto/scion/scion-pki/file"
"github.com/scionproto/scion/scion-pki/key"
)
Expand Down Expand Up @@ -125,7 +126,9 @@ func newCreateCmd(pather command.Pather) *cobra.Command {
notAfter flag.Time
ca string
caKey string
caKms string
existingKey string
kms string
curve string
bundle bool
force bool
Expand Down Expand Up @@ -193,7 +196,7 @@ A valid example for a JSON formatted template::
Args: cobra.RangeArgs(2, 3),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 2 && flags.existingKey == "" {
return serrors.New("positional key file is required")
return serrors.New("the positional key file is required")
}
ct, err := parseCertType(flags.profile)
if err != nil {
Expand All @@ -203,6 +206,9 @@ A valid example for a JSON formatted template::
if err != nil {
return serrors.Wrap("creating subject", err)
}
if flags.existingKey == "" && flags.kms != "" {
return serrors.New("the kms flag is only allowed with an existing key")
}

// Only check that the flags are set appropriately here.
// Do the actual parsing after the usage help message is silenced.
Expand All @@ -220,10 +226,10 @@ A valid example for a JSON formatted template::

cmd.SilenceUsage = true

var privKey key.PrivateKey
var privKey crypto.Signer
var encodedKey []byte
if flags.existingKey != "" {
if privKey, err = key.LoadPrivateKey(flags.existingKey); err != nil {
if privKey, err = key.LoadPrivateKey(flags.kms, flags.existingKey); err != nil {
return serrors.Wrap("loading existing private key", err)
}
} else {
Expand All @@ -234,18 +240,23 @@ A valid example for a JSON formatted template::
return serrors.Wrap("encoding fresh private key", err)
}
}
if !key.IsX509Signer(privKey) {
return serrors.New("the private key cannot be used in X.509 certificates",
"type", fmt.Sprintf("%T", privKey),
)
}

var caCertRaw []byte
var caCert *x509.Certificate
var caKey key.PrivateKey
var caKey crypto.Signer
if loadCA {
if caCertRaw, err = os.ReadFile(flags.ca); err != nil {
return serrors.Wrap("read CA certificate", err)
}
if caCert, err = parseCertificate(caCertRaw); err != nil {
return serrors.Wrap("parsing CA certificate", err)
}
if caKey, err = key.LoadPrivateKey(flags.caKey); err != nil {
if caKey, err = key.LoadPrivateKey(flags.caKms, flags.caKey); err != nil {
return serrors.Wrap("loading CA private key", err)
}
}
Expand All @@ -272,6 +283,12 @@ A valid example for a JSON formatted template::
}
fmt.Printf("CSR successfully written to %q\n", csrFile)
} else {
if !key.IsX509Signer(caKey) {
return serrors.New("the CA key cannot be used to create X.509 certificates",
"type", fmt.Sprintf("%T", caKey),
)
}

cert, err := CreateCertificate(CertParams{
Type: ct,
Subject: subject,
Expand Down Expand Up @@ -359,7 +376,8 @@ offset from the current time.`,
cmd.Flags().BoolVar(&flags.force, "force", false,
"Force overwritting existing files",
)

scionpki.BindFlagKmsCA(cmd.Flags(), &flags.caKms)
scionpki.BindFlagKms(cmd.Flags(), &flags.kms)
return cmd
}

Expand Down
2 changes: 1 addition & 1 deletion scion-pki/certs/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ func TestNewCreateCmdCSR(t *testing.T) {
Validate: func(t *testing.T, csr *x509.CertificateRequest) {
require.NoError(t, csr.CheckSignature())
require.Equal(t, "1-ff00:0:111 Certificate", csr.Subject.CommonName)
priv, err := key.LoadPrivateKey("testdata/create/private.key")
priv, err := key.LoadPrivateKey("", "testdata/create/private.key")
require.NoError(t, err)
require.Equal(t, priv.Public(), csr.PublicKey)
},
Expand Down
9 changes: 6 additions & 3 deletions scion-pki/certs/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/scionproto/scion/pkg/private/serrors"
"github.com/scionproto/scion/pkg/scrypto/cppki"
"github.com/scionproto/scion/private/app/command"
scionpki "github.com/scionproto/scion/scion-pki"
"github.com/scionproto/scion/scion-pki/key"
)

Expand All @@ -45,6 +46,7 @@ func newMatchCmd(pather command.Pather) *cobra.Command {
func newMatchPrivateKey(pather command.Pather) *cobra.Command {
var flags struct {
separator string
kms string
}
cmd := &cobra.Command{
Use: "private <certificate> <private-key> [<private-key> ...]",
Expand Down Expand Up @@ -74,7 +76,7 @@ The output contains all the private keys that are authenticated by the certifica

var keys []string
for _, file := range args[1:] {
key, err := loadPackedPublicFromPrivate(file)
key, err := loadPackedPublicFromPrivate(flags.kms, file)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: ignoring %q: %s\n", file, err)
continue
Expand All @@ -92,11 +94,12 @@ The output contains all the private keys that are authenticated by the certifica
},
}
cmd.Flags().StringVar(&flags.separator, "separator", "\n", "The separator between file names")
scionpki.BindFlagKms(cmd.Flags(), &flags.kms)
return cmd
}

func loadPackedPublicFromPrivate(file string) ([]byte, error) {
key, err := key.LoadPrivateKey(file)
func loadPackedPublicFromPrivate(kms, name string) ([]byte, error) {
key, err := key.LoadPrivateKey(kms, name)
if err != nil {
return nil, err
}
Expand Down
6 changes: 5 additions & 1 deletion scion-pki/certs/renew.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,11 @@ The template is expressed in JSON. A valid example::
span.SetTag("remote-options", remotes)

// Load private key.
privPrev, err := key.LoadPrivateKey(keyFile)
// XXX(roosd): The renewal process does currently not support KMS.
// This is a bit more involved, and requires some refactoring of the
// flags and the key loading/creation process. For now, KMS is also
// not a direct use-case for AS certificates.
privPrev, err := key.LoadPrivateKey("", keyFile)
if err != nil {
return serrors.Wrap("reading private key", err)
}
Expand Down
2 changes: 1 addition & 1 deletion scion-pki/certs/renew_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func TestExtractChain(t *testing.T) {
chain := xtest.LoadChain(t, "testdata/renew/ISD1-ASff00_0_111.pem")

caChain := xtest.LoadChain(t, "testdata/renew/ISD1-ASff00_0_110.pem")
key, err := key.LoadPrivateKey("testdata/renew/cp-as-110.key")
key, err := key.LoadPrivateKey("", "testdata/renew/cp-as-110.key")
require.NoError(t, err)
caSigner := trust.Signer{
PrivateKey: key,
Expand Down
10 changes: 9 additions & 1 deletion scion-pki/certs/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/scionproto/scion/pkg/scrypto/cppki"
"github.com/scionproto/scion/private/app/command"
"github.com/scionproto/scion/private/app/flag"
scionpki "github.com/scionproto/scion/scion-pki"
"github.com/scionproto/scion/scion-pki/key"
)

Expand All @@ -39,6 +40,7 @@ func newSignCmd(pather command.Pather) *cobra.Command {
notAfter flag.Time
ca string
caKey string
caKms string
bundle bool
}
flags.notBefore = flag.Time{
Expand Down Expand Up @@ -115,10 +117,15 @@ and not to \--not-before.
if err != nil {
return serrors.Wrap("parsing CA certificate", err)
}
caKey, err := key.LoadPrivateKey(flags.caKey)
caKey, err := key.LoadPrivateKey(flags.caKms, flags.caKey)
if err != nil {
return serrors.Wrap("loading CA private key", err)
}
if !key.IsX509Signer(caKey) {
return serrors.New("the CA key cannot be used to create X.509 certificates",
"type", fmt.Sprintf("%T", caKey),
)
}

subject := csr.Subject
subject.ExtraNames = csr.Subject.Names
Expand Down Expand Up @@ -190,6 +197,7 @@ offset from the current time.`,
cmd.Flags().BoolVar(&flags.bundle, "bundle", false,
"Bundle the certificate with the issuer certificate as a certificate chain",
)
scionpki.BindFlagKmsCA(cmd.Flags(), &flags.caKms)
cmd.MarkFlagRequired("ca")
cmd.MarkFlagRequired("ca-key")

Expand Down
2 changes: 2 additions & 0 deletions scion-pki/cmd/scion-pki/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ go_library(
name = "go_default_library",
srcs = [
"gendocs.go",
"kms.go",
"main.go",
"version.go",
],
Expand All @@ -20,6 +21,7 @@ go_library(
"//pkg/private/serrors:go_default_library",
"//private/app:go_default_library",
"//private/env:go_default_library",
"//scion-pki:go_default_library",
"//scion-pki/certs:go_default_library",
"//scion-pki/key:go_default_library",
"//scion-pki/testcrypto:go_default_library",
Expand Down
Loading

0 comments on commit 5550a98

Please sign in to comment.