package apptoken import ( "context" "time" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" ) func pathConfigKeys(b *appTokenBackend) *framework.Path { return &framework.Path{ Pattern: "config/keys$", DisplayAttrs: &framework.DisplayAttributes{ OperationPrefix: "apptoken", OperationSuffix: "keys", }, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{Callback: b.pathConfigKeysRead}, }, HelpSynopsis: "Inspect the signing keyset.", HelpDescription: "Read the current signing key id plus every key id retained in the JWKS. Signing keys are generated lazily on first use.", } } func pathConfigKeysRotate(b *appTokenBackend) *framework.Path { return &framework.Path{ Pattern: "config/keys/rotate$", DisplayAttrs: &framework.DisplayAttributes{ OperationPrefix: "apptoken", OperationSuffix: "keys-rotate", }, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{Callback: b.pathConfigKeysRotate}, }, HelpSynopsis: "Rotate the signing key.", HelpDescription: "Generate a new current signing key. Up to retained_keys previous keys stay published in the JWKS so tokens signed before the rotation keep validating.", } } func (b *appTokenBackend) pathConfigKeysRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) { cfg, err := b.getConfigOrDefault(ctx, req.Storage) if err != nil { return nil, err } b.keyLock.Lock() defer b.keyLock.Unlock() ks, err := b.getOrCreateKeyset(ctx, req.Storage, cfg) if err != nil { return nil, err } keys := make([]map[string]interface{}, 0, len(ks.Keys)) for i := range ks.Keys { keys = append(keys, map[string]interface{}{ "key_id": ks.Keys[i].KeyID, "algorithm": ks.Keys[i].Algorithm, "created": ks.Keys[i].Created.Format(time.RFC3339), }) } return &logical.Response{Data: map[string]interface{}{ "current_key_id": ks.current().KeyID, "keys": keys, }}, nil } func (b *appTokenBackend) pathConfigKeysRotate(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) { cfg, err := b.getConfigOrDefault(ctx, req.Storage) if err != nil { return nil, err } b.keyLock.Lock() defer b.keyLock.Unlock() ks, err := b.getOrCreateKeyset(ctx, req.Storage, cfg) if err != nil { return nil, err } if err := ks.rotate(cfg.Algorithm, cfg.RetainedKeys, time.Now()); err != nil { return logical.ErrorResponse(err.Error()), nil } if err := b.putKeyset(ctx, req.Storage, ks); err != nil { return nil, err } return &logical.Response{Data: map[string]interface{}{ "current_key_id": ks.current().KeyID, }}, nil }