Skip to content

Commit aadebb8

Browse files
committed
Fix some provisioner prompt issues related to SCEP and policies
This PR fixes the following issues: - SCEP provisioners not detected in admin token flows - Invalid provisioner selection logic when managing provisioner policies - Unexpected error messages showing "issuer" instead or "provisioner" flag
1 parent ea5f95e commit aadebb8

File tree

7 files changed

+89
-29
lines changed

7 files changed

+89
-29
lines changed

command/ca/policy/acme/acme.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/smallstep/cli/command/ca/policy/actions"
99
"github.com/smallstep/cli/command/ca/policy/policycontext"
1010
"github.com/smallstep/cli/command/ca/policy/x509"
11+
"github.com/smallstep/cli/internal/provisionerflag"
1112
)
1213

1314
// Command returns the ACME account policy subcommand.
@@ -27,5 +28,9 @@ Please note that certificate issuance policies for ACME accounts are currently o
2728
actions.RemoveCommand(ctx),
2829
x509.Command(ctx),
2930
},
31+
Before: func(ctx *cli.Context) error {
32+
provisionerflag.Ignore()
33+
return nil
34+
},
3035
}
3136
}

command/ca/policy/provisioner/provisioner.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/smallstep/cli/command/ca/policy/policycontext"
1010
"github.com/smallstep/cli/command/ca/policy/ssh"
1111
"github.com/smallstep/cli/command/ca/policy/x509"
12+
"github.com/smallstep/cli/internal/provisionerflag"
1213
)
1314

1415
// Command returns the policy subcommand.
@@ -29,5 +30,9 @@ Please note that certificate issuance policies on the provisioner level are curr
2930
x509.Command(ctx),
3031
ssh.Command(ctx),
3132
},
33+
Before: func(ctx *cli.Context) error {
34+
provisionerflag.Ignore()
35+
return nil
36+
},
3237
}
3338
}

flags/flags.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -673,18 +673,24 @@ func parseCaURL(ctx *cli.Context, caURL string) (string, error) {
673673

674674
// FirstStringOf returns the value of the first defined flag from the input list.
675675
// If no defined flags, returns first flag with non-empty default value.
676-
func FirstStringOf(ctx *cli.Context, flags ...string) string {
676+
func FirstStringOf(ctx *cli.Context, flags ...string) (string, string) {
677677
// Return first defined flag.
678678
for _, f := range flags {
679679
if ctx.IsSet(f) {
680-
return ctx.String(f)
680+
return ctx.String(f), f
681681
}
682682
}
683683
// Return first non-empty, default, flag value.
684684
for _, f := range flags {
685685
if val := ctx.String(f); val != "" {
686-
return val
686+
return val, f
687687
}
688688
}
689-
return ""
689+
690+
var name = "<unknown>"
691+
if len(flags) > 0 {
692+
name = flags[0]
693+
}
694+
695+
return "", name
690696
}

flags/flags_test.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"testing"
1212

1313
"github.com/smallstep/assert"
14+
"github.com/stretchr/testify/require"
1415
"github.com/urfave/cli"
1516
"go.step.sm/crypto/fingerprint"
1617
)
@@ -242,6 +243,7 @@ func TestFirstStringOf(t *testing.T) {
242243
getContext func() *cli.Context
243244
inputs []string
244245
want string
246+
wantName string
245247
}{
246248
{
247249
name: "no-flags-empty",
@@ -250,8 +252,9 @@ func TestFirstStringOf(t *testing.T) {
250252
//_ = set.String("ca-url", "", "")
251253
return cli.NewContext(app, set, nil)
252254
},
253-
inputs: []string{"foo", "bar"},
254-
want: "",
255+
inputs: []string{"foo", "bar"},
256+
want: "",
257+
wantName: "foo",
255258
},
256259
{
257260
name: "return-first-set-flag",
@@ -265,8 +268,9 @@ func TestFirstStringOf(t *testing.T) {
265268
ctx.Set("baz", "test2")
266269
return ctx
267270
},
268-
inputs: []string{"foo", "bar", "baz"},
269-
want: "test1",
271+
inputs: []string{"foo", "bar", "baz"},
272+
want: "test1",
273+
wantName: "bar",
270274
},
271275
{
272276
name: "return-first-default-flag",
@@ -278,8 +282,9 @@ func TestFirstStringOf(t *testing.T) {
278282
ctx := cli.NewContext(app, set, nil)
279283
return ctx
280284
},
281-
inputs: []string{"foo", "bar", "baz"},
282-
want: "test1",
285+
inputs: []string{"foo", "bar", "baz"},
286+
want: "test1",
287+
wantName: "baz",
283288
},
284289
{
285290
name: "all-empty",
@@ -291,17 +296,17 @@ func TestFirstStringOf(t *testing.T) {
291296
ctx := cli.NewContext(app, set, nil)
292297
return ctx
293298
},
294-
inputs: []string{"foo", "bar", "baz"},
295-
want: "",
299+
inputs: []string{"foo", "bar", "baz"},
300+
want: "",
301+
wantName: "foo",
296302
},
297303
}
298304
for _, tt := range tests {
299305
t.Run(tt.name, func(t *testing.T) {
300306
ctx := tt.getContext()
301-
val := FirstStringOf(ctx, tt.inputs...)
302-
if val != tt.want {
303-
t.Errorf("expected %v, but got %v", tt.want, val)
304-
}
307+
val, name := FirstStringOf(ctx, tt.inputs...)
308+
require.Equal(t, tt.want, val)
309+
require.Equal(t, tt.wantName, name)
305310
})
306311
}
307312
}

