[ARVADOS] created: 3377c83ecf5937d5f02c15ef3683181572559137

git at public.curoverse.com git at public.curoverse.com
Wed Jun 11 23:15:53 EDT 2014


        at  3377c83ecf5937d5f02c15ef3683181572559137 (commit)


commit 3377c83ecf5937d5f02c15ef3683181572559137
Author: Tom Clegg <tom at curoverse.com>
Date:   Wed Jun 11 22:18:24 2014 -0400

    2755: Add api server config to enable mandatory Keep signatures.

diff --git a/services/api/app/controllers/arvados/v1/collections_controller.rb b/services/api/app/controllers/arvados/v1/collections_controller.rb
index 6c9d41e..97f004e 100644
--- a/services/api/app/controllers/arvados/v1/collections_controller.rb
+++ b/services/api/app/controllers/arvados/v1/collections_controller.rb
@@ -13,7 +13,6 @@ class Arvados::V1::CollectionsController < ApplicationController
 
     # Check permissions on the collection manifest.
     # If any signature cannot be verified, return 403 Permission denied.
-    perms_ok = true
     api_token = current_api_client_authorization.andand.api_token
     signing_opts = {
       key: Rails.configuration.blob_signing_key,
@@ -22,23 +21,28 @@ class Arvados::V1::CollectionsController < ApplicationController
     }
     resource_attrs[:manifest_text].lines.each do |entry|
       entry.split[1..-1].each do |tok|
-        # TODO(twp): in Phase 4, fail the request if the locator
-        # lacks a permission signature. (see #2755)
-        loc = Locator.parse(tok)
-        if loc and loc.signature
-          if !api_token
-            logger.warn "No API token present; cannot verify signature on #{loc}"
-            perms_ok = false
-          elsif !Blob.verify_signature tok, signing_opts
-            logger.warn "Invalid signature on locator #{loc}"
-            perms_ok = false
-          end
+        if /^[[:digit:]]+:[[:digit:]]+:/.match tok
+          # This is a filename token, not a blob locator. Note that we
+          # keep checking tokens after this, even though manifest
+          # format dictates that all subsequent tokens will also be
+          # filenames. Safety first!
+        elsif Blob.verify_signature tok, signing_opts
+          # OK.
+        elsif Locator.parse(tok).andand.signature
+          # Signature provided, but verify_signature did not like it.
+          logger.warn "Invalid signature on locator #{tok}"
+          raise ArvadosModel::PermissionDeniedError
+        elsif Rails.configuration.permit_create_collection_with_unsigned_manifest
+          # No signature provided, but we are running in insecure mode.
+          logger.debug "Missing signature on locator #{tok} ignored"
+        elsif Blob.new(tok).empty?
+          # No signature provided -- but no data to protect, either.
+        else
+          logger.warn "Missing signature on locator #{tok}"
+          raise ArvadosModel::PermissionDeniedError
         end
       end
     end
-    unless perms_ok
-      raise ArvadosModel::PermissionDeniedError
-    end
 
     # Remove any permission signatures from the manifest.
     resource_attrs[:manifest_text]
diff --git a/services/api/app/models/blob.rb b/services/api/app/models/blob.rb
index 5decd77..c8a8865 100644
--- a/services/api/app/models/blob.rb
+++ b/services/api/app/models/blob.rb
@@ -1,5 +1,13 @@
 class Blob
 
+  def initialize locator
+    @locator = locator
+  end
+
+  def empty?
+    !!@locator.match(/^d41d8cd98f00b204e9800998ecf8427e(\+.*)?$/)
+  end
+
   # In order to get a Blob from Keep, you have to prove either
   # [a] you have recently written it to Keep yourself, or
   # [b] apiserver has recently decided that you should be able to read it
diff --git a/services/api/config/application.default.yml b/services/api/config/application.default.yml
index 848675c..d62fb4e 100644
--- a/services/api/config/application.default.yml
+++ b/services/api/config/application.default.yml
@@ -129,3 +129,13 @@ common:
   # Amount of time (in seconds) for which a blob permission signature
   # remains valid.  Default: 2 weeks (1209600 seconds)
   blob_signing_ttl: 1209600
