enfcli 3.3.2.pre.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,562 @@
1
+ #
2
+ # Copyright 2018 Xaptum,Inc
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ require 'enfthor'
17
+ require 'enfapi'
18
+ require 'base64'
19
+ require 'digest'
20
+ require 'openssl'
21
+ require 'ipaddr'
22
+
23
+ module EnfCli
24
+ module Cmd
25
+
26
+ class Xiam < EnfThor
27
+ no_commands {
28
+ def write_pem_file keyfile, data
29
+ # check if keyfile ends with .pem extension
30
+ keyfile = "#{keyfile}.pem" unless keyfile.end_with?(".pem")
31
+
32
+ # Write to file
33
+ if write_to_file keyfile, data
34
+ say "Created #{keyfile}", :green
35
+ end
36
+ end
37
+
38
+ def write_to_file filename, data
39
+ begin
40
+ # check if file exists
41
+ filename = EnfCli::expand_path(filename)
42
+ if File.exists?( filename )
43
+ proceed = ask("Overwrite #{filename}(type 'yes' to continue...)?")
44
+ return nil unless (proceed.downcase == 'yes')
45
+ end
46
+
47
+ # write to file
48
+ open filename, 'w' do |io| io.write data end
49
+
50
+ # return success
51
+ return true
52
+ rescue
53
+ raise EnfCli::ERROR, "Unable to write to file #{filename}! Please check file premissions."
54
+ end
55
+ end
56
+
57
+ def calculate_gid(basename, gpk)
58
+ Digest::SHA256.hexdigest([gpk].pack("H*") << [basename].pack("H*")).upcase
59
+ end
60
+
61
+ def scan_group_qr_code()
62
+ # Initalize values
63
+ gpk_header = ''
64
+ gpk_info = ''
65
+
66
+ # Scan QR Code
67
+ gpk_header = EnfCli::ask_password('Scan DAA group information:')
68
+ if gpk_header.length < 16
69
+ gpk_info = EnfCli::ask_password('')
70
+ else
71
+ tmp_header = gpk_header[0..12]
72
+ gpk_info = gpk_header[13..gpk_header.length]
73
+ gpk_header = tmp_header
74
+ end
75
+
76
+ gpk_info
77
+ end
78
+
79
+ def read_ec_key_file(keyfile)
80
+ # expand path
81
+ keyfile = EnfCli::expand_path(keyfile)
82
+
83
+ ## return if keyfile not found
84
+ raise EnfCli::ERROR, "#{keyfile} not found!" unless File.exists?(keyfile)
85
+
86
+ # read the private key
87
+ OpenSSL::PKey::EC.new(File.read(keyfile))
88
+ end
89
+
90
+ def display_groups groups
91
+ headings = ['DAA Group', 'Base Name', 'Provisioned', 'Onboarded', 'Domain' ]
92
+ rows = groups.map{ |hash|
93
+ [ Base64.decode64(hash[:gid]).unpack("H*")[0], Base64.decode64(hash[:basename]),
94
+ hash[:provisioned_count], hash[:onboarded_count],
95
+ "#{IPAddr::new_ntoh( Base64.decode64(hash[:domain][:prefix]) )}/#{hash[:domain][:prefix_length]}"
96
+ ]
97
+ }
98
+ render_table(headings, rows)
99
+ end
100
+
101
+ def provision_endpoint(ipv6_address, pub)
102
+ # encode public key
103
+ hex = pub.to_bn.to_s(16)
104
+ pubkey = Base64.strict_encode64([hex].pack("H*"))
105
+
106
+ # create NEW_CREDENTIAL_ECDSA
107
+ new_credential = {
108
+ :type => "ecdsa_p256",
109
+ :key => pubkey
110
+ }
111
+
112
+ # create NEW_ENDPOINT_AUTH
113
+ new_endpoint = {
114
+ :cred_oneof => {
115
+ :auth => {
116
+ :ecdsa_credential => new_credential,
117
+ :address_request => {
118
+ :address_request_oneof => {
119
+ :address => Base64.strict_encode64( ipv6_address.hton )
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ data = EnfApi::Iam.instance.provision_endpoint new_endpoint
127
+ provisioned_ip = data[:address]
128
+ provisioned_ip = EnfCli::IPV6::ntoh Base64.decode64(provisioned_ip)
129
+
130
+ # Make sure provisioned ip is same as the requested ip
131
+ raise EnfCli::ERROR, "Hello World" unless provisioned_ip.to_s.eql?( ipv6_address.to_s )
132
+
133
+ say "Created new ipv6 endpoint #{provisioned_ip}", :green
134
+ end
135
+
136
+ def update_endpoint_credentials(ipv6_address, pub)
137
+ # encode public key
138
+ hex = pub.to_bn.to_s(16)
139
+ pubkey = Base64.strict_encode64([hex].pack("H*"))
140
+
141
+ # create NEW_CREDENTIAL_ECDSA
142
+ new_credential = {
143
+ :type => "ecdsa_p256",
144
+ :key => pubkey
145
+ }
146
+
147
+ # create NEW_ECDSA_CREDENTIAL
148
+ new_cred = {
149
+ :cred_oneof => {
150
+ :ecdsa => new_credential
151
+ }
152
+ }
153
+
154
+ EnfApi::Iam.instance.update_endpoint_key ipv6_address, new_cred
155
+
156
+ say "Updated #{ipv6_address} credentials!", :green
157
+ end
158
+ }
159
+
160
+ desc "list-group", "List DAA Group Information"
161
+ def list_group
162
+ try_with_rescue_in_session do
163
+ qr_code_info = scan_group_qr_code()
164
+
165
+ # Extract gpk and basename
166
+ gpk_info = qr_code_info.split(",")
167
+ basename = gpk_info[0]
168
+ gpk = gpk_info[1]
169
+
170
+ # Calculate gid
171
+ gid = calculate_gid basename, gpk
172
+
173
+ # url safe encode gid
174
+ enc_gid = Base64.urlsafe_encode64([gid].pack("H*"))
175
+
176
+ data = EnfApi::Iam.instance.list_group enc_gid
177
+ groups = [data]
178
+
179
+ # display data
180
+ display_groups groups
181
+ end
182
+ end
183
+
184
+ desc "set-group-default-network", "Set /64 network as default for the DAA group"
185
+ method_option :'network', :type => :string, :required => true
186
+ method_option :'gid', :type => :string
187
+ def set_group_default_network
188
+ try_with_rescue_in_session do
189
+ network = EnfCli::IPV6Cidr.new options[:network]
190
+
191
+ # scan if gid not in options
192
+ if options[:gid]
193
+ gid = options[:gid]
194
+ else
195
+ qr_code_info = scan_group_qr_code()
196
+
197
+ # Extract gpk and basename
198
+ gpk_info = qr_code_info.split(",")
199
+ basename = gpk_info[0]
200
+ gpk = gpk_info[1]
201
+
202
+ # Calculate gid
203
+ gid = calculate_gid basename, gpk
204
+ end
205
+
206
+ # create IPV6_NETWORK
207
+ ipv6_network = {
208
+ :prefix => Base64.strict_encode64( network.prefix_bytes ),
209
+ :prefix_length => network.prefix_len
210
+ }
211
+
212
+ # create MODIFIED_GROUP
213
+ modified_group = {
214
+ :gid => Base64.strict_encode64([gid].pack("H*")),
215
+ :default_network => ipv6_network
216
+ }
217
+
218
+ # url safe encode gid
219
+ enc_gid = Base64.urlsafe_encode64([gid].pack("H*"))
220
+
221
+ # Post to server
222
+ EnfApi::Iam.instance.update_group enc_gid, modified_group
223
+
224
+ say "Set #{network} as default network for group #{gid}", :green
225
+ end
226
+ end
227
+
228
+ desc "set-group-domain", "Set /48 domain for the DAA group"
229
+ method_option :'domain', :type => :string, :required => true
230
+ method_option :'gid', :type => :string
231
+ def set_group_domain
232
+ try_with_rescue_in_session do
233
+ domain_network = EnfCli::IPV6Cidr.new options[:domain]
234
+
235
+ # scan if gid not in options
236
+ if options[:gid]
237
+ gid = options[:gid]
238
+ else
239
+ qr_code_info = scan_group_qr_code()
240
+
241
+ # Extract gpk and basename
242
+ gpk_info = qr_code_info.split(",")
243
+ basename = gpk_info[0]
244
+ gpk = gpk_info[1]
245
+
246
+ # Calculate gid
247
+ gid = calculate_gid basename, gpk
248
+ end
249
+
250
+ # create IPV6_NETWORK
251
+ ipv6_network = {
252
+ :prefix => Base64.strict_encode64( domain_network.prefix_bytes ),
253
+ :prefix_length => domain_network.prefix_len
254
+ }
255
+
256
+ # create MODIFIED_GROUP
257
+ modified_group = {
258
+ :gid => Base64.strict_encode64([gid].pack("H*")),
259
+ :domain => ipv6_network
260
+ }
261
+
262
+ # url safe encode gid
263
+ enc_gid = Base64.urlsafe_encode64([gid].pack("H*"))
264
+
265
+ # Post to server
266
+ EnfApi::Iam.instance.update_group enc_gid, modified_group
267
+
268
+ say "Associated group #{gid} with #{domain_network}", :green
269
+ end
270
+ end
271
+
272
+ desc "add-group-to-network", "Associate a DAA Group with /64 network"
273
+ method_option :'network', :type => :string, :required => true
274
+ method_option :'gid', :type => :string
275
+ def add_group_to_network
276
+ try_with_rescue_in_session do
277
+ network = EnfCli::IPV6Cidr.new options[:network]
278
+
279
+ # scan if gid not in options
280
+ if options[:gid]
281
+ gid = options[:gid]
282
+ else
283
+ qr_code_info = scan_group_qr_code()
284
+
285
+ # Extract gpk and basename
286
+ gpk_info = qr_code_info.split(",")
287
+ basename = gpk_info[0]
288
+ gpk = gpk_info[1]
289
+
290
+ # Calculate gid
291
+ gid = calculate_gid basename, gpk
292
+ end
293
+
294
+ # create IPV6_NETWORK
295
+ ipv6_network = {
296
+ :prefix => Base64.strict_encode64( network.prefix_bytes ),
297
+ :prefix_length => network.prefix_len
298
+ }
299
+
300
+ # url safe encode gid
301
+ enc_gid = Base64.urlsafe_encode64([gid].pack("H*"))
302
+
303
+ # Post to server
304
+ EnfApi::Iam.instance.add_group_to_network enc_gid, ipv6_network
305
+
306
+ say "Associated group #{gid} with #{network}", :green
307
+ end
308
+ end
309
+
310
+ desc "provision-group", "Provision a DAA Group"
311
+ method_option :'device-count', :type => :numeric, :required => true
312
+ method_option :'operator', :type => :array, :required => true, :banner => "OPERATOR"
313
+ def provision_group
314
+ try_with_rescue_in_session do
315
+ qr_code_info = scan_group_qr_code()
316
+
317
+ # Extract gpk and basename
318
+ gpk_info = qr_code_info.split(",")
319
+ basename = gpk_info[0]
320
+ gpk = gpk_info[1]
321
+
322
+ # Calculate gid
323
+ gid = calculate_gid basename, gpk
324
+
325
+ # Extract options
326
+ operator = options.operator.join(" ").gsub(/\A"+(.*?)"+\Z/m, '\1')
327
+ quantity = options[:'device-count']
328
+
329
+ # base64 encode all binary data: basename, gpk, gid
330
+ enc_basename = Base64.strict_encode64([basename].pack("H*"))
331
+ enc_gpk = Base64.strict_encode64([gpk].pack("H*"))
332
+ enc_gid = Base64.strict_encode64([gid].pack("H*"))
333
+
334
+ # create NEW_PROVISIONING
335
+ new_provisioning = {
336
+ :group_id => enc_gid,
337
+ :quantity => quantity,
338
+ :operator => operator
339
+ }
340
+
341
+ # create NEW_GROUP
342
+ new_group = {
343
+ :group_public_key => enc_gpk,
344
+ :basename => enc_basename,
345
+ :provisionings => [ new_provisioning ]
346
+ }
347
+
348
+ # Post to server
349
+ EnfApi::Iam.instance.provision_group new_group
350
+
351
+ # display success
352
+ say "Provisioned a new DAA group #{gid}", :green
353
+ end
354
+ end
355
+
356
+ desc "list-network-groups", "List all DAA groups associated with a /64 network"
357
+ method_option :'network', :type => :string, :required => true
358
+ def list_network_groups
359
+ try_with_rescue_in_session do
360
+ # validate network
361
+ network = EnfCli::IPV6Cidr.new options[:network]
362
+
363
+ # call the api
364
+ data = EnfApi::Iam.instance.list_network_groups network.to_s
365
+ groups = data[:body]
366
+
367
+ # display data
368
+ display_groups groups
369
+ end
370
+ end
371
+
372
+ desc "create-endpoint-key", "Generate a pem encoded EC key pair"
373
+ method_option :'key-out-file', :type => :string, :required => true, :banner => '<file>'
374
+ method_option :'public-key-out-file', :type => :string, :required => true, :banner => '<file>'
375
+ def create_endpoint_key
376
+ try_with_rescue_in_session do
377
+ # get options
378
+ keyfile = options['key-out-file']
379
+ pubfile = options['public-key-out-file']
380
+
381
+ # Generate a key pair
382
+ key = OpenSSL::PKey::EC.new('prime256v1')
383
+ key.generate_key
384
+
385
+ # write key pair to file
386
+ write_pem_file keyfile, key.to_pem
387
+
388
+ # write public key to file
389
+ key.private_key = nil
390
+ write_pem_file pubfile, key.to_pem
391
+ end
392
+ end
393
+
394
+ desc "create-endpoint-cert", "Creates and signs a certificate for a particular identity using the provided private key"
395
+ method_option :'identity', :type => :string, :required => true, :banner => '<ipv6>'
396
+ method_option :'key-in-file', :type => :string, :required => true, :banner => '<file>'
397
+ method_option :'cert-out-file', :type => :string, :required => true, :banner => '<file>'
398
+ def create_endpoint_cert
399
+ try_with_rescue_in_session do
400
+ # get options
401
+ ipv6 = EnfCli::IPV6.new(options['identity']).to_s
402
+ keyfile = EnfCli::expand_path(options['key-in-file'])
403
+ certfile = EnfCli::expand_path(options['cert-out-file'])
404
+
405
+ # read the private key
406
+ key = read_ec_key_file keyfile
407
+
408
+ # generate cert
409
+ cert = EnfCli::generate_ec_cert(key, ipv6)
410
+
411
+ # write to file
412
+ write_pem_file certfile, cert.to_pem
413
+ end
414
+ end
415
+
416
+ desc "create-endpoint-in-network", "Create an IPV6 endpoint in /64 network"
417
+ method_option :'network', :type => :string, :required => true, :banner => "<ipv6 network>"
418
+ method_option :'public-key-in-file', :type => :string, :required => true, :banner => "<file>"
419
+ def create_endpoint_in_network
420
+ try_with_rescue_in_session do
421
+ # Read options
422
+ network = EnfCli::IPV6Cidr.new options[:network]
423
+ keyfile = EnfCli::expand_path(options['public-key-in-file'])
424
+
425
+ # read keyfile
426
+ key = read_ec_key_file keyfile
427
+
428
+ # encode public key
429
+ pub = key.public_key
430
+ hex = pub.to_bn.to_s(16)
431
+ pubkey = Base64.strict_encode64([hex].pack("H*"))
432
+
433
+ # create NEW_CREDENTIAL_ECDSA
434
+ new_credential = {
435
+ :type => "ecdsa_p256",
436
+ :key => pubkey
437
+ }
438
+
439
+ # create IPV6_NETWORK
440
+ ipv6_network = {
441
+ :prefix => Base64.strict_encode64( network.prefix_bytes ),
442
+ :prefix_length => network.prefix_len
443
+ }
444
+
445
+ # create NEW_ENDPOINT_AUTH
446
+ new_endpoint = {
447
+ :cred_oneof => {
448
+ :auth => {
449
+ :ecdsa_credential => new_credential,
450
+ :address_request => {
451
+ :address_request_oneof => {
452
+ :network => ipv6_network
453
+ }
454
+ }
455
+ }
456
+ }
457
+ }
458
+
459
+ data = EnfApi::Iam.instance.provision_endpoint new_endpoint
460
+ provisioned_ip = data[:address]
461
+ provisioned_ip = EnfCli::IPV6::ntoh Base64.decode64(provisioned_ip)
462
+
463
+ say "Created new ipv6 endpoint #{provisioned_ip}", :green
464
+ end
465
+ end
466
+
467
+ desc "create-endpoint-with-address", "Create an IPV6 endpoint in /64 network with user specified address"
468
+ method_option :'address', :type => :string, :required => true, :banner => "<ipv6 address>"
469
+ method_option :'public-key-in-file', :type => :string, :required => true, :banner => "<file>"
470
+ def create_endpoint_with_address
471
+ try_with_rescue_in_session do
472
+ # Read address
473
+ ipv6_address = EnfCli::IPV6.new options[:address]
474
+
475
+ # read key
476
+ keyfile = EnfCli::expand_path(options['public-key-in-file'])
477
+ key = read_ec_key_file keyfile
478
+ pub = key.public_key
479
+
480
+ # create_endpoint
481
+ provision_endpoint ipv6_address, pub
482
+
483
+ end
484
+ end
485
+
486
+ desc "create-endpoint-from-cert", "Create an IPV6 endpoint in /64 network with address from cert"
487
+ method_option :'cert-in-file', :type => :string, :required => true, :banner => "<file>"
488
+ def create_endpoint_from_cert
489
+ try_with_rescue_in_session do
490
+ # read options
491
+ certfile = EnfCli::expand_path(options['cert-in-file'])
492
+ raise EnfCli::ERROR, "#{certfile} does not exist!" unless File.exists?(certfile)
493
+
494
+ # read cert file
495
+ rawcert = File.read certfile
496
+ cert = OpenSSL::X509::Certificate.new rawcert
497
+
498
+ # get subject
499
+ subject = cert.subject.to_a
500
+
501
+ # get ipv6 CN record
502
+ cn = subject.select { |d| "CN".eql?( d[0].upcase ) }
503
+ ipv6_address = EnfCli::IPV6.new cn[0][1]
504
+
505
+ # get the public key
506
+ pub = cert.public_key.public_key
507
+
508
+ # create_endpoint
509
+ provision_endpoint ipv6_address, pub
510
+
511
+ end
512
+ end
513
+
514
+ desc "update-endpoint-key", "Update an IPV6 endpoint's credentials"
515
+ method_option :'address', :type => :string, :required => true, :banner => "<ipv6 address>"
516
+ method_option :'public-key-in-file', :type => :string, :required => true, :banner => "<file>"
517
+ def update_endpoint_key
518
+ try_with_rescue_in_session do
519
+ # Read address
520
+ ipv6_address = EnfCli::IPV6.new options[:address]
521
+
522
+ # read key
523
+ keyfile = EnfCli::expand_path(options['public-key-in-file'])
524
+ key = read_ec_key_file keyfile
525
+ pub = key.public_key
526
+
527
+ # update endpoint
528
+ update_endpoint_credentials ipv6_address, pub
529
+ end
530
+ end
531
+
532
+ desc "update-endpoint-cert", "Update an IPV6 endpoint's credentials"
533
+ method_option :'cert-in-file', :type => :string, :required => true, :banner => "<file>"
534
+ def update_endpoint_cert
535
+ try_with_rescue_in_session do
536
+ # read options
537
+ certfile = EnfCli::expand_path(options['cert-in-file'])
538
+ raise EnfCli::ERROR, "#{certfile} does not exist!" unless File.exists?(certfile)
539
+
540
+ # read cert file
541
+ rawcert = File.read certfile
542
+ cert = OpenSSL::X509::Certificate.new rawcert
543
+
544
+ # get subject
545
+ subject = cert.subject.to_a
546
+
547
+ # get ipv6 CN record
548
+ cn = subject.select { |d| "CN".eql?( d[0].upcase ) }
549
+ ipv6_address = EnfCli::IPV6.new cn[0][1]
550
+
551
+ # get the public key
552
+ pub = cert.public_key.public_key
553
+
554
+ # update endpoint
555
+ update_endpoint_credentials ipv6_address, pub
556
+ end
557
+ end
558
+
559
+ end
560
+ end
561
+ end
562
+
@@ -0,0 +1,18 @@
1
+ #
2
+ # Copyright 2018 Xaptum,Inc
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ module EnfCli
17
+ VERSION = '3.3.2-alpha'
18
+ end