aptible-api 0.9.13 → 0.9.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3f50076bb1d7c9974c01a2ead4175981911a9657
4
- data.tar.gz: 6a42f11ac1f0ea710c88ee9702f78816dbd08c67
3
+ metadata.gz: fca6144f0c1ad810abee436b30ed9b3e03df144f
4
+ data.tar.gz: 137b04d0be16b37453a23fe5d26b974178e7edc5
5
5
  SHA512:
6
- metadata.gz: d912b54ddad9f65164b1bb0bf1429c1d1b16bef07f3db3b4cf07fd4e4d7bfdbac99871523386c0099cfff06ecfe382520ec47f396eb1794f7c142b63ad8e476c
7
- data.tar.gz: f7ffaab3069a67be24ff36804ad70bfa9dfd63f3d2faf5570b46700bb1acbe0b53d639e85b1d1b785b242be77f374c60f2ffcec8070d7bda183ccb4096c36f4d
6
+ metadata.gz: c6fd7583ad12fc618073bf6fccc4c35bbc4b50836878c37837f166624d8eb47f5da9b79b5ab52ea15fe10657add1b64f4423290a3675395228c8ba992d628548
7
+ data.tar.gz: f577e098d4e40e56c00f18278e097e486524e5a4699b10519e7141624911274f0b07947f6a39082b18412d847b52a825ebff7b2f263070066821a431d6f7afcf
@@ -6,6 +6,8 @@ module Aptible
6
6
  belongs_to :account
7
7
  belongs_to :resource
8
8
 
9
+ has_many :ssh_portal_connections
10
+
9
11
  field :id
10
12
  field :type
11
13
  field :status
@@ -40,6 +42,62 @@ module Aptible
40
42
  def failed?
41
43
  status == 'failed'
42
44
  end
45
+
46
+ def with_ssh_cmd(private_key_file)
47
+ # We expect that the public key will be found next to the private key,
48
+ # which is also what SSH itself expects. If that's not the case, then
49
+ # we'll just fail. The Aptible CLI will always ensure credentials are
50
+ # set up properly (other consumers are of course responsible for doing
51
+ # the same!).
52
+ public_key_file = "#{private_key_file}.pub"
53
+
54
+ private_key = File.read(private_key_file)
55
+ public_key = File.read(public_key_file)
56
+
57
+ connection = create_ssh_portal_connection!(ssh_public_key: public_key)
58
+ certificate = connection.ssh_certificate_body
59
+
60
+ with_temporary_id(private_key, public_key, certificate) do |id_file|
61
+ cmd = [
62
+ 'ssh',
63
+ "#{connection.ssh_user}@#{account.bastion_host}",
64
+ '-p', account.ssh_portal_port.to_s
65
+ ] + ['-i', id_file]
66
+
67
+ # If we aren't allowed to create a pty, then we shouldn't try to
68
+ # allocate once, or we'll get an awkward error.
69
+ cmd << '-T' unless connection.ssh_pty
70
+
71
+ yield cmd, connection
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def with_temporary_id(private_key, public_key, certificate)
78
+ # Most versions of OpenSSH don't support specifying the SSH certificate
79
+ # to use when connecting, so we create a temporary directory with the
80
+ # credentials and the certificate. From a security perspective, the CLI
81
+ # makes sure to use an Aptible-CLI only SSH key to minimize exposure
82
+ # should we fail to clean out the temporary directory.
83
+ Dir.mktmpdir do |dir|
84
+ private_key_file = File.join(dir, 'id_rsa')
85
+ public_key_file = "#{private_key_file}.pub"
86
+ certificate_file = "#{private_key_file}-cert.pub"
87
+
88
+ pairs = [
89
+ [private_key, private_key_file],
90
+ [public_key, public_key_file],
91
+ [certificate, certificate_file]
92
+ ]
93
+
94
+ pairs.each do |contents, file|
95
+ File.open(file, 'w', 0o600) { |f| f.write(contents) }
96
+ end
97
+
98
+ yield private_key_file
99
+ end
100
+ end
43
101
  end
44
102
  end
45
103
  end
@@ -30,3 +30,4 @@ require 'aptible/api/permission'
30
30
  require 'aptible/api/release'
31
31
  require 'aptible/api/service'
32
32
  require 'aptible/api/vhost'
