Skip to content

Reject Git LFS Pushes When Namespace Storage Limits Are Exceeded

Jason Goodman requested to merge prevent-git-lfs-push-nss-exceeded into master

What does this MR do and why?

Reject git pushes that use LFS when namespace storage limits are exceeded.

Reject git LFS file locking requests.

Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/362951

How to set up and validate locally

Basic Setup

  1. Make sure the automatic_purchased_storage_allocation and enforce_namespace_storage_limit application settings are both enabled (true).
gitlabhq_development=# SELECT automatic_purchased_storage_allocation, enforce_namespace_storage_limit FROM application_settings;
 automatic_purchased_storage_allocation | enforce_namespace_storage_limit 
----------------------------------------+---------------------------------
 t                                      | t
(1 row)

gitlabhq_development=# 
  1. Enable the :namespace_storage_limit, :enforce_storage_limit_for_paid, :enforce_storage_limit_for_free, and :namespace_storage_limit_bypass_date_check feature flags in a rails console.
[1] pry(main)> Feature.enable(:namespace_storage_limit)
[...]
[2] pry(main)> Feature.enable(:enforce_storage_limit_for_paid)
[...]
[3] pry(main)> Feature.enable(:enforce_storage_limit_for_free)
[...]
[4] pry(main)> Feature.enable(:namespace_storage_limit_bypass_date_check)
[...]
[5] pry(main)> 
  1. Apply the following patch to your local gitlab instance. This patch disables some caching around storage size and storage limits.
diff --git a/ee/app/models/ee/namespace/root_storage_size.rb b/ee/app/models/ee/namespace/root_storage_size.rb
index fd612db00ec..b37918bdfbc 100644
--- a/ee/app/models/ee/namespace/root_storage_size.rb
+++ b/ee/app/models/ee/namespace/root_storage_size.rb
@@ -26,16 +26,16 @@ def usage_ratio
     end
 
     def current_size
-      @current_size ||= Rails.cache.fetch(['namespaces', root_namespace.id, CURRENT_SIZE_CACHE_KEY], expires_in: EXPIRATION_TIME) do
+      # @current_size ||= Rails.cache.fetch(['namespaces', root_namespace.id, CURRENT_SIZE_CACHE_KEY], expires_in: EXPIRATION_TIME) do
         root_namespace.root_storage_statistics&.storage_size
-      end
+      # end
     end
 
     def limit
-      @limit ||= Rails.cache.fetch(['namespaces', root_namespace.id, LIMIT_CACHE_KEY], expires_in: EXPIRATION_TIME) do
+      # @limit ||= Rails.cache.fetch(['namespaces', root_namespace.id, LIMIT_CACHE_KEY], expires_in: EXPIRATION_TIME) do
         root_namespace.actual_limits.storage_size_limit.megabytes +
             root_namespace.additional_purchased_storage_size.megabytes
-      end
+      # end
     end
 
     def remaining_storage_percentage
  1. Restart your GDK simulating saas mode with $ GITLAB_SIMULATE_SAAS=1 gdk start.

  2. Setup a project using git LFS. Clone the repository. For this example, files with *.iso extensions will use LFS, and files with *.png extensions will be able to use file locking. So you will want to run the following two commands while you setup LFS (these are the two commands in the examples in the documentation):

git lfs track "*.iso"

git lfs track "*.png" --lockable
  1. Navigate to your project's group page. In the example here, my project is "Storage LFS Project" under the "Storage Test Group" group so I navigate to the "Storage Test Group" page, pictured below.

Screen_Shot_2022-05-23_at_1.45.05_PM

Navigate to Settings > Usage Quotas. Click the Storage tab. You can see how much total namespace storage you currently have here.

Screen_Shot_2022-05-23_at_1_47_08_PM

This can give you a good idea of what number to use in the SQL UPDATE statements in the following steps.

  1. Find the plan_limit.id of your local plan_limit for the free plan. (The project in these examples is in a group on the free plan. You may need to use a different plan if your project is on an Ultimate or Premium plan.)
gitlabhq_development=# SELECT p.id, p.name, p.title, l.id, l.storage_size_limit FROM plans AS p JOIN plan_limits AS l ON p.id = l.plan_id;
 id |      name      |           title           | id | storage_size_limit 
----+----------------+---------------------------+----+--------------------
  1 | default        | Default                   |  1 |                  0
  2 | bronze         | Bronze                    |  6 |                  0
  3 | silver         | Silver                    |  7 |                  0
  4 | gold           | Gold                      |  8 |                  0
  5 | free           |                           |  5 |                 30
  6 | premium        | Premium (Formerly Silver) | 23 |                  0
  7 | ultimate       | Ultimate (Formerly Gold)  | 26 |                  0
  8 | ultimate_trial | Ultimate Trial            |  3 |                  0
  9 | premium_trial  | Premium Trial             |  4 |                  0
 10 | opensource     | Open Source Program       | 28 |                  0
(10 rows)

gitlabhq_development=# 

Make sure that the storage_size_limit for the free plan is either 0 or larger than your namespace storage size.

