Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions pkg/block/blocktest/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,6 @@ func getPresignedURLBasicTest(t *testing.T, adapter block.Adapter, storageNamesp
if errors.Is(err, block.ErrOperationNotSupported) {
t.Skip("GetPreSignedURL not supported")
}
// Google storage returns an error if no credentials are found, and we can't sign the URL
if err != nil && strings.Contains(err.Error(), "no credentials found") {
t.Skip("GetPreSignedURL no credentials found")
}
require.NoError(t, err)
return preSignedURL, &exp
}
Expand Down
29 changes: 28 additions & 1 deletion pkg/block/gs/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ type Adapter struct {
disablePreSignedUI bool
ServerSideEncryptionCustomerSupplied []byte
ServerSideEncryptionKmsKeyID string
nowFactory func() time.Time
// presignedGoogleAccessID and presignedPrivateKey are used for testing with fake-gcs-server
// which requires explicit signing credentials since the test client has no credentials
presignedGoogleAccessID string
presignedPrivateKey []byte
}

func WithPreSignedExpiry(v time.Duration) func(a *Adapter) {
Expand Down Expand Up @@ -73,12 +78,28 @@ func WithDisablePreSignedUI(b bool) func(a *Adapter) {
}
}

func WithNowFactory(f func() time.Time) func(a *Adapter) {
return func(a *Adapter) {
a.nowFactory = f
}
}

// WithPresignedCredentials sets the Google Access ID and private key for signing URLs.
// This is primarily used for testing with fake-gcs-server where the client has no credentials.
func WithPresignedCredentials(googleAccessID string, privateKey []byte) func(a *Adapter) {
return func(a *Adapter) {
a.presignedGoogleAccessID = googleAccessID
a.presignedPrivateKey = privateKey
}
}

type AdapterOption func(a *Adapter)

