Skip to content

Commit b1cff60

Browse files
authored
Merge pull request #1375 from fuhry/cli-token-set-option
command/ca/token: support custom "user" claim
2 parents ac46960 + 8abadfc commit b1cff60

File tree

5 files changed

+79
-1
lines changed

5 files changed

+79
-1
lines changed

command/ca/token.go

+34-1
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,18 @@ 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. The example below can be
181+
accessed in a template as **{{ .Token.user.field }}**, rendering to the string
182+
"value".
183+
184+
This is distinct from **.Insecure.User**: any attributes set using this option
185+
are added to a claim named "user" in the signed JWT produced by this command.
186+
This data may therefore be considered trusted (insofar as the token itself is
187+
trusted).
188+
'''
189+
$ step ca token --set field=value internal.example.com
177190
'''`,
178191
Flags: []cli.Flag{
179192
provisionerKidFlag,
@@ -244,6 +257,8 @@ be invalid for any other API request.`,
244257
flags.CaURL,
245258
flags.Root,
246259
flags.Context,
260+
flags.TemplateSet,
261+
flags.TemplateSetFile,
247262
},
248263
}
249264
}
@@ -350,11 +365,29 @@ func tokenAction(ctx *cli.Context) error {
350365
tokenOpts = append(tokenOpts, cautils.WithConfirmationFingerprint(cnf))
351366
}
352367

368+
templateData, err := flags.GetTemplateData(ctx)
369+
if err != nil {
370+
return err
371+
}
372+
if templateData != nil {
373+
tokenOpts = append(tokenOpts, cautils.WithCustomAttributes(templateData))
374+
}
375+
353376
// --san and --type revoke are incompatible. Revocation tokens do not support SANs.
354377
if typ == cautils.RevokeType && len(sans) > 0 {
355378
return errs.IncompatibleFlagWithFlag(ctx, "san", "revoke")
356379
}
357380

381+
// --offline doesn't support tokenOpts, so reject set/set-file
382+
if offline {
383+
if len(ctx.StringSlice("set")) > 0 {
384+
return errs.IncompatibleFlagWithFlag(ctx, "offline", "set")
385+
}
386+
if ctx.String("set-file") != "" {
387+
return errs.IncompatibleFlagWithFlag(ctx, "offline", "set-file")
388+
}
389+
}
390+
358391
// parse times or durations
359392
notBefore, ok := flags.ParseTimeOrDuration(ctx.String("not-before"))
360393
if !ok {

token/options.go

+19
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

+3
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

+13
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

+10
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)