├── .gitignore
├── .rubocop.yml
├── .ruby-version
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── TESTING.md
├── lib
├── vagrant-s3auth.rb
└── vagrant-s3auth
│ ├── errors.rb
│ ├── extension
│ └── downloader.rb
│ ├── middleware
│ └── expand_s3_urls.rb
│ ├── plugin.rb
│ ├── util.rb
│ └── version.rb
├── locales
└── en.yml
├── test
├── box
│ ├── minimal
│ ├── minimal.box
│ ├── public-minimal
│ └── public-minimal.box
├── cleanup.rb
├── run.bats
├── setup.rb
└── support.rb
└── vagrant-s3auth.gemspec
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | pkg
4 | *.gem
5 | .env
6 | Gemfile.lock
7 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | Lint/AssignmentInCondition:
2 | Enabled: false
3 |
4 | Metrics/AbcSize:
5 | Max: 40
6 |
7 | Metrics/CyclomaticComplexity:
8 | Max: 12
9 |
10 | Metrics/LineLength:
11 | Max: 100
12 |
13 | Metrics/MethodLength:
14 | CountComments: false
15 | Max: 25
16 |
17 | Metrics/PerceivedComplexity:
18 | Max: 15
19 |
20 | Style/AlignParameters:
21 | EnforcedStyle: with_fixed_indentation
22 |
23 | Style/Documentation:
24 | Enabled: false
25 |
26 | Style/FileName:
27 | Enabled: false
28 |
29 | Style/RescueModifier:
30 | Enabled: false
31 |
32 | Style/SignalException:
33 | EnforcedStyle: only_raise
34 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.2.3
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: ruby
4 | rvm:
5 | - 2.2.3
6 |
7 | addons:
8 | apt:
9 | packages:
10 | - bsdtar
11 | - libxslt1.1
12 |
13 | before_install:
14 | # Install Bats, the Bash testing framework
15 | - npm install bats
16 |
17 | # Speed up Nokogiri installation substantially by using precompiled libxslt
18 | - bundle config build.nokogiri --use-system-libraries
19 |
20 | # Older versions of Vagrant can't handle the current version of Bundler, which
21 | # ships with Travis.
22 | - |
23 | if [[ "$BUNDLER_VERSION" ]]
24 | then
25 | rvm @default,@global do gem uninstall bundler --all --executables
26 | gem install bundler -v "$BUNDLER_VERSION"
27 | fi
28 | - bundle --version
29 |
30 | before_script:
31 | - test/setup.rb
32 |
33 | after_script:
34 | - test/cleanup.rb
35 |
36 | env:
37 | global:
38 | - VAGRANT_S3AUTH_ATLAS_BOX_NAME="travis-$TRAVIS_JOB_NUMBER"
39 | - VAGRANT_S3AUTH_BUCKET="travis-$TRAVIS_JOB_NUMBER.vagrant-s3auth.com"
40 | - VAGRANT_S3AUTH_REGION_NONSTANDARD=eu-west-1
41 | - VAGRANT_S3AUTH_BOX_BASE=minimal
42 | matrix:
43 | - VAGRANT_VERSION=master BUNDLER_VERSION=
44 | - VAGRANT_VERSION=v1.9.1 BUNDLER_VERSION=
45 | - VAGRANT_VERSION=v1.8.7 BUNDLER_VERSION=1.12.5
46 | - VAGRANT_VERSION=v1.7.4 BUNDLER_VERSION=1.10.5
47 | - VAGRANT_VERSION=v1.6.5 BUNDLER_VERSION=1.6.9
48 | - VAGRANT_VERSION=v1.5.1 BUNDLER_VERSION=1.5.3
49 |
50 | deploy:
51 | provider: rubygems
52 | api_key:
53 | secure: b7ZiPX6EfA4DNV6B65ZvVJF8Xswne4N0MdIqwTkyQ5//0+3hSHg0ChTvjeb+eeTcPFiYxuh0UvXqJMtxi8hCJub03aJ5qeDDm6FJeM7WqsHmXx6A6UGFxnCTi6z7IaaBCs71jygzdjN6AaKOV9PuvhD079dci/yylr0SDHQgvrY=
54 | on:
55 | tags: true
56 | repo: WhoopInc/vagrant-s3auth
57 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.3.2
2 |
3 | **6 January 2016**
4 |
5 | Enhancements:
6 |
7 | * upgrade to AWS SDK v2.6.44
8 |
9 | ## 1.3.1
10 |
11 | **30 December 2016**
12 |
13 | Fixes:
14 |
15 | * suppress warning about invalid region with certain buckets ([#31])
16 |
17 | ## 1.3.0
18 |
19 | **18 January 2016**
20 |
21 | Enhancements:
22 |
23 | * upgrade to AWS SDK v2.2.10
24 |
25 | Fixes:
26 |
27 | * allow box update checks when offline ([#26])
28 | * support the Vagrant 1.8.x series ([#27])
29 |
30 | ## 1.2.0
31 |
32 | **20 August 2015**
33 |
34 | Enhancements:
35 |
36 | * output the discovered AWS access key and its source (environment variable or
37 | profile) when downloading an authenticated S3 box ([#21])
38 |
39 | Thanks, [@Daemoen][Daemoen]!
40 |
41 | ## 1.1.1
42 |
43 | **6 August 2015**
44 |
45 | Enhancements:
46 |
47 | * bump dependencies to latest patch versions and dev dependencies to latest
48 | versions
49 |
50 | ## 1.1.0
51 |
52 | **1 June 2015**
53 |
54 | Enhancements:
55 |
56 | * upgrade to AWS SDK v2 ([#15])
57 | * recommend the use of the AWS SDK's centralized credential file ([#14])
58 |
59 | Fixes:
60 |
61 | * allow up to ten minutes of time skew ([#16])
62 | * try an unauthenticated download before demanding AWS credentials ([#10])
63 |
64 | Thanks, [@kimpepper][kimpepper] and [@companykitchen-dev][companykitchen-dev]!
65 |
66 | ## 1.0.3
67 |
68 | **10 March 2015**
69 |
70 | Fixes:
71 |
72 | * fix namespace collisions with [vagrant-aws][vagrant-aws] ([#11])
73 |
74 | Thanks, [@andres-rojas][andres-rojas]!
75 |
76 |
77 | ## 1.0.2
78 |
79 | **25 December 2014**
80 |
81 | Enhancements:
82 |
83 | * provide better error messages when S3 API requests are denied ([#9])
84 | * include IAM policy recommendations in README
85 |
86 | ## 1.0.1
87 |
88 | **21 December 2014**
89 |
90 | Enhancements:
91 |
92 | * support bucket-in-host style S3 URLs to simplify usage instructions
93 |
94 | Fixes:
95 |
96 | * internal cleanup
97 | * improved detection of incompatible Vagrant versions
98 |
99 | ## 1.0.0
100 |
101 | **16 December 2014**
102 |
103 | Enhancements:
104 |
105 | * passes a complete acceptance test suite
106 | * detects full and shorthand S3 URLs at all download stages
107 |
108 | Fixes:
109 |
110 | * automatically determines region for shorthand S3 URLs ([#1], [#7])
111 |
112 | ## 0.1.0
113 |
114 | **13 June 2014**
115 |
116 | Enhancements:
117 |
118 | * support buckets hosted in any S3 region ([#1])
119 |
120 | Fixes:
121 |
122 | * properly authenticate requests for simple (non-metadata) S3 boxes ([#1])
123 |
124 | ## 0.0.2
125 |
126 | **6 June 2014**
127 |
128 | Enhancements:
129 |
130 | * formally license under MIT
131 |
132 | ## 0.0.1
133 |
134 | * initial release
135 |
136 | [#1]: https://github.com/WhoopInc/vagrant-s3auth/issues/1
137 | [#7]: https://github.com/WhoopInc/vagrant-s3auth/issues/7
138 | [#9]: https://github.com/WhoopInc/vagrant-s3auth/issues/9
139 | [#10]: https://github.com/WhoopInc/vagrant-s3auth/issues/10
140 | [#11]: https://github.com/WhoopInc/vagrant-s3auth/pull/11
141 | [#14]: https://github.com/WhoopInc/vagrant-s3auth/issues/14
142 | [#15]: https://github.com/WhoopInc/vagrant-s3auth/issues/15
143 | [#16]: https://github.com/WhoopInc/vagrant-s3auth/issues/16
144 | [#21]: https://github.com/WhoopInc/vagrant-s3auth/issues/21
145 | [#26]: https://github.com/WhoopInc/vagrant-s3auth/issues/26
146 | [#27]: https://github.com/WhoopInc/vagrant-s3auth/issues/27
147 | [#31]: https://github.com/WhoopInc/vagrant-s3auth/issues/31
148 |
149 | [Daemoen]: https://github.com/Daemoen
150 | [andres-rojas]: https://github.com/andres-rojas
151 | [companykitchen-dev]: https://github.com/companykitchen-dev
152 | [kimpepper]: https://github.com/kimpepper
153 |
154 | [vagrant-aws]: https://github.com/mitchellh/vagrant-aws
155 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We love contributions! Pull request away.
4 |
5 | ## Hacking
6 |
7 | You'll need Ruby and Bundler, of course. Then, check out the code and install
8 | the gems:
9 |
10 | ```bash
11 | $ git clone git@github.com:WhoopInc/vagrant-s3auth.git
12 | $ cd vagrant-s3auth
13 | $ bundle
14 | ```
15 |
16 | Hack away! When you're ready to test, either [run the test suite](TESTING.md) or
17 | run Vagrant manually *using the configured Bundler environment*:
18 |
19 | ```bash
20 | $ VAGRANT_LOG=debug bundle exec vagrant box add S3_URL
21 | ```
22 |
23 | If you forget the `bundle exec`, you'll use system Vagrant—not the Vagrant that
24 | has your plugin changes installed!
25 |
26 | ## Guidelines
27 |
28 | We do ask that all contributions pass the linter and test suite. Travis will
29 | automatically run these against your contribution once you submit the pull
30 | request, but you can also run them locally as you go!
31 |
32 | ### Linting
33 |
34 | ```bash
35 | $ rake lint
36 | ```
37 |
38 | ### Testing
39 |
40 | See [TESTING](TESTING.md).
41 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | VAGRANT_REF = ENV['VAGRANT_VERSION'] || 'master'
4 |
5 | group :development do
6 | gem 'vagrant', git: 'git://github.com/mitchellh/vagrant.git', ref: VAGRANT_REF
7 | end
8 |
9 | group :plugins do
10 | gemspec
11 | gem 'vagrant-aws', git: 'git://github.com/mitchellh/vagrant-aws.git', ref: 'master'
12 | end
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 WHOOP, Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Notice: This repository is no longer maintained as of 3/26/2024
2 |
3 |
4 | # vagrant-s3auth
5 |
6 |
7 |
9 |
10 |
11 | Private, versioned Vagrant boxes hosted on Amazon S3.
12 |
13 | ## Installation
14 |
15 | From the command line:
16 |
17 | ```bash
18 | $ vagrant plugin install vagrant-s3auth
19 | ```
20 |
21 | ### Requirements
22 |
23 | * [Vagrant][vagrant], v1.5.1+
24 |
25 | ## Usage
26 |
27 | vagrant-s3auth will automatically sign requests for S3 URLs
28 |
29 | ```
30 | s3://bucket.example.com/path/to/metadata
31 | ```
32 |
33 | with your AWS access key.
34 |
35 | This means you can host your team's sensitive, private boxes on S3, and use your
36 | developers' existing AWS credentials to securely grant access.
37 |
38 | If you've already got your credentials stored in the standard environment
39 | variables:
40 |
41 | ```ruby
42 | # Vagrantfile
43 |
44 | Vagrant.configure('2') do |config|
45 | config.vm.box = 'simple-secrets'
46 | config.vm.box_url = 's3://example.com/secret.box'
47 | end
48 | ```
49 |
50 | ### Configuration
51 |
52 | #### AWS credentials
53 |
54 | AWS credentials are read from the standard environment variables
55 | `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.
56 |
57 | You may find it more convenient to use the
58 | [centralized credential file][aws-cred-file] to create a credential
59 | profile. Select the appropriate profile using the `AWS_PROFILE`
60 | environment variable. For example:
61 |
62 | ```ini
63 | # ~/.aws/credentials
64 |
65 | [vagrant-s3auth]
66 | aws_access_key_id = AKIA...
67 | aws_secret_access_key = ...
68 | ```
69 |
70 | ```ruby
71 | # Vagrantfile
72 |
73 | ENV.delete_if { |name| name.start_with?('AWS_') } # Filter out rogue env vars.
74 | ENV['AWS_PROFILE'] = 'vagrant-s3auth'
75 |
76 | Vagrant.configure("2") { |config| ... }
77 | ```
78 |
79 | **CAUTION:** If `AWS_ACCESS_KEY_ID` exists in your environment, it will
80 | take precedence over `AWS_PROFILE`! Either take care to filter rogue
81 | environment variables as above, or set the access key explicitly:
82 |
83 | ```ruby
84 | access_key, secret_key = whizbang_inc_api.fetch_api_creds()
85 | ENV['AWS_ACCESS_KEY_ID'] = access_key
86 | ENV['AWS_SECRET_ACCESS_KEY'] = secret_key
87 | ```
88 |
89 | The detected AWS access key and its source (environment variable or
90 | profile file) will be displayed when the box is downloaded. If you use
91 | multiple AWS credentials and see authentication errors, verify that the
92 | correct access key was detected.
93 |
94 | ##### IAM configuration
95 |
96 | IAM accounts will need at least the following policy:
97 |
98 | ```json
99 | {
100 | "Version": "2012-10-17",
101 | "Statement": [
102 | {
103 | "Effect": "Allow",
104 | "Action": "s3:GetObject",
105 | "Resource": "arn:aws:s3:::BUCKET/*"
106 | },
107 | {
108 | "Effect": "Allow",
109 | "Action": ["s3:GetBucketLocation", "s3:ListBucket"],
110 | "Resource": "arn:aws:s3:::BUCKET"
111 | }
112 | ]
113 | }
114 | ```
115 |
116 | **IMPORTANT:** You must split up bucket and object permissions into separate policy statements as written above! See [Writing IAM Policies: How to grant access to an Amazon S3 Bucket][aws-s3-iam].
117 |
118 | Also note that `s3:ListBucket` permission is not strictly necessary. vagrant-s3auth will never
119 | make a ListBucket request, but without ListBucket permission, a misspelled box
120 | name results in a 403 Forbidden error instead of a 404 Not Found error. ([Why?][aws-403-404])
121 |
122 | See [AWS S3 Guide: User Policy Examples][aws-user-policy] for more.
123 |
124 | #### S3 URLs
125 |
126 | You can use any valid HTTP(S) URL for your object:
127 |
128 | ```bash
129 | # path style
130 | http://s3.amazonaws.com/bucket/resource
131 | https://s3.amazonaws.com/bucket/resource
132 |
133 | # host style
134 | http://bucket.s3.amazonaws.com/resource
135 | https://bucket.s3.amazonaws.com/resource
136 | ```
137 |
138 | Or the S3 protocol shorthand
139 |
140 | ```
141 | s3://bucket/resource
142 | ```
143 |
144 | which expands to the path-style HTTPS URL.
145 |
146 | ##### Non-standard regions
147 |
148 | If your bucket is not hosted in the US Standard region, you'll need to specify
149 | the correct region endpoint as part of the URL:
150 |
151 | ```
152 | https://s3-us-west-2.amazonaws.com/bucket/resource
153 | https://bucket.s3-us-west-2.amazonaws.com/resource
154 | ```
155 |
156 | Or just use the S3 protocol shorthand, which will automatically determine the
157 | correct region at the cost of an extra API call:
158 |
159 | ```
160 | s3://bucket/resource
161 | ```
162 |
163 | For additional details on specifying S3 URLs, refer to the [S3 Developer Guide:
164 | Virtual hosting of buckets][bucket-vhost].
165 |
166 | #### Simple boxes
167 |
168 | Simply point your `box_url` at a [supported S3 URL](#s3-url):
169 |
170 | ```ruby
171 | Vagrant.configure('2') do |config|
172 | config.vm.box = 'simple-secrets'
173 | config.vm.box_url = 'https://s3.amazonaws.com/bucket.example.com/secret.box'
174 | end
175 | ```
176 |
177 | #### Vagrant Cloud
178 |
179 | If you've got a box version on [Vagrant Cloud][vagrant-cloud], just point it at
180 | a [supported S3 URL](#s3-urls):
181 |
182 | 
183 |
184 | Then configure your Vagrantfile like normal:
185 |
186 | ```ruby
187 | Vagrant.configure('2') do |config|
188 | config.vm.box = 'benesch/test-box'
189 | end
190 | ```
191 |
192 | #### Metadata (versioned) boxes
193 |
194 | [Metadata boxes][metadata-boxes] were added to Vagrant in 1.5 and power Vagrant
195 | Cloud. You can host your own metadata and bypass Vagrant Cloud entirely.
196 |
197 | Essentially, you point your `box_url` at a [JSON metadata file][metadata-boxes]
198 | that tells Vagrant where to find all possible versions:
199 |
200 | ```ruby
201 | # Vagrantfile
202 |
203 | Vagrant.configure('2') do |config|
204 | config.vm.box = 'examplecorp/secrets'
205 | config.vm.box_url = 's3://example.com/secrets'
206 | end
207 | ```
208 |
209 | ```json
210 | "s3://example.com/secrets"
211 |
212 | {
213 | "name": "examplecorp/secrets",
214 | "description": "This box contains company secrets.",
215 | "versions": [{
216 | "version": "0.1.0",
217 | "providers": [{
218 | "name": "virtualbox",
219 | "url": "https://s3.amazonaws.com/example.com/secrets.box",
220 | "checksum_type": "sha1",
221 | "checksum": "foo"
222 | }]
223 | }]
224 | }
225 | ```
226 |
227 | Within your metadata JSON, be sure to use [supported S3 URLs](#s3-urls).
228 |
229 | Note that the metadata itself doesn't need to be hosted on S3. Any metadata that
230 | points to a supported S3 URL will result in an authenticated request.
231 |
232 | **IMPORTANT:** Your metadata *must* be served with `Content-Type: application/json`
233 | or Vagrant will not recognize it as metadata! Most S3 uploader tools (and most
234 | webservers) will *not* automatically set the `Content-Type` header when the file
235 | extension is not `.json`. Consult your tool's documentation for instructions on
236 | manually setting the content type.
237 |
238 | ## Auto-install
239 |
240 | The beauty of Vagrant is the magic of "`vagrant up` and done." Making your users
241 | install a plugin is lame.
242 |
243 | But wait! Just stick some shell in your Vagrantfile:
244 |
245 | ```ruby
246 | unless Vagrant.has_plugin?('vagrant-s3auth')
247 | # Attempt to install ourself. Bail out on failure so we don't get stuck in an
248 | # infinite loop.
249 | system('vagrant plugin install vagrant-s3auth') || exit!
250 |
251 | # Relaunch Vagrant so the plugin is detected. Exit with the same status code.
252 | exit system('vagrant', *ARGV)
253 | end
254 | ```
255 |
256 | [aws-403-404]: https://forums.aws.amazon.com/thread.jspa?threadID=56531#jive-message-210346
257 | [aws-cred-file]: http://blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs
258 | [aws-s3-iam]: http://blogs.aws.amazon.com/security/post/Tx3VRSWZ6B3SHAV/Writing-IAM-Policies-How-to-grant-access-to-an-Amazon-S3-bucket
259 | [aws-signed]: http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#ConstructingTheAuthenticationHeader
260 | [aws-user-policy]: http://docs.aws.amazon.com/AmazonS3/latest/dev/example-policies-s3.html
261 | [bucket-vhost]: http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html#VirtualHostingExamples
262 | [metadata-boxes]: http://docs.vagrantup.com/v2/boxes/format.html
263 | [vagrant]: http://vagrantup.com
264 | [vagrant-cloud]: http://vagrantcloud.com
265 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler/setup'
3 | require 'rubocop/rake_task'
4 |
5 | Dir.chdir(File.expand_path('../', __FILE__))
6 |
7 | Bundler::GemHelper.install_tasks
8 |
9 | RuboCop::RakeTask.new(:lint)
10 |
11 | task :test do
12 | sh 'bats test/run.bats'
13 | end
14 |
15 | task default: %w[lint test]
16 |
--------------------------------------------------------------------------------
/TESTING.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | No unit testing, since the project is so small. But a full suite of acceptance
4 | tests that run using [Bats: Bash Automated Testing System][bats]! Basically, the
5 | acceptance tests run `vagrant box add S3_URL` with a bunch of S3 URLs and box
6 | types, and assert that everything works!
7 |
8 | See [the .travis.yml CI configuration](.travis.yml) for a working example.
9 |
10 | ## Environment variables
11 |
12 | You'll need to export the below. Recommended values included when not sensitive.
13 |
14 | ```bash
15 | # AWS credentials with permissions to create S3 buckets
16 | export AWS_ACCESS_KEY_ID=
17 | export AWS_SECRET_ACCESS_KEY=
18 |
19 | # Atlas (Vagrant Cloud) API credentials
20 | export ATLAS_USERNAME="vagrant-s3auth"
21 | export ATLAS_TOKEN
22 |
23 | # Base name of bucket. Must be unique.
24 | export VAGRANT_S3AUTH_BUCKET="testing.vagrant-s3auth.com"
25 |
26 | # If specified as 'metadata', will upload 'box/metadata' and 'box/metadata.box'
27 | # to each S3 bucket
28 | export VAGRANT_S3AUTH_BOX_BASE="minimal"
29 |
30 | # Base name of Atlas (Vagrant Cloud) box. Atlas boxes can never re-use a once
31 | # existing name, so include a timestamp or random string in the name.
32 | export VAGRANT_S3AUTH_ATLAS_BOX_NAME="vagrant-s3auth-192458"
33 |
34 | # Additional S3 region to use in testing. US Standard is always used.
35 | export VAGRANT_S3AUTH_REGION_NONSTANDARD="eu-west-1"
36 | ```
37 |
38 | [bats]: https://github.com/sstephenson/bats
39 |
40 | ## Running tests
41 |
42 | You'll need [Bats][bats] installed! Then:
43 |
44 | ```bash
45 | # export env vars as described
46 | $ test/setup.rb
47 | $ rake test
48 | # hack hack hack
49 | $ rake test
50 | $ test/cleanup.rb
51 | ```
52 |
53 | ## Scripts
54 |
55 | ### test/setup.rb
56 |
57 | Creates two S3 buckets—one in US Standard (`us-east-1`) and one in
58 | `$VAGRANT_S3AUTH_REGION_NONSTANDARD`, both with the contents of the box
59 | directory.
60 |
61 | Then creates an Atlas (Vagrant Cloud) box with one version with one VirtualBox
62 | provider that points to one of the S3 boxes at random.
63 |
64 | ### test/cleanup.rb
65 |
66 | Destroys S3 buckets and Atlas box.
67 |
68 | ## run.bats
69 |
70 | Attempts to `vagrant box add` the boxes on S3 in every way possible.
71 |
--------------------------------------------------------------------------------
/lib/vagrant-s3auth.rb:
--------------------------------------------------------------------------------
1 | require 'pathname'
2 |
3 | require 'vagrant-s3auth/plugin'
4 |
5 | module VagrantPlugins
6 | module S3Auth
7 | def self.source_root
8 | @source_root ||= Pathname.new(File.expand_path('../../', __FILE__))
9 | end
10 |
11 | I18n.load_path << File.expand_path('locales/en.yml', source_root)
12 | I18n.reload!
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/vagrant-s3auth/errors.rb:
--------------------------------------------------------------------------------
1 | require 'vagrant'
2 |
3 | module VagrantPlugins
4 | module S3Auth
5 | module Errors
6 | class VagrantS3AuthError < Vagrant::Errors::VagrantError
7 | error_namespace('vagrant_s3auth.errors')
8 | end
9 |
10 | class MissingCredentialsError < VagrantS3AuthError
11 | error_key(:missing_credentials)
12 | end
13 |
14 | class MalformedShorthandURLError < VagrantS3AuthError
15 | error_key(:malformed_shorthand_url)
16 | end
17 |
18 | class BucketLocationAccessDeniedError < VagrantS3AuthError
19 | error_key(:bucket_location_access_denied_error)
20 | end
21 |
22 | class S3APIError < VagrantS3AuthError
23 | error_key(:s3_api_error)
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/vagrant-s3auth/extension/downloader.rb:
--------------------------------------------------------------------------------
1 | require 'uri'
2 |
3 | require 'vagrant/util/downloader'
4 | require 'vagrant-s3auth/util'
5 |
6 | S3Auth = VagrantPlugins::S3Auth
7 |
8 | module Vagrant
9 | module Util
10 | class Downloader
11 | def s3auth_credential_source
12 | credential_provider = S3Auth::Util.s3_credential_provider
13 | case credential_provider
14 | when ::Aws::Credentials
15 | I18n.t(
16 | 'vagrant_s3auth.downloader.env_credential_provider',
17 | access_key: credential_provider.credentials.access_key_id,
18 | env_var: S3Auth::Util::AWS_ACCESS_KEY_ENV_VARS.find { |k| ENV.key?(k) }
19 | )
20 | when ::Aws::SharedCredentials
21 | I18n.t(
22 | 'vagrant_s3auth.downloader.profile_credential_provider',
23 | access_key: credential_provider.credentials.access_key_id,
24 | profile: credential_provider.profile_name
25 | )
26 | end
27 | end
28 |
29 | def s3auth_download(options, subprocess_options, &data_proc)
30 | # The URL sent to curl is always the last argument. We have to rely
31 | # on this implementation detail because we need to hook into both
32 | # HEAD and GET requests.
33 | url = options.last
34 |
35 | s3_object = S3Auth::Util.s3_object_for(url)
36 | return unless s3_object
37 |
38 | @logger.info("s3auth: Discovered S3 URL: #{@source}")
39 | @logger.debug("s3auth: Bucket: #{s3_object.bucket.name.inspect}")
40 | @logger.debug("s3auth: Key: #{s3_object.key.inspect}")
41 |
42 | method = options.any? { |o| o == '-I' } ? :head : :get
43 |
44 | @logger.info("s3auth: Generating signed URL for #{method.upcase}")
45 |
46 | @ui.detail(s3auth_credential_source) if @ui
47 |
48 | url.replace(S3Auth::Util.s3_url_for(method, s3_object).to_s)
49 |
50 | execute_curl_without_s3auth(options, subprocess_options, &data_proc)
51 | rescue Errors::DownloaderError => e
52 | if e.message =~ /403 Forbidden/
53 | e.message << "\n\n"
54 | e.message << I18n.t('vagrant_s3auth.errors.box_download_forbidden',
55 | bucket: s3_object && s3_object.bucket.name)
56 | end
57 | raise
58 | rescue ::Aws::Errors::MissingCredentialsError
59 | raise S3Auth::Errors::MissingCredentialsError
60 | rescue ::Aws::Errors::ServiceError => e
61 | raise S3Auth::Errors::S3APIError, error: e
62 | rescue ::Seahorse::Client::NetworkingError => e
63 | # Vagrant ignores download errors during e.g. box update checks
64 | # because an internet connection isn't necessary if the box is
65 | # already downloaded. Vagrant isn't expecting AWS's
66 | # Seahorse::Client::NetworkingError, so we cast it to the
67 | # DownloaderError Vagrant expects.
68 | raise Errors::DownloaderError, message: e
69 | end
70 |
71 | def execute_curl_with_s3auth(options, subprocess_options, &data_proc)
72 | execute_curl_without_s3auth(options, subprocess_options, &data_proc)
73 | rescue Errors::DownloaderError => e
74 | # Ensure the progress bar from the just-failed request is cleared.
75 | @ui.clear_line if @ui
76 |
77 | s3auth_download(options, subprocess_options, &data_proc) || (raise e)
78 | end
79 |
80 | alias execute_curl_without_s3auth execute_curl
81 | alias execute_curl execute_curl_with_s3auth
82 | end
83 | end
84 | end
85 |
--------------------------------------------------------------------------------
/lib/vagrant-s3auth/middleware/expand_s3_urls.rb:
--------------------------------------------------------------------------------
1 | require 'uri'
2 |
3 | module VagrantPlugins
4 | module S3Auth
5 | class ExpandS3Urls
6 | def initialize(app, _)
7 | @app = app
8 | end
9 |
10 | def call(env)
11 | env[:box_urls].map! do |url_string|
12 | url = URI(url_string)
13 |
14 | if url.scheme == 's3'
15 | bucket = url.host
16 | key = url.path[1..-1]
17 | raise Errors::MalformedShorthandURLError, url: url unless bucket && key
18 | next "http://s3-placeholder.amazonaws.com/#{bucket}/#{key}"
19 | end
20 |
21 | url_string
22 | end
23 |
24 | @app.call(env)
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/vagrant-s3auth/plugin.rb:
--------------------------------------------------------------------------------
1 | begin
2 | require 'vagrant'
3 | rescue LoadError
4 | raise 'The Vagrant S3Auth plugin must be run within Vagrant.'
5 | end
6 |
7 | require_relative 'errors'
8 | require_relative 'extension/downloader'
9 |
10 | module VagrantPlugins
11 | module S3Auth
12 | class Plugin < Vagrant.plugin('2')
13 | Vagrant.require_version('>= 1.5.1')
14 |
15 | name 's3auth'
16 |
17 | description <<-DESC
18 | Use versioned Vagrant boxes with S3 authentication.
19 | DESC
20 |
21 | action_hook(:s3_urls, :authenticate_box_url) do |hook|
22 | require_relative 'middleware/expand_s3_urls'
23 | hook.prepend(ExpandS3Urls)
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/vagrant-s3auth/util.rb:
--------------------------------------------------------------------------------
1 | require 'aws-sdk'
2 | require 'log4r'
3 | require 'net/http'
4 | require 'uri'
5 |
6 | module VagrantPlugins
7 | module S3Auth
8 | module Util
9 | S3_HOST_MATCHER = /^((?[[:alnum:]\-\.]+).)?s3([[:alnum:]\-\.]+)?\.amazonaws\.com$/
10 |
11 | # The list of environment variables that the AWS Ruby SDK searches
12 | # for access keys. Sadly, there's no better way to determine which
13 | # environment variable the Ruby SDK is using without mirroring the
14 | # logic ourself.
15 | #
16 | # See: https://github.com/aws/aws-sdk-ruby/blob/ab0eb18d0ce0a515254e207dae772864c34b048d/aws-sdk-core/lib/aws-sdk-core/credential_provider_chain.rb#L42
17 | AWS_ACCESS_KEY_ENV_VARS = %w[AWS_ACCESS_KEY_ID AMAZON_ACCESS_KEY_ID AWS_ACCESS_KEY].freeze
18 |
19 | DEFAULT_REGION = 'us-east-1'.freeze
20 |
21 | LOCATION_TO_REGION = Hash.new { |_, key| key }.merge(
22 | '' => DEFAULT_REGION,
23 | 'EU' => 'eu-west-1'
24 | )
25 |
26 | class NullObject
27 | def method_missing(*) # rubocop:disable Style/MethodMissing
28 | nil
29 | end
30 | end
31 |
32 | def self.s3_client(region = DEFAULT_REGION)
33 | ::Aws::S3::Client.new(region: region)
34 | end
35 |
36 | def self.s3_resource(region = DEFAULT_REGION)
37 | ::Aws::S3::Resource.new(client: s3_client(region))
38 | end
39 |
40 | def self.s3_object_for(url, follow_redirect = true)
41 | url = URI(url)
42 |
43 | if url.scheme == 's3'
44 | bucket = url.host
45 | key = url.path[1..-1]
46 | raise Errors::MalformedShorthandURLError, url: url unless bucket && key
47 | elsif match = S3_HOST_MATCHER.match(url.host)
48 | components = url.path.split('/').delete_if(&:empty?)
49 | bucket = match['bucket'] || components.shift
50 | key = components.join('/')
51 | end
52 |
53 | if bucket && key
54 | s3_resource(get_bucket_region(bucket)).bucket(bucket).object(key)
55 | elsif follow_redirect
56 | response = Net::HTTP.get_response(url) rescue nil
57 | if response.is_a?(Net::HTTPRedirection)
58 | s3_object_for(response['location'], false)
59 | end
60 | end
61 | end
62 |
63 | def self.s3_url_for(method, s3_object)
64 | s3_object.presigned_url(method, expires_in: 60 * 10)
65 | end
66 |
67 | def self.get_bucket_region(bucket)
68 | LOCATION_TO_REGION[
69 | s3_client.get_bucket_location(bucket: bucket).location_constraint
70 | ]
71 | rescue ::Aws::S3::Errors::AccessDenied
72 | raise Errors::BucketLocationAccessDeniedError, bucket: bucket
73 | end
74 |
75 | def self.s3_credential_provider
76 | # Providing a NullObject here is the same as instantiating a
77 | # client without specifying a credentials config, like we do in
78 | # `self.s3_client`.
79 | ::Aws::CredentialProviderChain.new(NullObject.new).resolve
80 | end
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/lib/vagrant-s3auth/version.rb:
--------------------------------------------------------------------------------
1 | module VagrantPlugins
2 | module S3Auth
3 | VERSION = '1.3.2'.freeze
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | vagrant_s3auth:
3 | downloader:
4 | env_credential_provider: |-
5 | Signing S3 request with key '%{access_key}' loaded from $%{env_var}
6 |
7 | profile_credential_provider: |-
8 | Signing S3 request with key '%{access_key}' loaded from profile '%{profile}'
9 |
10 | errors:
11 | missing_credentials: |-
12 | Unable to find AWS credentials.
13 |
14 | Ensure the following variables are set in your environment, or set
15 | them at the top of your Vagrantfile:
16 |
17 | AWS_ACCESS_KEY_ID
18 | AWS_SECRET_ACCESS_KEY
19 |
20 | Alternatively, you can create a credential profile and set the
21 |
22 | AWS_PROFILE
23 |
24 | environment variable. Consult the documentation for details.
25 |
26 | malformed_shorthand_url: |-
27 | Malformed shorthand S3 box URL:
28 |
29 | %{url}
30 |
31 | Check your `box_url` setting.
32 |
33 | s3_api_error: |-
34 | Unable to communicate with Amazon S3 to download box. The S3 API reports:
35 |
36 | %{error}
37 |
38 | bucket_location_access_denied_error: |-
39 | Request for box's Amazon S3 region was denied.
40 |
41 | This usually indicates that your user account is misconfigured. Ensure
42 | your IAM policy allows the "s3:GetBucketLocation" action for your bucket:
43 |
44 | arn:aws:s3:::%{bucket}
45 |
46 | box_download_forbidden: |-
47 | This box is hosted on Amazon S3. A 403 Forbidden error usually indicates
48 | that your user account is misconfigured. Ensure your IAM policy allows
49 | the "s3:GetObject" action for your bucket:
50 |
51 | arn:aws:s3:::%{bucket}/*
52 |
53 | It may also indicate the box does not exist, so check your spelling.
54 |
--------------------------------------------------------------------------------
/test/box/minimal:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vagrant-s3auth/minimal",
3 | "description": "This box contains company secrets.",
4 | "versions": [{
5 | "version": "1.0.1",
6 | "providers": [{
7 | "name": "virtualbox",
8 | "url": "%{box_url}",
9 | "checksum_type": "sha1",
10 | "checksum": "8ea536dd3092cf159f02405edd44ded5b62ba4e6"
11 | }]
12 | }]
13 | }
14 |
--------------------------------------------------------------------------------
/test/box/minimal.box:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WhoopInc/vagrant-s3auth/76eb6b651a2b509602504b209ecdfa08aa21c0d5/test/box/minimal.box
--------------------------------------------------------------------------------
/test/box/public-minimal:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vagrant-s3auth/public-minimal",
3 | "description": "This box contains no company secrets.",
4 | "versions": [{
5 | "version": "1.0.1",
6 | "providers": [{
7 | "name": "virtualbox",
8 | "url": "%{box_url}",
9 | "checksum_type": "sha1",
10 | "checksum": "8ea536dd3092cf159f02405edd44ded5b62ba4e6"
11 | }]
12 | }]
13 | }
14 |
--------------------------------------------------------------------------------
/test/box/public-minimal.box:
--------------------------------------------------------------------------------
1 | minimal.box
--------------------------------------------------------------------------------
/test/cleanup.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'bundler/setup'
4 | require 'aws-sdk'
5 |
6 | require_relative 'support'
7 |
8 | [REGION_STANDARD, REGION_NONSTANDARD].each do |region|
9 | s3 = Aws::S3::Resource.new(region: region)
10 |
11 | buckets = if ARGV.include?('--all')
12 | s3.buckets.select do |b|
13 | b.name.include?('vagrant-s3auth.com') && b.name.include?(region)
14 | end
15 | else
16 | [s3.bucket("#{region}.#{BUCKET}")]
17 | end
18 |
19 | buckets.each { |b| b.delete! if b.exists? }
20 | end
21 |
22 | atlas = Atlas.new(ATLAS_TOKEN, ATLAS_USERNAME)
23 | atlas.delete_box(ATLAS_BOX_NAME)
24 |
--------------------------------------------------------------------------------
/test/run.bats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bats
2 |
3 | missing_vars=()
4 |
5 | require_var() {
6 | [[ "${!1}" ]] || missing_vars+=("$1")
7 | }
8 |
9 | require_var AWS_ACCESS_KEY_ID
10 | require_var AWS_SECRET_ACCESS_KEY
11 | require_var ATLAS_TOKEN
12 | require_var ATLAS_USERNAME
13 | require_var VAGRANT_S3AUTH_BUCKET
14 | require_var VAGRANT_S3AUTH_BOX_BASE
15 | require_var VAGRANT_S3AUTH_ATLAS_BOX_NAME
16 | require_var VAGRANT_S3AUTH_REGION_NONSTANDARD
17 |
18 | if [[ ${#missing_vars[*]} -gt 0 ]]; then
19 | echo "Missing required environment variables:"
20 | printf ' %s\n' "${missing_vars[@]}"
21 | exit 1
22 | fi
23 |
24 | teardown() {
25 | bundle exec vagrant box remove "$VAGRANT_S3AUTH_BOX_BASE" > /dev/null 2>&1 || true
26 | bundle exec vagrant box remove "public-$VAGRANT_S3AUTH_BOX_BASE" > /dev/null 2>&1 || true
27 | bundle exec vagrant box remove "vagrant-s3auth/$VAGRANT_S3AUTH_BOX_BASE" > /dev/null 2>&1 || true
28 | bundle exec vagrant box remove "vagrant-s3auth/public-$VAGRANT_S3AUTH_BOX_BASE" > /dev/null 2>&1 || true
29 | bundle exec vagrant box remove "$ATLAS_USERNAME/$VAGRANT_S3AUTH_ATLAS_BOX_NAME" > /dev/null 2>&1 || true
30 | }
31 |
32 | @test "vagrant cloud" {
33 | bundle exec vagrant box add "$ATLAS_USERNAME/$VAGRANT_S3AUTH_ATLAS_BOX_NAME"
34 | }
35 |
36 | @test "simple box with full path standard url" {
37 | bundle exec vagrant box add \
38 | --name "$VAGRANT_S3AUTH_BOX_BASE" \
39 | "https://s3.amazonaws.com/us-east-1.$VAGRANT_S3AUTH_BUCKET/$VAGRANT_S3AUTH_BOX_BASE.box"
40 | }
41 |
42 | @test "public simple box with full path standard url without credentials" {
43 | AWS_ACCESS_KEY_ID= \
44 | bundle exec vagrant box add \
45 | --name "$VAGRANT_S3AUTH_BOX_BASE" \
46 | "https://s3.amazonaws.com/us-east-1.$VAGRANT_S3AUTH_BUCKET/public-$VAGRANT_S3AUTH_BOX_BASE.box"
47 | }
48 |
49 | @test "simple box with full host standard url" {
50 | bundle exec vagrant box add \
51 | --name "$VAGRANT_S3AUTH_BOX_BASE" \
52 | "https://us-east-1.$VAGRANT_S3AUTH_BUCKET.s3.amazonaws.com/$VAGRANT_S3AUTH_BOX_BASE.box"
53 | }
54 |
55 | @test "simple box with shorthand standard url" {
56 | bundle exec vagrant box add \
57 | --name "$VAGRANT_S3AUTH_BOX_BASE" \
58 | "s3://us-east-1.$VAGRANT_S3AUTH_BUCKET/$VAGRANT_S3AUTH_BOX_BASE.box"
59 | }
60 |
61 | @test "simple box with full path nonstandard url" {
62 | bundle exec vagrant box add \
63 | --name "$VAGRANT_S3AUTH_BOX_BASE" \
64 | "https://s3-$VAGRANT_S3AUTH_REGION_NONSTANDARD.amazonaws.com/$VAGRANT_S3AUTH_REGION_NONSTANDARD.$VAGRANT_S3AUTH_BUCKET/$VAGRANT_S3AUTH_BOX_BASE.box"
65 | }
66 |
67 | @test "public simple box with full path nonstandard url without credentials" {
68 | AWS_ACCESS_KEY_ID= \
69 | bundle exec vagrant box add \
70 | --name "$VAGRANT_S3AUTH_BOX_BASE" \
71 | "https://s3-$VAGRANT_S3AUTH_REGION_NONSTANDARD.amazonaws.com/$VAGRANT_S3AUTH_REGION_NONSTANDARD.$VAGRANT_S3AUTH_BUCKET/public-$VAGRANT_S3AUTH_BOX_BASE.box"
72 | }
73 |
74 | @test "simple box with full host nonstandard url" {
75 | bundle exec vagrant box add \
76 | --name "$VAGRANT_S3AUTH_BOX_BASE" \
77 | "https://$VAGRANT_S3AUTH_REGION_NONSTANDARD.$VAGRANT_S3AUTH_BUCKET.s3-$VAGRANT_S3AUTH_REGION_NONSTANDARD.amazonaws.com/$VAGRANT_S3AUTH_BOX_BASE.box"
78 | }
79 |
80 | @test "simple box with shorthand nonstandard url" {
81 | bundle exec vagrant box add \
82 | --name "$VAGRANT_S3AUTH_BOX_BASE" \
83 | "s3://$VAGRANT_S3AUTH_REGION_NONSTANDARD.$VAGRANT_S3AUTH_BUCKET/$VAGRANT_S3AUTH_BOX_BASE.box"
84 | }
85 |
86 | @test "metadata box with full path standard url" {
87 | bundle exec vagrant box add \
88 | --name "vagrant-s3auth/$VAGRANT_S3AUTH_BOX_BASE" \
89 | "https://s3.amazonaws.com/us-east-1.$VAGRANT_S3AUTH_BUCKET/$VAGRANT_S3AUTH_BOX_BASE"
90 | }
91 |
92 | @test "public metadata box with full path standard url without credentials" {
93 | AWS_ACCESS_KEY_ID= \
94 | bundle exec vagrant box add \
95 | --name "vagrant-s3auth/public-$VAGRANT_S3AUTH_BOX_BASE" \
96 | "https://s3.amazonaws.com/us-east-1.$VAGRANT_S3AUTH_BUCKET/public-$VAGRANT_S3AUTH_BOX_BASE"
97 | }
98 |
99 | @test "metadata box with full host standard url" {
100 | bundle exec vagrant box add \
101 | --name "vagrant-s3auth/$VAGRANT_S3AUTH_BOX_BASE" \
102 | "https://us-east-1.$VAGRANT_S3AUTH_BUCKET.s3.amazonaws.com/$VAGRANT_S3AUTH_BOX_BASE"
103 | }
104 |
105 | @test "metadata box with shorthand standard url" {
106 | bundle exec vagrant box add \
107 | --name "vagrant-s3auth/$VAGRANT_S3AUTH_BOX_BASE" \
108 | "s3://us-east-1.$VAGRANT_S3AUTH_BUCKET/$VAGRANT_S3AUTH_BOX_BASE"
109 | }
110 |
111 | @test "metadata box with full path nonstandard url" {
112 | bundle exec vagrant box add \
113 | --name "vagrant-s3auth/$VAGRANT_S3AUTH_BOX_BASE" \
114 | "https://s3-$VAGRANT_S3AUTH_REGION_NONSTANDARD.amazonaws.com/$VAGRANT_S3AUTH_REGION_NONSTANDARD.$VAGRANT_S3AUTH_BUCKET/$VAGRANT_S3AUTH_BOX_BASE"
115 | }
116 |
117 | @test "public metadata box with full path nonstandard url without credentials" {
118 | AWS_ACCESS_KEY_ID= \
119 | bundle exec vagrant box add \
120 | --name "vagrant-s3auth/public-$VAGRANT_S3AUTH_BOX_BASE" \
121 | "https://s3-$VAGRANT_S3AUTH_REGION_NONSTANDARD.amazonaws.com/$VAGRANT_S3AUTH_REGION_NONSTANDARD.$VAGRANT_S3AUTH_BUCKET/public-$VAGRANT_S3AUTH_BOX_BASE"
122 | }
123 |
124 |
125 | @test "metadata box with full host nonstandard url" {
126 | bundle exec vagrant box add \
127 | --name "vagrant-s3auth/$VAGRANT_S3AUTH_BOX_BASE" \
128 | "https://$VAGRANT_S3AUTH_REGION_NONSTANDARD.$VAGRANT_S3AUTH_BUCKET.s3-$VAGRANT_S3AUTH_REGION_NONSTANDARD.amazonaws.com/$VAGRANT_S3AUTH_BOX_BASE"
129 | }
130 |
131 | @test "metadata box with shorthand nonstandard url" {
132 | bundle exec vagrant box add \
133 | --name "vagrant-s3auth/$VAGRANT_S3AUTH_BOX_BASE" \
134 | "s3://$VAGRANT_S3AUTH_REGION_NONSTANDARD.$VAGRANT_S3AUTH_BUCKET/$VAGRANT_S3AUTH_BOX_BASE"
135 | }
136 |
137 | @test "garbage shorthand url" {
138 | run bundle exec vagrant box add --name "$VAGRANT_S3AUTH_BOX_BASE" s3://smoogedydoop
139 | [[ "$status" -eq 1 ]]
140 | [[ "$output" == *"Malformed shorthand S3 box URL"* ]]
141 | }
142 |
143 | @test "garbage full url" {
144 | run bundle exec vagrant box add --name "$VAGRANT_S3AUTH_BOX_BASE" https://smoogedydoop
145 | [[ "$status" -eq 1 ]]
146 | [[ "$output" == *"error occurred while downloading the remote file"* ]]
147 | }
148 |
--------------------------------------------------------------------------------
/test/setup.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'bundler/setup'
4 | require 'aws-sdk'
5 |
6 | require_relative 'support'
7 |
8 | ROOT = Pathname.new(File.dirname(__FILE__))
9 |
10 | box_urls = [REGION_STANDARD, REGION_NONSTANDARD].flat_map do |region|
11 | s3 = Aws::S3::Resource.new(region: region)
12 | bucket = s3.create_bucket(bucket: "#{region}.#{BUCKET}")
13 |
14 | [BOX_BASE, 'public-' + BOX_BASE].flat_map do |box_name|
15 | box = bucket.object("#{box_name}.box")
16 | box.upload_file(ROOT + Pathname.new("box/#{box_name}.box"))
17 | box.acl.put(acl: 'public-read') if box_name.start_with?('public')
18 |
19 | metadata_string = format(File.read(ROOT + Pathname.new("box/#{box_name}")),
20 | box_url: box.public_url)
21 |
22 | metadata = bucket.object(box_name)
23 | metadata.put(body: metadata_string, content_type: 'application/json')
24 | metadata.acl.put(acl: 'public-read') if box_name.start_with?('public')
25 |
26 | box.public_url
27 | end
28 | end
29 |
30 | atlas = Atlas.new(ATLAS_TOKEN, ATLAS_USERNAME)
31 | atlas.create_box(ATLAS_BOX_NAME)
32 | atlas.create_version(ATLAS_BOX_NAME, '1.0.1')
33 | atlas.create_provider(ATLAS_BOX_NAME, '1.0.1', box_urls.sample)
34 | atlas.release_version(ATLAS_BOX_NAME, '1.0.1')
35 |
--------------------------------------------------------------------------------
/test/support.rb:
--------------------------------------------------------------------------------
1 | require 'http'
2 |
3 | BOX_BASE = ENV['VAGRANT_S3AUTH_BOX_BASE'].freeze
4 | BUCKET = ENV['VAGRANT_S3AUTH_BUCKET'].freeze
5 | REGION_STANDARD = 'us-east-1'.freeze
6 | REGION_NONSTANDARD = ENV['VAGRANT_S3AUTH_REGION_NONSTANDARD'].freeze
7 |
8 | ATLAS_TOKEN = ENV['ATLAS_TOKEN'].freeze
9 | ATLAS_USERNAME = ENV['ATLAS_USERNAME'].freeze
10 | ATLAS_BOX_NAME = ENV['VAGRANT_S3AUTH_ATLAS_BOX_NAME'].freeze
11 |
12 | class Atlas
13 | BASE_URL = 'https://app.vagrantup.com/api/v1'.freeze
14 |
15 | BOX_CREATE_URL = "#{BASE_URL}/boxes".freeze
16 | BOX_RESOURCE_URL = "#{BASE_URL}/box/%s/%s".freeze
17 |
18 | VERSION_CREATE_URL = "#{BOX_RESOURCE_URL}/versions".freeze
19 | VERSION_RESOURCE_URL = "#{BOX_RESOURCE_URL}/version/%s".freeze
20 | VERSION_RELEASE_URL = "#{VERSION_RESOURCE_URL}/release".freeze
21 |
22 | PROVIDER_CREATE_URL = "#{VERSION_RESOURCE_URL}/providers".freeze
23 | PROVIDER_RESOURCE_URL = "#{VERSION_RESOURCE_URL}/provider/%s".freeze
24 |
25 | attr_accessor :provider
26 |
27 | def initialize(token, username)
28 | raise if !token || token.empty?
29 | raise if !username || username.empty?
30 |
31 | @token = token
32 | @username = username
33 | @provider = 'virtualbox'
34 | end
35 |
36 | def create_box(box_name)
37 | post(BOX_CREATE_URL, data: { box: { name: box_name, is_private: false } })
38 | end
39 |
40 | def delete_box(box_name)
41 | url_params = { box_name: box_name }
42 | delete(BOX_RESOURCE_URL, url_params: url_params)
43 | end
44 |
45 | def create_version(box_name, version)
46 | post(VERSION_CREATE_URL,
47 | data: { version: { version: version } },
48 | url_params: { box_name: box_name })
49 | end
50 |
51 | def release_version(box_name, version)
52 | put(VERSION_RELEASE_URL,
53 | url_params: { box_name: box_name, version: version })
54 | end
55 |
56 | def create_provider(box_name, version, url)
57 | post(PROVIDER_CREATE_URL,
58 | data: { provider: { name: @provider, url: url } },
59 | url_params: { box_name: box_name, version: version })
60 | end
61 |
62 | def request(method, url, options)
63 | url_params = (options[:url_params] || {}).merge(username: @username)
64 | data = (options[:data] || {})
65 |
66 | url = (url % url_params) + "?access_token=#{@token}"
67 | response = HTTP.request(method, url, json: data)
68 | raise response unless response.code >= 200 && response.code < 400
69 | end
70 |
71 | def post(url, options)
72 | request(:post, url, options)
73 | end
74 |
75 | def put(url, options)
76 | request(:put, url, options)
77 | end
78 |
79 | def delete(url, options)
80 | request(:delete, url, options)
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/vagrant-s3auth.gemspec:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2 |
3 | require 'vagrant-s3auth/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = 'vagrant-s3auth'
7 | spec.version = VagrantPlugins::S3Auth::VERSION
8 | spec.authors = ['Nikhil Benesch']
9 | spec.email = ['benesch@whoop.com']
10 | spec.summary = 'Private, versioned Vagrant boxes hosted on Amazon S3.'
11 | spec.homepage = 'https://github.com/WhoopInc/vagrant-s3auth'
12 | spec.license = 'MIT'
13 |
14 | spec.files = `git ls-files -z`.split("\x0")
15 | spec.test_files = spec.files.grep(/spec/)
16 | spec.require_paths = ['lib']
17 |
18 | spec.add_dependency 'aws-sdk', '~> 2.6.44'
19 |
20 | spec.add_development_dependency 'bundler', '~> 1.5'
21 | spec.add_development_dependency 'http', '~> 1.0.2'
22 | spec.add_development_dependency 'rake', '~> 12.0'
23 | spec.add_development_dependency 'rubocop', '~> 0.46'
24 | end
25 |
--------------------------------------------------------------------------------