mirror of
https://github.com/smallstep/cli.git
synced 2025-04-19 10:42:15 +03:00
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 <dan@fuhry.com>
This commit is contained in:
parent
f5a0bca360
commit
8abadfcd59
@ -34,7 +34,8 @@ func tokenCommand() cli.Command {
|
||||
[**--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>]`,
|
||||
[**--ca-url**=<uri>] [**--root**=<file>] [**--context**=<name>]
|
||||
[**--set**=<key=value>] [**--set-file**=<file>]`,
|
||||
Description: `**step ca token** command generates a one-time token granting access to the
|
||||
certificates authority.
|
||||
|
||||
@ -174,6 +175,18 @@ add the intermediate and the root in the provisioner configuration:
|
||||
$ step ca token --kms yubikey:pin-value=123456 \
|
||||
--x5c-cert yubikey:slot-id=82 --x5c-key yubikey:slot-id=82 \
|
||||
internal.example.com
|
||||
'''
|
||||
|
||||
Generate a token with custom data in the "user" claim. The example below can be
|
||||
accessed in a template as **{{ .Token.user.field }}**, rendering to the string
|
||||
"value".
|
||||
|
||||
This is distinct from **.Insecure.User**: any attributes set using this option
|
||||
are added to a claim named "user" in the signed JWT produced by this command.
|
||||
This data may therefore be considered trusted (insofar as the token itself is
|
||||
trusted).
|
||||
'''
|
||||
$ step ca token --set field=value internal.example.com
|
||||
'''`,
|
||||
Flags: []cli.Flag{
|
||||
provisionerKidFlag,
|
||||
@ -244,6 +257,8 @@ be invalid for any other API request.`,
|
||||
flags.CaURL,
|
||||
flags.Root,
|
||||
flags.Context,
|
||||
flags.TemplateSet,
|
||||
flags.TemplateSetFile,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -350,11 +365,29 @@ func tokenAction(ctx *cli.Context) error {
|
||||
tokenOpts = append(tokenOpts, cautils.WithConfirmationFingerprint(cnf))
|
||||
}
|
||||
|
||||
templateData, err := flags.GetTemplateData(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if templateData != nil {
|
||||
tokenOpts = append(tokenOpts, cautils.WithCustomAttributes(templateData))
|
||||
}
|
||||
|
||||
// --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")
|
||||
}
|
||||
|
||||
// --offline doesn't support tokenOpts, so reject set/set-file
|
||||
if offline {
|
||||
if len(ctx.StringSlice("set")) > 0 {
|
||||
return errs.IncompatibleFlagWithFlag(ctx, "offline", "set")
|
||||
}
|
||||
if ctx.String("set-file") != "" {
|
||||
return errs.IncompatibleFlagWithFlag(ctx, "offline", "set-file")
|
||||
}
|
||||
}
|
||||
|
||||
// parse times or durations
|
||||
notBefore, ok := flags.ParseTimeOrDuration(ctx.String("not-before"))
|
||||
if !ok {
|
||||
|
@ -80,6 +80,25 @@ func WithStep(v interface{}) Options {
|
||||
}
|
||||
}
|
||||
|
||||
// WithUserData returns an Option function that merges the provided map with the
|
||||
// existing user claim in the payload.
|
||||
func WithUserData(v map[string]interface{}) Options {
|
||||
return func(c *Claims) error {
|
||||
if _, ok := c.ExtraClaims[UserClaim]; !ok {
|
||||
c.Set(UserClaim, make(map[string]interface{}))
|
||||
}
|
||||
s := c.ExtraClaims[UserClaim]
|
||||
sm, ok := s.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("%q claim is %T, not map[string]interface{}", UserClaim, s)
|
||||
}
|
||||
for k, val := range v {
|
||||
sm[k] = val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSSH returns an Options function that sets the step claim with the ssh
|
||||
// property in the value.
|
||||
func WithSSH(v interface{}) Options {
|
||||
|
@ -32,6 +32,9 @@ const SANSClaim = "sans"
|
||||
// StepClaim is the property name for a JWT claim the stores the custom information in the certificate.
|
||||
const StepClaim = "step"
|
||||
|
||||
// UserClaim is the property name for a JWT claim that stores user-provided custom information.
|
||||
const UserClaim = "user"
|
||||
|
||||
// ConfirmationClaim is the property name for a JWT claim that stores a JSON
|
||||
// object used as Proof-Of-Possession.
|
||||
const ConfirmationClaim = "cnf"
|
||||
|
@ -43,6 +43,7 @@ type flowContext struct {
|
||||
SSHPublicKey ssh.PublicKey
|
||||
CertificateRequest *x509.CertificateRequest
|
||||
ConfirmationFingerprint string
|
||||
CustomAttributes map[string]interface{}
|
||||
}
|
||||
|
||||
// sharedContext is used to share information between commands.
|
||||
@ -88,6 +89,18 @@ func WithConfirmationFingerprint(fp string) Option {
|
||||
})
|
||||
}
|
||||
|
||||
// WithCustomAttributes adds custom attributes to be set in the "user" claim.
|
||||
func WithCustomAttributes(v map[string]interface{}) Option {
|
||||
return newFuncFlowOption(func(fo *flowContext) {
|
||||
if fo.CustomAttributes == nil {
|
||||
fo.CustomAttributes = make(map[string]interface{})
|
||||
}
|
||||
for k, val := range v {
|
||||
fo.CustomAttributes[k] = val
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// NewCertificateFlow initializes a cli flow to get a new certificate.
|
||||
func NewCertificateFlow(ctx *cli.Context, opts ...Option) (*CertificateFlow, error) {
|
||||
var err error
|
||||
|
@ -108,6 +108,11 @@ func (t *TokenGenerator) SignToken(sub string, sans []string, opts ...token.Opti
|
||||
opts = append(opts, token.WithConfirmationFingerprint(sharedContext.ConfirmationFingerprint))
|
||||
}
|
||||
|
||||
// Add custom user data, if set.
|
||||
if sharedContext.CustomAttributes != nil {
|
||||
opts = append(opts, token.WithUserData(sharedContext.CustomAttributes))
|
||||
}
|
||||
|
||||
return t.Token(sub, opts...)
|
||||
}
|
||||
|
||||
@ -126,6 +131,11 @@ func (t *TokenGenerator) SignSSHToken(sub, certType string, principals []string,
|
||||
ValidBefore: notAfter,
|
||||
})}, opts...)
|
||||
|
||||
// Add custom user data, if set.
|
||||
if sharedContext.CustomAttributes != nil {
|
||||
opts = append(opts, token.WithUserData(sharedContext.CustomAttributes))
|
||||
}
|
||||
|
||||
return t.Token(sub, opts...)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user