func NewAdapter(client *storage.Client, opts ...AdapterOption) *Adapter {
a := &Adapter{
client: client,
preSignedExpiry: block.DefaultPreSignExpiryDuration,
nowFactory: time.Now, // current time function can be mocked out via injection for testing purposes
}
for _, opt := range opts {
opt(a)
Expand All @@ -103,7 +124,7 @@ func (a *Adapter) log(ctx context.Context) logging.Logger {
}

func (a *Adapter) newPreSignedTime() time.Time {
return time.Now().UTC().Add(a.preSignedExpiry)
return a.nowFactory().UTC().Add(a.preSignedExpiry)
}

// withReadHandle returns a corresponding handle for reading object based on the encryption settings.
Expand Down Expand Up @@ -243,6 +264,12 @@ func (a *Adapter) GetPreSignedURL(ctx context.Context, obj block.ObjectPointer,
Expires: a.newPreSignedTime(),
}

// Use explicit signing credentials if provided (for testing with fake-gcs-server)
if a.presignedGoogleAccessID != "" && len(a.presignedPrivateKey) > 0 {
opts.GoogleAccessID = a.presignedGoogleAccessID
opts.PrivateKey = a.presignedPrivateKey
}

// Add content-disposition if filename provided
if mode == block.PreSignModeRead && filename != "" {
contentDisposition := mime.FormatMediaType("attachment", map[string]string{
Expand Down
43 changes: 40 additions & 3 deletions pkg/block/gs/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,42 @@ import (
"github.com/treeverse/lakefs/pkg/config"
)

func newAdapter() *gs.Adapter {
return gs.NewAdapter(client)
// testGoogleAccessID is a fake Google Access ID used for testing signed URLs
const testGoogleAccessID = "fake@test-project.iam.gserviceaccount.com"

// testPrivateKey is a PEM-encoded RSA private key for testing signed URLs with fake-gcs-server.
// This is a test-only key generated with: `openssl genrsa 2048`
var testPrivateKey = []byte(`-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC0LBKd6cf913PB
GIsh9qfrBT2limGijI8ctSQCH7CrbEjCGI4gtyUhgxKaFMV9hKeAAca9Kilck+xH
rjBM2kjzTHlkVgWa3TNy3VM2v26DsK9Q8v6p2enMLE6ofqWTyyNaaSDXuXVfNKGg
vzahyIUp7kZG1f8HKuBIybZk74gTCubYF4wNZQ/asJuq+o7QGTQpK6v4SfdQtwxM
6w0bRDc737M7WJLNn0r2dF4hOytQW7cO9vX02GrW94P3j+N5tT2ktrULo4XQxTZO
uA6DawWGRg2jf1hsZ2aiOJdUU71FBx9iU7Z9tL4QyB29TOPxi4OugK3NMWlMhv/G
93/feUHRAgMBAAECggEAB3KWcEC2ilhJJ0YEKKVf49EZxHbjyg1gyY/1zaDWeQGP
AOH1DGZnAnt+7c+rvwsNmvbyf9rcg+sz7khwTpmhKsMiS0rbLLTV1dAkX/waCFd0
fxu/pJEBecsqPbYNfV5dZzUhsnUkDvP3oJPNi/K5tBs5ZwpybNm21MoXcBTu2qhB
RvQ3IvhcQj2PUfPw9S7kx5bCtW9aLcn5u7ySv6WNRBPbaEiMQhCEpFNQuG9w3keR
tJiQUzgvUZlutnyczMU+EZchmSHMUQMyE6ah+QStiyyUtOC1vtV95ZvLboEc6NJk
sPmx8ESugdj3Tx8PSciRD5T4C2RmktVJpxYaGEHauQKBgQDysPnIHEPsV7OjviZu
UfblYWGiWol/MRHj8/TKIGYU/Ss1qXMsICLudq/sghBmYA0h6vN1i44RR1w1HtOU
8urv7u90CCFigfu28UzPl2ajTkLbbg9wQwQ6qundcMSScSVEu9xRUrVkJToy5zuM
itl5yOSavusdOvWwZlFaqiTQeQKBgQC+DWvaE4z+reVPghVyD4Ob76DW81eGoq0Y
6JIMKnZqETVNEzWjWcsPF5dE747gmxtapHRvQrjrz0hfNttQgp7VcPbyLVx6MXrK
qL/wqLpZpCx8yJ5MQUHe6a+DJGSJeFH1nEZ2Bw6aOjoOD2GvdPp6flDytwrDMXZM
9cVbIwqWGQKBgQDn/jVIDX0AmHWouUSTgNa7PvPN9y4o4AdyGOqPrZjnx3teuLTY
IYBC5EIXm92Bf6AOJELGwrjz23tRbD5lzDC5W3abPIptWEP/BXufleMPiOhwSi2H
6whH7MnSXNIMCwzNP6fENYQgT1XrAw/xsWli+Z9OLeMi9hGWprhuKuc2QQKBgQCM
RnO4fn2u7MM4MBeMHI9TZUcd4HZV1XRV0jMZ761/FDx3KxqH+xq5hPwN0ZNvjIxg
Fsop5OGAi3orbN3rSr3ZZIugrIJ5XlP3iR5CjwccauS7JYhRWEk6MtlsvkvGe5xi
4HnRW9wXUarP/eJoErtd9iXhP+EduUBMBYspfW+u4QKBgF/kF/fucq2QkMCpDf+H
ITIxEup3Tg81lZAbtnZpfTU7TcPvgBRLWtg3gEKJfTIGL79hl7lPhdqUG9w5egl8
KOGyY30wiwFQ4Lu1MX+BZTKgQG9FVDtOQ43JQOLCbYdcYeKU9tJi7FUgvZmdZRaL
sGXLHjxoneUJGohAIXVzlIGW
-----END PRIVATE KEY-----`)

func newAdapter(opts ...gs.AdapterOption) *gs.Adapter {
return gs.NewAdapter(client, opts...)
}

func TestAdapter(t *testing.T) {
Expand All @@ -28,7 +62,10 @@ func TestAdapter(t *testing.T) {
externalPath, err := url.JoinPath(basePath, "external")
require.NoError(t, err)

adapter := newAdapter()
adapter := newAdapter(
gs.WithNowFactory(blocktest.NowMockDefault),
gs.WithPresignedCredentials(testGoogleAccessID, testPrivateKey),
)
defer func() {
require.NoError(t, adapter.Close())
}()
Expand Down
Loading