Skip to content

Commit

Permalink
Experiment with JWT-based worker redirection
Browse files Browse the repository at this point in the history
  • Loading branch information
turt2live committed Aug 9, 2024
1 parent eb774d8 commit ec59b61
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 14 deletions.
86 changes: 84 additions & 2 deletions api/_responses/redirect.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,91 @@
package _responses

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"net/url"
"os"
"time"

"github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/t2bot/matrix-media-repo/api/_apimeta"
"github.com/t2bot/matrix-media-repo/common/rcontext"
)

// TODO: Persist and store key (or use some other source of information)
var jwtKey, keyErr = rsa.GenerateKey(rand.Reader, 2048)
var jwtSig, jwtErr = jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: []byte("0102030405060708090A0B0C0D0E0F10")}, (&jose.SignerOptions{}).WithType("JWT"))
var jweEnc, jweErr = jose.NewEncrypter(jose.A128GCM, jose.Recipient{Algorithm: jose.RSA_OAEP, Key: &jwtKey.PublicKey}, nil)

func init() {
// We don't expect these to happen, so just panic
if keyErr != nil {
panic(keyErr)
}
if jwtErr != nil {
panic(jwtErr)
}
if jweErr != nil {
panic(jweErr)
}

f1, _ := os.Create("./gdpr-data/jwe.rsa")
f2, _ := os.Create("./gdpr-data/jwe.rsa.pub")
defer f1.Close()
defer f2.Close()
keyPem := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(jwtKey),
})
pubPem := pem.EncodeToMemory(&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: x509.MarshalPKCS1PublicKey(&jwtKey.PublicKey),
})
f1.Write(keyPem)
f2.Write(pubPem)
}

type RedirectResponse struct {
ToUrl string
}

func Redirect(url string) *RedirectResponse {
return &RedirectResponse{ToUrl: url}
func Redirect(ctx rcontext.RequestContext, toUrl string, auth _apimeta.AuthContext) *RedirectResponse {
if auth.IsAuthenticated() {
// Figure out who we're authenticating as, and add that as JWT claims
claims := jwt.Claims{
Issuer: ctx.Request.Host,
}
moreClaims := struct {
Admin bool `json:"adm,omitempty"`
AccessToken string `json:"tok,omitempty"`
}{}
if auth.Server.ServerName != "" {
claims.Subject = auth.Server.ServerName

// The server won't necessarily re-auth itself with the redirect, so we just put an expiration on it instead
claims.Expiry = jwt.NewNumericDate(time.Now().Add(2 * time.Minute))
} else {
claims.Subject = auth.User.UserId
moreClaims.Admin = auth.User.IsShared
moreClaims.AccessToken = auth.User.AccessToken
}
raw, err := jwt.Encrypted(jweEnc).Claims(claims).Claims(moreClaims).Serialize()
if err != nil {
panic(err) // should never happen if we've done things correctly
}

// Now add the query string
parsedUrl, err := url.Parse(toUrl)
if err != nil {
panic(err) // it wouldn't have worked anyways
}
qs := parsedUrl.Query()
qs.Set("org.matrix.media_auth", raw)
parsedUrl.RawQuery = qs.Encode()
toUrl = parsedUrl.String()
}
return &RedirectResponse{ToUrl: toUrl}
}
46 changes: 46 additions & 0 deletions api/custom/byid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package custom

import (
"net/http"

"github.com/t2bot/matrix-media-repo/api/_apimeta"
"github.com/t2bot/matrix-media-repo/api/_responses"
"github.com/t2bot/matrix-media-repo/api/_routers"
"github.com/t2bot/matrix-media-repo/common/rcontext"
"github.com/t2bot/matrix-media-repo/database"
"github.com/t2bot/matrix-media-repo/datastores"
"github.com/t2bot/matrix-media-repo/pipelines/_steps/download"
)

func GetMediaById(r *http.Request, rctx rcontext.RequestContext, user _apimeta.UserInfo) interface{} {
if !user.IsShared {
return _responses.AuthFailed()
}

// TODO: This is beyond dangerous and needs proper filtering

db := database.GetInstance().Media.Prepare(rctx)
ds, err := datastores.Pick(rctx, datastores.LocalMediaKind)
if err != nil {
panic(err)
}
objectId := _routers.GetParam("objectId", r)
medias, err := db.GetByLocation(ds.Id, objectId)
if err != nil {
panic(err)
}

media := medias[0]
stream, err := download.OpenStream(rctx, media.Locatable)
if err != nil {
panic(err)
}

return &_responses.DownloadResponse{
ContentType: media.ContentType,
Filename: media.UploadName,
SizeBytes: media.SizeBytes,
Data: stream,
TargetDisposition: "infer",
}
}
2 changes: 1 addition & 1 deletion api/r0/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func DownloadMedia(r *http.Request, rctx rcontext.RequestContext, auth _apimeta.
} else if errors.Is(err, common.ErrMediaNotYetUploaded) {
return _responses.NotYetUploaded()
} else if errors.As(err, &redirect) {
return _responses.Redirect(redirect.RedirectUrl)
return _responses.Redirect(rctx, redirect.RedirectUrl, auth)
}
rctx.Log.Error("Unexpected error locating media: ", err)
sentry.CaptureException(err)
Expand Down
2 changes: 1 addition & 1 deletion api/r0/thumbnail.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func ThumbnailMedia(r *http.Request, rctx rcontext.RequestContext, auth _apimeta
}
}
} else if errors.As(err, &redirect) {
return _responses.Redirect(redirect.RedirectUrl)
return _responses.Redirect(rctx, redirect.RedirectUrl, auth)
}
rctx.Log.Error("Unexpected error locating media: ", err)
sentry.CaptureException(err)
Expand Down
2 changes: 2 additions & 0 deletions api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
const PrefixMedia = "/_matrix/media"
const PrefixClient = "/_matrix/client"
const PrefixFederation = "/_matrix/federation"
const PrefixMMR = "/_mmr"