+
+  # Allow clients to create collections by providing a manifest with
+  # unsigned data blob locators. IMPORTANT: This effectively disables
+  # access controls for data stored in Keep: a client who knows a hash
+  # can write a manifest that references the hash, pass it to
+  # collections.create (which will create a permission link), use
+  # collections.get to obtain a signature for that data locator, and
+  # use that signed locator to retrieve the data from Keep.
+  # Do not use turn this on if you want to 
+  permit_create_collection_with_unsigned_manifest: false
diff --git a/services/api/test/functional/arvados/v1/collections_controller_test.rb b/services/api/test/functional/arvados/v1/collections_controller_test.rb
index 6830c6b..e4bbd5c 100644
--- a/services/api/test/functional/arvados/v1/collections_controller_test.rb
+++ b/services/api/test/functional/arvados/v1/collections_controller_test.rb
@@ -2,6 +2,21 @@ require 'test_helper'
 
 class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
 
+  setup do
+    # Unless otherwise specified in the test, we want normal/secure behavior.
+    permit_unsigned_manifests false
+  end
+
+  teardown do
+    # Reset to secure behavior after each test.
+    permit_unsigned_manifests false
+  end
+
+  def permit_unsigned_manifests isok=true
+    # Set security model for the life of a test.
+    Rails.configuration.permit_create_collection_with_unsigned_manifest = isok
+  end
+
   test "should get index" do
     authorize_with :active
     get :index
@@ -42,7 +57,8 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
     assert_equal 99999, resp['offset']
   end
 
