aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--config/default.yaml14
-rw-r--r--server/controllers/object-storage-proxy.ts8
-rw-r--r--server/controllers/static.ts20
-rw-r--r--server/initializers/config.ts6
-rw-r--r--server/middlewares/validators/index.ts1
-rw-r--r--server/middlewares/validators/object-storage-proxy.ts20
-rw-r--r--server/models/video/video-file.ts3
-rw-r--r--server/models/video/video-streaming-playlist.ts5
-rw-r--r--server/tests/api/object-storage/video-static-file-privacy.ts166
-rw-r--r--server/tests/api/videos/video-static-file-privacy.ts33
-rw-r--r--shared/server-commands/server/object-storage-command.ts16
11 files changed, 232 insertions, 60 deletions
diff --git a/config/default.yaml b/config/default.yaml
index 7753821da..2e249cc76 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -140,6 +140,10 @@ storage:
140 # If not, peertube will fallback to the default file 140 # If not, peertube will fallback to the default file
141 client_overrides: 'storage/client-overrides/' 141 client_overrides: 'storage/client-overrides/'
142 142
143static_files:
144 # Require and check user authentication when accessing private files (internal/private video files)
145 private_files_require_auth: true
146
143object_storage: 147object_storage:
144 enabled: false 148 enabled: false
145 149
@@ -151,9 +155,17 @@ object_storage:
151 upload_acl: 155 upload_acl:
152 # Set this ACL on each uploaded object of public/unlisted videos 156 # Set this ACL on each uploaded object of public/unlisted videos
153 public: 'public-read' 157 public: 'public-read'
154 # Set this ACL on each uploaded object of private/internal videos 158 # Set this ACL on each uploaded object of private/internal videos
159 # PeerTube can proxify requests to private objects so your users can access them
155 private: 'private' 160 private: 'private'
156 161
162 proxy:
163 # If private files (private/internal video files) have a private ACL, users can't access directly the ressource
164 # PeerTube can proxify requests between your object storage service and your users
165 # If you disable PeerTube proxy, ensure you use your own proxy that is able to access the private files
166 # Or you can also set a public ACL for private files in object storage if you don't want to use a proxy
167 proxify_private_files: true
168
157 credentials: 169 credentials:
158 # You can also use AWS_ACCESS_KEY_ID env variable 170 # You can also use AWS_ACCESS_KEY_ID env variable
159 access_key_id: '' 171 access_key_id: ''
diff --git a/server/controllers/object-storage-proxy.ts b/server/controllers/object-storage-proxy.ts
index 6fedcfd8f..3ce279671 100644
--- a/server/controllers/object-storage-proxy.ts
+++ b/server/controllers/object-storage-proxy.ts
@@ -1,11 +1,13 @@
1import cors from 'cors' 1import cors from 'cors'
2import express from 'express' 2import express from 'express'
3import { logger } from '@server/helpers/logger'
3import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants' 4import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants'
4import { getHLSFileReadStream, getWebTorrentFileReadStream } from '@server/lib/object-storage' 5import { getHLSFileReadStream, getWebTorrentFileReadStream } from '@server/lib/object-storage'
5import { 6import {
6 asyncMiddleware, 7 asyncMiddleware,
7 ensureCanAccessPrivateVideoHLSFiles, 8 ensureCanAccessPrivateVideoHLSFiles,
8 ensureCanAccessVideoPrivateWebTorrentFiles, 9 ensureCanAccessVideoPrivateWebTorrentFiles,
10 ensurePrivateObjectStorageProxyIsEnabled,
9 optionalAuthenticate 11 optionalAuthenticate
10} from '@server/middlewares' 12} from '@server/middlewares'
11import { HttpStatusCode } from '@shared/models' 13import { HttpStatusCode } from '@shared/models'
@@ -15,12 +17,14 @@ const objectStorageProxyRouter = express.Router()
15objectStorageProxyRouter.use(cors()) 17objectStorageProxyRouter.use(cors())
16 18
17objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.PRIVATE_WEBSEED + ':filename', 19objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.PRIVATE_WEBSEED + ':filename',
20 ensurePrivateObjectStorageProxyIsEnabled,
18 optionalAuthenticate, 21 optionalAuthenticate,
19 asyncMiddleware(ensureCanAccessVideoPrivateWebTorrentFiles), 22 asyncMiddleware(ensureCanAccessVideoPrivateWebTorrentFiles),
20 asyncMiddleware(proxifyWebTorrent) 23 asyncMiddleware(proxifyWebTorrent)
21) 24)
22 25
23objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:filename', 26objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:filename',
27 ensurePrivateObjectStorageProxyIsEnabled,
24 optionalAuthenticate, 28 optionalAuthenticate,
25 asyncMiddleware(ensureCanAccessPrivateVideoHLSFiles), 29 asyncMiddleware(ensureCanAccessPrivateVideoHLSFiles),
26 asyncMiddleware(proxifyHLS) 30 asyncMiddleware(proxifyHLS)
@@ -35,6 +39,8 @@ export {
35async function proxifyWebTorrent (req: express.Request, res: express.Response) { 39async function proxifyWebTorrent (req: express.Request, res: express.Response) {
36 const filename = req.params.filename 40 const filename = req.params.filename
37 41
42 logger.debug('Proxifying WebTorrent file %s from object storage.', filename)
43
38 try { 44 try {
39 const stream = await getWebTorrentFileReadStream({ 45 const stream = await getWebTorrentFileReadStream({
40 filename, 46 filename,
@@ -52,6 +58,8 @@ async function proxifyHLS (req: express.Request, res: express.Response) {
52 const video = res.locals.onlyVideo 58 const video = res.locals.onlyVideo
53 const filename = req.params.filename 59 const filename = req.params.filename
54 60
61 logger.debug('Proxifying HLS file %s from object storage.', filename)
62
55 try { 63 try {
56 const stream = await getHLSFileReadStream({ 64 const stream = await getHLSFileReadStream({
57 playlist: playlist.withVideo(video), 65 playlist: playlist.withVideo(video),
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index dc091455a..6ef9154b9 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -15,11 +15,17 @@ const staticRouter = express.Router()
15// Cors is very important to let other servers access torrent and video files 15// Cors is very important to let other servers access torrent and video files
16staticRouter.use(cors()) 16staticRouter.use(cors())
17 17
18// ---------------------------------------------------------------------------
18// WebTorrent/Classic videos 19// WebTorrent/Classic videos
20// ---------------------------------------------------------------------------
21
22const privateWebTorrentStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AUTH === true
23 ? [ optionalAuthenticate, asyncMiddleware(ensureCanAccessVideoPrivateWebTorrentFiles) ]
24 : []
25
19staticRouter.use( 26staticRouter.use(
20 STATIC_PATHS.PRIVATE_WEBSEED, 27 STATIC_PATHS.PRIVATE_WEBSEED,
21 optionalAuthenticate, 28 ...privateWebTorrentStaticMiddlewares,
22 asyncMiddleware(ensureCanAccessVideoPrivateWebTorrentFiles),
23 express.static(DIRECTORIES.VIDEOS.PRIVATE, { fallthrough: false }), 29 express.static(DIRECTORIES.VIDEOS.PRIVATE, { fallthrough: false }),
24 handleStaticError 30 handleStaticError
25) 31)
@@ -35,11 +41,17 @@ staticRouter.use(
35 handleStaticError 41 handleStaticError
36) 42)
37 43
44// ---------------------------------------------------------------------------
38// HLS 45// HLS
46// ---------------------------------------------------------------------------
47
48const privateHLSStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AUTH === true
49 ? [ optionalAuthenticate, asyncMiddleware(ensureCanAccessPrivateVideoHLSFiles) ]
50 : []
51
39staticRouter.use( 52staticRouter.use(
40 STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS, 53 STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS,
41 optionalAuthenticate, 54 ...privateHLSStaticMiddlewares,
42 asyncMiddleware(ensureCanAccessPrivateVideoHLSFiles),
43 express.static(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, { fallthrough: false }), 55 express.static(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, { fallthrough: false }),
44 handleStaticError 56 handleStaticError
45) 57)
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
index ab5e645ad..7652da24a 100644
--- a/server/initializers/config.ts
+++ b/server/initializers/config.ts
@@ -113,6 +113,9 @@ const CONFIG = {
113 CLIENT_OVERRIDES_DIR: buildPath(config.get<string>('storage.client_overrides')), 113 CLIENT_OVERRIDES_DIR: buildPath(config.get<string>('storage.client_overrides')),
114 WELL_KNOWN_DIR: buildPath(config.get<string>('storage.well_known')) 114 WELL_KNOWN_DIR: buildPath(config.get<string>('storage.well_known'))
115 }, 115 },
116 STATIC_FILES: {
117 PRIVATE_FILES_REQUIRE_AUTH: config.get<boolean>('static_files.private_files_require_auth')
118 },
116 OBJECT_STORAGE: { 119 OBJECT_STORAGE: {
117 ENABLED: config.get<boolean>('object_storage.enabled'), 120 ENABLED: config.get<boolean>('object_storage.enabled'),
118 MAX_UPLOAD_PART: bytes.parse(config.get<string>('object_storage.max_upload_part')), 121 MAX_UPLOAD_PART: bytes.parse(config.get<string>('object_storage.max_upload_part')),
@@ -126,6 +129,9 @@ const CONFIG = {
126 ACCESS_KEY_ID: config.get<string>('object_storage.credentials.access_key_id'), 129 ACCESS_KEY_ID: config.get<string>('object_storage.credentials.access_key_id'),
127 SECRET_ACCESS_KEY: config.get<string>('object_storage.credentials.secret_access_key') 130 SECRET_ACCESS_KEY: config.get<string>('object_storage.credentials.secret_access_key')
128 }, 131 },
132 PROXY: {
133 PROXIFY_PRIVATE_FILES: config.get<boolean>('object_storage.proxy.proxify_private_files')
134 },
129 VIDEOS: { 135 VIDEOS: {
130 BUCKET_NAME: config.get<string>('object_storage.videos.bucket_name'), 136 BUCKET_NAME: config.get<string>('object_storage.videos.bucket_name'),
131 PREFIX: config.get<string>('object_storage.videos.prefix'), 137 PREFIX: config.get<string>('object_storage.videos.prefix'),
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts
index 899da229a..9bc8887ff 100644
--- a/server/middlewares/validators/index.ts
+++ b/server/middlewares/validators/index.ts
@@ -11,6 +11,7 @@ export * from './follows'
11export * from './jobs' 11export * from './jobs'
12export * from './logs' 12export * from './logs'
13export * from './metrics' 13export * from './metrics'
14export * from './object-storage-proxy'
14export * from './oembed' 15export * from './oembed'
15export * from './pagination' 16export * from './pagination'
16export * from './plugins' 17export * from './plugins'
diff --git a/server/middlewares/validators/object-storage-proxy.ts b/server/middlewares/validators/object-storage-proxy.ts
new file mode 100644
index 000000000..bbd77f262
--- /dev/null
+++ b/server/middlewares/validators/object-storage-proxy.ts
@@ -0,0 +1,20 @@
1import express from 'express'
2import { CONFIG } from '@server/initializers/config'
3import { HttpStatusCode } from '@shared/models'
4
5const ensurePrivateObjectStorageProxyIsEnabled = [
6 (req: express.Request, res: express.Response, next: express.NextFunction) => {
7 if (CONFIG.OBJECT_STORAGE.PROXY.PROXIFY_PRIVATE_FILES !== true) {
8 return res.fail({
9 message: 'Private object storage proxy is not enabled',
10 status: HttpStatusCode.BAD_REQUEST_400
11 })
12 }
13
14 return next()
15 }
16]
17
18export {
19 ensurePrivateObjectStorageProxyIsEnabled
20}
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index c20c90c1b..9c4e6d078 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -54,6 +54,7 @@ import { doesExist } from '../shared'
54import { parseAggregateResult, throwIfNotValid } from '../utils' 54import { parseAggregateResult, throwIfNotValid } from '../utils'
55import { VideoModel } from './video' 55import { VideoModel } from './video'
56import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 56import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
57import { CONFIG } from '@server/initializers/config'
57 58
58export enum ScopeNames { 59export enum ScopeNames {
59 WITH_VIDEO = 'WITH_VIDEO', 60 WITH_VIDEO = 'WITH_VIDEO',
@@ -511,7 +512,7 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
511 // --------------------------------------------------------------------------- 512 // ---------------------------------------------------------------------------
512 513
513 getObjectStorageUrl (video: MVideo) { 514 getObjectStorageUrl (video: MVideo) {
514 if (video.hasPrivateStaticPath()) { 515 if (video.hasPrivateStaticPath() && CONFIG.OBJECT_STORAGE.PROXY.PROXIFY_PRIVATE_FILES === true) {
515 return this.getPrivateObjectStorageUrl(video) 516 return this.getPrivateObjectStorageUrl(video)
516 } 517 }
517 518
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts
index 1318a4dae..0386edf28 100644
--- a/server/models/video/video-streaming-playlist.ts
+++ b/server/models/video/video-streaming-playlist.ts
@@ -15,6 +15,7 @@ import {
15 Table, 15 Table,
16 UpdatedAt 16 UpdatedAt
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { CONFIG } from '@server/initializers/config'
18import { getHLSPrivateFileUrl, getHLSPublicFileUrl } from '@server/lib/object-storage' 19import { getHLSPrivateFileUrl, getHLSPublicFileUrl } from '@server/lib/object-storage'
19import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename } from '@server/lib/paths' 20import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename } from '@server/lib/paths'
20import { isVideoInPrivateDirectory } from '@server/lib/video-privacy' 21import { isVideoInPrivateDirectory } from '@server/lib/video-privacy'
@@ -260,7 +261,7 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi
260 } 261 }
261 262
262 private getMasterPlaylistObjectStorageUrl (video: MVideo) { 263 private getMasterPlaylistObjectStorageUrl (video: MVideo) {
263 if (video.hasPrivateStaticPath()) { 264 if (video.hasPrivateStaticPath() && CONFIG.OBJECT_STORAGE.PROXY.PROXIFY_PRIVATE_FILES === true) {
264 return getHLSPrivateFileUrl(video, this.playlistFilename) 265 return getHLSPrivateFileUrl(video, this.playlistFilename)
265 } 266 }
266 267
@@ -282,7 +283,7 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi
282 } 283 }
283 284
284 private getSha256SegmentsObjectStorageUrl (video: MVideo) { 285 private getSha256SegmentsObjectStorageUrl (video: MVideo) {
285 if (video.hasPrivateStaticPath()) { 286 if (video.hasPrivateStaticPath() && CONFIG.OBJECT_STORAGE.PROXY.PROXIFY_PRIVATE_FILES === true) {
286 return getHLSPrivateFileUrl(video, this.segmentsSha256Filename) 287 return getHLSPrivateFileUrl(video, this.segmentsSha256Filename)
287 } 288 }
288 289
diff --git a/server/tests/api/object-storage/video-static-file-privacy.ts b/server/tests/api/object-storage/video-static-file-privacy.ts
index c6d7a1a2c..ed8855b3b 100644
--- a/server/tests/api/object-storage/video-static-file-privacy.ts
+++ b/server/tests/api/object-storage/video-static-file-privacy.ts
@@ -19,6 +19,12 @@ import {
19 waitJobs 19 waitJobs
20} from '@shared/server-commands' 20} from '@shared/server-commands'
21 21
22function extractFilenameFromUrl (url: string) {
23 const parts = basename(url).split(':')
24
25 return parts[parts.length - 1]
26}
27
22describe('Object storage for video static file privacy', function () { 28describe('Object storage for video static file privacy', function () {
23 // We need real world object storage to check ACL 29 // We need real world object storage to check ACL
24 if (areScalewayObjectStorageTestsDisabled()) return 30 if (areScalewayObjectStorageTestsDisabled()) return
@@ -26,75 +32,81 @@ describe('Object storage for video static file privacy', function () {
26 let server: PeerTubeServer 32 let server: PeerTubeServer
27 let userToken: string 33 let userToken: string
28 34
29 before(async function () { 35 // ---------------------------------------------------------------------------
30 this.timeout(120000)
31 36
32 server = await createSingleServer(1, ObjectStorageCommand.getDefaultScalewayConfig(1)) 37 async function checkPrivateVODFiles (uuid: string) {
33 await setAccessTokensToServers([ server ]) 38 const video = await server.videos.getWithToken({ id: uuid })
34 await setDefaultVideoChannel([ server ])
35 39
36 await server.config.enableMinimumTranscoding() 40 for (const file of video.files) {
41 expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/webseed/private/')
37 42
38 userToken = await server.users.generateUserAndToken('user1') 43 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
39 }) 44 }
40 45
41 describe('VOD', function () { 46 for (const file of getAllFiles(video)) {
42 let privateVideoUUID: string 47 const internalFileUrl = await server.sql.getInternalFileUrl(file.id)
43 let publicVideoUUID: string 48 expectStartWith(internalFileUrl, ObjectStorageCommand.getScalewayBaseUrl())
44 let userPrivateVideoUUID: string 49 await makeRawRequest({ url: internalFileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
50 }
51
52 const hls = getHLS(video)
53
54 if (hls) {
55 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
56 expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
57 }
45 58
46 async function checkPrivateFiles (uuid: string) { 59 await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
47 const video = await server.videos.getWithToken({ id: uuid }) 60 await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
48 61
49 for (const file of video.files) { 62 for (const file of hls.files) {
50 expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/webseed/private/') 63 expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
51 64
52 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) 65 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
53 } 66 }
67 }
68 }
54 69
55 for (const file of getAllFiles(video)) { 70 async function checkPublicVODFiles (uuid: string) {
56 const internalFileUrl = await server.sql.getInternalFileUrl(file.id) 71 const video = await server.videos.getWithToken({ id: uuid })
57 expectStartWith(internalFileUrl, ObjectStorageCommand.getScalewayBaseUrl())
58 await makeRawRequest({ url: internalFileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
59 }
60 72
61 const hls = getHLS(video) 73 for (const file of getAllFiles(video)) {
74 expectStartWith(file.fileUrl, ObjectStorageCommand.getScalewayBaseUrl())
62 75
63 if (hls) { 76 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
64 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) { 77 }
65 expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
66 }
67 78
68 await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) 79 const hls = getHLS(video)
69 await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
70 80
71 for (const file of hls.files) { 81 if (hls) {
72 expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/streaming-playlists/hls/private/') 82 expectStartWith(hls.playlistUrl, ObjectStorageCommand.getScalewayBaseUrl())
83 expectStartWith(hls.segmentsSha256Url, ObjectStorageCommand.getScalewayBaseUrl())
73 84
74 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) 85 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
75 } 86 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
76 }
77 } 87 }
88 }
78 89
79 async function checkPublicFiles (uuid: string) { 90 // ---------------------------------------------------------------------------
80 const video = await server.videos.getWithToken({ id: uuid })
81 91
82 for (const file of getAllFiles(video)) { 92 before(async function () {
83 expectStartWith(file.fileUrl, ObjectStorageCommand.getScalewayBaseUrl()) 93 this.timeout(120000)
84 94
85 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 }) 95 server = await createSingleServer(1, ObjectStorageCommand.getDefaultScalewayConfig({ serverNumber: 1 }))
86 } 96 await setAccessTokensToServers([ server ])
97 await setDefaultVideoChannel([ server ])
87 98
88 const hls = getHLS(video) 99 await server.config.enableMinimumTranscoding()
89 100
90 if (hls) { 101 userToken = await server.users.generateUserAndToken('user1')
91 expectStartWith(hls.playlistUrl, ObjectStorageCommand.getScalewayBaseUrl()) 102 })
92 expectStartWith(hls.segmentsSha256Url, ObjectStorageCommand.getScalewayBaseUrl())
93 103
94 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 }) 104 describe('VOD', function () {
95 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 }) 105 let privateVideoUUID: string
96 } 106 let publicVideoUUID: string
97 } 107 let userPrivateVideoUUID: string
108
109 // ---------------------------------------------------------------------------
98 110
99 async function getSampleFileUrls (videoId: string) { 111 async function getSampleFileUrls (videoId: string) {
100 const video = await server.videos.getWithToken({ id: videoId }) 112 const video = await server.videos.getWithToken({ id: videoId })
@@ -105,6 +117,8 @@ describe('Object storage for video static file privacy', function () {
105 } 117 }
106 } 118 }
107 119
120 // ---------------------------------------------------------------------------
121
108 it('Should upload a private video and have appropriate object storage ACL', async function () { 122 it('Should upload a private video and have appropriate object storage ACL', async function () {
109 this.timeout(60000) 123 this.timeout(60000)
110 124
@@ -120,7 +134,7 @@ describe('Object storage for video static file privacy', function () {
120 134
121 await waitJobs([ server ]) 135 await waitJobs([ server ])
122 136
123 await checkPrivateFiles(privateVideoUUID) 137 await checkPrivateVODFiles(privateVideoUUID)
124 }) 138 })
125 139
126 it('Should upload a public video and have appropriate object storage ACL', async function () { 140 it('Should upload a public video and have appropriate object storage ACL', async function () {
@@ -131,7 +145,7 @@ describe('Object storage for video static file privacy', function () {
131 145
132 publicVideoUUID = uuid 146 publicVideoUUID = uuid
133 147
134 await checkPublicFiles(publicVideoUUID) 148 await checkPublicVODFiles(publicVideoUUID)
135 }) 149 })
136 150
137 it('Should not get files without appropriate OAuth token', async function () { 151 it('Should not get files without appropriate OAuth token', async function () {
@@ -182,7 +196,7 @@ describe('Object storage for video static file privacy', function () {
182 196
183 await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.INTERNAL } }) 197 await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.INTERNAL } })
184 198
185 await checkPrivateFiles(publicVideoUUID) 199 await checkPrivateVODFiles(publicVideoUUID)
186 }) 200 })
187 201
188 it('Should update private video to public', async function () { 202 it('Should update private video to public', async function () {
@@ -190,7 +204,7 @@ describe('Object storage for video static file privacy', function () {
190 204
191 await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.PUBLIC } }) 205 await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.PUBLIC } })
192 206
193 await checkPublicFiles(publicVideoUUID) 207 await checkPublicVODFiles(publicVideoUUID)
194 }) 208 })
195 }) 209 })
196 210
@@ -203,6 +217,8 @@ describe('Object storage for video static file privacy', function () {
203 217
204 let unrelatedFileToken: string 218 let unrelatedFileToken: string
205 219
220 // ---------------------------------------------------------------------------
221
206 async function checkLiveFiles (live: LiveVideo, liveId: string) { 222 async function checkLiveFiles (live: LiveVideo, liveId: string) {
207 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey }) 223 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
208 await server.live.waitUntilPublished({ videoId: liveId }) 224 await server.live.waitUntilPublished({ videoId: liveId })
@@ -260,6 +276,8 @@ describe('Object storage for video static file privacy', function () {
260 } 276 }
261 } 277 }
262 278
279 // ---------------------------------------------------------------------------
280
263 before(async function () { 281 before(async function () {
264 await server.config.enableMinimumTranscoding() 282 await server.config.enableMinimumTranscoding()
265 283
@@ -320,6 +338,52 @@ describe('Object storage for video static file privacy', function () {
320 }) 338 })
321 }) 339 })
322 340
341 describe('With private files proxy disabled and public ACL for private files', function () {
342 let videoUUID: string
343
344 before(async function () {
345 this.timeout(240000)
346
347 await server.kill()
348
349 const config = ObjectStorageCommand.getDefaultScalewayConfig({
350 serverNumber: server.internalServerNumber,
351 enablePrivateProxy: false,
352 privateACL: 'public-read'
353 })
354 await server.run(config)
355
356 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
357 videoUUID = uuid
358
359 await waitJobs([ server ])
360 })
361
362 it('Should display object storage path for a private video and be able to access them', async function () {
363 this.timeout(60000)
364
365 await checkPublicVODFiles(videoUUID)
366 })
367
368 it('Should not be able to access object storage proxy', async function () {
369 const privateVideo = await server.videos.getWithToken({ id: videoUUID })
370 const webtorrentFilename = extractFilenameFromUrl(privateVideo.files[0].fileUrl)
371 const hlsFilename = extractFilenameFromUrl(getHLS(privateVideo).files[0].fileUrl)
372
373 await makeRawRequest({
374 url: server.url + '/object-storage-proxy/webseed/private/' + webtorrentFilename,
375 token: server.accessToken,
376 expectedStatus: HttpStatusCode.BAD_REQUEST_400
377 })
378
379 await makeRawRequest({
380 url: server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + videoUUID + '/' + hlsFilename,
381 token: server.accessToken,
382 expectedStatus: HttpStatusCode.BAD_REQUEST_400
383 })
384 })
385 })
386
323 after(async function () { 387 after(async function () {
324 this.timeout(60000) 388 this.timeout(60000)
325 389
diff --git a/server/tests/api/videos/video-static-file-privacy.ts b/server/tests/api/videos/video-static-file-privacy.ts
index bdbe85127..eaaed5aad 100644
--- a/server/tests/api/videos/video-static-file-privacy.ts
+++ b/server/tests/api/videos/video-static-file-privacy.ts
@@ -383,6 +383,39 @@ describe('Test video static file privacy', function () {
383 }) 383 })
384 }) 384 })
385 385
386 describe('With static file right check disabled', function () {
387 let videoUUID: string
388
389 before(async function () {
390 this.timeout(240000)
391
392 await server.kill()
393
394 await server.run({
395 static_files: {
396 private_files_require_auth: false
397 }
398 })
399
400 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.INTERNAL })
401 videoUUID = uuid
402
403 await waitJobs([ server ])
404 })
405
406 it('Should not check auth for private static files', async function () {
407 const video = await server.videos.getWithToken({ id: videoUUID })
408
409 for (const file of getAllFiles(video)) {
410 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
411 }
412
413 const hls = video.streamingPlaylists[0]
414 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
415 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
416 })
417 })
418
386 after(async function () { 419 after(async function () {
387 await cleanupTests([ server ]) 420 await cleanupTests([ server ])
388 }) 421 })
diff --git a/shared/server-commands/server/object-storage-command.ts b/shared/server-commands/server/object-storage-command.ts
index 405e1b043..a1fe4f0f7 100644
--- a/shared/server-commands/server/object-storage-command.ts
+++ b/shared/server-commands/server/object-storage-command.ts
@@ -81,7 +81,13 @@ export class ObjectStorageCommand extends AbstractCommand {
81 81
82 // --------------------------------------------------------------------------- 82 // ---------------------------------------------------------------------------
83 83
84 static getDefaultScalewayConfig (serverNumber: number) { 84 static getDefaultScalewayConfig (options: {
85 serverNumber: number
86 enablePrivateProxy?: boolean // default true
87 privateACL?: 'private' | 'public-read' // default 'private'
88 }) {
89 const { serverNumber, enablePrivateProxy = true, privateACL = 'private' } = options
90
85 return { 91 return {
86 object_storage: { 92 object_storage: {
87 enabled: true, 93 enabled: true,
@@ -90,6 +96,14 @@ export class ObjectStorageCommand extends AbstractCommand {
90 96
91 credentials: this.getScalewayCredentialsConfig(), 97 credentials: this.getScalewayCredentialsConfig(),
92 98
99 upload_acl: {
100 private: privateACL
101 },
102
103 proxy: {
104 proxify_private_files: enablePrivateProxy
105 },
106
93 streaming_playlists: { 107 streaming_playlists: {
94 bucket_name: this.DEFAULT_SCALEWAY_BUCKET, 108 bucket_name: this.DEFAULT_SCALEWAY_BUCKET,
95 prefix: `test:server-${serverNumber}-streaming-playlists:` 109 prefix: `test:server-${serverNumber}-streaming-playlists:`