Verify LFS pushes are rejected

  1. Create a new branch. Add an iso file to the branch. Commit the file and push the branch.
$ git checkout -b add-pictures
Switched to a new branch 'add-pictures'
$ echo 'some data' > file.iso
$ git add file.iso 
$ git commit -m 'Add an iso file'
[add-pictures 415fada] Add an iso file
 1 file changed, 3 insertions(+)
 create mode 100644 file.iso
$ git push -u local-gitlab add-pictures
Uploading LFS objects: 100% (1/1), 10 B | 0 B/s, done.                                                                                                                                                                                                                                                                                                                    
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 375 bytes | 375.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: 
remote: To create a merge request for add-pictures, visit:
remote:   http://local-gitlab:3000/storage-test-group/storage-lfs-project/-/merge_requests/new?merge_request%5Bsource_branch%5D=add-pictures
remote: 
To ssh://127.0.0.1:2222/storage-test-group/storage-lfs-project.git
 * [new branch]      add-pictures -> add-pictures
branch 'add-pictures' set up to track 'local-gitlab/add-pictures'.
$ 
  1. Update your local plan limits for the free plan. Pick a value under the total namespace storage. Your id may be different than the one seen below.
gitlabhq_development=# UPDATE plan_limits SET storage_size_limit = 3 WHERE id = 5;
UPDATE 1
gitlabhq_development=# 
  1. Update the iso file. Try git push. The push is rejected.
$ echo 'more data' >> file.iso 
$ git add file.iso 
$ git commit -m 'Update iso file'
[add-pictures fd4a3e6] Update iso file
 1 file changed, 2 insertions(+), 2 deletions(-)
$ git push
Remote "local-gitlab" does not support the Git LFS locking API. Consider disabling it with:
  $ git config lfs.https://127.0.0.1/storage-test-group/storage-lfs-project.git/info/lfs.locksverify false
Your push to this repository has been rejected because the namespace storage limit of 3 MB has been reached. Reduce your namespace storage or purchase additional storage.
error: failed to push some refs to 'ssh://127.0.0.1:2222/storage-test-group/storage-lfs-project.git'
$ 

Verify that LFS locks are rejected

  1. Update the storage limit. Pick a number over the total namespace storage.
gitlabhq_development=# UPDATE plan_limits SET storage_size_limit = 30 WHERE id = 5;
UPDATE 1
gitlabhq_development=# 
  1. Add two new .png files - cat.png and dog.png. Commit and push them. Lock cat.png.
$ echo 'data' > cat.png
$ git add cat.png 
$ git commit -m 'Add cat picture'
[add-pictures ef3ab92] Add cat picture
 1 file changed, 3 insertions(+)
 create mode 100644 cat.png
$ git push
Uploading LFS objects: 100% (1/1), 5 B | 0 B/s, done.                                                                                                                                                                                                                                                                                                                     
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 373 bytes | 373.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: 
remote: To create a merge request for add-pictures, visit:
remote:   http://local-gitlab:3000/storage-test-group/storage-lfs-project/-/merge_requests/new?merge_request%5Bsource_branch%5D=add-pictures
remote: 
To ssh://127.0.0.1:2222/storage-test-group/storage-lfs-project.git
   9acf2f3..ef3ab92  add-pictures -> add-pictures
$ echo 'other data' > dog.png
$ git add dog.png 
$ git commit -m 'Add dog picture'
[add-pictures 2135e80] Add dog picture
 1 file changed, 3 insertions(+)
 create mode 100644 dog.png
$ git push
Uploading LFS objects: 100% (1/1), 11 B | 0 B/s, done.                                                                                                                                                                                                                                                                                                                    
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 377 bytes | 377.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: 
remote: To create a merge request for add-pictures, visit:
remote:   http://local-gitlab:3000/storage-test-group/storage-lfs-project/-/merge_requests/new?merge_request%5Bsource_branch%5D=add-pictures
remote: 
To ssh://127.0.0.1:2222/storage-test-group/storage-lfs-project.git
   ef3ab92..2135e80  add-pictures -> add-pictures
$ git lfs lock cat.png 
Locked cat.png
$ git lfs locks
cat.png Stan Smith      ID:5
$ 
  1. Update the storage limit. Pick a number under the total namespace storage.
gitlabhq_development=# UPDATE plan_limits SET storage_size_limit = 3 WHERE id = 5;
UPDATE 1
gitlabhq_development=# 
  1. Try to lock dog.png. Verify that the lock is rejected.
$ git lfs lock dog.png 
Locking dog.png failed: Your push to this repository has been rejected because the namespace storage limit of 3 MB has been reached. Reduce your namespace storage or purchase additional storage.
$ git lfs locks
cat.png Stan Smith      ID:5
$ 
  1. Verify that attempting to unlock cat.png is rejected.
$ git lfs unlock cat.png 
Your push to this repository has been rejected because the namespace storage limit of 3 MB has been reached. Reduce your namespace storage or purchase additional storage.
$ git lfs locks
cat.png Stan Smith      ID:5
$ 

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Jason Goodman

Merge request reports