Skip to content

Conversation

ygalblum
Copy link
Contributor

Does this PR introduce a user-facing change?

Yes

Quadlet - new unit for podman login
Quadlet - Add support for AuthFile to container and kube units

@openshift-ci openshift-ci bot added release-note approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Jun 24, 2025
Copy link

[NON-BLOCKING] Packit jobs failed. @containers/packit-build please check. Everyone else, feel free to ignore.

@ygalblum ygalblum force-pushed the quadlet-login branch 2 times, most recently from 004b64f to b0edc68 Compare June 24, 2025 21:38

stringKeys := map[string]string{
KeyCertDir: "--cert-dir",
KeyPassword: "--password",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is insecure so we should not do that as it is not any better than before, it is the same issue as I described in #26484 (comment).

Security relevant stuff should never be passed as on cli argument. I think what this must do is a pass --password-stdin. Then in the systemd unit configure

StandardInput=data
StandardInputText=password

And lastly I am not sure on the perms of current generated files but we should make sure they are 600 so no other user could read the password from there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is insecure

While I understand your comment, Quadlet already does something similar in .image files: https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#creds.
I don't expect users to use it with real registries. But, do we need to block it? We can add a warning in the man page.

I think what this must do is a pass --password-stdin

I don't see the benefit here. Isn't the password still leaked just under a different key? What am I missing?

And lastly I am not sure on the perms of current generated files but we should make sure they are 600 so no other user could read the password from there.

Currently, all service files are created with 644. Is your comment about permissions specific to the Password key or as a whole to all .login units?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but again this is horrible security practise so we must discourage it. The login unit should be a solution and not do the exact same error again. The issue is that running podman login --password mypw leaks the password to all users who can view the process list which by default is everyone.

My solution is more secure because the password is never passed via cli argument but rather stdin which is "private". The permissions on the .login file are up to the user who creates them and only something quadlet could warn. But the permissions on the generated -login.service file is up to us so we must ensure the service file is only accessible by the same user because the service also has the password in clear text.

Copy link
Contributor Author

@ygalblum ygalblum Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow why using StandardInputText is better. Doesn't it just mean that instead of:

[Login]
Password=password

The .login unit will have:

[Login]
PasswordSTDIN=yes

[Service]
StandardInput=data
StandardInputText=password

Which also means that it will be copied to the .service file

From what I can see, the actual podman login command does not show in the journal. So, it's not leaked:

Jun 25 08:28:47 lima-default systemd[1001]: Starting image_test_t1-wmsnex2o-login.service...
Jun 25 08:28:47 lima-default image_test_t1-wmsnex2o-login[894538]: Login Succeeded!
Jun 25 08:28:47 lima-default systemd[1001]: Finished image_test_t1-wmsnex2o-login.service.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any user can just run ps auxww and see all the process currently running and does the password is leaked to all users. For a short lived command such as podman login it is hard to actually hit the right window to see it but if you try hard enough and run ps in a loop you might get lucky. Certainly not a secure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. So, I'll remove the support for Password and replace it with PasswordSTDIN and document the requirement for setting StandardInputText. Quadlet will not verify the existence of StandardInputText when PasswordSTDIN is set because it can be set in a drop-in.

As for permissions, the question is on the generated .service files. Do you think that all .service files generated for .login units should have 600? Only the ones with PasswordSTDIN? Not change the implementation at all and warn the user in the doc?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear you don't have to remove support for "Password" as quadlet field. What I try to say here is that we (quadlet) internally map the Password to mean set --password-stdin and

[Service]
StandardInput=data
StandardInputText=<value of Password field>

The user should not need to worry about the implementation details but to me it is very important to not accidentally leak an important secret as cli arg.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've adjusted the code, the tests and the documentation with your recommendations

# Use the same directory for all quadlet files to make sure later steps access previous ones
mkdir $quadlet_tmpdir
# Shutdown the login service to logout and let the dependency kick in
service_cleanup $login_service inactive
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is the podman login --authfile $authfile --get-login $registry command you could run to check if you are logged into the registry. which should fail as the service stop should run logout

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I added checked around starting and stopping of the login service (directly and indirectly)


Path of the authentication file.

This field is mandatory when linking between an `AuthFile` key of a different unit and the `.login` unit.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to instead default to %t/<unit-name>?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, I'm not sure.
Not setting one, means that the login information is stored in the default file. So, units can depend on on the .login unit (in the Wants and After) without setting AuthKey.
But, maybe it is cleaner

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is likely fine. Though I guess there is the gotcha that one cannot use unit specific specifiers like %n as this would get resolved to a different name in the contianer unit where --authfile would be added. Though stuff like %t would till be fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought was that if the .login file doesn't specify an AuthFile then login info will be stored in the default file. So, the .container does not need to specify an AuthFile either, but it will need to specify the dependecy.
As for %n, you are correct. I'll add it to the doc

Copy link
Contributor

@PhrozenByte PhrozenByte Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO I don't see much benefit in forcing users to specify AuthFile: Auth files don't have to be unique and can store the credentials of multiple registries (i.e. using the default auth file is totally file). Furthermore, a .login unit doesn't have to be an appendix of a .container unit: Just think about a global quay.io.login unit (i.e. a .login unit per registry, not per container, intentionally also working for podman run on the shell). Let users decide how to use it.

It doesn't even mean that one must add an explicit dependency to .container units manually then: It all depends on Systemd's execution tree, there are many ways to assert that the .login unit is started before any .container unit (e.g. with intermediary .target units).

It should be noted in the docs though…

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auth files don't have to be unique and can store the credentials of multiple registries (i.e. using the default auth file is totally file).

I think the main issue here is AFAICS there is no locking for writing the authfile. So if you actually use the same file in many login units they would all race against each other and potentially overwrite a previous login causing hard to find race conditions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a valid point indeed. However, shouldn't this rather be solved by adding a note that users must keep that in mind and act accordingly? Neither a Quadlet-specific default value, nor forcing the user to specify AuthFile manually solves this, because one could easily specify the same AuthFile twice. It's basically the same as for ordering the .login unit before the necessary .container unit(s). Both should be noted in the docs though.

Copy link
Contributor

@PhrozenByte PhrozenByte Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This got me thinking: I don't know Quadlet's code, but since AuthFile=private-repo.login in .container units works, Quadlet must be aware of other Quadlet units when generating the actual Systemd units. So, how about grouping .login units that attempt to write to the same auth file together and adding After= directives to the generated Systemd units to ensure that they run sequentially? This would actually solve the issue with race conditions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main issue here is AFAICS there is no locking for writing the authfile. So if you actually use the same file in many login units they would all race against each other and potentially overwrite a previous login causing hard to find race conditions.

https://github.com/containers/image/issues/1365 for the record.

@ygalblum ygalblum force-pushed the quadlet-login branch 4 times, most recently from 84997b1 to 7d8e147 Compare June 25, 2025 19:43

This field is mandatory when linking between an `AuthFile` key of a different unit and the `.login` unit.

This is equivalent to the `--authfile` option.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This is equivalent to the `--authfile` option.
This is equivalent to the Podman `--authfile` option.

In some places, you have Podman for a line like this, in others, you do not. I prefer having Podman for each. However, your call, I'd just ask for consistency.


### `LogoutOnStop=` (defaults to `false`)

When set to `true` the logout will be called when the service is stopped
Copy link
Member

@TomSweeneyRedHat TomSweeneyRedHat Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When set to `true` the logout will be called when the service is stopped
When set to `true`, the logout will be called when the service is stopped.


This key contains a list of arguments passed directly to the end of the `podman login` command
in the generated file. It can be used to access Podman features otherwise unsupported by the generator. Since the generator is unaware
of what unexpected interactions can be caused by these arguments, is not recommended to use
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
of what unexpected interactions can be caused by these arguments, is not recommended to use
of the unexpected interactions that can be caused by these arguments, it is not recommended to use


### `Secret=`

Use a podman secret for the credentials
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Use a podman secret for the credentials
Use a podman secret for the credentials.

@ygalblum
Copy link
Contributor Author

@TomSweeneyRedHat I made the requested changes and added an example

Copy link
Member

@Luap99 Luap99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly LGTM just some minor comments

".login": {
Extension: ".login",
Order: 1,
ServiceFileMode: os.FileMode(0600),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a small comment here why this is special could help.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

".pod": 5,
SupportedExtensions = map[string]ExtensionInfo{
".container": {
Extension: ".container",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is Extension a field if the key has already the same value? I don't see where this would be needed and liek this it just feels like it is wasting a bit of memory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I thought we might get the struct and pass it around and then the Extension can be used. Since there's no usage of it, you're right and I removed it.

@ygalblum ygalblum force-pushed the quadlet-login branch 4 times, most recently from 843c621 to 192b3cf Compare July 2, 2025 17:33
Implement a new unit for podman login
Add support for AuthFile to container and kube units
Allow linking between AuthFile and a login unit

Signed-off-by: Ygal Blum <[email protected]>
Copy link
Member

@Luap99 Luap99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

openshift-ci bot commented Jul 2, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: Luap99, ygalblum

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ygalblum
Copy link
Contributor Author

ygalblum commented Jul 2, 2025

@containers/podman-maintainers PTAL

@mtrmac
Copy link
Collaborator

mtrmac commented Jul 4, 2025

I don’t know. How can one ship a .login file?

@ygalblum
Copy link
Contributor Author

ygalblum commented Jul 4, 2025

When I said "ship a .login file" I also said that the user will need to add their credentials into the file. But, all the rest of is ready made. That is, all other parameters are already there. In the .login file, the user does not need to know the URL of the registry, they don't need to care where the authfile is stored and don't need to point other units (e.g. .container) to it.

As you wrote the .authfile is an empty file and the user need to know where it is because they need to set the location in the login command. So, if they run podman login --authfile $myApp.authfil instead of podman login --authfile $myApp.authfile, yes they login successfully, but they store it in the wrong place and fail later.

@mtrmac
Copy link
Collaborator

mtrmac commented Jul 4, 2025

I’m naive about these things.

In my mental model, all of those .image and .container files have some authoritative git repo, and Ansible automation to deploy them from that repo to the target machine. (Or, maybe, the application is a tarball of files and an install.sh script. Same thing.)

Users work in that git repo, or whatever it is. Paths on the final machine don’t matter, the deployment scripts handle that.

Or, alternatively, all the .container / … files are deployed directly to the target machine using RPM, and users are told that they must edit $path.login completely manually. Well, they would be told a different command.

In none of these situations are users thinking about the paths at application deployment time. The paths are always a design-time concern, just like systemd unit names.

@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 4, 2025
@openshift-merge-robot
Copy link
Collaborator

PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@@ -1963,7 +1989,12 @@ This is equivalent to the Podman `--arch` option.

Path of the authentication file.

This is equivalent to the Podman `--authfile` option.
This is equivalent to the `--authfile` option.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This is equivalent to the `--authfile` option.
This is equivalent to the Podman `--authfile` option.

I'm not sure why "Podman" was dropped here, but not in other places?


### `Secret=`

Use a podman secret for the credentials.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Use a podman secret for the credentials.
Use a Podman secret for the credentials.

@TomSweeneyRedHat
Copy link
Member

Two minor nits. Otherwise LGTM. Sorry about the lag, just back from PTO.

@TomSweeneyRedHat
Copy link
Member

And it looks like you need a rebase at this point to FWIW.

@Luap99
Copy link
Member

Luap99 commented Jul 9, 2025

@TomSweeneyRedHat Have read the discussion here? The question is do we need to that at all or would it not be better to point user to authfiles directly.

Copy link
Contributor

@PhrozenByte PhrozenByte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't do a code review here, I rather just reviewed the proposed docs and want to join the discussion: I absolutely support this (great work @ygalblum btw ❤️).

To this day the only way to store registry credentials persistently without previous setup (like adding --authfile to every single Podman command (which is particularly hard for auto-update, see #12706 (comment)), or adding REGISTRY_AUTH_FILE to the global environment) is the Docker fallback $HOME/.docker/config.json. Also see rejected #12706.

Doing this with Quadlet .login units is way more powerful: It e.g. allows one to easily manage different auth files for different containers and could play particularly well with #26462 and #26488.


Valid options for `[Login]` are listed below:

| **[Login] options** | **podman login** |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| **[Login] options** | **podman login** |
| **[Login] options** | **podman login equivalent** |


Path of the authentication file.

This field is mandatory when linking between an `AuthFile` key of a different unit and the `.login` unit.
Copy link
Contributor

@PhrozenByte PhrozenByte Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO I don't see much benefit in forcing users to specify AuthFile: Auth files don't have to be unique and can store the credentials of multiple registries (i.e. using the default auth file is totally file). Furthermore, a .login unit doesn't have to be an appendix of a .container unit: Just think about a global quay.io.login unit (i.e. a .login unit per registry, not per container, intentionally also working for podman run on the shell). Let users decide how to use it.

It doesn't even mean that one must add an explicit dependency to .container units manually then: It all depends on Systemd's execution tree, there are many ways to assert that the .login unit is started before any .container unit (e.g. with intermediary .target units).

It should be noted in the docs though…


### `LogoutOnStop=` (defaults to `false`)

When set to `true`, the logout will be called when the service is stopped.
Copy link
Contributor

@PhrozenByte PhrozenByte Jul 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When set to `true`, the logout will be called when the service is stopped.
When set to `true`, `podman logout` will be called when the service stops.

Login files are named with a `.login` extension and contain a section `[Login]` describing the
login command. The generated service is a one-time command that logs into a registry.

Using login units allows pulling images from private registries without having to login manually.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A note about Systemd unit ordering should be added here (i.e. that users must ensure that the .login unit runs before any .container unit that wants to use these credentials starts - either manually, or by using AuthFile=private-repo.login in the .container unit)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though it might be a little off-topic, hinting users into the right direction might be helpful. How about something like the following:

Note that Systemd generally starts units in parallel. This can cause ordering issues, because a .container unit can't pull images from an authenticated source unless the .login unit with the necessary credentials runs already. Quadlet automatically ensures proper order when adding AuthFile=private-repo.login to a .container unit. However, else one must either add proper After= directives (e.g. After=private-repo-login.service) to the .container units manually, or use a custom synchronisation point like creating a containers.target unit and adding Before=containers.target to all .login units and After=containers.target to all .container units.

@Luap99
Copy link
Member

Luap99 commented Jul 21, 2025

@PhrozenByte How is creating/distributing a login file easier/better is your eyes than creating/distributing the authfile directly? Because that is what the argument is about here. You can already create a authfile once and ship it next to the quadlet file and refer to it in the container unit.

@jankaluza
Copy link
Member

jankaluza commented Jul 21, 2025

@Luap99 , I think the .login file is easier for end users to consume and digest. The @ygalblum users most likely do not care about podman and its command line arguments and they do not understand what the authfile even is. They want to run the containerized service the easiest way possible and the podman and quadlets are just implementation details.

The easiest way most likely is downloading the quadlets with instruction to "edit the .login file and change your username/password there". People are used to edit config files like that and there are much more users used to work with systemd than users used to work with podman (just guessing?)?

The authfile way is probably more complex, because the users have to run the "podman login --authfile". This is not the usual command people run and remember to re-run in case the username/pass changes and for @ygalblum , it is harder to find out sane "default path" which would work across different distributions.

Or is there some default path for authfiles which can be used already? If it's up to user to find out good place where to store the authfile, the user also has to change other systemd files to set the AuthFile to right location. This is yet another step. I think the workaround could be just using /opt/myapp/authfile and let the user run podman login --authfile /opt/myapp/authfile. Not sure what prevents @ygalblum to do that.

Edit: There is definitely not a default authfile per systemd service :-).

@mtrmac is right that if there is some install.sh or ansible involved, nothing from that matter anymore most likely, because you can just script username/password or authfile creation and make it an implementation detail for a user as well.

I also like that this could in the future allow passing authfile as systemd credentials.

@PhrozenByte
Copy link
Contributor

PhrozenByte commented Jul 21, 2025

@Luap99 Multiple advantages actually:

  • Right now it's impossible to persistently store credentials without prior setup ("prior setup" meaning to either add --authfile to every run and auto-update (which is particularly hard for unprivileged containers), or adding REGISTRY_AUTH_FILE to the global environment, see Add support for ${XDG_CONFIG_HOME}/containers/auth.json #12706, or writing custom scripts to run login)
  • Auth files aren't in a "human writable format", but .login units are - i.e. one can't reasonably tell users to edit a JSON file due to its strict syntax, but editing a Systemd unit is generally safe
  • Therefore one must create auth files on the command line; IMHO the whole point of Quadlet is to not require users to use and understand the command line
  • Furthermore, it requires one to install Podman on the machine one is deploying the server from; might not be an issue at all, or a huge issue, depending on the setup, but requiring users to install Podman locally to setup a server isn't great
  • Lastly, there's no well-known path for persistently storing auth files in; this is not as simple as documenting e.g. /etc/containers/auth/, because the path depends on whether one runs a rootful or unprivileged container, therefore making the .container units no longer agnostic of how the container is run
  • .login units will get more powerful in the future, e.g. when adding support for Systemd credentials (could be as easy as adding a --password-file option to login), or when adding support for external secret providers (see Idea: Load secrets from external providers #26488) that can then be utilised with Secret=

@mtrmac
Copy link
Collaborator

mtrmac commented Jul 21, 2025

  • Right now it's impossible to persistently store credentials without prior setup ("prior setup" meaning to either add --authfile to every run and auto-update.

Adding a dependency on .login to every unit is exactly the same amount of work, isn't it?

  • Auth files aren't in a "human writable format", but .login units are - i.e. one can't reasonably tell users to edit a JSON file due to its strict syntax, but editing a Systemd unit is generally safe

That’s an anti-feature. E.g. the Docker Hub registry is, over time, slowly but seemingly purposefully, moving towards a model where authentication happens via OAuth, perhaps using a browser. Also, many of the cloud-specific registry services similarly require a cloud credential and a cloud-specific tool to log in.

It seems fairly unlikely that users editing files with static credentials will remain the default, or even possible, workflow, long-term. A tool will have to be involved.

  • Therefore one must create auth files on the command line; IMHO the whole point of Quadlet is to not require users to use and understand the command line

I don’t know how reading a man page and writing a file, vs. reading a man page and running a command, is substantially different, but I’ll defer to UI experts on this.

  • Furthermore, it requires one to install Podman on the machine one is deploying the server from

FWIW Skopeo would suffice, the login code path is ~the same — although that might not be any easier to deploy.

  • Lastly, there's no well-known path for persistently storing auth files in; this is not as simple as documenting e.g. /etc/containers/auth/, because the path depends on whether one runs a rootful or unprivileged container, therefore making the .container units no longer agnostic of how the container is run

Yes; I think that’s an interesting UI/design/workflow concern, and it would be valuable to figure this out. (Not just for passphrases, but for any other files, e.g. configuration one might want to mount into a vendor-provided otherwise-unmodified container image.)

  • .login units will get more powerful in the future

The current design which contains a single credential to be written to an exclusively-owned authfile is already insufficient for use with mirrors.

, e.g. when adding support for Systemd credentials (could be as easy as adding a --password-file option to login)

N/A. Just point --authfile at the systemd credential, no need to do anything else.

, or when adding support for external secret providers (see Idea: Load secrets from external providers #26488) that can then be utilised with Secret=

That could be a future feature without ever providing a “create a systemd unit with a password in it” feature as proposed here.

@PhrozenByte
Copy link
Contributor

Adding a dependency on .login to every unit is exactly the same amount of work, isn't it?

Not necessarily: Systemd uses .target units as well-known synchronisation points. By adding Before=some.target to .login units and After=some.target to .container units one tells Systemd to start these units sequentially without requiring container-specific modifications. This can be a custom containers.target, or a more specific http.target, but also e.g. default.target (or multi-user.target).

That’s an anti-feature. E.g. the Docker Hub registry is, over time, slowly but seemingly purposefully, moving towards a model where authentication happens via OAuth, perhaps using a browser. Also, many of the cloud-specific registry services similarly require a cloud credential and a cloud-specific tool to log in.

It seems fairly unlikely that users editing files with static credentials will remain the default, or even possible, workflow, long-term. A tool will have to be involved.

Can you please elaborate on how an OAuth workflow involving a browser is supposed to work on a server? I assume this is a misunderstanding: OAuth is solely used to get access tokens. Access tokens are still "static credentials".

If the access tokens are long-living, one simply deploys them to the server as before.

If they are short-living, we got a strong argument pro .login units: If authentication isn't as simple as a long-living static credential, one needs authentication providers like e.g. #26488. Deploying an auth file won't ever work then - one must rather let the server deal with getting that short-living access token and then run login with this static credential. Running login on the server is exactly what this .login unit is proposing?

The current design which contains a single credential to be written to an exclusively-owned authfile is already insufficient for use with mirrors.

Please elaborate.

N/A. Just point --authfile at the systemd credential, no need to do anything else.

systemd-creds is supposed to store credentials, not config files. A auth provider doesn't know Podman's auth file format, but just returns the credentials. It's up to login to bring these credentials into Podman's auth file format. That's exactly what this .login unit is proposing.

That could be a future feature without ever providing a “create a systemd unit with a password in it” feature as proposed here.

I'm confused. You vote for not introducing a .login unit and therefore not running login on the server, but to deploy client-generated auth files instead. Please elaborate on how these future features could be implemented without running login on the server?

Or do you solely oppose the Password= directive? What's the issue with that?

@mtrmac
Copy link
Collaborator

mtrmac commented Jul 21, 2025

Adding a dependency on .login to every unit is exactly the same amount of work, isn't it?

Not necessarily: Systemd uses .target units as well-known synchronisation points. By adding Before=some.target to .login units and After=some.target to .container units …

I meant adding Authfile: /some/path vs. Authfile: some-unit.login. Exactly one line to add to every consumer unit in both cases.

That’s an anti-feature. E.g. the Docker Hub registry is, over time, slowly but seemingly purposefully, moving towards a model where authentication happens via OAuth, perhaps using a browser. Also, many of the cloud-specific registry services similarly require a cloud credential and a cloud-specific tool to log in.
It seems fairly unlikely that users editing files with static credentials will remain the default, or even possible, workflow, long-term. A tool will have to be involved.

Can you please elaborate on how an OAuth workflow involving a browser is supposed to work on a server? I assume this is a misunderstanding: OAuth is solely used to get access tokens. Access tokens are still "static credentials".

Yes, but explicitly created using a tool, perhaps using a sequence of HTTP requests. So the workflow is login-using-a-tool --out=authfile.json and then use the file as is, vs. login-using-a-tool --out=some-temporary-authfile.json and then manually copy&paste the now-static credential from one file to another. The manual edits are just extra work.

The current design which contains a single credential to be written to an exclusively-owned authfile is already insufficient for use with mirrors.

Please elaborate.

How do I create an authfile that contains credentials for the primary + 2 mirrors? The current login syntax supports one password.

(Yes, in that other thread there is a discussion about using multiple .login units and telling users to manually order them in sequence. Manual and error-prone work compared to pre-creating a single file.)

That could be a future feature without ever providing a “create a systemd unit with a password in it” feature as proposed here.

I'm confused. You vote for not introducing a .login unit and therefore not running login on the server, but to deploy client-generated auth files instead.

I’m confused about which computer is the "client" and which one is the "server" here. On which one are the Quadlet units deployed? On which one are they set up / maintained / edited, to be deployed to the other one?

Or do you solely oppose the Password= directive?

That one I oppose strongly, and I wrote why (separation of secrets vs. sensitive-but-not-secret configuration). And overall I think the “one credential is necessary” mental model is incorrect, but that’s a weaker objection.

@PhrozenByte
Copy link
Contributor

PhrozenByte commented Jul 21, 2025

I’m confused about which computer is the "client" and which one is the "server" here. On which one are the Quadlet units deployed? On which one are they set up / maintained / edited, to be deployed to the other one?

Let's call "server" the machine the Quadlet units are deployed to, and "client" the machine that is responsible for preparing and deploying the config to the "server".

Yes, but explicitly created using a tool, perhaps using a sequence of HTTP requests. So the workflow is login-using-a-tool --out=authfile.json and then use the file as is, vs. login-using-a-tool --out=some-temporary-authfile.json and then manually copy&paste the now-static credential from one file to another. The manual edits are just extra work.

First, I don't think that login-using-a-tool would ever directly write to a Podman auth file, because Podman auth files are in no way an established file format for credentials. So, in reality it's likely more like login-using-a-tool | podman login --authfile=authfile.json --password-stdin.

Second, the whole idea of .login units is to not run login on the client, but on the server. When assuming short-living OAuth access tokens, one simply can't run login-using-a-tool on the client, but must run it on the server. How does your approach work without running login on the server?

So, in the end it's more like running login-using-a-tool | podman login --authfile=authfile.json --password-stdin on the client and then deploying auth.json to the server vs. just running login-using-a-tool | podman login --authfile=authfile.json --password-stdin on the server.

How do I create an authfile that contains credentials for the primary + 2 mirrors? The current login syntax supports one password.

(Yes, in that other thread there is a discussion about using multiple .login units and telling users to manually order them in sequence. Manual and error-prone work compared to pre-creating a single file.)

By using three .login units. From what I see from the outside how Quadlet works (I don't know the code, @ygalblum WDYT?), I think that it's possible to let Quadlet order .login units to not run into race conditions, see #26507 (comment)

Or do you solely oppose the Password= directive?

That one I oppose strongly, and I wrote why (separation of secrets vs. sensitive-but-not-secret configuration).

I honestly don't see much of a difference whether one deploys a auth.json with appropriate file permissions, or a .login unit with appropriate file permissions in this regard. From my experience it's rather rare for applications to store static credentials in separate files - and if they do, it's usually more a implementation detail than a design decision. I consider this more a personal preference - admittedly, as RH employee your personal preference is more important than mine 😉 Or, are there any hard facts I miss, like security implications?

And overall I think the “one credential is necessary” mental model is incorrect, but that’s a weaker objection.

I honestly still don't understand how that opposes running login on the server. This is only true when using the Password= directive. The .login unit doesn't just consist of the Password= directive.

@ygalblum
Copy link
Contributor Author

By using three .login units. From what I see from the outside how Quadlet works (I don't know the code, @ygalblum WDYT?), I think that it's possible to let Quadlet order .login units to not run into race conditions, see #26507 (comment)

I don't think Quadlet can do it on its own. How will it know the order? Or do you mean that the order itself does not matter as long as they are not executed in parallel?

@PhrozenByte
Copy link
Contributor

PhrozenByte commented Jul 21, 2025

By using three .login units. From what I see from the outside how Quadlet works (I don't know the code, @ygalblum WDYT?), I think that it's possible to let Quadlet order .login units to not run into race conditions, see #26507 (comment)

I don't think Quadlet can do it on its own. How will it know the order? Or do you mean that the order itself does not matter as long as they are not executed in parallel?

The latter.

Order doesn't matter, only that multiple login processes don't attempt to write to the same file in parallel. Multiple login processes can run in parallel as long as they write to different files though.

I don't know Go, but in other languages I'd simply group the .login units by AuthFile= (including a "no AuthFile= group") and then add After= directives to the units referencing their respective previous .login unit within that same group. This way all but the first .login unit in a group gets an After= directive. Two .login units writing to e.g. AuthFile=/etc/containers/auth/private-repo.json then run sequentially, but .login units writing to different files (e.g. AuthFile=/etc/containers/auth/other-repo.json and AuthFile=/etc/containers/auth/yet-another-repo.json) still run in parallel.

@ygalblum
Copy link
Contributor Author

The latter.

I see. It will require some additional code in the way Quadlet handles the dependency between files. But, it should be doable.

Having said that, I want to add that before I make any changes I need to get an approval on the premise of this feature. No point for me to continue working on this if it will never get approved

@mtrmac
Copy link
Collaborator

mtrmac commented Jul 21, 2025

Yes, but explicitly created using a tool, perhaps using a sequence of HTTP requests. So the workflow is login-using-a-tool --out=authfile.json and then use the file as is, vs. login-using-a-tool --out=some-temporary-authfile.json and then manually copy&paste the now-static credential from one file to another. The manual edits are just extra work.

First, I don't think that login-using-a-tool would ever directly write to a Podman auth file, because Podman auth files are in no way an established file format for credentials. So, in reality it's likely more like login-using-a-tool | podman login --authfile=authfile.json --password-stdin.

The files (well, “credentials in .docker/config.json) are actually somewhat widely used as a file format. See gcloud auth configure-docker, az acr login; AWS ECR similarly recommends configuring the file to use a helper, not using a password (although piping a password/token is an option, to be fair).

  • there is an identityToken field in the files, as an explicit alternative to a password. That is designed to be entirely tool-managed, podman login will refuse to do anything.

Second, the whole idea of .login units is to not run login on the client, but on the server. When assuming short-living OAuth access tokens, one simply can't run login-using-a-tool on the client, but must run it on the server. How does your approach work without running login on the server?

  • That would be an interesting discussion for .login items that are designed to implement regular OAuth re-authentications; not for the .login items proposed in this PR.
  • If the “server” should be running regular OAuth re-authentications from a single static credential located on that server… that seems pointless, the server still has a long-lasting credential. The hypothesis of such a feature is problematic (admitting that some registry operators’ policies might force “servers” do do such a thing).

And overall I think the “one credential is necessary” mental model is incorrect, but that’s a weaker objection.

I honestly still don't understand how that opposes running login on the server.

When running “login” on the server is unnecessary, to me that’s already a pretty strong reason not to design workflows that require or recommend it. Not a decisive reason, but there better be very significant benefits to designing things that way.

Personally I find the idea that, to support 3 mirrors, I must create 3 separate .login items that target the same file, and manually order them to avoid conflicts (well, this is a feature that should be fixed anyway), and create a dependency on all 3 items in every single consumer … way too much hassle to be elegant. I can see an argument for a philosophy where “simple things are done using one simple way, and more complex things are done using a completely different more complex say”… aesthetically I don’t prefer that kind of an approach, but that is really up to Quadlet maintainers.

@PhrozenByte
Copy link
Contributor

Second, the whole idea of .login units is to not run login on the client, but on the server. When assuming short-living OAuth access tokens, one simply can't run login-using-a-tool on the client, but must run it on the server. How does your approach work without running login on the server?

That would be an interesting discussion for .login items that are designed to implement regular OAuth re-authentications; not for the .login items proposed in this PR.

I don't really see a reason why one would want to add login-only secret providers, given that there's already #26488 and Secret= in .login units (resp. login --secret). Anyway, you're right, that's discussed elsewhere.

If the “server” should be running regular OAuth re-authentications from a single static credential located on that server… that seems pointless, the server still has a long-lasting credential. The hypothesis of such a feature is problematic (admitting that some registry operators’ policies might force “servers” do do such a thing).

Think about e.g. TPMs. But that's not the point. The point is that running login on the server is more flexible, because there are use-cases in which one simply can't run login on the client.

And overall I think the “one credential is necessary” mental model is incorrect, but that’s a weaker objection.

I honestly still don't understand how that opposes running login on the server.

When running “login” on the server is unnecessary, to me that’s already a pretty strong reason not to design workflows that require or recommend it. Not a decisive reason, but there better be very significant benefits to designing things that way.

My reason is exactly the same, just the other way round: When running login on the client is unnecessary, that's […].

Again, I might not even have Podman installed on the machine that deploys my servers (the "client"). Requiring users to install Podman there even though they simply don't need it is a bad thing and anything but "simple".

Personally I find the idea that, to support 3 mirrors, I must create 3 separate .login items that target the same file, and manually order them to avoid conflicts (well, this is a feature that should be fixed anyway), and create a dependency on all 3 items in every single consumer … way too much hassle to be elegant.

We've already discussed this, you don't have to do either one. Please see above.

Besides, adding a .login unit doesn't harm your workflow in any way. You can keep on doing it that way if that's simpler for you.

Running login on the server solves a whole bunch of other issues, including my personal issue with Podman not being able to use persistently stored credentials without either --authfile or REGISTRY_AUTH_FILE (i.e. #12706, containers/image#1746, containers/image#2600, and containers/container-libs#196) - which are anything but "simple". Even the most basic use cases like authenticating against ghcr.io to pull images from private repos is unnecessary hard right now. Deploying a single .login unit that runs before any .container unit solves that. You simply can't do that with an auth file alone. You either need to modify any Podman command to add --authfile, or add configs to modify the global environment to include REGISTRY_AUTH_FILE. That's undoubtedly harder.

@mtrmac
Copy link
Collaborator

mtrmac commented Jul 21, 2025

Deploying a single .login unit that runs before any .container unit solves that.

If you mean that the .login unit would edit root’s per-interactive-user private state… I think making that very hard is yet another good reason not to support .login. Per-user state is per-user state and not per-system state. Most systemd units, even if running as root, have no business reading root’s per-user state.

*sigh* maybe I really should just let this be and let people who like this kind of stuff build Quadlet how they like?

@ygalblum
Copy link
Contributor Author

Personally I find the idea that, to support 3 mirrors, I must create 3 separate .login items that target the same file, and manually order them to avoid conflicts (well, this is a feature that should be fixed anyway), and create a dependency on all 3 items in every single consumer … way too much hassle to be elegant. I can see an argument for a philosophy where “simple things are done using one simple way, and more complex things are done using a completely different more complex say”… aesthetically I don’t prefer that kind of an approach, but that is really up to Quadlet maintainers.

First, as you mentioned, the only reason Quadlet would need to maintain an order is because of a bug in podman login.

Second, I guess I hold the opposite view. Solve something for the 80% and let the 20% worry about their complex issues. For sure, I wouldn't block the solution for the 80% because it doesn't fit all the different options.

the Password= directive

Quadlet is not about deciding for users how they should use podman commands (we tried that in the beginning and ended up removing almost all opinions). If users want to use this directive, they should be able to do that along with the consequences.

Copy link

A friendly reminder that this PR had no activity for 30 days.

@ygalblum
Copy link
Contributor Author

Closing this PR following feedback from the containers team. Can be revisited later if there's any interest.

@ygalblum ygalblum closed this Aug 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. release-note stale-pr
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants