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

Add handling of cnf claim #1092

Merged
merged 6 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion command/ca/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func signCertificateAction(ctx *cli.Context) error {
}

// certificate flow unifies online and offline flows on a single api
flow, err := cautils.NewCertificateFlow(ctx)
flow, err := cautils.NewCertificateFlow(ctx, cautils.WithCertificateRequest(csr))
if err != nil {
return err
}
Expand Down
55 changes: 54 additions & 1 deletion command/ca/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"

"github.com/pkg/errors"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/pki"
"github.com/smallstep/cli/flags"
Expand All @@ -12,6 +13,8 @@ import (
"github.com/urfave/cli"
"go.step.sm/cli-utils/command"
"go.step.sm/cli-utils/errs"
"go.step.sm/crypto/pemutil"
"golang.org/x/crypto/ssh"
)

func tokenCommand() cli.Command {
Expand All @@ -27,6 +30,7 @@ func tokenCommand() cli.Command {
[**--output-file**=<file>] [**--kms**=uri] [**--key**=<file>] [**--san**=<SAN>] [**--offline**]
[**--revoke**] [**--x5c-cert**=<file>] [**--x5c-key**=<file>] [**--x5c-insecure**]
[**--sshpop-cert**=<file>] [**--sshpop-key**=<file>]
[**--cnf**=<fingerprint>] [**--cnf-file**=<file>]
[**--ssh**] [**--host**] [**--principal**=<name>] [**--k8ssa-token-path**=<file>]
[**--ca-url**=<uri>] [**--root**=<file>] [**--context**=<name>]`,
Description: `**step ca token** command generates a one-time token granting access to the
Expand Down Expand Up @@ -82,6 +86,18 @@ Get a new token that becomes valid in 30 minutes and expires 5 minutes after tha
$ step ca token --not-before 30m --not-after 35m internal.example.com
'''

Get a new token with a confirmation claim to enforce a given CSR fingerprint:
'''
$ step certificate fingerprint --format base64-url-raw internal.csr
PJLNhtQoBE1yGN_ZKzr4Y2U5pyqIGiyyszkoz2raDOw
$ step ca token --cnf PJLNhtQoBE1yGN_ZKzr4Y2U5pyqIGiyyszkoz2raDOw internal.smallstep.com
'''

Get a new token with a confirmation claim to enforce the use of a given CSR:
'''
step ca token --cnf-file internal.csr internal.smallstep.com
'''

Get a new token signed with the given private key, the public key must be
configured in the certificate authority:
'''
Expand Down Expand Up @@ -133,6 +149,11 @@ Get a new token for an SSH host certificate:
$ step ca token my-remote.hostname --ssh --host
'''

Get a new token with a confirmation claim to enforce the use of a given public key:
'''
step ca token --ssh --host --cnf-file internal.pub internal.smallstep.com
'''

Generate a renew token and use it in a renew after expiry request:
'''
$ TOKEN=$(step ca token --x5c-cert internal.crt --x5c-key internal.key --renew internal.example.com)
Expand Down Expand Up @@ -186,6 +207,8 @@ multiple principals.`,
flags.SSHPOPKey,
flags.NebulaCert,
flags.NebulaKey,
flags.Confirmation,
flags.ConfirmationFile,
cli.StringFlag{
Name: "key",
Usage: `The private key <file> used to sign the JWT. This is usually downloaded from
Expand Down Expand Up @@ -240,6 +263,9 @@ func tokenAction(ctx *cli.Context) error {
isSSH := ctx.Bool("ssh")
isHost := ctx.Bool("host")
principals := ctx.StringSlice("principal")
// confirmation claims
cnfFile := ctx.String("cnf-file")
cnf := ctx.String("cnf")

switch {
case isSSH && len(sans) > 0:
Expand All @@ -252,6 +278,8 @@ func tokenAction(ctx *cli.Context) error {
return errs.RequiredWithFlag(ctx, "host", "ssh")
case !isSSH && len(principals) > 0:
return errs.RequiredWithFlag(ctx, "principal", "ssh")
case cnfFile != "" && cnf != "":
return errs.IncompatibleFlagWithFlag(ctx, "cnf-file", "cnf")
}

// Default token type is always a 'Sign' token.
Expand Down Expand Up @@ -295,6 +323,31 @@ func tokenAction(ctx *cli.Context) error {
}
}

// Add options to create a confirmation claim if a CSR or SSH public key is
// passed.
var tokenOpts []cautils.Option
if cnfFile != "" {
in, err := utils.ReadFile(cnfFile)
if err != nil {
return err
}
if isSSH {
sshPub, _, _, _, err := ssh.ParseAuthorizedKey(in)
if err != nil {
return errors.Wrap(err, "error parsing ssh public key")
}
tokenOpts = append(tokenOpts, cautils.WithSSHPublicKey(sshPub))
} else {
csr, err := pemutil.ParseCertificateRequest(in)
if err != nil {
return errors.Wrap(err, "error parsing certificate request")
}
tokenOpts = append(tokenOpts, cautils.WithCertificateRequest(csr))
Comment on lines +334 to +345
Copy link
Member

@hslatman hslatman Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing the CSR or SSH public key, the claim itself could be calculated here, and just pass it as the fingerprint directly? Or is there some flow in which to defer calculation of the fingerprint is necessary? step ca sign, maybe?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step ca sign use cautils.WithCertificateRequest(csr) and step ssh certificate use cautils.WithSSHPublicKey(sshPub), although the SSH one is currently totally ignored. These fields go to the shared context of the cautils package and end up in the token package, which takes care of calculating the fingerprint and adding the proper attribute to the final token. Right now, only the CSR gets into the token package, but sending the type allows us to specify the right attribute now that we don't share just kid.

}
} else if cnf != "" {
tokenOpts = append(tokenOpts, cautils.WithConfirmationFingerprint(cnf))
}

// --san and --type revoke are incompatible. Revocation tokens do not support SANs.
if typ == cautils.RevokeType && len(sans) > 0 {
return errs.IncompatibleFlagWithFlag(ctx, "san", "revoke")
Expand Down Expand Up @@ -327,7 +380,7 @@ func tokenAction(ctx *cli.Context) error {
return err
}
} else {
token, err = cautils.NewTokenFlow(ctx, typ, subject, sans, caURL, root, notBefore, notAfter, certNotBefore, certNotAfter)
token, err = cautils.NewTokenFlow(ctx, typ, subject, sans, caURL, root, notBefore, notAfter, certNotBefore, certNotAfter, tokenOpts...)
if err != nil {
return err
}
Expand Down
18 changes: 16 additions & 2 deletions command/certificate/fingerprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func fingerprintCommand() cli.Command {
[**--bundle**] [**--roots**=<root-bundle>] [**--servername**=<servername>]
[**--format**=<format>] [**--sha1**] [**--insecure**]`,
Description: `**step certificate fingerprint** reads a certificate and prints to STDOUT the
certificate SHA256 of the raw certificate.
certificate SHA256 of the raw certificate or certificate signing request.

If <crt-file> contains multiple certificates (i.e., it is a certificate
"bundle") the fingerprint of the first certificate in the bundle will be
Expand Down Expand Up @@ -55,6 +55,12 @@ Get the fingerprints for a remote certificate with its intermediate:
$ step certificate fingerprint --bundle https://smallstep.com
e2c4f12edfc1816cc610755d32e6f45d5678ba21ecda1693bb5b246e3c48c03d
25847d668eb4f04fdd40b12b6b0740c567da7d024308eb6c2c96fe41d9de218d
'''

Get the fingerprint for a CSR using base64-url encoding without padding:
'''
$ step certificate fingerprint --format base64-url-raw hello.csr
PJLNhtQoBE1yGN_ZKzr4Y2U5pyqIGiyyszkoz2raDOw
'''`,
Flags: []cli.Flag{
cli.StringFlag{
Expand Down Expand Up @@ -128,7 +134,15 @@ func fingerprintAction(ctx *cli.Context) error {
default:
certs, err = pemutil.ReadCertificateBundle(crtFile)
if err != nil {
return err
// Fallback to parse a CSR
csr, csrErr := pemutil.ReadCertificateRequest(crtFile)
if csrErr != nil {
return err
}
// We will only need the raw DER bytes to generate a fingerprint.
certs = []*x509.Certificate{
{Raw: csr.Raw},
}
}
}

Expand Down
70 changes: 37 additions & 33 deletions command/ssh/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,43 @@ func certificateAction(ctx *cli.Context) error {
}
}

flow, err := cautils.NewCertificateFlow(ctx)
var (
sshPub ssh.PublicKey
pub, priv interface{}
flowOptions []cautils.Option
)

if isSign {
// Use public key supplied as input.
in, err := utils.ReadFile(keyFile)
if err != nil {
return err
}

sshPub, _, _, _, err = ssh.ParseAuthorizedKey(in)
if err != nil {
return errors.Wrap(err, "error parsing ssh public key")
}
if sshPrivKeyFile != "" {
if priv, err = pemutil.Read(sshPrivKeyFile); err != nil {
return errors.Wrap(err, "error parsing private key")
}
}
flowOptions = append(flowOptions, cautils.WithSSHPublicKey(sshPub))
} else {
// Generate keypair
pub, priv, err = keyutil.GenerateKeyPair(kty, curve, size)
if err != nil {
return err
}

sshPub, err = ssh.NewPublicKey(pub)
if err != nil {
return errors.Wrap(err, "error creating public key")
}
}

flow, err := cautils.NewCertificateFlow(ctx, flowOptions...)
if err != nil {
return err
}
Expand Down Expand Up @@ -382,38 +418,6 @@ func certificateAction(ctx *cli.Context) error {
identityKey = key
}

var sshPub ssh.PublicKey
var pub, priv interface{}

if isSign {
// Use public key supplied as input.
in, err := utils.ReadFile(keyFile)
if err != nil {
return err
}

sshPub, _, _, _, err = ssh.ParseAuthorizedKey(in)
if err != nil {
return errors.Wrap(err, "error parsing ssh public key")
}
if sshPrivKeyFile != "" {
if priv, err = pemutil.Read(sshPrivKeyFile); err != nil {
return errors.Wrap(err, "error parsing private key")
}
}
} else {
// Generate keypair
pub, priv, err = keyutil.GenerateKeyPair(kty, curve, size)
if err != nil {
return err
}

sshPub, err = ssh.NewPublicKey(pub)
if err != nil {
return errors.Wrap(err, "error creating public key")
}
}

var sshAuPub ssh.PublicKey
var sshAuPubBytes []byte
var auPub, auPriv interface{}
Expand Down
13 changes: 13 additions & 0 deletions flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,19 @@ be stored in the 'sshpop' header.`,
be stored in the 'nebula' header.`,
}

// Confirmation is a cli.Flag used to add a confirmation claim in the token.
Confirmation = cli.StringFlag{
Name: "cnf",
Usage: `The <fingerprint> of the CSR to restrict this token for.`,
}

// ConfirmationFile is a cli.Flag used to add a confirmation claim in the
// tokens. It will add a confirmation kid with the fingerprint of the CSR.
ConfirmationFile = cli.StringFlag{
Name: "cnf-file",
Usage: `The CSR <file> to restrict this token for.`,
}

Comment on lines +382 to +394
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these include something about the SSH public key, or want to keep it simple?

Copy link
Collaborator Author

@maraino maraino Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They used to include it, but due to a comment in the certificates PR that suggested removing the cnf for SSH, I've removed it from here. Although I kept the SSH public key in the context.

// Team is a cli.Flag used to pass the team ID.
Team = cli.StringFlag{
Name: "team",
Expand Down
36 changes: 36 additions & 0 deletions token/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package token

import (
"bytes"
"crypto"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/ed25519"
Expand All @@ -15,6 +16,7 @@ import (

"github.com/pkg/errors"
nebula "github.com/slackhq/nebula/cert"
"go.step.sm/crypto/fingerprint"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x25519"
Expand Down Expand Up @@ -84,6 +86,40 @@ func WithSSH(v interface{}) Options {
})
}

// WithConfirmationFingerprint returns an Options function that sets the cnf
// claim with the given CSR fingerprint.
func WithConfirmationFingerprint(fp string) Options {
return func(c *Claims) error {
c.Set(ConfirmationClaim, map[string]string{
"x5rt#S256": fp,
})
return nil
}
}

// WithFingerprint returns an Options function that the cnf claims with
// "x5rt#S256" representing the fingerprint of the CSR
func WithFingerprint(v any) Options {
return func(c *Claims) error {
var data []byte
switch vv := v.(type) {
case *x509.CertificateRequest:
data = vv.Raw
default:
return fmt.Errorf("unsupported fingerprint for %T", v)
}

kid, err := fingerprint.New(data, crypto.SHA256, fingerprint.Base64RawURLFingerprint)
if err != nil {
return err
}
c.Set(ConfirmationClaim, map[string]string{
"x5rt#S256": kid,
maraino marked this conversation as resolved.
Show resolved Hide resolved
})
return nil
}
}

// WithValidity validates boundary inputs and sets the 'nbf' (NotBefore) and
// 'exp' (expiration) options.
func WithValidity(notBefore, expiration time.Time) Options {
Expand Down
23 changes: 23 additions & 0 deletions token/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x25519"
"golang.org/x/crypto/ssh"
)

func TestOptions(t *testing.T) {
Expand All @@ -35,6 +37,11 @@
p256ECDHSigner, err := p256Signer.ECDH()
require.NoError(t, err)

testCSR, err := pemutil.ReadCertificateRequest("testdata/test.csr")
require.NoError(t, err)

testSSH := mustReadSSHPublicKey(t, "testdata/ssh-key.pub")

wrongNebulaContentsFilename := "testdata/ca.crt"

emptyFile, err := os.CreateTemp(tempDir, "empty-file")
Expand Down Expand Up @@ -79,6 +86,10 @@
{"WithNebulaCurve25519Cert empty file fail", WithNebulaCert(emptyFile.Name(), nil), empty, true},
{"WithNebulaCurve25519Cert invalid content fail", WithNebulaCert(c25519CertFilename, nil), empty, true},
{"WithNebulaCurve25519Cert mismatching key fail", WithNebulaCert(c25519CertFilename, p256Signer), empty, true},
{"WithConfirmationFingerprint ok", WithConfirmationFingerprint("my-kid"), &Claims{ExtraClaims: map[string]any{"cnf": map[string]string{"kid": "my-kid"}}}, false},
{"WithFingerprint csr ok", WithFingerprint(testCSR), &Claims{ExtraClaims: map[string]any{"cnf": map[string]string{"kid": "ak6j6CwuZbd_mOQ-pNOUwhpmtSN0mY0xrLvaQL4J5l8"}}}, false},
{"WithFingerprint ssh ok", WithFingerprint(testSSH), &Claims{ExtraClaims: map[string]any{"cnf": map[string]string{"kid": "hpTQOoB7fIRxTp-FhXCIm94mGBv7_dzr_5SxLn1Pnwk"}}}, false},
{"WithFingerprint fail", WithFingerprint("unexpected type"), empty, true},
}

for _, tt := range tests {
Expand All @@ -90,12 +101,24 @@
return
}

assert.NoError(t, err)

Check failure on line 104 in token/options_test.go

View workflow job for this annotation

GitHub Actions / ci / test / test (stable)

options_test.go:104: Error Trace: /home/runner/work/cli/cli/token/options_test.go:104 Error: Received unexpected error: unsupported fingerprint for ssh.ed25519PublicKey Test: TestOptions/WithFingerprint_ssh_ok

Check failure on line 104 in token/options_test.go

View workflow job for this annotation

GitHub Actions / ci / test / test (oldstable)

options_test.go:104: Error Trace: /home/runner/work/cli/cli/token/options_test.go:104 Error: Received unexpected error: unsupported fingerprint for ssh.ed25519PublicKey Test: TestOptions/WithFingerprint_ssh_ok
assert.Equal(t, tt.want, claim)

Check failure on line 105 in token/options_test.go

View workflow job for this annotation

GitHub Actions / ci / test / test (stable)

options_test.go:105: Error Trace: /home/runner/work/cli/cli/token/options_test.go:105 Error: Not equal: expected: &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}{"cnf":map[string]string{"kid":"my-kid"}}, ExtraHeaders:map[string]interface {}(nil)} actual : &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}{"cnf":map[string]string{"x5rt#S256":"my-kid"}}, ExtraHeaders:map[string]interface {}(nil)} Diff: --- Expected +++ Actual @@ -12,3 +12,3 @@ (string) (len=3) "cnf": (map[string]string) (len=1) { - (string) (len=3) "kid": (string) (len=6) "my-kid" + (string) (len=9) "x5rt#S256": (string) (len=6) "my-kid" } Test: TestOptions/WithConfirmationFingerprint_ok ---

Check failure on line 105 in token/options_test.go

View workflow job for this annotation

GitHub Actions / ci / test / test (stable)

options_test.go:105: Error Trace: /home/runner/work/cli/cli/token/options_test.go:105 Error: Not equal: expected: &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}{"cnf":map[string]string{"kid":"ak6j6CwuZbd_mOQ-pNOUwhpmtSN0mY0xrLvaQL4J5l8"}}, ExtraHeaders:map[string]interface {}(nil)} actual : &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}{"cnf":map[string]string{"x5rt#S256":"ak6j6CwuZbd_mOQ-pNOUwhpmtSN0mY0xrLvaQL4J5l8"}}, ExtraHeaders:map[string]interface {}(nil)} Diff: --- Expected +++ Actual @@ -12,3 +12,3 @@ (string) (len=3) "cnf": (map[string]string) (len=1) { - (string) (len=3) "kid": (string) (len=43) "ak6j6CwuZbd_mOQ-pNOUwhpmtSN0mY0xrLvaQL4J5l8" + (string) (len=9) "x5rt#S256": (string) (len=43) "ak6j6CwuZbd_mOQ-pNOUwhpmtSN0mY0xrLvaQL4J5l8" } Test: TestOptions/WithFingerprint_csr_ok ---

Check failure on line 105 in token/options_test.go

View workflow job for this annotation

GitHub Actions / ci / test / test (stable)

options_test.go:105: Error Trace: /home/runner/work/cli/cli/token/options_test.go:105 Error: Not equal: expected: &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}{"cnf":map[string]string{"kid":"hpTQOoB7fIRxTp-FhXCIm94mGBv7_dzr_5SxLn1Pnwk"}}, ExtraHeaders:map[string]interface {}(nil)} actual : &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}(nil), ExtraHeaders:map[string]interface {}(nil)} Diff: --- Expected +++ Actual @@ -10,7 +10,3 @@ }, - ExtraClaims: (map[string]interface {}) (len=1) { - (string) (len=3) "cnf": (map[string]string) (len=1) { - (string) (len=3) "kid": (string) (len=43) "hpTQOoB7fIRxTp-FhXCIm94mGBv7_dzr_5SxLn1Pnwk" - } - }, + ExtraClaims: (map[string]interface {}) <nil>, ExtraHeaders: (map[string]interface {}) <nil> Test: TestOptions/WithFingerprint_ssh_ok ---

Check failure on line 105 in token/options_test.go

View workflow job for this annotation

GitHub Actions / ci / test / test (oldstable)

options_test.go:105: Error Trace: /home/runner/work/cli/cli/token/options_test.go:105 Error: Not equal: expected: &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}{"cnf":map[string]string{"kid":"my-kid"}}, ExtraHeaders:map[string]interface {}(nil)} actual : &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}{"cnf":map[string]string{"x5rt#S256":"my-kid"}}, ExtraHeaders:map[string]interface {}(nil)} Diff: --- Expected +++ Actual @@ -12,3 +12,3 @@ (string) (len=3) "cnf": (map[string]string) (len=1) { - (string) (len=3) "kid": (string) (len=6) "my-kid" + (string) (len=9) "x5rt#S256": (string) (len=6) "my-kid" } Test: TestOptions/WithConfirmationFingerprint_ok ---

Check failure on line 105 in token/options_test.go

View workflow job for this annotation

GitHub Actions / ci / test / test (oldstable)

options_test.go:105: Error Trace: /home/runner/work/cli/cli/token/options_test.go:105 Error: Not equal: expected: &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}{"cnf":map[string]string{"kid":"ak6j6CwuZbd_mOQ-pNOUwhpmtSN0mY0xrLvaQL4J5l8"}}, ExtraHeaders:map[string]interface {}(nil)} actual : &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}{"cnf":map[string]string{"x5rt#S256":"ak6j6CwuZbd_mOQ-pNOUwhpmtSN0mY0xrLvaQL4J5l8"}}, ExtraHeaders:map[string]interface {}(nil)} Diff: --- Expected +++ Actual @@ -12,3 +12,3 @@ (string) (len=3) "cnf": (map[string]string) (len=1) { - (string) (len=3) "kid": (string) (len=43) "ak6j6CwuZbd_mOQ-pNOUwhpmtSN0mY0xrLvaQL4J5l8" + (string) (len=9) "x5rt#S256": (string) (len=43) "ak6j6CwuZbd_mOQ-pNOUwhpmtSN0mY0xrLvaQL4J5l8" } Test: TestOptions/WithFingerprint_csr_ok ---

Check failure on line 105 in token/options_test.go

View workflow job for this annotation

GitHub Actions / ci / test / test (oldstable)

options_test.go:105: Error Trace: /home/runner/work/cli/cli/token/options_test.go:105 Error: Not equal: expected: &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}{"cnf":map[string]string{"kid":"hpTQOoB7fIRxTp-FhXCIm94mGBv7_dzr_5SxLn1Pnwk"}}, ExtraHeaders:map[string]interface {}(nil)} actual : &token.Claims{Claims:jwt.Claims{Issuer:"", Subject:"", Audience:jwt.Audience(nil), Expiry:(*jwt.NumericDate)(nil), NotBefore:(*jwt.NumericDate)(nil), IssuedAt:(*jwt.NumericDate)(nil), ID:""}, ExtraClaims:map[string]interface {}(nil), ExtraHeaders:map[string]interface {}(nil)} Diff: --- Expected +++ Actual @@ -10,7 +10,3 @@ }, - ExtraClaims: (map[string]interface {}) (len=1) { - (string) (len=3) "cnf": (map[string]string) (len=1) { - (string) (len=3) "kid": (string) (len=43) "hpTQOoB7fIRxTp-FhXCIm94mGBv7_dzr_5SxLn1Pnwk" - } - }, + ExtraClaims: (map[string]interface {}) <nil>, ExtraHeaders: (map[string]interface {}) <nil> Test: TestOptions/WithFingerprint_ssh_ok ---
})
}
}

func mustReadSSHPublicKey(t *testing.T, filename string) ssh.PublicKey {
t.Helper()

b, err := os.ReadFile(filename)
require.NoError(t, err)

pub, _, _, _, err := ssh.ParseAuthorizedKey(b)
require.NoError(t, err)

return pub
}

func serializeAndWriteNebulaCert(t *testing.T, tempDir string, cert *nebula.NebulaCertificate) (string, []byte) {
file, err := os.CreateTemp(tempDir, "nebula-test-cert-*")
require.NoError(t, err)
Expand Down
1 change: 1 addition & 0 deletions token/testdata/ssh-key.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF14RP3HJkO1yoZHjo9t/4bJgyJGiSPxhm6FApa3VtG1 [email protected]
8 changes: 8 additions & 0 deletions token/testdata/test.csr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBBDCBqwIBADAbMRkwFwYDVQQDDBB0ZXN0QGV4YW1wbGUuY29tMFkwEwYHKoZI
zj0CAQYIKoZIzj0DAQcDQgAEPj0tlICeGPiz361yM+AGlZmDK+N/cT0SVloozOQH
1ljdNbookliEX8eRnFnelZRaql1KhrVOXhfwBmd/eGhti6AuMCwGCSqGSIb3DQEJ
DjEfMB0wGwYDVR0RBBQwEoEQdGVzdEBleGFtcGxlLmNvbTAKBggqhkjOPQQDAgNI
ADBFAiEA4WuukEVIFJQHNqlZVsWtsWsSVLNRCxBBJfH7/+txNw4CIGyK3eo5MDvR
DepPHVRF16/b+iW/4HgAgIC90+5Q4IrL
-----END CERTIFICATE REQUEST-----
Loading
Loading