-  test "should create" do
+  test "create with unsigned manifest" do
+    permit_unsigned_manifests
     authorize_with :active
     test_collection = {
       manifest_text: <<-EOS
@@ -105,6 +121,7 @@ EOS
   end
 
   test "create with owner_uuid set to owned group" do
+    permit_unsigned_manifests
     authorize_with :active
     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
     post :create, {
@@ -120,6 +137,7 @@ EOS
   end
 
   test "create with owner_uuid set to group i can_manage" do
+    permit_unsigned_manifests
     authorize_with :active
     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
     post :create, {
@@ -135,6 +153,7 @@ EOS
   end
 
   test "create with owner_uuid set to group with no can_manage permission" do
+    permit_unsigned_manifests
     authorize_with :active
     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
     post :create, {
@@ -148,6 +167,7 @@ EOS
   end
 
   test "admin create with owner_uuid set to group with no permission" do
+    permit_unsigned_manifests
     authorize_with :admin
     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
     post :create, {
@@ -161,6 +181,7 @@ EOS
   end
 
   test "should create with collection passed as json" do
+    permit_unsigned_manifests
     authorize_with :active
     post :create, {
       collection: <<-EOS
@@ -174,6 +195,7 @@ EOS
   end
 
   test "should fail to create with checksum mismatch" do
+    permit_unsigned_manifests
     authorize_with :active
     post :create, {
       collection: <<-EOS
@@ -187,6 +209,7 @@ EOS
   end
 
   test "collection UUID is normalized when created" do
+    permit_unsigned_manifests
     authorize_with :active
     post :create, {
       collection: {
@@ -243,48 +266,59 @@ EOS
     assert_equal true, !!found.index('1f4b0bc7583c2a7f9102c395f4ffc5e3+45')
   end
 
-  test "create collection with signed manifest" do
-    authorize_with :active
-    locators = %w(
+  [false, true].each do |permit_unsigned|
+    test "create collection with signed manifest, permit_unsigned=#{permit_unsigned}" do
+      permit_unsigned_manifests permit_unsigned
+      authorize_with :active
+      locators = %w(
       d41d8cd98f00b204e9800998ecf8427e+0
       acbd18db4cc2f85cedef654fccc4a4d8+3
       ea10d51bcf88862dbcc36eb292017dfd+45)
 
-    unsigned_manifest = locators.map { |loc|
-      ". " + loc + " 0:0:foo.txt\n"
-    }.join()
-    manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) +
-      '+' +
-      unsigned_manifest.length.to_s
-
-    # build a manifest with both signed and unsigned locators.
-    # TODO(twp): in phase 4, all locators will need to be signed, so
-    # this test should break and will need to be rewritten. Issue #2755.
-    signing_opts = {
-      key: Rails.configuration.blob_signing_key,
-      api_token: api_token(:active),
-    }
-    signed_manifest =
-      ". " + locators[0] + " 0:0:foo.txt\n" +
-      ". " + Blob.sign_locator(locators[1], signing_opts) + " 0:0:foo.txt\n" +
-      ". " + Blob.sign_locator(locators[2], signing_opts) + " 0:0:foo.txt\n"
-
-    post :create, {
-      collection: {
-        manifest_text: signed_manifest,
-        uuid: manifest_uuid,
+      unsigned_manifest = locators.map { |loc|
+        ". " + loc + " 0:0:foo.txt\n"
+      }.join()
+      manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) +
+        '+' +
+        unsigned_manifest.length.to_s
+
+      # Build a manifest with both signed and unsigned locators.
+      signing_opts = {
+        key: Rails.configuration.blob_signing_key,
+        api_token: api_token(:active),
       }
-    }
-    assert_response :success
-    assert_not_nil assigns(:object)
-    resp = JSON.parse(@response.body)
-    assert_equal manifest_uuid, resp['uuid']
-    assert_equal 48, resp['data_size']
-    # All of the locators in the output must be signed.
-    resp['manifest_text'].lines.each do |entry|
-      m = /([[:xdigit:]]{32}\+\S+)/.match(entry)
-      if m
-        assert Blob.verify_signature m[0], signing_opts
+      signed_locators = locators.collect do |x|
+        Blob.sign_locator x, signing_opts
+      end
+      if permit_unsigned
+        # Leave a non-empty blob unsigned.
+        signed_locators[1] = locators[1]
+      else
+        # Leave the empty blob unsigned. This should still be allowed.
+        signed_locators[0] = locators[0]
+      end
+      signed_manifest =
+        ". " + signed_locators[0] + " 0:0:foo.txt\n" +
+        ". " + signed_locators[1] + " 0:0:foo.txt\n" +
+        ". " + signed_locators[2] + " 0:0:foo.txt\n"
+
+      post :create, {
+        collection: {
+          manifest_text: signed_manifest,
+          uuid: manifest_uuid,
+        }
+      }
+      assert_response :success
+      assert_not_nil assigns(:object)
+      resp = JSON.parse(@response.body)
+      assert_equal manifest_uuid, resp['uuid']
+      assert_equal 48, resp['data_size']
+      # All of the locators in the output must be signed.
+      resp['manifest_text'].lines.each do |entry|
+        m = /([[:xdigit:]]{32}\+\S+)/.match(entry)
+        if m
+          assert Blob.verify_signature m[0], signing_opts
+        end
       end
     end
   end
@@ -391,6 +425,7 @@ EOS
   end
 
   test "multiple locators per line" do
+    permit_unsigned_manifests
     authorize_with :active
     locators = %w(
       d41d8cd98f00b204e9800998ecf8427e+0
@@ -423,6 +458,7 @@ EOS
   end
 
   test "multiple signed locators per line" do
+    permit_unsigned_manifests
     authorize_with :active
     locators = %w(
       d41d8cd98f00b204e9800998ecf8427e+0
@@ -465,4 +501,20 @@ EOS
     end
     assert_equal locators.count, returned_locator_count
   end
+
+  test 'Reject manifest with unsigned blob' do
+    authorize_with :active
+    unsigned_manifest = ". 0cc175b9c0f1b6a831c399e269772661+1 0:1:a.txt\n"
+    manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest)
+    post :create, {
+      collection: {
+        manifest_text: unsigned_manifest,
+        uuid: manifest_uuid,
+      }
+    }
+    assert_response 403,
+    "Creating a collection with unsigned blobs should respond 403"
+    assert_empty Collection.where('uuid like ?', manifest_uuid+'%'),
+    "Collection should not exist in database after failed create"
+  end
 end
diff --git a/services/api/test/integration/collections_api_test.rb b/services/api/test/integration/collections_api_test.rb
index b0fddb8..bc89c00 100644
--- a/services/api/test/integration/collections_api_test.rb
+++ b/services/api/test/integration/collections_api_test.rb
@@ -72,9 +72,15 @@ class CollectionsApiTest < ActionDispatch::IntegrationTest
   end
 
   test "store collection as json" do
+    signing_opts = {
+      key: Rails.configuration.blob_signing_key,
+      api_token: api_token(:active),
+    }
+    signed_locator = Blob.sign_locator('bad42fa702ae3ea7d888fef11b46f450+44',
+                                       signing_opts)
     post "/arvados/v1/collections", {
       format: :json,
-      collection: "{\"manifest_text\":\". bad42fa702ae3ea7d888fef11b46f450+44 0:44:md5sum.txt\\n\",\"uuid\":\"ad02e37b6a7f45bbe2ead3c29a109b8a+54\"}"
+      collection: "{\"manifest_text\":\". #{signed_locator} 0:44:md5sum.txt\\n\",\"uuid\":\"ad02e37b6a7f45bbe2ead3c29a109b8a+54\"}"
     }, auth(:active)
     assert_response 200
     assert_equal 'ad02e37b6a7f45bbe2ead3c29a109b8a+54', json_response['uuid']

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list