├── .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 | ![Adding a S3 box to Vagrant Cloud](https://cloud.githubusercontent.com/assets/882976/3273399/d5d70966-f323-11e3-8393-22195050aeac.png) 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 | --------------------------------------------------------------------------------