Repository: nats-io/nsc
Pull request #350 (2021-01-20 20:36:41)

[added] force import of operator with different subject
added operator name option to reissue

Signed-off-by: Matthias Hanel <mh@synadia.com>


Patch:
diff --git a/cmd/addoperator.go b/cmd/addoperator.go
index 42c8084..74e5803 100644
--- a/cmd/addoperator.go
+++ b/cmd/addoperator.go
@@ -345,8 +345,11 @@ func (p *AddOperatorParams) Run(_ ActionCtx) (store.Status, error) {
 			}
 			if oc.Subject != ocNew.Subject {
 				err := fmt.Errorf("existing and new operator represent different entities")
-				r.AddError("%v resolve conflict first", err)
-				return r, err
+				if !p.force {
+					r.AddError(`%v resolve conflict first or provide "--force" to continue`, err)
+					return r, err
+				}
+				r.AddWarning("%v, forced to continue", err)
 			}
 		} else if err.(*store.ResourceErr).Err != store.ErrNotExist {
 			return nil, err
diff --git a/cmd/reissueoperator.go b/cmd/reissueoperator.go
index 92d6581..bf6e4fc 100644
--- a/cmd/reissueoperator.go
+++ b/cmd/reissueoperator.go
@@ -40,6 +40,7 @@ func createReIssueOperatorCmd() *cobra.Command {
 	}
 	cmd.Flags().BoolVarP(&params.turnIntoSigningKey, "convert-to-signing-key", "", false,
 		"turn operator identity key into signing key (avoids account re-signing)")
+	cmd.Flags().StringVarP(&params.name, "name", "n", "", "operator name")
 	return cmd
 }
 
@@ -56,6 +57,7 @@ func init() {
 
 type reIssueOperator struct {
 	turnIntoSigningKey bool
+	name               string
 }
 
 func (p *reIssueOperator) PreInteractive(ctx ActionCtx) error {
@@ -75,18 +77,34 @@ func (p *reIssueOperator) SetDefaults(ctx ActionCtx) error {
 }
 
 func (p *reIssueOperator) Validate(ctx ActionCtx) error {
-	if ctx.StoreCtx().Store.IsManaged() {
+	store := ctx.StoreCtx().Store
+	if p.name != "" {
+		var err error
+		store, err = GetStoreForOperator(p.name)
+		if err != nil {
+			return err
+		}
+	}
+	if store.IsManaged() {
 		return fmt.Errorf("resign is only supported in non managed stores")
 	}
-	if _, err := ctx.StoreCtx().Store.ReadOperatorClaim(); err != nil {
+	if _, err := store.ReadOperatorClaim(); err != nil {
 		return err
 	}
 	return nil
 }
 
 func (p *reIssueOperator) Run(ctx ActionCtx) (store.Status, error) {
+	var err error
 	r := store.NewDetailedReport(true)
-	op, err := ctx.StoreCtx().Store.ReadOperatorClaim()
+	s := ctx.StoreCtx().Store
+	if p.name != "" {
+		if s, err = GetStoreForOperator(p.name); err != nil {
+			r.AddError("failed to load operator %s: %v", p.name, err)
+			return r, err
+		}
+	}
+	op, err := s.ReadOperatorClaim()
 	if err != nil {
 		r.AddError("failed to obtain operator: %v", err)
 		return r, err
@@ -113,7 +131,7 @@ func (p *reIssueOperator) Run(ctx ActionCtx) (store.Status, error) {
 	if opJWT, err := op.Encode(opKp); err != nil {
 		r.AddError("failed to encode new operator jwt: %v", err)
 		return r, err
-	} else if err := ctx.StoreCtx().Store.StoreRaw([]byte(opJWT)); err != nil {
+	} else if err := s.StoreRaw([]byte(opJWT)); err != nil {
 		r.AddError("failed to store new operator jwt: %v", err)
 		return r, err
 	}
@@ -122,12 +140,12 @@ func (p *reIssueOperator) Run(ctx ActionCtx) (store.Status, error) {
 		r.AddOK("old operator key %q turned into signing key", opPub)
 		return r, nil
 	}
-	accounts, err := ctx.StoreCtx().Store.ListSubContainers(store.Accounts)
+	accounts, err := s.ListSubContainers(store.Accounts)
 	if err != nil {
 		r.AddError("failed to obtain accounts: %v", err)
 	}
 	for _, acName := range accounts {
-		claim, err := ctx.StoreCtx().Store.ReadAccountClaim(acName)
+		claim, err := s.ReadAccountClaim(acName)
 		if err != nil {
 			r.AddError("failed reading account %s: %v", acName, err)
 		}
@@ -137,7 +155,7 @@ func (p *reIssueOperator) Run(ctx ActionCtx) (store.Status, error) {
 		}
 		if accJWT, err := claim.Encode(opKp); err != nil {
 			r.AddError("account %q error during encoding: %v", acName, err)
-		} else if err := ctx.StoreCtx().Store.StoreRaw([]byte(accJWT)); err != nil {
+		} else if err := s.StoreRaw([]byte(accJWT)); err != nil {
 			r.AddError("failed to store new account jwt for account %q: %v", acName, err)
 		}
 		r.AddOK("account %q re-signed", acName)


Test patch:
diff --git a/cmd/addoperator_test.go b/cmd/addoperator_test.go
index 2b88e13..ef679b9 100644
--- a/cmd/addoperator_test.go
+++ b/cmd/addoperator_test.go
@@ -277,3 +277,40 @@ func TestImportOperatorV2(t *testing.T) {
 	require.Error(t, err)
 	require.Contains(t, stdErr, JWTUpgradeBannerJWT(1).Error())
 }
+
+func TestImportReIssuedOperator(t *testing.T) {
+	ts := NewEmptyStore(t)
+	defer ts.Done(t)
+
+	checkOp := func(pub string) {
+		s, err := GetStoreForOperator("O")
+		require.NoError(t, err)
+		claim, err := s.ReadOperatorClaim()
+		require.NoError(t, err)
+		require.Equal(t, claim.Subject, pub)
+	}
+
+	_, pubOld, kpOld := CreateOperatorKey(t)
+	oc := jwt.NewOperatorClaims(pubOld)
+	oc.Name = "O"
+	token, err := oc.Encode(kpOld)
+	require.NoError(t, err)
+	tf := filepath.Join(ts.Dir, "Oold.jwt")
+	err = Write(tf, []byte(token))
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf)
+	require.NoError(t, err)
+	checkOp(pubOld)
+
+	// simulate reissuing operator
+	_, pubNew, kpNew := CreateOperatorKey(t)
+	oc.Subject = pubNew
+	token, err = oc.Encode(kpNew)
+	require.NoError(t, err)
+	tf = filepath.Join(ts.Dir, "Onew.jwt")
+	err = Write(tf, []byte(token))
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf, "--force")
+	require.NoError(t, err)
+	checkOp(pubNew)
+}
diff --git a/cmd/reissueoperator_test.go b/cmd/reissueoperator_test.go
index b01f829..8568266 100644
--- a/cmd/reissueoperator_test.go
+++ b/cmd/reissueoperator_test.go
@@ -47,7 +47,7 @@ func Test_ReIssue(t *testing.T) {
 	require.NoError(t, err)
 	require.True(t, op3.DidSign(ac))
 
-	_, _, err = ExecuteCmd(createReIssueOperatorCmd())
+	_, _, err = ExecuteCmd(createReIssueOperatorCmd(), "--name", "O")
 	require.NoError(t, err)
 	op4, err := ts.Store.ReadOperatorClaim()
 	require.NoError(t, err)