Skip to content

Commit dad1159

Browse files
committed
Add Dav Signing Functionality
WebDAV needs to sign URLs against two different endpoints: one for internal consumers (Diego cells via BOSH network) and one for external consumers (CAPI downloads via gorouter). Adds public_endpoint to DAV config, SignInternal and SignPublic methods to the DAV client, and sign-internal/sign-public CLI commands via a type assertion so the cross-backend Storager interface stays unchanged.
1 parent f93f8a2 commit dad1159

6 files changed

Lines changed: 397 additions & 10 deletions

File tree

dav/client/client.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,42 @@ func (d *DavBlobstore) Sign(dest string, action string, expiration time.Duration
138138
}
139139
}
140140

141+
func (d *DavBlobstore) SignInternal(dest string, action string, expiration time.Duration) (string, error) {
142+
slog.Info("signing internal url for webdav", "dest", dest, "action", action, "expiration", expiration)
143+
if err := validateBlobID(dest); err != nil {
144+
return "", err
145+
}
146+
action = strings.ToUpper(action)
147+
switch action {
148+
case "GET", "PUT":
149+
signedURL, err := d.storageClient.SignInternal(dest, action, expiration)
150+
if err != nil {
151+
return "", fmt.Errorf("failed to sign internal URL: %w", err)
152+
}
153+
return signedURL, nil
154+
default:
155+
return "", fmt.Errorf("action not implemented: %s", action)
156+
}
157+
}
158+
159+
func (d *DavBlobstore) SignPublic(dest string, action string, expiration time.Duration) (string, error) {
160+
slog.Info("signing public url for webdav", "dest", dest, "action", action, "expiration", expiration)
161+
if err := validateBlobID(dest); err != nil {
162+
return "", err
163+
}
164+
action = strings.ToUpper(action)
165+
switch action {
166+
case "GET", "PUT":
167+
signedURL, err := d.storageClient.SignPublic(dest, action, expiration)
168+
if err != nil {
169+
return "", fmt.Errorf("failed to sign public URL: %w", err)
170+
}
171+
return signedURL, nil
172+
default:
173+
return "", fmt.Errorf("action not implemented: %s", action)
174+
}
175+
}
176+
141177
func (d *DavBlobstore) DeleteRecursive(prefix string) error {
142178
slog.Info("deleting blobs recursively from webdav", "prefix", prefix)
143179
return d.storageClient.DeleteRecursive(prefix)

dav/client/client_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,142 @@ var _ = Describe("Client", func() {
185185
})
186186
})
187187

