Skip to content

Commit 40ca610

Browse files
committed
Add lazy signing support for WebDAV StorageCliClient
Implements on-demand URL signing for WebDAV blobstore to support dual endpoints (internal for Diego, public for external users). - Add supports_lazy_signing? method to detect DAV provider - Add sign_internal_url and sign_public_url methods for DAV - StorageCliBlob generates URLs on-demand when lazy signing enabled - Non-DAV providers continue using eager signing (pre-generated URLs) - Add comprehensive test coverage for lazy signing functionality This maintains backward compatibility with existing fog/webdav behavior where internal and public signed URLs are generated on-demand with different endpoints.
1 parent 0c5419f commit 40ca610

4 files changed

Lines changed: 364 additions & 22 deletions

File tree

lib/cloud_controller/blobstore/storage_cli/storage_cli_blob.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,26 @@ module Blobstore
33
class StorageCliBlob < Blob
44
attr_reader :key
55

6-
def initialize(key, properties: nil, signed_url: nil)
6+
def initialize(key, properties: nil, signed_url: nil, storage_cli_client: nil, expires_in_seconds: 3600)
77
@key = key
88
@signed_url = signed_url if signed_url
9+
@storage_cli_client = storage_cli_client
10+
@expires_in_seconds = expires_in_seconds
911
# Set properties to an empty hash if nil to avoid nil errors
1012
@properties = properties || {}
1113
end
1214

1315
def internal_download_url
16+
# For DAV with lazy signing support, generate URL on-demand
17+
return @storage_cli_client.sign_internal_url(@key, verb: 'get', expires_in_seconds: @expires_in_seconds) if @storage_cli_client&.supports_lazy_signing?
18+
1419
signed_url
1520
end
1621

1722
def public_download_url
23+
# For DAV with lazy signing support, generate URL on-demand
24+
return @storage_cli_client.sign_public_url(@key, verb: 'get', expires_in_seconds: @expires_in_seconds) if @storage_cli_client&.supports_lazy_signing?
25+
1826
signed_url
1927
end
2028

lib/cloud_controller/blobstore/storage_cli/storage_cli_client.rb

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,29 @@ def blob(key)
168168
properties = properties(key)
169169
return nil if properties.nil? || properties.empty?
170170

171-
signed_url = sign_url(partitioned_key(key), verb: 'get', expires_in_seconds: 3600)
172-
StorageCliBlob.new(key, properties:, signed_url:)
171+
# For DAV with lazy signing support, pass client reference for on-demand signing
172+
# For other providers, generate signed URL eagerly
173+
if supports_lazy_signing?
174+
StorageCliBlob.new(key, properties: properties, storage_cli_client: self, expires_in_seconds: 3600)
175+
else
176+
signed_url = sign_url(partitioned_key(key), verb: 'get', expires_in_seconds: 3600)
177+
StorageCliBlob.new(key, properties:, signed_url:)
178+
end
179+
end
180+
181+
def supports_lazy_signing?
182+
# Only DAV with external signer needs lazy signing for internal vs public endpoints
183+
@storage_type == 'dav'
184+
end
185+
186+
def sign_internal_url(key, verb:, expires_in_seconds:)
187+
stdout, _status = run_cli('sign-internal', partitioned_key(key), verb.to_s.downcase, "#{expires_in_seconds}s")
188+
stdout.strip
189+
end
190+
191+
def sign_public_url(key, verb:, expires_in_seconds:)
192+
stdout, _status = run_cli('sign-public', partitioned_key(key), verb.to_s.downcase, "#{expires_in_seconds}s")
193+
stdout.strip
173194
end
174195

175196
def files_for(prefix, _ignored_directory_prefixes=[])

spec/unit/lib/cloud_controller/blobstore/storage_cli/storage_cli_blob_spec.rb

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,134 @@ module Blobstore
1818
end
1919

