- Issue created by @sanduhrs
- ๐บ๐ธUnited States hestenet Portland, OR ๐บ๐ธ
@hestenet - I am going to work with @sanduhrs and @fgm to set up access to our development system on cloud-iam so that they can get this ready. Both have already signed contributor agreement and NDA.
I will coordinate this with @heddn who is doing primary development
- heddn Nicaragua
Ah, I think understand what we're doing here. We're wanting to authenticate from random drupal sites to submit (as authenticated d.o users) translations. In the case, the ability to authenticate using keycloak is very much a possibility. Feel free to reach out on slack (heddn) and I can orient you guys on the sandbox environment we have setup.
- heddn Nicaragua
While we are working on the actual migration of d.o users to keycloak, might I suggest spinning up a playground: https://www.keycloak.org/getting-started/getting-started-docker? Then wire up l10n_client to this generic keycloak instance, figure out what is needed and propose that solution to all of us. Our setup of keycloak is very traditional. Not much special (yet) added to the out of the box setup. Developing with a localdev is going to be way more efficient then using any server or cloud hosted solution too. Just document the setup steps proposed here.
- ๐บ๐ธUnited States drumm NY, US
Keycloak may or may not be the solution here. Iโm interpreting this as needing something to manage API keys that can be used for authenticating write access only this localization service.
Full access to the localize site as the user might be okay, it is a bit of extra access, but there is not a lot more going on in the site. Full access as a user who can approve/etc translations might not be intended.
We certainly donโt want to be using keys which have access to authenticate as the user everywhere else on *.drupal.org. That would be unnecessary risk.
- ๐ฉ๐ชGermany Harlor Berlin
Ah, I think understand what we're doing here. We're wanting to authenticate from random drupal sites to submit (as authenticated d.o users) translations.
Yes - That's the idea.
We certainly donโt want to be using keys which have access to authenticate as the user everywhere else on *.drupal.org. That would be unnecessary risk.
Yes - I hope we find a solution with have access-tokens/api-keys that can be used for the contribution endpoints authentication only.
Btw. we started working on the server endpoints: https://www.drupal.org/project/l10n_server/issues/3395508 ๐ New endpoints for l10n_client_contributor Needs review
And the corresponding adjustments in the l10n_client: https://www.drupal.org/project/l10n_client/issues/3395327 ๐ Replace xmlrpc for new localize.drupal.org Needs work - First commit to issue fork.
- ๐ช๐ธSpain fjgarlin
After talking with @drumm at DrupalCon:
We can create a "secret" or "token" filed in the user profile in localize.drupal.org, and then use that as a way to allow other Drupal sites to communicate with the localize.drupal.org.
We can use modules like https://www.drupal.org/project/field_encrypt โ to encrypt the value in the database. Ideally, users can regenerate or change that "secret" when they want and that's what they will use to post translations to localize.drupal.org.
I guess we'll need to create a plugin (https://git.drupalcode.org/project/l10n_server/-/tree/3.0.x/l10n_remote) or submodule (?) to read the token, try to find the user, and the do the rest once the user is validated.
- ๐ช๐ธSpain guiu.rocafort.ferrer Barcelona
guiu.rocafort.ferrer โ made their first commit to this issueโs fork.
- ๐ฉ๐ชGermany donquixote
I would have an alternative proposal.
Goals
Mainly we want to prove that a translation is coming from a user who owns a specific drupal.org account.
In addition, we should (but currently don't) prove that the translation is coming from an authorized Drupal website.
(This become relevant if a user would trust a secret token to a malicious Drupal website. But it also makes the following proposal easier.)Both of these can be regarded as "signature" problems: We send some information, and prove that it is legitimate and who sent it.
Problems with the current proposal
The main problem I see with the current proposal is that we are asking users to copy around secret tokens to different websites, some of which might not be trustworthy, or might become compromised. Then we send this literal token with each translation request.
Anything that gets its hand on one of these tokens will be able to submit translations.
New proposal
Data model
In l10n_client_contributor:
We introduce a global private + public keypair (ssh, gpg or whatever) that is unique per website.
- The key could be rotated regularly (that is, a new key generated).
- We have yet to figure out how it would be stored, e.g. with key module.
- We provide a public url that allows to download the public key from that website.TBD: We introduce a permission to allow a user to contribute translations to drupal.org.
TBD: We introduce a checkbox per user to opt in or out of submitting translations when they are saved.We introduce a new permission that allows to register the website in drupal.org.
In l10n_server / drupal.org:
We introduce an entity type "Website that can contribute translations".
With each such entity we store:- Url of the website. This is to identify the website, and to download a key.
- Public key.
This can be downloaded automatically (for publicly accessible websites) or set manually (for localhost websites). - (TBD) Old public key, to ease the rotation.
- A boolean / checkbox to enable/disable.
- An entity owner field.
We introduce a special permission that allows privileged users to register such contributing websites in drupal.org.
(This operation requires a signature from that website, see below, to avoid fake entries.)For drupal.org user accounts, we add a multiple-value field or a separate entity, where for each record, we have:
- The drupal.org user id.
(this is a given if it is a field on the user account) - A reference to one of the above contributing website entities.
- A user id on that contributing website.
TBD: Normally each drupal.org user should register only one remote account per website, but we could allow multiple for testing purposes. - A boolean / checkbox to enable/disable.
Translation process
On the contributing website:
- A user saves a translation in a website with l10n_client.
- If the user has the respective permission on that website, and has not opted out of the feature, the website will submit the translation to drupal.org.
- The request contains the translation, the user id (from the contributing website), the url of the contributing website as registered in the drupal.org entity, and a signature using the private key.On drupal.org:
- The l10n_server module finds the entity that matches the contributing website url.
- The l10n_server module verifies the signature using the public key for that website.
- The l10n_server module finds the drupal.org user account based on the user id.
- The l10n_server module checks permissions of the drupal.org user account.
- The l10n_server module accepts the translation, and logs where it is coming from.Adding a contributing site on drupal.org
- A privileged user logs into the contributing website.
- The user goes to a specific page with a confirm form (no input fields), saying "register this website on drupal.org for submitting translations".
- The user is redirected to a special page with a form in drupal.org. The url query contains a signature that was generated with the private key, and the url of the website.
- The user must be logged in on drupal.org, or is asked to log in now.
- The l10n_server module verifies the permission to register translation-contributing websites.
- The l10n_server module downloads the public key from the website.
- The l10n_server module verifies the signature in the url, using the public key.
- The l10n_server module looks for already existing entities with the same url.
- The l10n_server module presents a summary of what will be saved in the new entity.
- The user clicks save/submit.
- The new entity is created, with the user as the owner.Connect your website account with your drupal.org account
- A translator logs into the contributing website.
- The translator goes to a specific page with a confirm form (no input fields), saying "link this account with my drupal.org account to submit translations".
- The translator is redirected to a url in drupal.org. The url contains a signature and the user id from the source website.
- The translator must be logged in on drupal.org, or is asked to do so now.
- The l10n_server module checks permission and the token.
- The l10n_server checks for possible duplicate entries.
- The l10n_server shows a confirm form which presents the data.
- The user clicks save.
- The new field item or entity is created, linking the website user account to the drupal.org user account.Key rotation
A new private + public key is generated on a contributing website, e.g. from a cron job.
The website pings drupal.org.
The l10n_server module requests the new public key from the contributing website.OR
A new private + public key is generated on a contributing website, e.g. from a cron job.
A translator submits a request.
The key does not match.
The l10n_server module requests the new public key from the contributing website.Block/cancel an untrusted or abandoned website
(by the owner)
The owner logs in to drupal.org, and disables or deletes the entity for that website.(if the owner is still trusted)
Somebody disables or deletes the entity for that website.
(can be the owner, or another privileged user if the owner is not available)(... if the owner is no longer trusted)
Somebody disables or deletes the entity for that website, and removes privileges (roles) from the owner's account.Block/cancel a specific translator
(if the drupal.org account is still trusted)
Somebody disables or removes the field item that links the website account and drupal.org account.(if the drupal.org account is no longer trusted)
Somebody removes privileges (roles) from that drupal.org account.Benefits
We don't ask users to copy around their secret token to possibly untrusted websites.
We don't need to store secrets per user.
We can block compromised or abandoned websites.
We have more granular reporting where a translation came from.TBD
Non publicly accessible websites
A question came up whether to support localhost websites that are not publicly available, so drupal.org cannot download the public key.
For such websites, the public key would have to be manually set.
In that case, the url alone cannot be the only identifier, otherwise we get duplicates because plenty of sites will be called localhost or e.g. mydrupaltest.ddev.site but all will have different public/private key.A question would be do we need this?
If the only reason is testing, it is better to have a local docker/compose/ddev setup where one test site has l10n_server and the other has l10n_client.New issue?
We could continue this discussion in a new issue, if it would cause too much distraction here.
- ๐ฉ๐ชGermany donquixote
Alternatively to a public/private key check, we could also have drupal.org query the contributing website each time to verify that a submitted translation is legitimate. This still requires some kind of secret in that website, e.g. a JWT key, but drupal.org would not need to store public keys. But is this really better?
I am sure similar systems exist elsewhere, would be interesting to see.
- ๐ฉ๐ชGermany donquixote
A lot of parts of the above could be covered as separate modules:
(and some might already exist)- Public/private key storage with endpoint to get the public key.
- Entities for authorized websites with public key.
Possibly with different "scopes", where translation contribution is only one scope. - User field for a linked account on a separate website.
The field and field type could be in different submodules to avoid the dependency trap.
- ๐บ๐ธUnited States drumm NY, US
Iโm not sure if this should be a requirement or not, but I have heard of contributing websites that are intranets or otherwise not accessible from the public internet. That would make any localize.drupal.org โ contributing site communication impossible.
- ๐ฉ๐ชGermany donquixote
If we want to support local development websites and intranets, we could still go with a public/private keypair approach.
Only in these cases, we need to be able to directly put the public key into localize.drupal.org, instead of having it fetched from an API endpoint from the contributing site.And to easily support multiple local Drupal sites (e.g. something.ddev.site, or just "localhost"), the keypair could be shared between those instances, and localize.drupal.org could store the public key without associating it with a specific hostname.
----
There is a big design space between the originally proposed token solution and the specific keypair solution proposed in #16.
Secrets per user or per site
We can have a solution with per-user secrets (e.g. the token proposed here), or one with per-website secrets.
With per-user secrets, we either want some kind of encrypted field, or we accept the risk that these are compromised.
Also the user needs to trust the website with (one of) their secret tokens.
Secore storage per user is going to be harder than securely storing a site-level secret.(Currently the consensus seems to be that encryption of tokens is not necessary)
However, if we have site-level secrets, we do need some other way to associate user accounts between localize.drupal.org and the contributing website, while preventing improper impersonation, which leads to the complex data model in #16.
There could be a shortcut for this data model for local sites where each account is basically the same user on localize.drupal.org.
Keypair vs token + hash
A keypair would be generated on the contributing site, and the private key does not need to be copied around. The private key is not sent with requests, only the generated signature.
A token would be generated on localize.drupal.org and does need to be copied around manually. It also has to be sent literally with every request.In theory we could have per-user key pairs generated on the contributing website, to avoid the copying around of secrets. But this would again require secure storage per user (if we care about that).