Skip to content

Commit 109b948

Browse files
committed
[FEAT] added reissue account
1 parent 8305343 commit 109b948

File tree

3 files changed

+228
-2
lines changed

3 files changed

+228
-2
lines changed

cmd/expirations_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package cmd
1717

1818
import (
1919
"encoding/json"
20-
"path/filepath"
2120
"testing"
2221
"time"
2322

@@ -234,5 +233,5 @@ func Test_ExpirationsTable(t *testing.T) {
234233
require.Contains(t, stderr, "| O/A")
235234
require.Contains(t, stderr, "| Soon | O/A/U")
236235
require.Contains(t, stderr, "In 59 Minutes |")
237-
require.Contains(t, stderr, filepath.FromSlash("creds/O/A/U.creds"))
236+
require.Contains(t, stderr, "creds/O/A/U.creds")
238237
}

cmd/reissueaccount.go

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Copyright 2020-2023 The NATS Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package cmd
17+
18+
import (
19+
"fmt"
20+
21+
"github.com/nats-io/nkeys"
22+
"github.com/nats-io/nsc/v2/cmd/store"
23+
"github.com/spf13/cobra"
24+
)
25+
26+
func createReissueAccountCmd() *cobra.Command {
27+
var params reissueAccount
28+
cmd := &cobra.Command{
29+
Use: "account",
30+
Short: "Re-issues all accounts with a new identity and re-signs affected users",
31+
Example: `nsc reissue account`,
32+
Args: MaxArgs(0),
33+
SilenceUsage: false,
34+
RunE: func(cmd *cobra.Command, args []string) error {
35+
if err := RunMaybeStorelessAction(cmd, args, &params); err != nil {
36+
return err
37+
}
38+
return nil
39+
},
40+
}
41+
cmd.Flags().BoolVarP(&params.turnIntoSigningKey, "convert-to-signing-key", "", false,
42+
"turn account identity key into signing key (avoids user re-signing)")
43+
params.AccountContextParams.BindFlags(cmd)
44+
45+
return cmd
46+
}
47+
48+
type reissueAccount struct {
49+
AccountContextParams
50+
turnIntoSigningKey bool
51+
name string
52+
}
53+
54+
func (p *reissueAccount) SetDefaults(ctx ActionCtx) error {
55+
return p.AccountContextParams.SetDefaults(ctx)
56+
}
57+
58+
func (p *reissueAccount) PreInteractive(ctx ActionCtx) error {
59+
return nil
60+
}
61+
62+
func (p *reissueAccount) Load(ctx ActionCtx) error {
63+
return nil
64+
}
65+
66+
func (p *reissueAccount) PostInteractive(ctx ActionCtx) error {
67+
return nil
68+
}
69+
70+
func (p *reissueAccount) Validate(ctx ActionCtx) error {
71+
store := ctx.StoreCtx().Store
72+
if store.IsManaged() {
73+
return fmt.Errorf("resign is only supported in non managed stores")
74+
}
75+
return nil
76+
}
77+
78+
func (p *reissueAccount) Run(ctx ActionCtx) (store.Status, error) {
79+
var err error
80+
r := store.NewDetailedReport(true)
81+
s := ctx.StoreCtx().Store
82+
ks := ctx.StoreCtx().KeyStore
83+
accounts, err := s.ListSubContainers(store.Accounts)
84+
85+
if err != nil {
86+
return nil, err
87+
}
88+
for _, n := range accounts {
89+
rr := store.NewDetailedReport(false)
90+
r.Add(rr)
91+
ac, err := s.ReadAccountClaim(n)
92+
if err != nil {
93+
rr.AddError("failed to load account %s: %v", n, err)
94+
return r, err
95+
}
96+
97+
akp, err := nkeys.CreateAccount()
98+
if err != nil {
99+
rr.AddError("failed to generate new account identity: %v", err)
100+
return r, err
101+
}
102+
if _, err := ks.Store(akp); err != nil {
103+
rr.AddError("failed to store new account identity: %v", err)
104+
return r, err
105+
}
106+
old := ac.Subject
107+
ac.Subject, err = akp.PublicKey()
108+
if p.turnIntoSigningKey {
109+
ac.SigningKeys.Add(old)
110+
}
111+
// if we have access to the existing operator signing key that issued the
112+
// account we are going to use it to re-sign the account
113+
pk := ac.Issuer
114+
sk, err := ks.GetKeyPair(pk)
115+
if sk == nil {
116+
err = fmt.Errorf("failed to find key %q", pk)
117+
}
118+
if err == nil {
119+
_, err = sk.PrivateKey()
120+
}
121+
if err != nil {
122+
rr.AddWarning("failed to obtain find key account signer %q", ac.Issuer)
123+
// if we failed, we are just going to attempt to sign with any operator key
124+
keys, err := ctx.StoreCtx().GetOperatorKeys()
125+
if err != nil {
126+
rr.AddError("failed to read the operator keys: %v", err)
127+
return r, err
128+
}
129+
for _, k := range keys {
130+
pk = k
131+
sk, err = ks.GetKeyPair(k)
132+
if sk == nil {
133+
err = fmt.Errorf("failed to find key %q", pk)
134+
}
135+
if err == nil {
136+
_, err = sk.PrivateKey()
137+
}
138+
if err == nil {
139+
break
140+
}
141+
}
142+
}
143+
if sk == nil {
144+
if err != nil {
145+
rr.AddError("failed to read load any of the operator keys")
146+
return r, err
147+
}
148+
}
149+
150+
token, err := ac.Encode(sk)
151+
if err != nil {
152+
rr.AddError("failed to sign account with %s: %v", pk, err)
153+
return r, err
154+
}
155+
156+
if err := s.StoreRaw([]byte(token)); err != nil {
157+
rr.AddError("failed to store updated account: %v", err)
158+
return r, err
159+
}
160+
161+
rr.AddOK("account %q re-was reissued with new identity: %s", ac.Name, ac.Subject)
162+
163+
users, err := s.ListEntries(store.Accounts, ac.Name, store.Users)
164+
if err != nil {
165+
rr.AddError("failed to list users for account %s: %v", ac.Name, err)
166+
return r, err
167+
}
168+
for _, u := range users {
169+
uc, err := s.ReadUserClaim(ac.Name, u)
170+
if err != nil {
171+
rr.AddError("failed to load user %q account %s: %v", u, ac.Name, err)
172+
return r, err
173+
}
174+
issuer := uc.Issuer
175+
ask, err := ks.GetKeyPair(issuer)
176+
if ask == nil {
177+
err = fmt.Errorf("failed to find key %q", pk)
178+
}
179+
if err == nil {
180+
_, err = ask.PrivateKey()
181+
}
182+
if err != nil {
183+
rr.AddWarning("failed to obtain find key account signing key %q", issuer)
184+
// if we failed, we are just going to attempt to sign with any operator key
185+
keys, err := ctx.StoreCtx().GetAccountKeys(ac.Name)
186+
if err != nil {
187+
r.AddError("failed to read the account keys: %v", err)
188+
return r, err
189+
}
190+
for _, k := range keys {
191+
issuer = k
192+
ask, err = ks.GetKeyPair(k)
193+
if err == nil {
194+
_, err = ask.PrivateKey()
195+
}
196+
if err == nil {
197+
break
198+
}
199+
}
200+
}
201+
if ask == nil {
202+
if err != nil {
203+
rr.AddError("failed to read load any of the operator keys")
204+
return r, err
205+
}
206+
}
207+
uc.IssuerAccount = ""
208+
if issuer != ac.Subject {
209+
uc.IssuerAccount = ac.Subject
210+
}
211+
ut, err := uc.Encode(ask)
212+
if err != nil {
213+
rr.AddError("failed to sign user %q on account %s with %s: %v", u, ac.Name, issuer, err)
214+
return r, err
215+
}
216+
if err := s.StoreRaw([]byte(ut)); err != nil {
217+
rr.AddError("failed to store updated user: %v", err)
218+
return r, err
219+
}
220+
221+
rr.AddOK("user %q re-was reissued", uc.Name)
222+
}
223+
224+
}
225+
return r, nil
226+
}

cmd/reissueoperator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ var reIssue = &cobra.Command{
5858
func init() {
5959
GetRootCmd().AddCommand(reIssue)
6060
reIssue.AddCommand(createReIssueOperatorCmd())
61+
reIssue.AddCommand(createReissueAccountCmd())
6162
}
6263

6364
type reIssueOperator struct {

0 commit comments

Comments
 (0)