2020
describe 'download_urls' do
21-
it 'returns the internal download URL of the blob' do
22-
expect(blob.internal_download_url).to eq(signed_url)
21+
context 'with pre-generated signed URL (eager signing for non-DAV providers)' do
22+
it 'returns the internal download URL of the blob' do
23+
expect(blob.internal_download_url).to eq(signed_url)
24+
end
25+
26+
it 'returns the public download URL of the blob' do
27+
expect(blob.public_download_url).to eq(signed_url)
28+
end
29+
end
30+
31+
context 'with lazy signing (for DAV provider)' do
32+
let(:storage_cli_client) { double('StorageCliClient') }
33+
34+
before do
35+
allow(storage_cli_client).to receive(:supports_lazy_signing?).and_return(true)
36+
end
37+
38+
subject(:lazy_blob) do
39+
StorageCliBlob.new(
40+
'dr/op/droplet-guid',
41+
properties: properties,
42+
storage_cli_client: storage_cli_client,
43+
expires_in_seconds: 3600
44+
)
45+
end
46+
47+
describe '#internal_download_url' do
48+
it 'calls sign_internal_url on the storage_cli_client' do
49+
expect(storage_cli_client).to receive(:sign_internal_url).with(
50+
'dr/op/droplet-guid',
51+
verb: 'get',
52+
expires_in_seconds: 3600
53+
).and_return('https://blobstore.internal:4443/read/cc-droplets/dr/op/droplet-guid?md5=internal123&expires=789')
54+
55+
url = lazy_blob.internal_download_url
56+
57+
expect(url).to eq('https://blobstore.internal:4443/read/cc-droplets/dr/op/droplet-guid?md5=internal123&expires=789')
58+
end
59+
60+
it 'generates URL on-demand each time it is called' do
61+
call_count = 0
62+
allow(storage_cli_client).to receive(:sign_internal_url) do
63+
call_count += 1
64+
"https://blobstore.internal/url-#{call_count}"
65+
end
66+
67+
url1 = lazy_blob.internal_download_url
68+
url2 = lazy_blob.internal_download_url
69+
70+
expect(url1).to eq('https://blobstore.internal/url-1')
71+
expect(url2).to eq('https://blobstore.internal/url-2')
72+
expect(call_count).to eq(2)
73+
end
74+
end
75+
76+
describe '#public_download_url' do
77+
it 'calls sign_public_url on the storage_cli_client' do
78+
expect(storage_cli_client).to receive(:sign_public_url).with(
79+
'dr/op/droplet-guid',
80+
verb: 'get',
81+
expires_in_seconds: 3600
82+
).and_return('https://blobstore.example.com/read/cc-droplets/dr/op/droplet-guid?md5=public456&expires=999')
83+
84+
url = lazy_blob.public_download_url
85+
86+
expect(url).to eq('https://blobstore.example.com/read/cc-droplets/dr/op/droplet-guid?md5=public456&expires=999')
87+
end
88+
89+
it 'generates URL on-demand each time it is called' do
90+
call_count = 0
91+
allow(storage_cli_client).to receive(:sign_public_url) do
92+
call_count += 1
93+
"https://blobstore.public/url-#{call_count}"
94+
end
95+
96+
url1 = lazy_blob.public_download_url
97+
url2 = lazy_blob.public_download_url
98+
99+
expect(url1).to eq('https://blobstore.public/url-1')
100+
expect(url2).to eq('https://blobstore.public/url-2')
101+
expect(call_count).to eq(2)
102+
end
103+
end
104+
105+
it 'uses custom expires_in_seconds when provided' do
106+
custom_blob = StorageCliBlob.new(
107+
'test-key',
108+
properties: properties,
109+
storage_cli_client: storage_cli_client,
110+
expires_in_seconds: 7200
111+
)
112+
113+
expect(storage_cli_client).to receive(:sign_internal_url).with(
114+
'test-key',
115+
verb: 'get',
116+
expires_in_seconds: 7200
117+
).and_return('url')
118+
119+
custom_blob.internal_download_url
120+
end
23121
end
24122

25-
it 'returns the public download URL of the blob' do
26-
expect(blob.public_download_url).to eq(signed_url)
123+
context 'when storage_cli_client does not support lazy signing' do
124+
let(:storage_cli_client) { double('StorageCliClient') }
125+
126+
before do
127+
allow(storage_cli_client).to receive(:supports_lazy_signing?).and_return(false)
128+
end
129+
130+
subject(:non_lazy_blob) do
131+
StorageCliBlob.new(
132+
'test-blob',
133+
properties: properties,
134+
signed_url: signed_url,
135+
storage_cli_client: storage_cli_client
136+
)
137+
end
138+
139+
it 'falls back to using pre-generated signed_url for internal_download_url' do
140+
expect(non_lazy_blob.internal_download_url).to eq(signed_url)
141+
end
142+
143+
it 'falls back to using pre-generated signed_url for public_download_url' do
144+
expect(non_lazy_blob.public_download_url).to eq(signed_url)
145+
end
27146
end
28147

29-
context 'when signed_url is not provided' do
148+
context 'when signed_url is not provided and no storage_cli_client' do
30149
subject(:blob_without_signed_url) { StorageCliBlob.new('test-blob', properties:) }
31150

32151
it 'raises an error when accessing internal_download_url' do

0 commit comments

Comments
 (0)