aspera-cli 4.9.0 → 4.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/README.md +749 -667
- data/docs/test_env.conf +6 -4
- data/examples/dascli +9 -4
- data/examples/faspex4.rb +1 -1
- data/examples/node.rb +1 -1
- data/examples/server.rb +1 -1
- data/lib/aspera/cli/info.rb +2 -2
- data/lib/aspera/cli/main.rb +1 -1
- data/lib/aspera/cli/manager.rb +6 -4
- data/lib/aspera/cli/plugin.rb +5 -1
- data/lib/aspera/cli/plugins/aoc.rb +1 -1
- data/lib/aspera/cli/plugins/config.rb +100 -84
- data/lib/aspera/cli/plugins/faspex.rb +3 -3
- data/lib/aspera/cli/plugins/faspex5.rb +5 -1
- data/lib/aspera/cli/plugins/node.rb +24 -4
- data/lib/aspera/cli/plugins/preview.rb +1 -1
- data/lib/aspera/cli/transfer_agent.rb +3 -2
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/environment.rb +19 -1
- data/lib/aspera/fasp/agent_direct.rb +4 -0
- data/lib/aspera/fasp/agent_httpgw.rb +147 -88
- data/lib/aspera/fasp/installation.rb +4 -4
- data/lib/aspera/fasp/parameters.rb +13 -1
- data/lib/aspera/fasp/parameters.yaml +5 -2
- data/lib/aspera/fasp/resume_policy.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +36 -98
- data/lib/aspera/keychain/macos_security.rb +127 -57
- data/lib/aspera/persistency_folder.rb +3 -2
- data/lib/aspera/proxy_auto_config.rb +12 -5
- data/lib/aspera/rest.rb +19 -10
- data.tar.gz.sig +0 -0
- metadata +5 -33
- metadata.gz.sig +0 -0
@@ -1,91 +1,161 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# https://github.com/fastlane-community/security
|
4
|
+
require 'aspera/cli/info'
|
4
5
|
|
5
6
|
# enhance the gem to support other keychains
|
6
|
-
module
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
module Aspera
|
8
|
+
module Keychain
|
9
|
+
module MacosSecurity
|
10
|
+
# keychain based on macOS keychain, using `security` cmmand line
|
11
|
+
class Keychain
|
12
|
+
DOMAINS = %i[user system common dynamic].freeze
|
13
|
+
LIST_OPTIONS={
|
14
|
+
domain: :c
|
15
|
+
}
|
16
|
+
ADD_PASS_OPTIONS={
|
17
|
+
account: :a,
|
18
|
+
creator: :c,
|
19
|
+
type: :C,
|
20
|
+
domain: :d,
|
21
|
+
kind: :D,
|
22
|
+
value: :G,
|
23
|
+
comment: :j,
|
24
|
+
label: :l,
|
25
|
+
path: :p,
|
26
|
+
port: :P,
|
27
|
+
protocol: :r,
|
28
|
+
server: :s,
|
29
|
+
service: :s,
|
30
|
+
auth: :t,
|
31
|
+
password: :w,
|
32
|
+
getpass: :g
|
33
|
+
}.freeze
|
34
|
+
class << self
|
35
|
+
def execute(command,options=nil,supported=nil,lastopt=nil)
|
36
|
+
url = options&.delete(:url)
|
37
|
+
if !url.nil?
|
38
|
+
uri = URI.parse(url)
|
39
|
+
raise 'only https' unless uri.scheme.eql?('https')
|
40
|
+
options[:protocol] = 'htps'
|
41
|
+
raise 'host required in URL' if uri.host.nil?
|
42
|
+
options[:server] = uri.host
|
43
|
+
options[:path] = uri.path unless ['','/'].include?(uri.path)
|
44
|
+
options[:port] = uri.port unless uri.port.eql?(443) && !url.include?(':443/')
|
45
|
+
end
|
46
|
+
cmd=['security',command]
|
47
|
+
options&.each do |k,v|
|
48
|
+
raise "unknown option: #{k}" unless supported.has_key?(k)
|
49
|
+
next if v.nil?
|
50
|
+
cmd.push("-#{supported[k]}")
|
51
|
+
cmd.push(v.shellescape) unless v.empty?
|
52
|
+
end
|
53
|
+
cmd.push(lastopt) unless lastopt.nil?
|
54
|
+
Log.log.debug("executing>>#{cmd.join(' ')}")
|
55
|
+
result=%x(#{cmd.join(' ')} 2>&1)
|
56
|
+
Log.log.debug("result>>[#{result}]")
|
57
|
+
return result
|
58
|
+
end
|
14
59
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
options
|
28
|
-
|
29
|
-
|
60
|
+
def keychains(output)
|
61
|
+
output.split("\n").collect { |line| new(line.strip.gsub(/^"|"$/, '')) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def default
|
65
|
+
keychains(execute('default-keychain')).first
|
66
|
+
end
|
67
|
+
|
68
|
+
def login
|
69
|
+
keychains(execute('login-keychain')).first
|
70
|
+
end
|
71
|
+
|
72
|
+
def list(options={})
|
73
|
+
raise ArgumentError,"Invalid domain #{options[:domain]}, expected one of: #{DOMAINS}" unless options[:domain].nil? || DOMAINS.include?(options[:domain])
|
74
|
+
keychains(execute('list-keychains',options,LIST_OPTIONS))
|
75
|
+
end
|
76
|
+
|
77
|
+
def by_name(name)
|
78
|
+
list.find{|kc|kc.path.end_with?("/#{name}.keychain-db")}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
attr_reader :path
|
82
|
+
|
83
|
+
def initialize(path)
|
84
|
+
@path = path
|
85
|
+
end
|
86
|
+
|
87
|
+
def decode_hex_blob(string)
|
88
|
+
[string].pack('H*').force_encoding('UTF-8')
|
89
|
+
end
|
90
|
+
|
91
|
+
def password(operation,passtype,options)
|
92
|
+
raise "wrong operation: #{operation}" unless %i[add find delete].include?(operation)
|
93
|
+
raise "wrong passtype: #{passtype}" unless %i[generic internet].include?(passtype)
|
94
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
95
|
+
missing=(operation.eql?(:add) ? %i[account service password] : %i[label])-options.keys
|
96
|
+
raise "missing options: #{missing}" unless missing.empty?
|
97
|
+
options[:getpass]='' if operation.eql?(:find)
|
98
|
+
output=self.class.execute("#{operation}-#{passtype}-password",options,ADD_PASS_OPTIONS,@path)
|
99
|
+
raise output.gsub(/^.*: /,'') if output.start_with?('security: ')
|
100
|
+
return nil unless operation.eql?(:find)
|
101
|
+
attributes = {}
|
102
|
+
output.split("\n").each do |line|
|
103
|
+
case line
|
104
|
+
when /^keychain: "(.+)"/
|
105
|
+
# ignore
|
106
|
+
when /0x00000007 .+="(.+)"/
|
107
|
+
attributes['label'] = Regexp.last_match(1)
|
108
|
+
when /"(\w{4})".+="(.+)"/
|
109
|
+
attributes[Regexp.last_match(1)] = Regexp.last_match(2)
|
110
|
+
when /"(\w{4})"<blob>=0x([[:xdigit:]]+)/
|
111
|
+
attributes[Regexp.last_match(1)] = decode_hex_blob(Regexp.last_match(2))
|
112
|
+
when /^password: "(.+)"/
|
113
|
+
attributes['password'] = Regexp.last_match(1)
|
114
|
+
when /^password: 0x([[:xdigit:]]+)/
|
115
|
+
attributes['password'] = decode_hex_blob(Regexp.last_match(1))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
return attributes
|
30
119
|
end
|
31
|
-
flags = [orig_flags_for_options(options)]
|
32
|
-
flags.push(keychain.filename) unless keychain.nil?
|
33
|
-
flags.join(' ')
|
34
120
|
end
|
35
121
|
end
|
36
|
-
end
|
37
|
-
end
|
38
122
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
class MacosSecurity
|
43
|
-
def initialize(name=nil)
|
44
|
-
@keychain = name.nil? ? Security::Keychain.default_keychain : Security::Keychain.by_name(name)
|
123
|
+
class MacosSystem
|
124
|
+
def initialize(name=nil,password=nil)
|
125
|
+
@keychain = name.nil? ? MacosSecurity::Keychain.default_keychain : MacosSecurity::Keychain.by_name(name)
|
45
126
|
raise "no such keychain #{name}" if @keychain.nil?
|
46
127
|
end
|
47
128
|
|
48
129
|
def set(options)
|
49
130
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
50
|
-
unsupported = options.keys - %i[username url
|
131
|
+
unsupported = options.keys - %i[label username password url description]
|
51
132
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
52
|
-
|
53
|
-
|
54
|
-
url = options[:url]
|
55
|
-
raise 'options shall have url' if url.nil?
|
56
|
-
secret = options[:secret]
|
57
|
-
raise 'options shall have secret' if secret.nil?
|
58
|
-
raise 'set not implemented'
|
133
|
+
@keychain.password(:add,:generic, service: options[:label],
|
134
|
+
account: options[:username] || 'none', password: options[:password], comment: options[:description])
|
59
135
|
end
|
60
136
|
|
61
137
|
def get(options)
|
62
138
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
63
|
-
unsupported = options.keys - %i[
|
139
|
+
unsupported = options.keys - %i[label]
|
64
140
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
65
|
-
|
66
|
-
raise 'options shall have username' if username.nil?
|
67
|
-
url = options[:url]
|
68
|
-
raise 'options shall have url' if url.nil?
|
69
|
-
info = Security::InternetPassword.find(keychain: @keychain, url: url, account: username)
|
141
|
+
info = @keychain.password(:find,:generic,label: options[:label])
|
70
142
|
raise 'not found' if info.nil?
|
71
143
|
result = options.clone
|
72
|
-
result[:secret] = info
|
73
|
-
result[:description] = info
|
144
|
+
result[:secret] = info['password']
|
145
|
+
result[:description] = info['icmt']
|
74
146
|
return result
|
75
147
|
end
|
76
148
|
|
77
149
|
def list
|
78
|
-
|
150
|
+
# the only way to list is `dump-keychain` which triggers security alert
|
151
|
+
raise 'list not implemented, use macos keychain app'
|
79
152
|
end
|
80
153
|
|
81
154
|
def delete(options)
|
82
155
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
83
|
-
unsupported = options.keys - %i[
|
156
|
+
unsupported = options.keys - %i[label]
|
84
157
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
85
|
-
|
86
|
-
raise 'options shall have username' if username.nil?
|
87
|
-
url = options[:url]
|
88
|
-
raise "delete not implemented #{url}"
|
158
|
+
raise 'delete not implemented, use macos keychain app'
|
89
159
|
end
|
90
160
|
end
|
91
161
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
4
|
require 'aspera/log'
|
5
|
+
require 'aspera/environment'
|
5
6
|
|
6
7
|
# search: persistency_folder PersistencyFolder
|
7
8
|
|
@@ -38,7 +39,7 @@ module Aspera
|
|
38
39
|
Log.log.debug("persistency saving: #{persist_filepath}")
|
39
40
|
File.delete(persist_filepath) if File.exist?(persist_filepath)
|
40
41
|
File.write(persist_filepath,value)
|
41
|
-
|
42
|
+
Environment.restrict_file_access(persist_filepath)
|
42
43
|
@cache[object_id] = value
|
43
44
|
end
|
44
45
|
|
@@ -68,7 +69,7 @@ module Aspera
|
|
68
69
|
def id_to_filepath(object_id)
|
69
70
|
raise 'object_id: only String supported' unless object_id.is_a?(String)
|
70
71
|
FileUtils.mkdir_p(@folder)
|
71
|
-
|
72
|
+
Environment.restrict_file_access(@folder)
|
72
73
|
return File.join(@folder,"#{object_id}#{FILE_SUFFIX}")
|
73
74
|
#.gsub(/[^a-z]+/,FILE_FIELD_SEPARATOR)
|
74
75
|
end
|
@@ -52,13 +52,15 @@ END_OF_JAVASCRIPT
|
|
52
52
|
|
53
53
|
public
|
54
54
|
|
55
|
+
attr_writer :proxy_user,:proxy_pass
|
56
|
+
|
55
57
|
# @param proxy_auto_config the proxy auto config script to be evaluated
|
56
58
|
def initialize(proxy_auto_config)
|
57
59
|
# user provided javascript with FindProxyForURL function
|
58
60
|
@proxy_auto_config = proxy_auto_config
|
59
61
|
# avoid multiple execution, this does not support load balancing
|
60
62
|
@cache = {}
|
61
|
-
@pac_functions = nil
|
63
|
+
@proxy_user=@proxy_pass=@pac_functions = nil
|
62
64
|
end
|
63
65
|
|
64
66
|
def register_uri_generic
|
@@ -106,23 +108,28 @@ END_OF_JAVASCRIPT
|
|
106
108
|
case parts.shift
|
107
109
|
when 'DIRECT'
|
108
110
|
raise 'DIRECT has no param' unless parts.empty?
|
111
|
+
Log.log.debug('ignoring proxy DIRECT')
|
109
112
|
when 'PROXY'
|
110
113
|
addr_port = parts.shift
|
111
114
|
raise 'PROXY shall have one param' unless addr_port.is_a?(String) && parts.empty?
|
112
115
|
begin
|
113
116
|
# PAC proxy addresses are <host>:<port>
|
114
117
|
if /:[0-9]+$/.match?(addr_port)
|
115
|
-
|
116
|
-
|
118
|
+
uri=URI.parse("proxy://#{addr_port}")
|
119
|
+
# ruby v>2.6 allows
|
120
|
+
uri.user=@proxy_user
|
121
|
+
uri.password=@proxy_pass
|
122
|
+
uri_list.push(uri)
|
117
123
|
else
|
118
124
|
Log.log.warn("PAC: PROXY must be <address>:<port>, ignoring #{addr_port}")
|
119
125
|
end
|
120
|
-
rescue StandardError
|
121
|
-
Log.log.warn("PAC: cannot parse #{addr_port}")
|
126
|
+
rescue StandardError => e
|
127
|
+
Log.log.warn("PAC: cannot parse #{addr_port} #{e}")
|
122
128
|
end
|
123
129
|
else Log.log.warn("PAC: ignoring proxy type #{parts.first}: not supported")
|
124
130
|
end
|
125
131
|
end
|
132
|
+
Log.log.debug("Proxies: #{uri_list}")
|
126
133
|
return uri_list
|
127
134
|
end
|
128
135
|
end
|
data/lib/aspera/rest.rb
CHANGED
@@ -37,7 +37,9 @@ module Aspera
|
|
37
37
|
user_agent: 'Ruby',
|
38
38
|
download_partial_suffix: '.http_partial',
|
39
39
|
# a lambda which takes the Net::HTTP as arg, use this to change parameters
|
40
|
-
session_cb: nil
|
40
|
+
session_cb: nil,
|
41
|
+
proxy_user: nil,
|
42
|
+
proxy_pass: nil
|
41
43
|
}
|
42
44
|
|
43
45
|
class << self
|
@@ -75,6 +77,21 @@ module Aspera
|
|
75
77
|
end
|
76
78
|
return uri
|
77
79
|
end
|
80
|
+
|
81
|
+
def start_http_session(base_url)
|
82
|
+
uri = build_uri(base_url)
|
83
|
+
# this honors http_proxy env var
|
84
|
+
http_session = Net::HTTP.new(uri.host, uri.port)
|
85
|
+
http_session.proxy_user=proxy_user
|
86
|
+
http_session.proxy_pass=proxy_pass
|
87
|
+
http_session.use_ssl = uri.scheme.eql?('https')
|
88
|
+
http_session.set_debug_output($stdout) if debug
|
89
|
+
# set http options in callback, such as timeout and cert. verification
|
90
|
+
session_cb&.call(http_session)
|
91
|
+
# manually start session for keep alive (if supported by server, else, session is closed every time)
|
92
|
+
http_session.start
|
93
|
+
return http_session
|
94
|
+
end
|
78
95
|
end
|
79
96
|
|
80
97
|
private
|
@@ -82,15 +99,7 @@ module Aspera
|
|
82
99
|
# create and start keep alive connection on demand
|
83
100
|
def http_session
|
84
101
|
if @http_session.nil?
|
85
|
-
|
86
|
-
# this honors http_proxy env var
|
87
|
-
@http_session = Net::HTTP.new(uri.host, uri.port)
|
88
|
-
@http_session.use_ssl = uri.scheme.eql?('https')
|
89
|
-
@http_session.set_debug_output($stdout) if self.class.debug
|
90
|
-
# set http options in callback, such as timeout and cert. verification
|
91
|
-
self.class.session_cb&.call(@http_session)
|
92
|
-
# manually start session for keep alive (if supported by server, else, session is closed every time)
|
93
|
-
@http_session.start
|
102
|
+
@http_session=self.class.start_http_session(@params[:base_url])
|
94
103
|
end
|
95
104
|
return @http_session
|
96
105
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aspera-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Laurent Martin
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
ZjkOWbUc1aLIsfaQFHWyNfisY9X2RgkFHjX0p5493wnoA7aWh52MUhc145npFh8z
|
36
36
|
v4P9xwkT02Shkert4B4iwNvVjoAUGk+J4090svZCroAyXBjon5LV7MJ4fyw=
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2022-
|
38
|
+
date: 2022-12-01 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: execjs
|
@@ -128,19 +128,19 @@ dependencies:
|
|
128
128
|
- !ruby/object:Gem::Version
|
129
129
|
version: '2.0'
|
130
130
|
- !ruby/object:Gem::Dependency
|
131
|
-
name:
|
131
|
+
name: symmetric-encryption
|
132
132
|
requirement: !ruby/object:Gem::Requirement
|
133
133
|
requirements:
|
134
134
|
- - "~>"
|
135
135
|
- !ruby/object:Gem::Version
|
136
|
-
version: '
|
136
|
+
version: '4.6'
|
137
137
|
type: :runtime
|
138
138
|
prerelease: false
|
139
139
|
version_requirements: !ruby/object:Gem::Requirement
|
140
140
|
requirements:
|
141
141
|
- - "~>"
|
142
142
|
- !ruby/object:Gem::Version
|
143
|
-
version: '
|
143
|
+
version: '4.6'
|
144
144
|
- !ruby/object:Gem::Dependency
|
145
145
|
name: terminal-table
|
146
146
|
requirement: !ruby/object:Gem::Requirement
|
@@ -197,20 +197,6 @@ dependencies:
|
|
197
197
|
- - "~>"
|
198
198
|
- !ruby/object:Gem::Version
|
199
199
|
version: '1.2'
|
200
|
-
- !ruby/object:Gem::Dependency
|
201
|
-
name: websocket-client-simple
|
202
|
-
requirement: !ruby/object:Gem::Requirement
|
203
|
-
requirements:
|
204
|
-
- - "~>"
|
205
|
-
- !ruby/object:Gem::Version
|
206
|
-
version: '0.3'
|
207
|
-
type: :runtime
|
208
|
-
prerelease: false
|
209
|
-
version_requirements: !ruby/object:Gem::Requirement
|
210
|
-
requirements:
|
211
|
-
- - "~>"
|
212
|
-
- !ruby/object:Gem::Version
|
213
|
-
version: '0.3'
|
214
200
|
- !ruby/object:Gem::Dependency
|
215
201
|
name: xml-simple
|
216
202
|
requirement: !ruby/object:Gem::Requirement
|
@@ -225,20 +211,6 @@ dependencies:
|
|
225
211
|
- - "~>"
|
226
212
|
- !ruby/object:Gem::Version
|
227
213
|
version: '1.0'
|
228
|
-
- !ruby/object:Gem::Dependency
|
229
|
-
name: grpc
|
230
|
-
requirement: !ruby/object:Gem::Requirement
|
231
|
-
requirements:
|
232
|
-
- - "~>"
|
233
|
-
- !ruby/object:Gem::Version
|
234
|
-
version: '1.0'
|
235
|
-
type: :development
|
236
|
-
prerelease: false
|
237
|
-
version_requirements: !ruby/object:Gem::Requirement
|
238
|
-
requirements:
|
239
|
-
- - "~>"
|
240
|
-
- !ruby/object:Gem::Version
|
241
|
-
version: '1.0'
|
242
214
|
- !ruby/object:Gem::Dependency
|
243
215
|
name: mimemagic
|
244
216
|
requirement: !ruby/object:Gem::Requirement
|
metadata.gz.sig
CHANGED
Binary file
|