@@ -22,6 +22,7 @@ import (
22
22
"strconv"
23
23
"time"
24
24
25
+ "github.com/nats-io/jwt/v2"
25
26
au "github.com/nats-io/natscli/internal/auth"
26
27
iu "github.com/nats-io/natscli/internal/util"
27
28
@@ -102,6 +103,10 @@ type authAccountCommand struct {
102
103
tags []string
103
104
rmTags []string
104
105
signingKey string
106
+ mapSource string
107
+ mapTarget string
108
+ mapWeight uint
109
+ inputFile string
105
110
}
106
111
107
112
func configureAuthAccountCommand (auth commandHost ) {
@@ -280,6 +285,30 @@ func configureAuthAccountCommand(auth commandHost) {
280
285
skrm .Flag ("key" , "The key to remove" ).StringVar (& c .skRole )
281
286
skrm .Flag ("operator" , "Operator to act on" ).StringVar (& c .operatorName )
282
287
skrm .Flag ("force" , "Removes without prompting" ).Short ('f' ).UnNegatableBoolVar (& c .force )
288
+
289
+ mappings := acct .Command ("mappings" , "Manage account level subject mapping and partitioning" ).Alias ("m" )
290
+
291
+ mappingsaAdd := mappings .Command ("add" , "Add a new mapping" ).Alias ("new" ).Alias ("a" ).Action (c .mappingAddAction )
292
+ mappingsaAdd .Arg ("account" , "Account to create the mappings on" ).StringVar (& c .accountName )
293
+ mappingsaAdd .Arg ("source" , "The source subject of the mapping" ).StringVar (& c .mapSource )
294
+ mappingsaAdd .Arg ("target" , "The target subject of the mapping" ).StringVar (& c .mapTarget )
295
+ mappingsaAdd .Arg ("weight" , "The weight (%) of the mappingmapping" ).Default ("100" ).UintVar (& c .mapWeight )
296
+ mappingsaAdd .Flag ("operator" , "Operator to act on" ).StringVar (& c .operatorName )
297
+ mappingsaAdd .Flag ("config" , "JWT file to read configuration from" ).ExistingFileVar (& c .inputFile )
298
+
299
+ mappingsls := mappings .Command ("ls" , "List mappings" ).Alias ("list" ).Action (c .mappingListAction )
300
+ mappingsls .Arg ("account" , "Account to create the mappings on" ).StringVar (& c .accountName )
301
+ mappingsls .Flag ("operator" , "Operator to act on" ).StringVar (& c .operatorName )
302
+
303
+ mappingsrm := mappings .Command ("rm" , "Remove a mapping" ).Action (c .mappingRmAction )
304
+ mappingsrm .Arg ("account" , "Account to create the mappings on" ).StringVar (& c .accountName )
305
+ mappingsrm .Arg ("source" , "The source subject of the mapping" ).StringVar (& c .mapSource )
306
+ mappingsrm .Flag ("operator" , "Operator to act on" ).StringVar (& c .operatorName )
307
+
308
+ mappingsinfo := mappings .Command ("info" , "Show information about a mapping" ).Alias ("i" ).Alias ("show" ).Alias ("view" ).Action (c .mappingInfoAction )
309
+ mappingsinfo .Arg ("account" , "Account to create the mappings on" ).StringVar (& c .accountName )
310
+ mappingsinfo .Arg ("source" , "The source subject of the mapping" ).StringVar (& c .mapSource )
311
+ mappingsinfo .Flag ("operator" , "Operator to act on" ).StringVar (& c .operatorName )
283
312
}
284
313
285
314
func (c * authAccountCommand ) selectAccount (pick bool ) (* ab.AuthImpl , ab.Operator , ab.Account , error ) {
@@ -1101,3 +1130,220 @@ func (c *authAccountCommand) validTiers(acct ab.Account) []int8 {
1101
1130
1102
1131
return tiers
1103
1132
}
1133
+
1134
+ func (c * authAccountCommand ) loadJwt () (* jwt.AccountClaims , error ) {
1135
+ if c .inputFile != "" {
1136
+ f , err := os .ReadFile (c .inputFile )
1137
+ if err != nil {
1138
+ return nil , err
1139
+ }
1140
+
1141
+ claims , err := jwt .DecodeAccountClaims (string (f ))
1142
+ if err != nil {
1143
+ return nil , fmt .Errorf ("failed to decode JWT: %w" , err )
1144
+ }
1145
+ return claims , nil
1146
+ }
1147
+ return nil , nil
1148
+
1149
+ }
1150
+
1151
+ func (c * authAccountCommand ) parseJwtMappings (mappings map [string ][]ab.Mapping , jwtMappings jwt.Mapping ) {
1152
+ for subject , weightedMappings := range jwtMappings {
1153
+ mappings [string (subject )] = []ab.Mapping {}
1154
+ for _ , m := range weightedMappings {
1155
+ mappings [string (subject )] = append (mappings [string (subject )], ab.Mapping {Weight : m .Weight , Subject : string (m .Subject ), Cluster : m .Cluster })
1156
+ }
1157
+ }
1158
+ }
1159
+
1160
+ func (c * authAccountCommand ) mappingAddAction (_ * fisk.ParseContext ) error {
1161
+ mappings := map [string ][]ab.Mapping {}
1162
+ if c .inputFile != "" {
1163
+ cfg , err := c .loadJwt ()
1164
+ if err != nil {
1165
+ return err
1166
+ }
1167
+ c .accountName = cfg .Name
1168
+ c .parseJwtMappings (mappings , cfg .Mappings )
1169
+ }
1170
+
1171
+ auth , _ , acct , err := c .selectAccount (true )
1172
+ if err != nil {
1173
+ return err
1174
+ }
1175
+
1176
+ if c .inputFile == "" {
1177
+ if c .mapSource == "" {
1178
+ err := iu .AskOne (& survey.Input {
1179
+ Message : "Source subject" ,
1180
+ Help : "The source subject of the mapping" ,
1181
+ }, & c .mapSource , survey .WithValidator (survey .Required ))
1182
+ if err != nil {
1183
+ return err
1184
+ }
1185
+ }
1186
+
1187
+ if c .mapTarget == "" {
1188
+ err := iu .AskOne (& survey.Input {
1189
+ Message : "Target subject" ,
1190
+ Help : "The target subject of the mapping" ,
1191
+ }, & c .mapTarget , survey .WithValidator (survey .Required ))
1192
+ if err != nil {
1193
+ return err
1194
+ }
1195
+ }
1196
+
1197
+ mapping := ab.Mapping {Subject : c .mapTarget , Weight : uint8 (c .mapWeight )}
1198
+ // check if there are mappings already set for the source
1199
+ currentMappings := acct .SubjectMappings ().Get (c .mapSource )
1200
+ if len (currentMappings ) > 0 {
1201
+ // Check that we don't overwrite the current mapping
1202
+ for _ , m := range currentMappings {
1203
+ if m .Subject == c .mapTarget {
1204
+ return fmt .Errorf ("mapping %s -> %s already exists" , c .mapSource , c .mapTarget )
1205
+ }
1206
+ }
1207
+ }
1208
+ currentMappings = append (currentMappings , mapping )
1209
+ mappings [c .mapSource ] = currentMappings
1210
+ }
1211
+
1212
+ for subject , m := range mappings {
1213
+ err = acct .SubjectMappings ().Set (subject , m ... )
1214
+ if err != nil {
1215
+ return err
1216
+ }
1217
+ }
1218
+
1219
+ err = auth .Commit ()
1220
+ if err != nil {
1221
+ return err
1222
+ }
1223
+
1224
+ return c .fShowMappings (os .Stdout , mappings )
1225
+ }
1226
+
1227
+ func (c * authAccountCommand ) mappingInfoAction (_ * fisk.ParseContext ) error {
1228
+ _ , _ , acct , err := c .selectAccount (true )
1229
+ if err != nil {
1230
+ return err
1231
+ }
1232
+
1233
+ accountMappings := acct .SubjectMappings ().List ()
1234
+ if len (accountMappings ) == 0 {
1235
+ fmt .Println ("No mappings defined" )
1236
+ return nil
1237
+ }
1238
+
1239
+ if c .mapSource == "" {
1240
+ err = iu .AskOne (& survey.Select {
1241
+ Message : "Select a mapping to inspect" ,
1242
+ Options : accountMappings ,
1243
+ PageSize : iu .SelectPageSize (len (accountMappings )),
1244
+ }, & c .mapSource )
1245
+ if err != nil {
1246
+ return err
1247
+ }
1248
+ }
1249
+
1250
+ mappings := map [string ][]ab.Mapping {
1251
+ c .mapSource : acct .SubjectMappings ().Get (c .mapSource ),
1252
+ }
1253
+
1254
+ return c .fShowMappings (os .Stdout , mappings )
1255
+ }
1256
+
1257
+ func (c * authAccountCommand ) mappingListAction (_ * fisk.ParseContext ) error {
1258
+ _ , _ , acct , err := c .selectAccount (true )
1259
+ if err != nil {
1260
+ return err
1261
+ }
1262
+
1263
+ mappings := acct .SubjectMappings ().List ()
1264
+ if len (mappings ) == 0 {
1265
+ fmt .Println ("No mappings defined" )
1266
+ return nil
1267
+ }
1268
+
1269
+ tbl := iu .NewTableWriter (opts (), "Subject mappings for account %s" , acct .Name ())
1270
+ tbl .AddHeaders ("Source Subject" , "Target Subject" , "Weight" , "Cluster" )
1271
+
1272
+ for _ , fromMapping := range acct .SubjectMappings ().List () {
1273
+ subjectMaps := acct .SubjectMappings ().Get (fromMapping )
1274
+ for _ , m := range subjectMaps {
1275
+ tbl .AddRow (fromMapping , m .Subject , m .Weight , m .Cluster )
1276
+ }
1277
+ }
1278
+
1279
+ fmt .Println (tbl .Render ())
1280
+ return nil
1281
+ }
1282
+
1283
+ func (c * authAccountCommand ) mappingRmAction (_ * fisk.ParseContext ) error {
1284
+ auth , _ , acct , err := c .selectAccount (true )
1285
+ if err != nil {
1286
+ return err
1287
+ }
1288
+
1289
+ mappings := acct .SubjectMappings ().List ()
1290
+ if len (mappings ) == 0 {
1291
+ fmt .Println ("No mappings defined" )
1292
+ return nil
1293
+ }
1294
+
1295
+ if c .mapSource == "" {
1296
+ err = iu .AskOne (& survey.Select {
1297
+ Message : "Select a mapping to delete" ,
1298
+ Options : mappings ,
1299
+ PageSize : iu .SelectPageSize (len (mappings )),
1300
+ }, & c .mapSource )
1301
+ if err != nil {
1302
+ return err
1303
+ }
1304
+ }
1305
+
1306
+ err = acct .SubjectMappings ().Delete (c .mapSource )
1307
+ if err != nil {
1308
+ return err
1309
+ }
1310
+
1311
+ err = auth .Commit ()
1312
+ if err != nil {
1313
+ return err
1314
+ }
1315
+
1316
+ fmt .Printf ("Deleted mapping {%s}\n " , c .mapSource )
1317
+ return nil
1318
+ }
1319
+
1320
+ func (c * authAccountCommand ) fShowMappings (w io.Writer , mappings map [string ][]ab.Mapping ) error {
1321
+ out , err := c .showMappings (mappings )
1322
+ if err != nil {
1323
+ return err
1324
+ }
1325
+
1326
+ _ , err = fmt .Fprintln (w , out )
1327
+ return err
1328
+ }
1329
+
1330
+ func (c * authAccountCommand ) showMappings (mappings map [string ][]ab.Mapping ) (string , error ) {
1331
+ cols := newColumns ("Subject mappings" )
1332
+ cols .AddSectionTitle ("Configuration" )
1333
+ for source , m := range mappings {
1334
+ // Remove when delete is fixed
1335
+ totalWeight := 0
1336
+ for _ , wm := range m {
1337
+ cols .AddRow ("Source" , source )
1338
+ cols .AddRow ("Target" , wm .Subject )
1339
+ cols .AddRow ("Weight" , wm .Weight )
1340
+ cols .AddRow ("Cluster" , wm .Cluster )
1341
+ cols .AddRow ("" , "" )
1342
+ totalWeight += int (wm .Weight )
1343
+ }
1344
+ cols .AddRow ("Total weight:" , totalWeight )
1345
+ cols .AddRow ("" , "" )
1346
+ }
1347
+
1348
+ return cols .Render ()
1349
+ }
0 commit comments