func buildRoutes() http.Handler {
counter := &_routers.RequestCounter{}
Expand Down Expand Up @@ -61,6 +62,7 @@ func buildRoutes() http.Handler {
purgeOneRoute := makeRoute(_routers.RequireAccessToken(custom.PurgeIndividualRecord), "purge_individual_media", counter)
register([]string{"DELETE"}, PrefixMedia, "download/:server/:mediaId", mxUnstable, router, purgeOneRoute)
register([]string{"GET"}, PrefixMedia, "usage", msc4034, router, makeRoute(_routers.RequireAccessToken(unstable.PublicUsage), "usage", counter))
register([]string{"GET"}, PrefixMMR, "byid/:objectId", mxNoVersion, router, makeRoute(_routers.RequireRepoAdmin(custom.GetMediaById), "byid", counter))

// Custom and top-level features
router.Handler("GET", fmt.Sprintf("%s/version", PrefixMedia), makeRoute(_routers.OptionalAccessToken(custom.GetVersion), "get_version", counter))
Expand Down
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@ require (
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
github.com/sirupsen/logrus v1.9.3
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/image v0.18.0
golang.org/x/net v0.25.0
)

require (
github.com/didip/tollbooth/v7 v7.0.1
github.com/docker/docker v25.0.5+incompatible
github.com/docker/go-connections v0.5.0
github.com/go-jose/go-jose/v4 v4.0.4
github.com/go-redsync/redsync/v4 v4.12.1
github.com/julienschmidt/httprouter v1.3.0
github.com/minio/minio-go/v7 v7.0.69
Expand All @@ -62,7 +64,7 @@ require (
github.com/testcontainers/testcontainers-go v0.26.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.26.0
golang.org/x/sync v0.7.0
golang.org/x/term v0.20.0
golang.org/x/term v0.22.0
)

require (
Expand All @@ -81,7 +83,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/docker v25.0.5+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
github.com/fatih/color v1.16.0 // indirect
Expand Down Expand Up @@ -139,7 +140,7 @@ require (
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
Expand Down
14 changes: 8 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWE
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
Expand Down Expand Up @@ -415,8 +417,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
Expand Down Expand Up @@ -498,16 +500,16 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
Expand Down

0 comments on commit ec59b61

Please sign in to comment.