Skip to content

Commit 2da3a3f

Browse files
committed
command/ca/token: support custom "user" claim
Add the `--set` and `--set-file` flags to the `step ca token` command, allowing the user to set keys in the "user" claim in the resulting JWT. Signed-off-by: Dan Fuhry <[email protected]>
1 parent f5a0bca commit 2da3a3f

File tree

5 files changed

+73
-1
lines changed

5 files changed

+73
-1
lines changed

command/ca/token.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ func tokenCommand() cli.Command {
3434
[**--sshpop-cert**=<file>] [**--sshpop-key**=<file>]
3535
[**--cnf**=<fingerprint>] [**--cnf-file**=<file>]
3636
[**--ssh**] [**--host**] [**--principal**=<name>] [**--k8ssa-token-path**=<file>]
37-
[**--ca-url**=<uri>] [**--root**=<file>] [**--context**=<name>]`,
37+
[**--ca-url**=<uri>] [**--root**=<file>] [**--context**=<name>]
38+
[**--set**=<key=value>] [**--set-file**=<file>]`,
3839
Description: `**step ca token** command generates a one-time token granting access to the
3940
certificates authority.
4041
@@ -174,6 +175,12 @@ add the intermediate and the root in the provisioner configuration:
174175
$ step ca token --kms yubikey:pin-value=123456 \
175176
--x5c-cert yubikey:slot-id=82 --x5c-key yubikey:slot-id=82 \
176177
internal.example.com
178+
'''
179+
180+
Generate a token with custom data in the "user" claim: (accessible in templates as
181+
**.Token.user.field**)
182+
'''
183+
$ step ca token --set field=value internal.example.com
177184
'''`,
178185
Flags: []cli.Flag{
179186
provisionerKidFlag,
@@ -244,6 +251,8 @@ be invalid for any other API request.`,
244251
flags.CaURL,
245252
flags.Root,
246253
flags.Context,
254+
flags.TemplateSet,
255+
flags.TemplateSetFile,
247256
},
248257
}
249258
}
@@ -350,11 +359,29 @@ func tokenAction(ctx *cli.Context) error {
350359
tokenOpts = append(tokenOpts, cautils.WithConfirmationFingerprint(cnf))
351360
}
352361

362+
templateData, err := flags.GetTemplateData(ctx)
363+
if err != nil {
364+
return err
365+
}
366+
if templateData != nil {
367+
tokenOpts = append(tokenOpts, cautils.WithCustomAttributes(templateData))
368+
}
369+
353370
// --san and --type revoke are incompatible. Revocation tokens do not support SANs.
354371
if typ == cautils.RevokeType && len(sans) > 0 {
355372
return errs.IncompatibleFlagWithFlag(ctx, "san", "revoke")
356373
}
357374

375+
// --offline doesn't support tokenOpts, so reject set/set-file
376+
if offline {
377+
if len(ctx.StringSlice("set")) > 0 {
378+
return errs.IncompatibleFlagWithFlag(ctx, "offline", "set")
379+
}
380+
if ctx.String("set-file") != "" {
381+
return errs.IncompatibleFlagWithFlag(ctx, "offline", "set-file")
382+
}
383+
}
384+
358385
// parse times or durations
359386
notBefore, ok := flags.ParseTimeOrDuration(ctx.String("not-before"))
360387
if !ok {

token/options.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,25 @@ func WithStep(v interface{}) Options {
8080
}
8181
}
8282

83+
// WithUserData returns an Option function that merges the provided map with the
84+
// existing user claim in the payload.
85+
func WithUserData(v map[string]interface{}) Options {
86+
return func(c *Claims) error {
87+
if _, ok := c.ExtraClaims[UserClaim]; !ok {
88+
c.Set(UserClaim, make(map[string]interface{}))
89+
}
90+
s := c.ExtraClaims[UserClaim]
91+
sm, ok := s.(map[string]interface{})
92+
if !ok {
93+
return fmt.Errorf("%q claim is %T, not map[string]interface{}", UserClaim, s)
94+
}
95+
for k, val := range v {
96+
sm[k] = val
97+
}
98+
return nil
99+
}
100+
}
101+
83102
// WithSSH returns an Options function that sets the step claim with the ssh
84103
// property in the value.
85104
func WithSSH(v interface{}) Options {

token/token.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ const SANSClaim = "sans"
3232
// StepClaim is the property name for a JWT claim the stores the custom information in the certificate.
3333
const StepClaim = "step"
3434

35+
// UserClaim is the property name for a JWT claim that stores user-provided custom information.
36+
const UserClaim = "user"
37+
3538
// ConfirmationClaim is the property name for a JWT claim that stores a JSON
3639
// object used as Proof-Of-Possession.
3740
const ConfirmationClaim = "cnf"

utils/cautils/certificate_flow.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type flowContext struct {
4343
SSHPublicKey ssh.PublicKey
4444
CertificateRequest *x509.CertificateRequest
4545
ConfirmationFingerprint string
46+
CustomAttributes map[string]interface{}
4647
}
4748

4849
// sharedContext is used to share information between commands.
@@ -88,6 +89,18 @@ func WithConfirmationFingerprint(fp string) Option {
8889
})
8990
}
9091

92+
// WithCustomAttributes adds custom attributes to be set in the "user" claim.
93+
func WithCustomAttributes(v map[string]interface{}) Option {
94+
return newFuncFlowOption(func(fo *flowContext) {
95+
if fo.CustomAttributes == nil {
96+
fo.CustomAttributes = make(map[string]interface{})
97+
}
98+
for k, val := range v {
99+
fo.CustomAttributes[k] = val
100+
}
101+
})
102+
}
103+
91104
// NewCertificateFlow initializes a cli flow to get a new certificate.
92105
func NewCertificateFlow(ctx *cli.Context, opts ...Option) (*CertificateFlow, error) {
93106
var err error

utils/cautils/token_generator.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ func (t *TokenGenerator) SignToken(sub string, sans []string, opts ...token.Opti
108108
opts = append(opts, token.WithConfirmationFingerprint(sharedContext.ConfirmationFingerprint))
109109
}
110110

111+
// Add custom user data, if set.
112+
if sharedContext.CustomAttributes != nil {
113+
opts = append(opts, token.WithUserData(sharedContext.CustomAttributes))
114+
}
115+
111116
return t.Token(sub, opts...)
112117
}
113118

@@ -126,6 +131,11 @@ func (t *TokenGenerator) SignSSHToken(sub, certType string, principals []string,
126131
ValidBefore: notAfter,
127132
})}, opts...)
128133

134+
// Add custom user data, if set.
135+
if sharedContext.CustomAttributes != nil {
136+
opts = append(opts, token.WithUserData(sharedContext.CustomAttributes))
137+
}
138+
129139
return t.Token(sub, opts...)
130140
}
131141

0 commit comments

Comments
 (0)