internal/provisionerflag/ignore.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package provisionerflag
2+
3+
import (
4+
"sync/atomic"
5+
)
6+
7+
var disabled atomic.Bool
8+
9+
// Ignore marks the provisionerflag to be ignored
10+
func Ignore() {
11+
disabled.Store(true)
12+
}
13+
14+
// ShouldBeIgnored returns true if the provisioner flag should be ignored
15+
func ShouldBeIgnored() bool {
16+
return disabled.Load()
17+
}

utils/cautils/offline.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ import (
1212
"time"
1313

1414
"github.com/pkg/errors"
15+
"github.com/urfave/cli"
16+
"golang.org/x/crypto/ssh"
17+
1518
"github.com/smallstep/certificates/api"
1619
"github.com/smallstep/certificates/authority"
1720
"github.com/smallstep/certificates/authority/config"
1821
"github.com/smallstep/certificates/authority/provisioner"
19-
"github.com/smallstep/cli/utils"
20-
"github.com/urfave/cli"
2122
"go.step.sm/crypto/pemutil"
2223
"go.step.sm/crypto/x509util"
23-
"golang.org/x/crypto/ssh"
24+
25+
"github.com/smallstep/cli/utils"
2426
)
2527

2628
// OfflineCA is a wrapper on top of the certificates authority methods that is
@@ -585,6 +587,8 @@ func (c *OfflineCA) GenerateToken(ctx *cli.Context, tokType int, subject string,
585587
return p.GetIdentityToken(subject, c.CaURL())
586588
case *provisioner.ACME: // Return an error with the provisioner ID.
587589
return "", &ACMETokenError{p.GetName()}
590+
case *provisioner.SCEP:
591+
return "", &SCEPTokenError{p.GetName()}
588592
default: // Default is assumed to be a standard JWT.
589593
jwkP, ok := p.(*provisioner.JWK)
590594
if !ok {

utils/cautils/token_flow.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/smallstep/cli-utils/ui"
1616

1717
"github.com/smallstep/cli/flags"
18+
"github.com/smallstep/cli/internal/provisionerflag"
1819
"github.com/smallstep/cli/utils"
1920
)
2021

@@ -86,6 +87,17 @@ func (e *ACMETokenError) Error() string {
8687
return "step ACME provisioners do not support token auth flows"
8788
}
8889

90+
// SCEPTokenError is the error type returned when the user attempts a Token Flow
91+
// while using a SCEP provisioner.
92+
type SCEPTokenError struct {
93+
Name string
94+
}
95+
96+
// Error implements the error interface.
97+
func (e *SCEPTokenError) Error() string {
98+
return "step SCEP provisioners do not support token auth flows"
99+
}
100+
89101
// NewTokenFlow implements the common flow used to generate a token
90102
func NewTokenFlow(ctx *cli.Context, tokType int, subject string, sans []string, caURL, root string, notBefore, notAfter time.Time, certNotBefore, certNotAfter provisioner.TimeDuration, opts ...Option) (string, error) {
91103
// Apply options to shared context
@@ -164,6 +176,8 @@ func NewTokenFlow(ctx *cli.Context, tokType int, subject string, sans []string,
164176
return p.GetIdentityToken(subject, caURL)
165177
case *provisioner.ACME: // Return an error with the provisioner ID.
166178
return "", &ACMETokenError{p.GetName()}
179+
case *provisioner.SCEP:
180+
return "", &SCEPTokenError{p.GetName()}
167181
default:
168182
return "", errors.Errorf("unknown provisioner type %T", p)
169183
}
@@ -212,13 +226,13 @@ func OfflineTokenFlow(ctx *cli.Context, typ int, subject string, sans []string,
212226
}
213227

214228
kid := ctx.String("kid")
215-
issuer := flags.FirstStringOf(ctx, "provisioner", "issuer")
229+
issuer, flag := flags.FirstStringOf(ctx, "provisioner", "issuer")
216230

217231
// Require issuer and keyFile if ca.json does not exists.
218232
// kid can be passed or created using jwk.Thumbprint.
219233
switch {
220234
case issuer == "":
221-
return "", errs.RequiredWithFlag(ctx, "offline", "issuer")
235+
return "", errs.RequiredWithFlag(ctx, "offline", flag)
222236
case ctx.String("key") == "":
223237
return "", errs.RequiredWithFlag(ctx, "offline", "key")
224238
}
@@ -293,7 +307,7 @@ func provisionerPrompt(ctx *cli.Context, provisioners provisioner.List) (provisi
293307
provisioners = provisionerFilter(provisioners, func(p provisioner.Interface) bool {
294308
switch p.GetType() {
295309
case provisioner.TypeJWK, provisioner.TypeOIDC,
296-
provisioner.TypeACME, provisioner.TypeK8sSA,
310+
provisioner.TypeACME, provisioner.TypeSCEP, provisioner.TypeK8sSA,
297311
provisioner.TypeX5C, provisioner.TypeSSHPOP, provisioner.TypeNebula:
298312
return true
299313
case provisioner.TypeGCP, provisioner.TypeAWS, provisioner.TypeAzure:
@@ -325,23 +339,27 @@ func provisionerPrompt(ctx *cli.Context, provisioners provisioner.List) (provisi
325339
}
326340
}
327341

328-
// Filter by issuer (provisioner name)
329-
if issuer := flags.FirstStringOf(ctx, "provisioner", "issuer"); issuer != "" {
342+
// Filter by admin-provisioner (provisioner name)
343+
if issuer := ctx.String("admin-provisioner"); issuer != "" {
330344
provisioners = provisionerFilter(provisioners, func(p provisioner.Interface) bool {
331345
return p.GetName() == issuer
332346
})
333347
if len(provisioners) == 0 {
334-
return nil, errs.InvalidFlagValue(ctx, "issuer", issuer, "")
348+
return nil, errs.InvalidFlagValue(ctx, "admin-provisioner", issuer, "")
335349
}
336350
}
337351

338-
// Filter by admin-provisioner (provisioner name)
339-
if issuer := ctx.String("admin-provisioner"); issuer != "" {
352+
// Filter by provisioner / issuer (provisioner name)
353+
if issuer, flag := flags.FirstStringOf(ctx, "provisioner", "issuer"); issuer != "" {
340354
provisioners = provisionerFilter(provisioners, func(p provisioner.Interface) bool {
355+
if provisionerflag.ShouldBeIgnored() {
356+
return true // fake match; effectively skipping provisioner flag value for provisioner-dependent policy commands
357+
}
358+
341359
return p.GetName() == issuer
342360
})
343361
if len(provisioners) == 0 {
344-
return nil, errs.InvalidFlagValue(ctx, "admin-provisioner", issuer, "")
362+
return nil, errs.InvalidFlagValue(ctx, flag, issuer, "")
345363
}
346364
}
347365

@@ -364,7 +382,7 @@ func provisionerPrompt(ctx *cli.Context, provisioners provisioner.List) (provisi
364382
Name: fmt.Sprintf("%s (%s) [tenant: %s]", p.Name, p.GetType(), p.TenantID),
365383
Provisioner: p,
366384
})
367-
case *provisioner.GCP, *provisioner.AWS, *provisioner.X5C, *provisioner.SSHPOP, *provisioner.ACME, *provisioner.Nebula:
385+
case *provisioner.GCP, *provisioner.AWS, *provisioner.X5C, *provisioner.SSHPOP, *provisioner.ACME, *provisioner.SCEP, *provisioner.Nebula:
368386
items = append(items, &provisionersSelect{
369387
Name: fmt.Sprintf("%s (%s)", p.GetName(), p.GetType()),
370388
Provisioner: p,

0 commit comments

Comments
 (0)