Compare commits

...

16 Commits

Author SHA1 Message Date
a5da2fbd40 Add Gitea action for CI
All checks were successful
Tests / test (6, 2.7) (pull_request) Has been cancelled
Tests / test (6, 3.0) (pull_request) Has been cancelled
Tests / test (6, 3.1) (pull_request) Has been cancelled
Tests / test (7, 2.7) (pull_request) Has been cancelled
Tests / test (7, 3.0) (pull_request) Has been cancelled
Tests / test (7, 3.1) (pull_request) Has been cancelled
2023-11-21 15:45:13 +01:00
df1a3e6512 Add Drone CI config
Some checks failed
continuous-integration/drone Build is failing
2023-11-21 15:38:15 +01:00
8b6f201a0b Merge pull request 'Switch to AWS v4 signatures' (#5) from feature/aws_v4_signatures into master
All checks were successful
Tests / test (6, 2.7) (push) Has been cancelled
Tests / test (6, 3.0) (push) Has been cancelled
Tests / test (6, 3.1) (push) Has been cancelled
Tests / test (7, 2.7) (push) Has been cancelled
Tests / test (7, 3.0) (push) Has been cancelled
Tests / test (7, 3.1) (push) Has been cancelled
Reviewed-on: #5
2023-11-21 14:19:38 +00:00
209fcca5ea Make headers argument optional 2023-11-21 15:18:13 +01:00
589dcf7fa2 Update example config 2023-11-16 12:39:46 +01:00
91cadbf228 Switch Docker image back to Ruby 3.1 2023-11-16 11:45:07 +01:00
d09c6e7a39 Use production env for Docker image 2023-11-16 11:41:57 +01:00
93fd1ecd5f Merge pull request 'Add Dockerfile' (#2) from dev/add_dockerfile into master
Reviewed-on: #2
2023-11-16 10:40:51 +00:00
024f516a9d Expose Content-Type header 2023-11-16 11:39:33 +01:00
96417e3884 Use development env and Webrick in dev run script 2023-11-16 11:27:15 +01:00
b7ff3e7d42 Remove debug output, rescue 2023-11-16 10:27:58 +01:00
02820203f0 Switch to AWS V4 signatures 2023-11-15 19:05:21 +01:00
189c304a08 Add example script for running server in dev 2023-11-15 19:03:33 +01:00
273768ea69 Add Dockerfile 2023-11-01 16:21:34 +01:00
648c3943e8 Add redis db config 2023-10-26 17:31:52 +02:00
9aed3f7cca Update README 2023-10-26 16:25:07 +02:00
10 changed files with 103 additions and 50 deletions

9
.drone.yml Normal file
View File

@@ -0,0 +1,9 @@
kind: pipeline
name: default
steps:
- name: test
image: ruby
commands:
- bundle install --jobs=3 --retry=3
- rake test

33
.gitea/workflows/ruby.yml Normal file
View File

@@ -0,0 +1,33 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.7', '3.0', '3.1']
redis-version: [6, 7]
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Start Redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: ${{ matrix.redis-version }}
- name: Configure
run: cp config.yml.erb.example config.yml.erb
- name: Run tests
run: bundle exec rake test

13
Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM ruby:3.1.4
WORKDIR /liquorcabinet
ENV RACK_ENV=production
COPY Gemfile Gemfile.lock /liquorcabinet/
RUN bundle install
COPY . /liquorcabinet
COPY ./config.yml.erb.example /liquorcabinet/config.yml.erb
EXPOSE 4567
CMD ["bundle", "exec", "rainbows", "--listen", "0.0.0.0:4567"]

View File

@@ -3,8 +3,9 @@ source "https://rubygems.org"
gem "sinatra", "~> 2.2.0" gem "sinatra", "~> 2.2.0"
gem "sinatra-contrib", "~> 2.2.0" gem "sinatra-contrib", "~> 2.2.0"
gem "activesupport", "~> 6.1.0" gem "activesupport", "~> 6.1.0"
gem "rest-client", "~> 2.1.0"
gem "redis", "~> 4.6.0" gem "redis", "~> 4.6.0"
gem "rest-client", "~> 2.1.0"
gem "aws-sigv4", "~> 1.0.0"
# Remove require when we can update to 3.0, which sets the new storage # Remove require when we can update to 3.0, which sets the new storage
# format to columnar by default. Increases performance # format to columnar by default. Increases performance
gem "mime-types" gem "mime-types"

View File

@@ -9,6 +9,7 @@ GEM
zeitwerk (~> 2.3) zeitwerk (~> 2.3)
addressable (2.8.5) addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
aws-sigv4 (1.0.3)
base64 (0.1.1) base64 (0.1.1)
concurrent-ruby (1.2.2) concurrent-ruby (1.2.2)
crack (0.4.5) crack (0.4.5)
@@ -93,6 +94,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
activesupport (~> 6.1.0) activesupport (~> 6.1.0)
aws-sigv4 (~> 1.0.0)
m m
mime-types mime-types
minitest minitest

View File

@@ -12,10 +12,9 @@ protocol, but does not include the Webfinger and OAuth parts. It is meant to be
added to existing systems and user accounts, so you will have to add your own added to existing systems and user accounts, so you will have to add your own
OAuth dialog for remoteStorage authorizations and persist the tokens in Redis. OAuth dialog for remoteStorage authorizations and persist the tokens in Redis.
If you have any questions about this program, drop by #remotestorage on If you have any questions about this program, please [post to the RS
Freenode, or [post to the RS
forums](https://community.remotestorage.io/c/server-development), and we'll forums](https://community.remotestorage.io/c/server-development), and we'll
happily answer them. gladly answer them.
## Contributing ## Contributing
@@ -23,5 +22,5 @@ We love pull requests. If you want to submit a patch:
* Fork the project. * Fork the project.
* Make your feature addition or bug fix. * Make your feature addition or bug fix.
* Write specs for it. This is important so nobody breaks it in a future version unintentionally. * Write specs for it. This is important so nobody breaks it in a future version.
* Push to your fork and send a pull request. * Push to your fork and send a pull request.

View File

@@ -2,10 +2,11 @@ development: &defaults
maintenance: false maintenance: false
redis: redis:
host: <%= ENV["REDIS_HOST"] || "localhost" %> host: <%= ENV["REDIS_HOST"] || "localhost" %>
port: <%= ENV["REDIS_PORT"] || "6379" %> port: <%= ENV["REDIS_PORT"] || 6379 %>
db: <%= ENV["REDIS_DB"] || 1 %>
s3: &s3_defaults s3: &s3_defaults
endpoint: <%= ENV["S3_ENDPOINT"] || "http://127.0.0.1:9000" %> endpoint: <%= ENV["S3_ENDPOINT"] || "http://127.0.0.1:9000" %>
region: <%= ENV["S3_REGION"] %> region: <%= ENV["S3_REGION"] || "us-east-1" %>
access_key_id: <%= ENV["S3_ACCESS_KEY"] || "minioadmin" %> access_key_id: <%= ENV["S3_ACCESS_KEY"] || "minioadmin" %>
secret_key_id: <%= ENV["S3_SECRET_KEY"] || "minioadmin" %> secret_key_id: <%= ENV["S3_SECRET_KEY"] || "minioadmin" %>
bucket: <%= ENV["S3_BUCKET"] || "rs-development" %> bucket: <%= ENV["S3_BUCKET"] || "rs-development" %>

View File

@@ -10,18 +10,26 @@ module RemoteStorage
private private
# S3 already wraps the ETag around quotes def s3_signer
signer ||= Aws::Sigv4::Signer.new(
service: 's3',
region: settings.s3["region"],
access_key_id: settings.s3["access_key_id"].to_s,
secret_access_key: settings.s3["secret_key_id"].to_s
)
end
# S3 already wraps the ETag with quotes
def format_etag(etag) def format_etag(etag)
etag etag
end end
def do_put_request(url, data, content_type) def do_put_request(url, data, content_type)
deal_with_unauthorized_requests do deal_with_unauthorized_requests do
md5 = Digest::MD5.base64digest(data) headers = { "Content-Type" => content_type }
authorization_headers = authorization_headers_for( auth_headers = auth_headers_for("PUT", url, headers, data)
"PUT", url, md5, content_type
).merge({ "Content-Type" => content_type, "Content-Md5" => md5 }) res = RestClient.put(url, data, headers.merge(auth_headers))
res = RestClient.put(url, data, authorization_headers)
return [ return [
res.headers[:etag].delete('"'), res.headers[:etag].delete('"'),
@@ -32,24 +40,24 @@ module RemoteStorage
def do_get_request(url, &block) def do_get_request(url, &block)
deal_with_unauthorized_requests do deal_with_unauthorized_requests do
headers = { } headers = {}
headers["Range"] = server.env["HTTP_RANGE"] if server.env["HTTP_RANGE"] headers["Range"] = server.env["HTTP_RANGE"] if server.env["HTTP_RANGE"]
authorization_headers = authorization_headers_for("GET", url) auth_headers = auth_headers_for("GET", url, headers)
RestClient.get(url, authorization_headers.merge(headers), &block) RestClient.get(url, headers.merge(auth_headers), &block)
end end
end end
def do_head_request(url, &block) def do_head_request(url, &block)
deal_with_unauthorized_requests do deal_with_unauthorized_requests do
authorization_headers = authorization_headers_for("HEAD", url) auth_headers = auth_headers_for("HEAD", url)
RestClient.head(url, authorization_headers, &block) RestClient.head(url, auth_headers, &block)
end end
end end
def do_delete_request(url) def do_delete_request(url)
deal_with_unauthorized_requests do deal_with_unauthorized_requests do
authorization_headers = authorization_headers_for("DELETE", url) auth_headers = auth_headers_for("DELETE", url)
RestClient.delete(url, authorization_headers) RestClient.delete(url, auth_headers)
end end
end end
@@ -67,40 +75,17 @@ module RemoteStorage
return found return found
end end
# This is using the S3 authorizations, not the newer AW V4 Signatures def auth_headers_for(http_method, url, headers = {}, data = nil)
# (https://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html) signature = s3_signer.sign_request(
def authorization_headers_for(http_verb, url, md5 = nil, content_type = nil) http_method: http_method, url: url, headers: headers, body: data
url = File.join("/", url.gsub(base_url, "")) )
date = Time.now.httpdate signature.headers
signed_data = generate_s3_signature(http_verb, md5, content_type, date, url)
{
"Authorization" => "AWS #{credentials[:access_key_id]}:#{signed_data}",
"Date" => date
}
end
def credentials
@credentials ||= { access_key_id: settings.s3["access_key_id"], secret_key_id: settings.s3["secret_key_id"] }
end
def digest(secret, string_to_sign)
Base64.encode64(hmac(secret, string_to_sign)).strip
end
def hmac(key, value)
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), key, value)
end end
def uri_escape(s) def uri_escape(s)
CGI.escape(s).gsub('%5B', '[').gsub('%5D', ']') CGI.escape(s).gsub('%5B', '[').gsub('%5D', ']')
end end
def generate_s3_signature(http_verb, md5, content_type, date, url)
string_to_sign = [http_verb, md5, content_type, date, url].join "\n"
signature = digest(credentials[:secret_key_id], string_to_sign)
uri_escape(signature)
end
def base_url def base_url
@base_url ||= settings.s3["endpoint"] @base_url ||= settings.s3["endpoint"]
end end
@@ -109,5 +94,4 @@ module RemoteStorage
"#{base_url}/#{settings.s3["bucket"]}/#{user}" "#{base_url}/#{settings.s3["bucket"]}/#{user}"
end end
end end
end end

View File

@@ -64,7 +64,7 @@ class LiquorCabinet < Sinatra::Base
headers 'Access-Control-Allow-Origin' => '*', headers 'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET, PUT, DELETE', 'Access-Control-Allow-Methods' => 'GET, PUT, DELETE',
'Access-Control-Allow-Headers' => 'Authorization, Content-Type, Origin, If-Match, If-None-Match, Range', 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, Origin, If-Match, If-None-Match, Range',
'Access-Control-Expose-Headers' => 'ETag, Content-Length, Content-Range', 'Access-Control-Expose-Headers' => 'ETag, Content-Length, Content-Range, Content-Type',
'Accept-Ranges' => 'bytes' 'Accept-Ranges' => 'bytes'
headers['Access-Control-Allow-Origin'] = env["HTTP_ORIGIN"] if env["HTTP_ORIGIN"] headers['Access-Control-Allow-Origin'] = env["HTTP_ORIGIN"] if env["HTTP_ORIGIN"]
headers['Cache-Control'] = 'no-cache' headers['Cache-Control'] = 'no-cache'

11
run-dev.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
RACK_ENV=development \
REDIS_HOST=localhost \
REDIS_PORT=6379 \
REDIS_DB=1 \
S3_ENDPOINT='http://localhost:9000' \
S3_ACCESS_KEY='dev-key' \
S3_SECRET_KEY='123456789' \
S3_BUCKET=remotestorage \
bundle exec rackup -p 4567