microsoft / playwright-dotnet

.NET version of the Playwright testing and automation library.
https://playwright.dev/dotnet/
MIT License
2.47k stars 235 forks source link

[BUG] Cannot initialize Playwright in rootless container #2770

Closed mu88 closed 10 months ago

mu88 commented 10 months ago

System info

Source code

Link to the GitHub repository with the repro

https://github.com/mu88/ScreenshotCreator/tree/feature/repro-Playwright

Test file (self-contained)

/

Steps

Expected

The app starts without errors.

Actual

Starting the app fails with the following error:

fail: Microsoft.Extensions.Hosting.Internal.Host[11]
      Hosting failed to start
      System.ComponentModel.Win32Exception (13): An error occurred trying to start process '/app/.playwright/node/linux-x64/playwright.sh' with working directory '/app'. Permission denied
         at System.Diagnostics.Process.ForkAndExecProcess(ProcessStartInfo startInfo, String resolvedFilename, String[] argv, String[] envp, String cwd, Boolean setCredentials, UInt32 userId, UInt32 groupId, UInt32[] groups, Int32& stdinFd, Int32& stdoutFd, Int32& stderrFd, Boolean usesTerminal, Boolean throwOnNoExec)
         at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo)
         at Microsoft.Playwright.Transport.StdIOTransport.StartProcessWithUTF8IOEncoding(Process process) in /_/src/Playwright/Transport/StdIOTransport.cs:line 167
         at Microsoft.Playwright.Transport.StdIOTransport..ctor() in /_/src/Playwright/Transport/StdIOTransport.cs:line 48
         at Microsoft.Playwright.Playwright.CreateAsync() in /_/src/Playwright/Playwright.cs:line 44
         at ScreenshotCreator.Logic.PlaywrightHelper.InitializePlaywrightAsync() in /src/src/ScreenshotCreator.Logic/PlaywrightHelper.cs:line 43

Comments

When either adding RUN chmod -R 777 /app (see here) or using a root container, everything works as expected.

mxschmitt commented 10 months ago

Looks like your user has no permissions to access /app. Have you tried the following instead of chmod?

RUN chown -R $APP_UID /app

This will give your user permission to /app.

mu88 commented 10 months ago

Correct me if I'm wrong, but doesn't chown change a directory's ownership, and therefore theoretically I could manipulate the /app directory using the app user? If yes, I shouldn't do that according to what I learned here:

That's kind of counter to the purpose of using a non-root user - the goal is that nothing in the application's binaries' directory can be changed.

-- cite>@baronfel</cite

mxschmitt commented 10 months ago

I see! So the executable bit of /app/.playwright/node/linux-x64/playwright.sh seems not to be set correctly.

Investigation:

When we download the drivers before creating the nuget files we seem to be doing the right thing:

   ❯ ls -la src/Playwright/.drivers/linux/playwright.sh                 
  -rwxr-xr-x  1 maxschmitt  staff  216 Nov 16 22:54 src/Playwright/.drivers/linux/playwright.sh

While on the user side the executable bit is only set for user:

  ❯ ls -la bin/Debug/net8.0/.playwright/node/darwin-arm64/playwright.sh
  -rwxr--r--  1 maxschmitt  staff  222 Nov 21 12:58 bin/Debug/net8.0/.playwright/node/darwin-arm64/playwright.sh

Workaround would be RUN chmod +x /app/.playwright/node/linux-x64/playwright.sh instead of the chown/chmod which is already better while we figure out a better solution.

cc @campersau looks like when we pack the bit gets filtered out?

campersau commented 10 months ago

The problem is that NuGet pack and also extract does not preserve the executable bit, in fact any "external" attribute is ignored. I have only seen an issue about the readonly attribute in the NuGet repro though: https://github.com/NuGet/Home/issues/11286 So maybe you can open an issue for it as well?

It might be possible to workaround it by adding some more logic to Microsoft.Playwright.targets and set it manually there but it is not that easy.


NuGet uses the ZipArchive for packing/extracting the nupkg. And only "recently" APIs to get/set "external" attributes like the execution bit were made available: https://apisof.net/catalog/a6f34fac-094f-db3b-e4bc-a1c7ed169602

Pack and extract only tries to preserve timestamps.

Pack: https://github.com/NuGet/NuGet.Client/blob/6e01722cb9d4ede0baa3edc5131a90fe71b6c5fa/src/NuGet.Core/NuGet.Packaging/PackageCreation/Authoring/PackageBuilder.cs#L1011-L1031 https://github.com/NuGet/NuGet.Client/blob/6e01722cb9d4ede0baa3edc5131a90fe71b6c5fa/src/NuGet.Core/NuGet.Packaging/PackageCreation/Authoring/PackageBuilder.cs#L1223-L1237

Extract: https://github.com/NuGet/NuGet.Client/blob/6e01722cb9d4ede0baa3edc5131a90fe71b6c5fa/src/NuGet.Core/NuGet.Packaging/PackageExtraction/ZipArchiveExtensions.cs#L57-L66

campersau commented 10 months ago

Interesting, it looks like it was there but was lost to time:

bbhxwl commented 9 months ago

image

mu88 commented 8 months ago