188+
Context("SignInternal", func() {
189+
var expiry = 100 * time.Second
190+
191+
It("forwards args to the storage client and returns the signed URL", func() {
192+
fakeStorageClient := &clientfakes.FakeStorageClient{}
193+
fakeStorageClient.SignInternalReturns("https://internal-url", nil)
194+
195+
davBlobstore := client.NewWithStorageClient(fakeStorageClient)
196+
url, err := davBlobstore.SignInternal("blob/path", "get", expiry)
197+
198+
Expect(err).NotTo(HaveOccurred())
199+
Expect(url).To(Equal("https://internal-url"))
200+
201+
Expect(fakeStorageClient.SignInternalCallCount()).To(Equal(1))
202+
object, action, expiration := fakeStorageClient.SignInternalArgsForCall(0)
203+
Expect(object).To(Equal("blob/path"))
204+
Expect(action).To(Equal("GET"))
205+
Expect(expiration).To(Equal(expiry))
206+
})
207+
208+
It("uppercases the action before forwarding", func() {
209+
fakeStorageClient := &clientfakes.FakeStorageClient{}
210+
fakeStorageClient.SignInternalReturns("https://internal-url", nil)
211+
212+
davBlobstore := client.NewWithStorageClient(fakeStorageClient)
213+
_, err := davBlobstore.SignInternal("blob/path", "put", expiry)
214+
Expect(err).NotTo(HaveOccurred())
215+
216+
_, action, _ := fakeStorageClient.SignInternalArgsForCall(0)
217+
Expect(action).To(Equal("PUT"))
218+
})
219+
220+
It("rejects an invalid blob ID without calling the storage client", func() {
221+
fakeStorageClient := &clientfakes.FakeStorageClient{}
222+
223+
davBlobstore := client.NewWithStorageClient(fakeStorageClient)
224+
url, err := davBlobstore.SignInternal("", "get", expiry)
225+
226+
Expect(err).To(HaveOccurred())
227+
Expect(url).To(Equal(""))
228+
Expect(fakeStorageClient.SignInternalCallCount()).To(Equal(0))
229+
})
230+
231+
It("rejects unknown actions without calling the storage client", func() {
232+
fakeStorageClient := &clientfakes.FakeStorageClient{}
233+
234+
davBlobstore := client.NewWithStorageClient(fakeStorageClient)
235+
url, err := davBlobstore.SignInternal("blob/path", "delete", expiry)
236+
237+
Expect(err).To(HaveOccurred())
238+
Expect(err.Error()).To(ContainSubstring("action not implemented"))
239+
Expect(url).To(Equal(""))
240+
Expect(fakeStorageClient.SignInternalCallCount()).To(Equal(0))
241+
})
242+
243+
It("propagates errors from the storage client", func() {
244+
fakeStorageClient := &clientfakes.FakeStorageClient{}
245+
fakeStorageClient.SignInternalReturns("", fmt.Errorf("internal-boom"))
246+
247+
davBlobstore := client.NewWithStorageClient(fakeStorageClient)
248+
url, err := davBlobstore.SignInternal("blob/path", "get", expiry)
249+
250+
Expect(err).To(HaveOccurred())
251+
Expect(err.Error()).To(ContainSubstring("internal-boom"))
252+
Expect(url).To(Equal(""))
253+
})
254+
})
255+
256+
Context("SignPublic", func() {
257+
var expiry = 100 * time.Second
258+
259+
It("forwards args to the storage client and returns the signed URL", func() {
260+
fakeStorageClient := &clientfakes.FakeStorageClient{}
261+
fakeStorageClient.SignPublicReturns("https://public-url", nil)
262+
263+
davBlobstore := client.NewWithStorageClient(fakeStorageClient)
264+
url, err := davBlobstore.SignPublic("blob/path", "get", expiry)
265+
266+
Expect(err).NotTo(HaveOccurred())
267+
Expect(url).To(Equal("https://public-url"))
268+
269+
Expect(fakeStorageClient.SignPublicCallCount()).To(Equal(1))
270+
object, action, expiration := fakeStorageClient.SignPublicArgsForCall(0)
271+
Expect(object).To(Equal("blob/path"))
272+
Expect(action).To(Equal("GET"))
273+
Expect(expiration).To(Equal(expiry))
274+
})
275+
276+
It("uppercases the action before forwarding", func() {
277+
fakeStorageClient := &clientfakes.FakeStorageClient{}
278+
fakeStorageClient.SignPublicReturns("https://public-url", nil)
279+
280+
davBlobstore := client.NewWithStorageClient(fakeStorageClient)
281+
_, err := davBlobstore.SignPublic("blob/path", "put", expiry)
282+
Expect(err).NotTo(HaveOccurred())
283+
284+
_, action, _ := fakeStorageClient.SignPublicArgsForCall(0)
285+
Expect(action).To(Equal("PUT"))
286+
})
287+
288+
It("rejects an invalid blob ID without calling the storage client", func() {
289+
fakeStorageClient := &clientfakes.FakeStorageClient{}
290+
291+
davBlobstore := client.NewWithStorageClient(fakeStorageClient)
292+
url, err := davBlobstore.SignPublic("", "get", expiry)
293+
294+
Expect(err).To(HaveOccurred())
295+
Expect(url).To(Equal(""))
296+
Expect(fakeStorageClient.SignPublicCallCount()).To(Equal(0))
297+
})
298+
299+
It("rejects unknown actions without calling the storage client", func() {
300+
fakeStorageClient := &clientfakes.FakeStorageClient{}
301+
302+
davBlobstore := client.NewWithStorageClient(fakeStorageClient)
303+
url, err := davBlobstore.SignPublic("blob/path", "delete", expiry)
304+
305+
Expect(err).To(HaveOccurred())
306+
Expect(err.Error()).To(ContainSubstring("action not implemented"))
307+
Expect(url).To(Equal(""))
308+
Expect(fakeStorageClient.SignPublicCallCount()).To(Equal(0))
309+
})
310+
311+
It("propagates errors from the storage client", func() {
312+
fakeStorageClient := &clientfakes.FakeStorageClient{}
313+
fakeStorageClient.SignPublicReturns("", fmt.Errorf("public-boom"))
314+
315+
davBlobstore := client.NewWithStorageClient(fakeStorageClient)
316+
url, err := davBlobstore.SignPublic("blob/path", "get", expiry)
317+
318+
Expect(err).To(HaveOccurred())
319+
Expect(err.Error()).To(ContainSubstring("public-boom"))
320+
Expect(url).To(Equal(""))
321+
})
322+
})
323+
188324
Context("Copy", func() {
189325
It("forwards source and destination to the storage client", func() {
190326
fakeStorageClient := &clientfakes.FakeStorageClient{}

dav/client/clientfakes/fake_storage_client.go

Lines changed: 162 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)