• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nats-io / nsc / 26181286570

20 May 2026 06:14PM UTC coverage: 74.843% (-0.05%) from 74.897%
26181286570

Pull #821

github

aricart
added coverage to the fips tests
Pull Request #821: feat: add FIPS 140-3 compliance support (#812)

34 of 99 new or added lines in 14 files covered. (34.34%)

2 existing lines in 1 file now uncovered.

13269 of 17729 relevant lines covered (74.84%)

3.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.6
/cmd/pull.go
1
// Copyright 2018-2021 The NATS Authors
2
// Licensed under the Apache License, Version 2.0 (the "License");
3
// you may not use this file except in compliance with the License.
4
// You may obtain a copy of the License at
5
//
6
// http://www.apache.org/licenses/LICENSE-2.0
7
//
8
// Unless required by applicable law or agreed to in writing, software
9
// distributed under the License is distributed on an "AS IS" BASIS,
10
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
// See the License for the specific language governing permissions and
12
// limitations under the License.
13

14
package cmd
15

16
import (
17
        "errors"
18
        "fmt"
19
        "strings"
20
        "sync"
21
        "time"
22

23
        cli "github.com/nats-io/cliprompts/v2"
24
        "github.com/nats-io/jwt/v2"
25
        "github.com/nats-io/nats.go"
26
        "github.com/nats-io/nsc/v2/cmd/store"
27
        "github.com/nats-io/nsc/v2/internal/fips"
28
        "github.com/spf13/cobra"
29
)
30

31
func createPullCmd() *cobra.Command {
4✔
32
        var params PullParams
4✔
33
        cmd := &cobra.Command{
4✔
34
                Use:   "pull",
4✔
35
                Short: "Pull an operator or account jwt replacing the local jwt with the server's version",
4✔
36
                RunE: func(cmd *cobra.Command, args []string) error {
8✔
37
                        return RunAction(cmd, args, &params)
4✔
38
                },
4✔
39
        }
40

41
        cmd.Flags().BoolVarP(&params.All, "all", "A", false, "operator and all accounts under the operator")
4✔
42
        cmd.Flags().BoolVarP(&params.Overwrite, "overwrite-newer", "F", false, "overwrite local JWTs that are newer than remote")
4✔
43
        cmd.Flags().StringVarP(&params.sysAcc, "system-account", "", "", "System account for use with nats-resolver enabled nats-server. (Default is system account specified by operator)")
4✔
44
        cmd.Flags().StringVarP(&params.sysAccUser, "system-user", "", "", "System account user for use with nats-resolver enabled nats-server. (Default to temporarily generated user)")
4✔
45

4✔
46
        bindClientTLSFlags(cmd.Flags())
4✔
47

4✔
48
        params.AccountContextParams.BindFlags(cmd)
4✔
49
        return cmd
4✔
50
}
51

52
func init() {
4✔
53
        rootCmd.AddCommand(createPullCmd())
4✔
54
}
4✔
55

56
type PullParams struct {
57
        AccountContextParams
58
        All        bool
59
        Jobs       PullJobs
60
        Overwrite  bool
61
        sysAccUser string // when present use
62
        sysAcc     string
63
}
64

65
type PullJob struct {
66
        Name       string
67
        ASU        string
68
        Err        error
69
        StoreErr   error
70
        LocalClaim jwt.Claims
71

72
        PullStatus *store.Report
73
}
74

75
func (j *PullJob) Token() (string, error) {
4✔
76
        if len(j.PullStatus.Data) == 0 {
4✔
77
                return "", errors.New("no data")
×
78
        }
×
79
        token, err := jwt.ParseDecoratedJWT(j.PullStatus.Data)
4✔
80
        if err != nil {
4✔
81
                return "", err
×
82
        }
×
83
        j.PullStatus.Data = []byte(token)
4✔
84
        gc, err := jwt.DecodeGeneric(token)
4✔
85
        if err != nil {
4✔
86
                return "", err
×
87
        }
×
88
        switch gc.ClaimType() {
4✔
89
        case jwt.AccountClaim:
4✔
90
                _, err := jwt.DecodeAccountClaims(token)
4✔
91
                if err != nil {
4✔
92
                        return "", err
×
93
                }
×
94
                return token, nil
4✔
95
        case jwt.OperatorClaim:
4✔
96
                _, err := jwt.DecodeOperatorClaims(token)
4✔
97
                if err != nil {
4✔
98
                        return "", err
×
99
                }
×
100
                return token, nil
4✔
101
        default:
×
102
                return "", fmt.Errorf("unsupported token type: %q", gc.ClaimType())
×
103
        }
104
}
105

106
func (j *PullJob) Run() {
4✔
107
        if j.PullStatus != nil {
8✔
108
                // already ran
4✔
109
                return
4✔
110
        }
4✔
111
        s, err := store.PullAccount(j.ASU)
4✔
112
        if err != nil {
8✔
113
                j.Err = err
4✔
114
                return
4✔
115
        }
4✔
116
        ps, ok := s.(*store.Report)
4✔
117
        if !ok {
4✔
118
                j.Err = errors.New("unable to convert pull status")
×
119
                return
×
120
        }
×
121
        j.PullStatus = ps
4✔
122
}
123

