4
4
"fmt"
5
5
"os"
6
6
7
+ "github.com/pkg/errors"
7
8
"github.com/smallstep/certificates/api"
8
9
"github.com/smallstep/certificates/pki"
9
10
"github.com/smallstep/cli/flags"
@@ -12,6 +13,8 @@ import (
12
13
"github.com/urfave/cli"
13
14
"go.step.sm/cli-utils/command"
14
15
"go.step.sm/cli-utils/errs"
16
+ "go.step.sm/crypto/pemutil"
17
+ "golang.org/x/crypto/ssh"
15
18
)
16
19
17
20
func tokenCommand () cli.Command {
@@ -27,6 +30,7 @@ func tokenCommand() cli.Command {
27
30
[**--output-file**=<file>] [**--kms**=uri] [**--key**=<file>] [**--san**=<SAN>] [**--offline**]
28
31
[**--revoke**] [**--x5c-cert**=<file>] [**--x5c-key**=<file>] [**--x5c-insecure**]
29
32
[**--sshpop-cert**=<file>] [**--sshpop-key**=<file>]
33
+ [**--cnf-file**=<file>] [**--cnf-kid**=<fingerprint>]
30
34
[**--ssh**] [**--host**] [**--principal**=<name>] [**--k8ssa-token-path**=<file>]
31
35
[**--ca-url**=<uri>] [**--root**=<file>] [**--context**=<name>]` ,
32
36
Description : `**step ca token** command generates a one-time token granting access to the
@@ -82,6 +86,11 @@ Get a new token that becomes valid in 30 minutes and expires 5 minutes after tha
82
86
$ step ca token --not-before 30m --not-after 35m internal.example.com
83
87
'''
84
88
89
+ Get a new token with a confirmation claim to enforce the use of a given CSR:
90
+ '''
91
+ step ca token --cnf-file internal.csr internal.smallstep.com
92
+ '''
93
+
85
94
Get a new token signed with the given private key, the public key must be
86
95
configured in the certificate authority:
87
96
'''
@@ -133,6 +142,11 @@ Get a new token for an SSH host certificate:
133
142
$ step ca token my-remote.hostname --ssh --host
134
143
'''
135
144
145
+ Get a new token with a confirmation claim to enforce the use of a given public key:
146
+ '''
147
+ step ca token --ssh --host --cnf-file internal.pub internal.smallstep.com
148
+ '''
149
+
136
150
Generate a renew token and use it in a renew after expiry request:
137
151
'''
138
152
$ TOKEN=$(step ca token --x5c-cert internal.crt --x5c-key internal.key --renew internal.example.com)
@@ -186,6 +200,8 @@ multiple principals.`,
186
200
flags .SSHPOPKey ,
187
201
flags .NebulaCert ,
188
202
flags .NebulaKey ,
203
+ flags .ConfirmationFile ,
204
+ flags .ConfirmationKid ,
189
205
cli.StringFlag {
190
206
Name : "key" ,
191
207
Usage : `The private key <file> used to sign the JWT. This is usually downloaded from
@@ -240,6 +256,9 @@ func tokenAction(ctx *cli.Context) error {
240
256
isSSH := ctx .Bool ("ssh" )
241
257
isHost := ctx .Bool ("host" )
242
258
principals := ctx .StringSlice ("principal" )
259
+ // confirmation claims
260
+ cnfFile := ctx .String ("cnf-file" )
261
+ cnfKid := ctx .String ("cnf-kid" )
243
262
244
263
switch {
245
264
case isSSH && len (sans ) > 0 :
@@ -252,6 +271,8 @@ func tokenAction(ctx *cli.Context) error {
252
271
return errs .RequiredWithFlag (ctx , "host" , "ssh" )
253
272
case ! isSSH && len (principals ) > 0 :
254
273
return errs .RequiredWithFlag (ctx , "principal" , "ssh" )
274
+ case cnfFile != "" && cnfKid != "" :
275
+ return errs .IncompatibleFlagWithFlag (ctx , "cnf-file" , "cnf-kid" )
255
276
}
256
277
257
278
// Default token type is always a 'Sign' token.
@@ -295,6 +316,31 @@ func tokenAction(ctx *cli.Context) error {
295
316
}
296
317
}
297
318
319
+ // Add options to create a confirmation claim if a CSR or SSH public key is
320
+ // passed.
321
+ var tokenOpts []cautils.Option
322
+ if cnfFile != "" {
323
+ in , err := utils .ReadFile (cnfFile )
324
+ if err != nil {
325
+ return err
326
+ }
327
+ if isSSH {
328
+ sshPub , _ , _ , _ , err := ssh .ParseAuthorizedKey (in )
329
+ if err != nil {
330
+ return errors .Wrap (err , "error parsing ssh public key" )
331
+ }
332
+ tokenOpts = append (tokenOpts , cautils .WithSSHPublicKey (sshPub ))
333
+ } else {
334
+ csr , err := pemutil .ParseCertificateRequest (in )
335
+ if err != nil {
336
+ return errors .Wrap (err , "error parsing certificate request" )
337
+ }
338
+ tokenOpts = append (tokenOpts , cautils .WithCertificateRequest (csr ))
339
+ }
340
+ } else if cnfKid != "" {
341
+ tokenOpts = append (tokenOpts , cautils .WithConfirmationKid (cnfKid ))
342
+ }
343
+
298
344
// --san and --type revoke are incompatible. Revocation tokens do not support SANs.
299
345
if typ == cautils .RevokeType && len (sans ) > 0 {
300
346
return errs .IncompatibleFlagWithFlag (ctx , "san" , "revoke" )
@@ -327,7 +373,7 @@ func tokenAction(ctx *cli.Context) error {
327
373
return err
328
374
}
329
375
} else {
330
- token , err = cautils .NewTokenFlow (ctx , typ , subject , sans , caURL , root , notBefore , notAfter , certNotBefore , certNotAfter )
376
+ token , err = cautils .NewTokenFlow (ctx , typ , subject , sans , caURL , root , notBefore , notAfter , certNotBefore , certNotAfter , tokenOpts ... )
331
377
if err != nil {
332
378
return err
333
379
}
0 commit comments