@mxschmitt would it be possible to add chmod +x /app/.playwright/node/*/node as well? When using a rootless container, the permissions inside the container currently look like this:

app@d9c7ae175924:/app$ ls -lh .playwright/node/linux-x64/
total 92M
-rwxr--r-- 1 root root 92M Jan 19 23:07 node

...which leads to the following error when running my rootless container:

      System.ComponentModel.Win32Exception (13): An error occurred trying to start process '/app/.playwright/node/linux-x64/node' with working directory '/app'. Permission denied
         at System.Diagnostics.Process.ForkAndExecProcess(ProcessStartInfo startInfo, String resolvedFilename, String[] argv, String[] envp, String cwd, Boolean setCredentials, UInt32 userId, UInt32 groupId, UInt32[] groups, Int32& stdinFd, Int32& stdoutFd, Int32& stderrFd, Boolean usesTerminal, Boolean throwOnNoExec)
         at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo)
         at Microsoft.Playwright.Transport.StdIOTransport.StartProcessWithUTF8IOEncoding(Process process) in /_/src/Playwright/Transport/StdIOTransport.cs:line 168
         at Microsoft.Playwright.Transport.StdIOTransport..ctor() in /_/src/Playwright/Transport/StdIOTransport.cs:line 47
         at Microsoft.Playwright.Playwright.CreateAsync() in /_/src/Playwright/Playwright.cs:line 44
         at ScreenshotCreator.Logic.PlaywrightHelper.InitializePlaywrightAsync() in /src/src/ScreenshotCreator.Logic/PlaywrightHelper.cs:line 36
         at ScreenshotCreator.Logic.ScreenshotCreator.CreateScreenshotAsync(UInt32 width, UInt32 height) in /src/src/ScreenshotCreator.Logic/ScreenshotCreator.cs:line 14
         at ScreenshotCreator.Api.BackgroundScreenshotCreator.ExecuteAsync(CancellationToken stoppingToken) in /src/src/ScreenshotCreator.Api/BackgroundScreenshotCreator.cs:line 32
         at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__15_1(IHostedService service, CancellationToken token)
         at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
Unhandled exception. System.ComponentModel.Win32Exception (13): An error occurred trying to start process '/app/.playwright/node/linux-x64/node' with working directory '/app'. Permission denied
   at System.Diagnostics.Process.ForkAndExecProcess(ProcessStartInfo startInfo, String resolvedFilename, String[] argv, String[] envp, String cwd, Boolean setCredentials, UInt32 userId, UInt32 groupId, UInt32[] groups, Int32& stdinFd, Int32& stdoutFd, Int32& stderrFd, Boolean usesTerminal, Boolean throwOnNoExec)
   at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo)
   at Microsoft.Playwright.Transport.StdIOTransport.StartProcessWithUTF8IOEncoding(Process process) in /_/src/Playwright/Transport/StdIOTransport.cs:line 168
   at Microsoft.Playwright.Transport.StdIOTransport..ctor() in /_/src/Playwright/Transport/StdIOTransport.cs:line 47
   at Microsoft.Playwright.Playwright.CreateAsync() in /_/src/Playwright/Playwright.cs:line 44

This error disappears when using (see here):

FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
RUN chmod +x /app/.playwright/node/*/node
ENTRYPOINT ["dotnet", "ScreenshotCreator.Api.dll"]
mxschmitt commented 8 months ago

We actually got rid of the playwright.sh file and do chmod +x to the node executable: https://github.com/microsoft/playwright-dotnet/blob/48aec3542af34b77fb927c04c8a993117173a81e/src/Playwright/build/Microsoft.Playwright.targets#L60-L66

Are you on 1.41.2?

bbhxwl commented 8 months ago

We actually got rid of the playwright.sh file and do chmod +x to the node executable:

https://github.com/microsoft/playwright-dotnet/blob/48aec3542af34b77fb927c04c8a993117173a81e/src/Playwright/build/Microsoft.Playwright.targets#L60-L66

Are you on 1.41.2?

1.40.0

mxschmitt commented 8 months ago

We actually got rid of the playwright.sh file and do chmod +x to the node executable:

https://github.com/microsoft/playwright-dotnet/blob/48aec3542af34b77fb927c04c8a993117173a81e/src/Playwright/build/Microsoft.Playwright.targets#L60-L66

Are you on 1.41.2?

1.40.0

Could you try 1.41.2?

mu88 commented 8 months ago

I'm on 1.41.2 (see here)

mu88 commented 8 months ago

Do you have any idea what the problem might be, @mxschmitt ?

mxschmitt commented 8 months ago

Some guesses would be that the user has no permission to run the driver aka. Node.js. Maybe the permission bits get lost when using FROM inside Docker? I would try tracking it backwards to see when the executable bit got lost. Or maybe try running it as root first, so it might be an ownership problem instead?

mu88 commented 8 months ago

Thx for that hint! You're right: when running COPY in a Dockerfile, the 'execute' bit gets lost (see here).
I was able to resolve the issue by moving the chmod command into my base image (see here) and configure PLAYWRIGHT_DRIVER_SEARCH_PATH. Now I'm even able to use the .NET SDK Container Building Tools for building the app image (see here).

Thx for your help and take care 🙂