124
type PullJobs []*PullJob
125

126
func (p *PullParams) SetDefaults(ctx ActionCtx) error {
4✔
127
        return p.AccountContextParams.SetDefaults(ctx)
4✔
128
}
4✔
129

130
func (p *PullParams) PreInteractive(ctx ActionCtx) error {
×
131
        var err error
×
132
        tc := GetConfig()
×
133
        p.All, err = cli.Confirm(fmt.Sprintf("pull operator %q and all accounts", tc.Operator), true)
×
134
        if err != nil {
×
135
                return err
×
136
        }
×
137
        if !p.All {
×
138
                if err := p.AccountContextParams.Edit(ctx); err != nil {
×
139
                        return err
×
140
                }
×
141
        }
142
        return nil
×
143
}
144

145
func (p *PullParams) Load(ctx ActionCtx) error {
4✔
146
        return nil
4✔
147
}
4✔
148

149
func (p *PullParams) PostInteractive(ctx ActionCtx) error {
×
150
        return nil
×
151
}
×
152

153
func (p *PullParams) Validate(ctx ActionCtx) error {
4✔
154
        if !p.All && p.Name == "" {
4✔
155
                return errors.New("specify --all or --account")
×
156
        }
×
157
        oc, err := ctx.StoreCtx().Store.ReadOperatorClaim()
4✔
158
        if err != nil {
4✔
159
                return err
×
160
        }
×
161
        if oc.AccountServerURL == "" {
8✔
162
                return fmt.Errorf("operator %q doesn't set account server url - unable to pull", ctx.StoreCtx().Operator.Name)
4✔
163
        }
4✔
164
        if IsResolverURL(oc.AccountServerURL) && !p.All {
4✔
165
                return fmt.Errorf("operator %q can only pull all jwt - unable to pull by account", ctx.StoreCtx().Operator.Name)
×
166
        }
×
167
        return nil
4✔
168
}
169

170
func (p *PullParams) setupJobs(ctx ActionCtx) error {
4✔
171
        s := ctx.StoreCtx().Store
4✔
172
        oc, err := s.ReadOperatorClaim()
4✔
173
        if err != nil {
4✔
174
                return err
×
175
        }
×
176
        if p.All {
8✔
177
                u, err := OperatorJwtURL(oc)
4✔
178
                if err != nil {
4✔
179
                        return err
×
180
                }
×
181
                // in ngs we are in v2 operator, so lets try /jwt/v2/operator first
182
                uv2 := strings.Replace(u, "/jwt/v1/operator", "/jwt/v2/operator", 1)
4✔
183
                j := PullJob{ASU: uv2, Name: oc.Name, LocalClaim: oc}
4✔
184
                j.Run()
4✔
185
                if j.Err == nil {
8✔
186
                        p.Jobs = append(p.Jobs, &j)
4✔
187
                } else {
8✔
188
                        j = PullJob{ASU: u, Name: oc.Name, LocalClaim: oc}
4✔
189
                        p.Jobs = append(p.Jobs, &j)
4✔
190
                }
4✔
191
                tc := GetConfig()
4✔
192
                accounts, err := tc.ListAccounts()
4✔
193
                if err != nil {
4✔
194
                        return err
×
195
                }
×
196
                for _, v := range accounts {
8✔
197
                        ac, err := s.ReadAccountClaim(v)
4✔
198
                        if err != nil {
4✔
199
                                return err
×
200
                        }
×
201
                        u, err := AccountJwtURL(oc, ac)
4✔
202
                        if err != nil {
4✔
203
                                return err
×
204
                        }
×
205
                        j := PullJob{ASU: u, Name: ac.Name, LocalClaim: ac}
4✔
206
                        p.Jobs = append(p.Jobs, &j)
4✔
207
                }
208
        } else {
4✔
209
                ac, err := s.ReadAccountClaim(p.Name)
4✔
210
                if err != nil {
4✔
211
                        return err
×
212
                }
×
213
                u, err := AccountJwtURL(oc, ac)
4✔
214
                if err != nil {
4✔
215
                        return err
×
216
                }
×
217
                j := PullJob{ASU: u, Name: ac.Name, LocalClaim: ac}
4✔
218
                p.Jobs = append(p.Jobs, &j)
4✔
219
        }
220

221
        return nil
4✔
222
}
223

