cmd/uplink: --max-object-ttl flag for share and access restrict commands

Context: https://github.com/storj/storj/issues/6249

Change-Id: Ic65a1d8aef61f1a88752a7b12a23fb854dac8f6d
This commit is contained in:
Kaloyan Raev 2023-09-07 14:35:07 +03:00 committed by Storj Robot
parent 9254dd2208
commit 3119b614ae
9 changed files with 124 additions and 80 deletions

View File

@ -29,6 +29,8 @@ type accessPermissions struct {
notBefore *time.Time notBefore *time.Time
notAfter *time.Time notAfter *time.Time
maxObjectTTL *time.Duration
} }
func (ap *accessPermissions) Setup(params clingy.Parameters, prefixFlags bool) { func (ap *accessPermissions) Setup(params clingy.Parameters, prefixFlags bool) {
@ -65,6 +67,12 @@ func (ap *accessPermissions) Setup(params clingy.Parameters, prefixFlags bool) {
"Disallow access after this time (e.g. '+2h', 'now', '2020-01-02T15:04:05Z0700', 'none')", "Disallow access after this time (e.g. '+2h', 'now', '2020-01-02T15:04:05Z0700', 'none')",
nil, clingy.Transform(parseHumanDateNotAfter), clingy.Type("relative_date"), clingy.Optional).(*time.Time) nil, clingy.Transform(parseHumanDateNotAfter), clingy.Type("relative_date"), clingy.Optional).(*time.Time)
params.Break()
ap.maxObjectTTL = params.Flag("max-object-ttl",
"The object is automatically deleted after this period. (e.g. '1h30m', '24h', '720h')",
nil, clingy.Transform(time.ParseDuration), clingy.Type("period"), clingy.Optional).(*time.Duration)
if !prefixFlags { if !prefixFlags {
ap.prefixes = params.Arg("prefix", "Key prefix access will be restricted to", ap.prefixes = params.Arg("prefix", "Key prefix access will be restricted to",
clingy.Transform(ulloc.Parse), clingy.Transform(ulloc.Parse),
@ -93,6 +101,7 @@ func (ap *accessPermissions) Apply(access *uplink.Access) (*uplink.Access, error
AllowUpload: ap.AllowUpload(), AllowUpload: ap.AllowUpload(),
NotBefore: ap.NotBefore(), NotBefore: ap.NotBefore(),
NotAfter: ap.NotAfter(), NotAfter: ap.NotAfter(),
MaxObjectTTL: ap.MaxObjectTTL(),
} }
// if we aren't actually restricting anything, then we don't need to Share. // if we aren't actually restricting anything, then we don't need to Share.
@ -120,9 +129,10 @@ func defaulted[T any](val *T, def T) T {
return def return def
} }
func (ap *accessPermissions) NotBefore() time.Time { return defaulted(ap.notBefore, time.Time{}) } func (ap *accessPermissions) NotBefore() time.Time { return defaulted(ap.notBefore, time.Time{}) }
func (ap *accessPermissions) NotAfter() time.Time { return defaulted(ap.notAfter, time.Time{}) } func (ap *accessPermissions) NotAfter() time.Time { return defaulted(ap.notAfter, time.Time{}) }
func (ap *accessPermissions) AllowDelete() bool { return !defaulted(ap.disallowDeletes, ap.readonly) } func (ap *accessPermissions) AllowDelete() bool { return !defaulted(ap.disallowDeletes, ap.readonly) }
func (ap *accessPermissions) AllowList() bool { return !defaulted(ap.disallowLists, ap.writeonly) } func (ap *accessPermissions) AllowList() bool { return !defaulted(ap.disallowLists, ap.writeonly) }
func (ap *accessPermissions) AllowDownload() bool { return !defaulted(ap.disallowReads, ap.writeonly) } func (ap *accessPermissions) AllowDownload() bool { return !defaulted(ap.disallowReads, ap.writeonly) }
func (ap *accessPermissions) AllowUpload() bool { return !defaulted(ap.disallowWrites, ap.readonly) } func (ap *accessPermissions) AllowUpload() bool { return !defaulted(ap.disallowWrites, ap.readonly) }
func (ap *accessPermissions) MaxObjectTTL() *time.Duration { return ap.maxObjectTTL }

View File

@ -104,15 +104,16 @@ func (c *cmdShare) Execute(ctx context.Context) error {
fmt.Fprintf(clingy.Stdout(ctx), "Sharing access to satellite %s\n", access.SatelliteAddress()) fmt.Fprintf(clingy.Stdout(ctx), "Sharing access to satellite %s\n", access.SatelliteAddress())
fmt.Fprintf(clingy.Stdout(ctx), "=========== ACCESS RESTRICTIONS ==========================================================\n") fmt.Fprintf(clingy.Stdout(ctx), "=========== ACCESS RESTRICTIONS ==========================================================\n")
fmt.Fprintf(clingy.Stdout(ctx), "Download : %s\n", formatPermission(c.ap.AllowDownload())) fmt.Fprintf(clingy.Stdout(ctx), "Download : %s\n", formatPermission(c.ap.AllowDownload()))
fmt.Fprintf(clingy.Stdout(ctx), "Upload : %s\n", formatPermission(c.ap.AllowUpload())) fmt.Fprintf(clingy.Stdout(ctx), "Upload : %s\n", formatPermission(c.ap.AllowUpload()))
fmt.Fprintf(clingy.Stdout(ctx), "Lists : %s\n", formatPermission(c.ap.AllowList())) fmt.Fprintf(clingy.Stdout(ctx), "Lists : %s\n", formatPermission(c.ap.AllowList()))
fmt.Fprintf(clingy.Stdout(ctx), "Deletes : %s\n", formatPermission(c.ap.AllowDelete())) fmt.Fprintf(clingy.Stdout(ctx), "Deletes : %s\n", formatPermission(c.ap.AllowDelete()))
fmt.Fprintf(clingy.Stdout(ctx), "NotBefore : %s\n", formatTimeRestriction(c.ap.NotBefore())) fmt.Fprintf(clingy.Stdout(ctx), "NotBefore : %s\n", formatTimeRestriction(c.ap.NotBefore()))
fmt.Fprintf(clingy.Stdout(ctx), "NotAfter : %s\n", formatTimeRestriction(c.ap.NotAfter())) fmt.Fprintf(clingy.Stdout(ctx), "NotAfter : %s\n", formatTimeRestriction(c.ap.NotAfter()))
fmt.Fprintf(clingy.Stdout(ctx), "Paths : %s\n", formatPaths(c.ap.prefixes)) fmt.Fprintf(clingy.Stdout(ctx), "MaxObjectTTL : %s\n", formatDuration(c.ap.maxObjectTTL))
fmt.Fprintf(clingy.Stdout(ctx), "Paths : %s\n", formatPaths(c.ap.prefixes))
fmt.Fprintf(clingy.Stdout(ctx), "=========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS ===========\n") fmt.Fprintf(clingy.Stdout(ctx), "=========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS ===========\n")
fmt.Fprintf(clingy.Stdout(ctx), "Access : %s\n", newAccessData) fmt.Fprintf(clingy.Stdout(ctx), "Access : %s\n", newAccessData)
if c.register { if c.register {
credentials, err := RegisterAccess(ctx, access, c.authService, c.public, c.caCert) credentials, err := RegisterAccess(ctx, access, c.authService, c.public, c.caCert)
@ -182,6 +183,13 @@ func formatTimeRestriction(t time.Time) string {
return formatTime(true, t) return formatTime(true, t)
} }
func formatDuration(d *time.Duration) string {
if d == nil {
return "Not set"
}
return d.String()
}
func formatPaths(sharePrefixes []uplink.SharePrefix) string { func formatPaths(sharePrefixes []uplink.SharePrefix) string {
if len(sharePrefixes) == 0 { if len(sharePrefixes) == 0 {
return "WARNING! The entire project is shared!" return "WARNING! The entire project is shared!"

View File

@ -33,15 +33,16 @@ func TestShare(t *testing.T) {
state.Succeed(t, "share", "sj://some/prefix").RequireStdoutGlob(t, ` state.Succeed(t, "share", "sj://some/prefix").RequireStdoutGlob(t, `
Sharing access to satellite * Sharing access to satellite *
=========== ACCESS RESTRICTIONS ========================================================== =========== ACCESS RESTRICTIONS ==========================================================
Download : Allowed Download : Allowed
Upload : Disallowed Upload : Disallowed
Lists : Allowed Lists : Allowed
Deletes : Disallowed Deletes : Disallowed
NotBefore : No restriction NotBefore : No restriction
NotAfter : No restriction NotAfter : No restriction
Paths : sj://some/prefix MaxObjectTTL : Not set
Paths : sj://some/prefix
=========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS =========== =========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS ===========
Access : * Access : *
`) `)
}) })
@ -51,15 +52,16 @@ func TestShare(t *testing.T) {
state.Succeed(t, "share", "--readonly", "sj://some/prefix").RequireStdoutGlob(t, ` state.Succeed(t, "share", "--readonly", "sj://some/prefix").RequireStdoutGlob(t, `
Sharing access to satellite * Sharing access to satellite *
=========== ACCESS RESTRICTIONS ========================================================== =========== ACCESS RESTRICTIONS ==========================================================
Download : Allowed Download : Allowed
Upload : Disallowed Upload : Disallowed
Lists : Allowed Lists : Allowed
Deletes : Disallowed Deletes : Disallowed
NotBefore : No restriction NotBefore : No restriction
NotAfter : No restriction NotAfter : No restriction
Paths : sj://some/prefix MaxObjectTTL : Not set
Paths : sj://some/prefix
=========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS =========== =========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS ===========
Access : * Access : *
`) `)
}) })
@ -69,15 +71,16 @@ func TestShare(t *testing.T) {
state.Succeed(t, "share", "--disallow-lists", "sj://some/prefix").RequireStdoutGlob(t, ` state.Succeed(t, "share", "--disallow-lists", "sj://some/prefix").RequireStdoutGlob(t, `
Sharing access to satellite * Sharing access to satellite *
=========== ACCESS RESTRICTIONS ========================================================== =========== ACCESS RESTRICTIONS ==========================================================
Download : Allowed Download : Allowed
Upload : Disallowed Upload : Disallowed
Lists : Disallowed Lists : Disallowed
Deletes : Disallowed Deletes : Disallowed
NotBefore : No restriction NotBefore : No restriction
NotAfter : No restriction NotAfter : No restriction
Paths : sj://some/prefix MaxObjectTTL : Not set
Paths : sj://some/prefix
=========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS =========== =========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS ===========
Access : * Access : *
`) `)
}) })
@ -87,15 +90,16 @@ func TestShare(t *testing.T) {
state.Succeed(t, "share", "--disallow-reads", "sj://some/prefix").RequireStdoutGlob(t, ` state.Succeed(t, "share", "--disallow-reads", "sj://some/prefix").RequireStdoutGlob(t, `
Sharing access to satellite * Sharing access to satellite *
=========== ACCESS RESTRICTIONS ========================================================== =========== ACCESS RESTRICTIONS ==========================================================
Download : Disallowed Download : Disallowed
Upload : Disallowed Upload : Disallowed
Lists : Allowed Lists : Allowed
Deletes : Disallowed Deletes : Disallowed
NotBefore : No restriction NotBefore : No restriction
NotAfter : No restriction NotAfter : No restriction
Paths : sj://some/prefix MaxObjectTTL : Not set
Paths : sj://some/prefix
=========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS =========== =========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS ===========
Access : * Access : *
`) `)
}) })
@ -116,33 +120,54 @@ func TestShare(t *testing.T) {
state.Succeed(t, "share", "--public", "--not-after=none", "sj://some/prefix").RequireStdoutGlob(t, ` state.Succeed(t, "share", "--public", "--not-after=none", "sj://some/prefix").RequireStdoutGlob(t, `
Sharing access to satellite * Sharing access to satellite *
=========== ACCESS RESTRICTIONS ========================================================== =========== ACCESS RESTRICTIONS ==========================================================
Download : Allowed Download : Allowed
Upload : Disallowed Upload : Disallowed
Lists : Allowed Lists : Allowed
Deletes : Disallowed Deletes : Disallowed
NotBefore : No restriction NotBefore : No restriction
NotAfter : No restriction NotAfter : No restriction
Paths : sj://some/prefix MaxObjectTTL : Not set
Paths : sj://some/prefix
=========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS =========== =========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS ===========
Access : * Access : *
`) `)
}) })
t.Run("share access with --not-after time restriction parameter", func(t *testing.T) { t.Run("share access with --not-after", func(t *testing.T) {
state := ultest.Setup(commands) state := ultest.Setup(commands)
state.Succeed(t, "share", "--not-after", "2022-01-01T15:01:01-01:00", "sj://some/prefix").RequireStdoutGlob(t, ` state.Succeed(t, "share", "--not-after", "2022-01-01T15:01:01-01:00", "sj://some/prefix").RequireStdoutGlob(t, `
Sharing access to satellite * Sharing access to satellite *
=========== ACCESS RESTRICTIONS ========================================================== =========== ACCESS RESTRICTIONS ==========================================================
Download : Allowed Download : Allowed
Upload : Disallowed Upload : Disallowed
Lists : Allowed Lists : Allowed
Deletes : Disallowed Deletes : Disallowed
NotBefore : No restriction NotBefore : No restriction
NotAfter : 2022-01-01 16:01:01 NotAfter : 2022-01-01 16:01:01
Paths : sj://some/prefix MaxObjectTTL : Not set
Paths : sj://some/prefix
=========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS =========== =========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS ===========
Access : * Access : *
`)
})
t.Run("share access with --max-object-ttl", func(t *testing.T) {
state := ultest.Setup(commands)
state.Succeed(t, "share", "--max-object-ttl", "720h", "--readonly=false", "sj://some/prefix").RequireStdoutGlob(t, `
Sharing access to satellite *
=========== ACCESS RESTRICTIONS ==========================================================
Download : Allowed
Upload : Allowed
Lists : Allowed
Deletes : Allowed
NotBefore : No restriction
NotAfter : No restriction
MaxObjectTTL : 720h0m0s
Paths : sj://some/prefix
=========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS ===========
Access : *
`) `)
}) })
@ -184,15 +209,16 @@ func TestShare(t *testing.T) {
expected := ` expected := `
Sharing access to satellite * Sharing access to satellite *
=========== ACCESS RESTRICTIONS ========================================================== =========== ACCESS RESTRICTIONS ==========================================================
Download : Allowed Download : Allowed
Upload : Disallowed Upload : Disallowed
Lists : Allowed Lists : Allowed
Deletes : Disallowed Deletes : Disallowed
NotBefore : No restriction NotBefore : No restriction
NotAfter : No restriction NotAfter : No restriction
Paths : sj://some/prefix MaxObjectTTL : Not set
Paths : sj://some/prefix
=========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS =========== =========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS ===========
Access : * Access : *
========== GATEWAY CREDENTIALS =========================================================== ========== GATEWAY CREDENTIALS ===========================================================
Access Key ID: accesskeyid Access Key ID: accesskeyid
Secret Key : secretkey Secret Key : secretkey

2
go.mod
View File

@ -65,7 +65,7 @@ require (
storj.io/drpc v0.0.33 storj.io/drpc v0.0.33
storj.io/monkit-jaeger v0.0.0-20220915074555-d100d7589f41 storj.io/monkit-jaeger v0.0.0-20220915074555-d100d7589f41
storj.io/private v0.0.0-20230824104110-1eac532af65a storj.io/private v0.0.0-20230824104110-1eac532af65a
storj.io/uplink v1.11.1-0.20230907104623-a2e90d4e0c18 storj.io/uplink v1.11.1-0.20230907122241-39cbd8e765dc
) )
require ( require (

4
go.sum
View File

@ -1023,5 +1023,5 @@ storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c h1:or/DtG5uaZpzimL61ahlgAA
storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c/go.mod h1:JCuc3C0gzCJHQ4J6SOx/Yjg+QTpX0D+Fvs5H46FETCk= storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c/go.mod h1:JCuc3C0gzCJHQ4J6SOx/Yjg+QTpX0D+Fvs5H46FETCk=
storj.io/private v0.0.0-20230824104110-1eac532af65a h1:x0OnU7z801JmR0XwFrFxmEBqcq+FDDuSn5jMbFoyfBo= storj.io/private v0.0.0-20230824104110-1eac532af65a h1:x0OnU7z801JmR0XwFrFxmEBqcq+FDDuSn5jMbFoyfBo=
storj.io/private v0.0.0-20230824104110-1eac532af65a/go.mod h1:6+MGr4KUXEBIOsOstFz1efPkA+8wVVfzsO8RpuAhhB4= storj.io/private v0.0.0-20230824104110-1eac532af65a/go.mod h1:6+MGr4KUXEBIOsOstFz1efPkA+8wVVfzsO8RpuAhhB4=
storj.io/uplink v1.11.1-0.20230907104623-a2e90d4e0c18 h1:xAC4VhvSQpMbUesyvQGBEORTQ64ZBK3+RfJWWa9zVbY= storj.io/uplink v1.11.1-0.20230907122241-39cbd8e765dc h1:B03iRQQ4N9zfDnBFPJK5jCLpcjU4XZJP8PPxyLkO7Zw=
storj.io/uplink v1.11.1-0.20230907104623-a2e90d4e0c18/go.mod h1:JOUmWW3FrzCkDK4wgrTonKrKTWafK7jRQA8zBb8mkxs= storj.io/uplink v1.11.1-0.20230907122241-39cbd8e765dc/go.mod h1:xj3uTha+PlDcb1Jyh8uH0CVb6MjwlWDBpssiGweLM7M=

View File

@ -13,7 +13,7 @@ require (
storj.io/private v0.0.0-20230824104110-1eac532af65a storj.io/private v0.0.0-20230824104110-1eac532af65a
storj.io/storj v1.63.1 storj.io/storj v1.63.1
storj.io/storjscan v0.0.0-20220926140643-1623c3b391b0 storj.io/storjscan v0.0.0-20220926140643-1623c3b391b0
storj.io/uplink v1.11.1-0.20230907104623-a2e90d4e0c18 storj.io/uplink v1.11.1-0.20230907122241-39cbd8e765dc
) )
require ( require (

View File

@ -1267,5 +1267,5 @@ storj.io/private v0.0.0-20230824104110-1eac532af65a h1:x0OnU7z801JmR0XwFrFxmEBqc
storj.io/private v0.0.0-20230824104110-1eac532af65a/go.mod h1:6+MGr4KUXEBIOsOstFz1efPkA+8wVVfzsO8RpuAhhB4= storj.io/private v0.0.0-20230824104110-1eac532af65a/go.mod h1:6+MGr4KUXEBIOsOstFz1efPkA+8wVVfzsO8RpuAhhB4=
storj.io/storjscan v0.0.0-20220926140643-1623c3b391b0 h1:pSfGf9E9OlUd17W7LSpL4tTONIyFji6dz8I2iTDd8BY= storj.io/storjscan v0.0.0-20220926140643-1623c3b391b0 h1:pSfGf9E9OlUd17W7LSpL4tTONIyFji6dz8I2iTDd8BY=
storj.io/storjscan v0.0.0-20220926140643-1623c3b391b0/go.mod h1:5nLgAOl1KTDVyqORAhvrp+167PtShEuS1L3pJgXPjwo= storj.io/storjscan v0.0.0-20220926140643-1623c3b391b0/go.mod h1:5nLgAOl1KTDVyqORAhvrp+167PtShEuS1L3pJgXPjwo=
storj.io/uplink v1.11.1-0.20230907104623-a2e90d4e0c18 h1:xAC4VhvSQpMbUesyvQGBEORTQ64ZBK3+RfJWWa9zVbY= storj.io/uplink v1.11.1-0.20230907122241-39cbd8e765dc h1:B03iRQQ4N9zfDnBFPJK5jCLpcjU4XZJP8PPxyLkO7Zw=
storj.io/uplink v1.11.1-0.20230907104623-a2e90d4e0c18/go.mod h1:JOUmWW3FrzCkDK4wgrTonKrKTWafK7jRQA8zBb8mkxs= storj.io/uplink v1.11.1-0.20230907122241-39cbd8e765dc/go.mod h1:xj3uTha+PlDcb1Jyh8uH0CVb6MjwlWDBpssiGweLM7M=

View File

@ -233,5 +233,5 @@ require (
storj.io/minio v0.0.0-20230118205046-c025fcc9eef3 // indirect storj.io/minio v0.0.0-20230118205046-c025fcc9eef3 // indirect
storj.io/monkit-jaeger v0.0.0-20220915074555-d100d7589f41 // indirect storj.io/monkit-jaeger v0.0.0-20220915074555-d100d7589f41 // indirect
storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c // indirect storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c // indirect
storj.io/uplink v1.11.1-0.20230907104623-a2e90d4e0c18 // indirect storj.io/uplink v1.11.1-0.20230907122241-39cbd8e765dc // indirect
) )

View File

@ -1987,6 +1987,6 @@ storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c h1:or/DtG5uaZpzimL61ahlgAA
storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c/go.mod h1:JCuc3C0gzCJHQ4J6SOx/Yjg+QTpX0D+Fvs5H46FETCk= storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c/go.mod h1:JCuc3C0gzCJHQ4J6SOx/Yjg+QTpX0D+Fvs5H46FETCk=
storj.io/private v0.0.0-20230824104110-1eac532af65a h1:x0OnU7z801JmR0XwFrFxmEBqcq+FDDuSn5jMbFoyfBo= storj.io/private v0.0.0-20230824104110-1eac532af65a h1:x0OnU7z801JmR0XwFrFxmEBqcq+FDDuSn5jMbFoyfBo=
storj.io/private v0.0.0-20230824104110-1eac532af65a/go.mod h1:6+MGr4KUXEBIOsOstFz1efPkA+8wVVfzsO8RpuAhhB4= storj.io/private v0.0.0-20230824104110-1eac532af65a/go.mod h1:6+MGr4KUXEBIOsOstFz1efPkA+8wVVfzsO8RpuAhhB4=
storj.io/uplink v1.11.1-0.20230907104623-a2e90d4e0c18 h1:xAC4VhvSQpMbUesyvQGBEORTQ64ZBK3+RfJWWa9zVbY= storj.io/uplink v1.11.1-0.20230907122241-39cbd8e765dc h1:B03iRQQ4N9zfDnBFPJK5jCLpcjU4XZJP8PPxyLkO7Zw=
storj.io/uplink v1.11.1-0.20230907104623-a2e90d4e0c18/go.mod h1:JOUmWW3FrzCkDK4wgrTonKrKTWafK7jRQA8zBb8mkxs= storj.io/uplink v1.11.1-0.20230907122241-39cbd8e765dc/go.mod h1:xj3uTha+PlDcb1Jyh8uH0CVb6MjwlWDBpssiGweLM7M=
storj.io/zipper v0.0.0-20220124122551-2ac2d53a46f6 h1:vJQmb+uAiYn8hVfkhMl6OqjnUyMWSCPnkzW8IsjF8vE= storj.io/zipper v0.0.0-20220124122551-2ac2d53a46f6 h1:vJQmb+uAiYn8hVfkhMl6OqjnUyMWSCPnkzW8IsjF8vE=