33
+ require 'aptible/api/ssh_portal_connection'
@@ -0,0 +1,11 @@
1
+ module Aptible
2
+ module Api
3
+ class SshPortalConnection < Resource
4
+ belongs_to :account
5
+ belongs_to :log_drain
6
+ belongs_to :operation
7
+
8
+ field :action
9
+ end
10
+ end
11
+ end
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module Api
3
- VERSION = '0.9.13'.freeze
3
+ VERSION = '0.9.14'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aptible::Api::Operation do
4
+ describe '#with_ssh_cmd' do
5
+ shared_examples '#with_ssh_cmd examples' do
6
+ let(:account) do
7
+ Aptible::Api::Account.new.tap do |account|
8
+ account.stub(
9
+ bastion_host: 'foo-bastion.com',
10
+ ssh_portal_port: 1022
11
+ )
12
+ end
13
+ end
14
+
15
+ let(:ssh_portal_connection) do
16
+ Aptible::Api::SshPortalConnection.new.tap do |connection|
17
+ connection.stub(
18
+ ssh_user: 'foo-user',
19
+ ssh_certificate_body: 'some certificate',
20
+ ssh_pty: ssh_pty
21
+ )
22
+ end
23
+ end
24
+
25
+ subject do
26
+ described_class.new.tap do |operation|
27
+ operation.stub(account: account)
28
+ end
29
+ end
30
+
31
+ let!(:work_dir) { Dir.mktmpdir }
32
+ after { FileUtils.remove_entry work_dir }
33
+
34
+ let(:private_key_file) { File.join(work_dir, 'id_rsa') }
35
+ let(:public_key_file) { "#{private_key_file}.pub" }
36
+
37
+ before do
38
+ File.open(private_key_file, 'w') { |f| f.write('some private key') }
39
+ File.open(public_key_file, 'w') { |f| f.write('some public key') }
40
+ end
41
+
42
+ it 'yields usable SSH connection arguments' do
43
+ expect(subject).to receive(:create_ssh_portal_connection!)
44
+ .with(ssh_public_key: 'some public key')
45
+ .and_return(ssh_portal_connection)
46
+
47
+ has_yielded = false
48
+
49
+ subject.with_ssh_cmd(private_key_file) do |cmd, connection|
50
+ _, dest, _, port, _, id_file, = cmd
51
+
52
+ expect(dest).to eq('foo-user@foo-bastion.com')
53
+ expect(port).to eq('1022')
54
+ expect(File.read(id_file)).to eq('some private key')
55
+ expect(File.read("#{id_file}.pub")).to eq('some public key')
56
+ expect(File.read("#{id_file}-cert.pub")).to eq('some certificate')
57
+
58
+ expect(File.readable?(id_file)).to be_truthy
59
+ expect(File.writable?(id_file)).to be_truthy
60
+
61
+ expect(File.world_readable?(id_file)).to be_falsey
62
+ expect(File.world_writable?(id_file)).to be_falsey
63
+
64
+ expect(connection).to be(ssh_portal_connection)
65
+
66
+ if ssh_pty
67
+ expect(cmd).not_to include('-T')
68
+ else
69
+ expect(cmd.last).to eq('-T')
70
+ end
71
+
72
+ has_yielded = true
73
+ end
74
+
75
+ expect(has_yielded).to be_truthy
76
+ end
77
+ end
78
+
79
+ context 'with a PTY' do
80
+ let(:ssh_pty) { true }
81
+ include_examples '#with_ssh_cmd examples'
82
+ end
83
+
84
+ context 'without a PTY' do
85
+ let(:ssh_pty) { false }
86
+ include_examples '#with_ssh_cmd examples'
87
+ end
88
+ end
89
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aptible-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.13
4
+ version: 0.9.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank Macreery
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-30 00:00:00.000000000 Z
11
+ date: 2016-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aptible-resource
@@ -198,9 +198,11 @@ files:
198
198
  - lib/aptible/api/release.rb
199
199
  - lib/aptible/api/resource.rb
200
200
  - lib/aptible/api/service.rb
201
+ - lib/aptible/api/ssh_portal_connection.rb
201
202
  - lib/aptible/api/version.rb
202
203
  - lib/aptible/api/vhost.rb
203
204
  - spec/aptible/api/agent_spec.rb
205
+ - spec/aptible/api/operation_spec.rb
204
206
  - spec/aptible/api/resource_spec.rb
205
207
  - spec/aptible/api_spec.rb
206
208
  - spec/shared/with_env.rb
@@ -231,6 +233,7 @@ specification_version: 4
231
233
  summary: Ruby client for api.aptible.com
232
234
  test_files:
233
235
  - spec/aptible/api/agent_spec.rb
236
+ - spec/aptible/api/operation_spec.rb
234
237
  - spec/aptible/api/resource_spec.rb
235
238
  - spec/aptible/api_spec.rb
236
239
  - spec/shared/with_env.rb