224
func (p *PullParams) maybeStoreJWT(ctx ActionCtx, sub *store.Report, token string) {
4✔
225
        remoteClaim, err := jwt.DecodeGeneric(token)
4✔
226
        if err != nil {
4✔
227
                sub.AddError("error decoding remote token: %v %s", err, token)
×
228
                return
×
229
        }
×
230
        orig := int64(0)
4✔
231
        if localClaim, err := ctx.StoreCtx().Store.ReadAccountClaim(remoteClaim.Name); err == nil {
8✔
232
                orig = localClaim.IssuedAt
4✔
233
        }
4✔
234
        remote := remoteClaim.IssuedAt
4✔
235
        if (orig > remote) && !p.Overwrite {
8✔
236
                sub.AddError("local jwt for %q is newer than remote version - specify --overwrite-newer to overwrite", remoteClaim.Name)
4✔
237
                return
4✔
238
        }
4✔
239
        if err := ctx.StoreCtx().Store.StoreRaw([]byte(token)); err != nil {
4✔
240
                sub.AddError("error storing %q: %v", remoteClaim.Name, err)
×
241
                return
×
242
        }
×
243
        sub.AddOK("stored %s %q", remoteClaim.ClaimType(), remoteClaim.Name)
4✔
244
        if sub.OK() {
8✔
245
                sub.Label = fmt.Sprintf("pulled %q from the account server", remoteClaim.Name)
4✔
246
        }
4✔
247
}
248

249
func (p *PullParams) Run(ctx ActionCtx) (store.Status, error) {
4✔
250
        r := store.NewDetailedReport(true)
4✔
251
        if op, err := ctx.StoreCtx().Store.ReadOperatorClaim(); err != nil {
4✔
252
                r.AddError("could not read operator claim: %v", err)
×
253
                return r, err
×
254
        } else if op.AccountServerURL == "" {
4✔
255
                err := fmt.Errorf("operator has no account server url")
×
256
                r.AddError("operator %s: %v", op.Name, err)
×
257
                return r, err
×
258
        } else if url := op.AccountServerURL; IsResolverURL(url) {
8✔
259
                subR := store.NewReport(store.OK, `pull from cluster using system account`)
4✔
260
                r.Add(subR)
4✔
261
                ib := nats.NewInbox()
4✔
262
                _, opt, err := getSystemAccountUser(ctx, p.sysAcc, p.sysAccUser, ib, "$SYS.REQ.CLAIMS.PACK")
4✔
263
                if err != nil {
8✔
264
                        subR.AddError("failed to obtain system user: %v", err)
4✔
265
                        return r, nil
4✔
266
                }
4✔
267
                if err := fips.CheckWebSocketURL(url); err != nil {
4✔
NEW
268
                        subR.AddError("%v", err)
×
NEW
269
                        return r, nil
×
NEW
270
                }
×
271
                nc, err := nats.Connect(url, createDefaultToolOptions("nsc_pull", ctx, opt)...)
4✔
272
                if err != nil {
4✔
273
                        subR.AddError("failed to connect to %s: %v", url, err)
×
274
                        return r, nil
×
275
                }
×
276
                defer nc.Close()
4✔
277

4✔
278
                sub, err := nc.SubscribeSync(ib)
4✔
279
                if err != nil {
4✔
280
                        subR.AddError("failed to subscribe to response subject: %v", err)
×
281
                        return r, nil
×
282
                }
×
283
                if err := nc.PublishRequest("$SYS.REQ.CLAIMS.PACK", ib, nil); err != nil {
4✔
284
                        subR.AddError("failed to pull accounts: %v", err)
×
285
                        return r, nil
×
286
                }
×
287
                for {
8✔
288
                        if resp, err := sub.NextMsg(time.Second); err != nil {
4✔
289
                                subR.AddError("failed to get response to pull: %v", err)
×
290
                                break
×
291
                        } else if msg := string(resp.Data); msg == "" { // empty response means end
8✔
292
                                break
4✔
293
                        } else if tk := strings.Split(string(resp.Data), "|"); len(tk) != 2 {
4✔
294
                                subR.AddError("pull response bad")
×
295
                                break
×
296
                        } else {
4✔
297
                                p.maybeStoreJWT(ctx, subR, tk[1])
4✔
298
                        }
4✔
299
                }
300
                return r, nil
4✔
301
        }
302

303
        ctx.CurrentCmd().SilenceUsage = true
4✔
304
        if err := p.setupJobs(ctx); err != nil {
4✔
305
                return nil, err
×
306
        }
×
307
        var wg sync.WaitGroup
4✔
308
        wg.Add(len(p.Jobs))
4✔
309
        for _, j := range p.Jobs {
8✔
310
                go func(j *PullJob) {
8✔
311
                        defer wg.Done()
4✔
312
                        j.Run()
4✔
313
                }(j)
4✔
314
        }
315
        wg.Wait()
4✔
316

4✔
317
        for _, j := range p.Jobs {
8✔
318
                sub := store.NewReport(store.OK, "pull %q from the account server", j.Name)
4✔
319
                sub.Opt = store.DetailsOnErrorOrWarning
4✔
320
                r.Add(sub)
4✔
321
                if j.PullStatus != nil {
8✔
322
                        sub.Add(store.HoistChildren(j.PullStatus)...)
4✔
323
                }
4✔
324
                if j.Err != nil {
4✔
325
                        sub.AddFromError(j.Err)
×
326
                        continue
×
327
                }
328
                if j.PullStatus.OK() {
8✔
329
                        token, _ := j.Token()
4✔
330
                        p.maybeStoreJWT(ctx, sub, token)
4✔
331
                }
4✔
332
        }
333
        return r, nil
4✔
334
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc