taskcluster / taskcluster-rfcs

Taskcluster team planning
Mozilla Public License 2.0
11 stars 20 forks source link

Sudo service #98

Closed jonasfj closed 7 years ago

jonasfj commented 7 years ago

Note: bug 1312915 suggests this is merged into issue #45, I'm not sure..

taskcluster-sudo, scope elevation service for admins

Allows for a client with scopes on the form: sudo:<email>:<scope> To obtain temporary credentials that cover <scope>, if said client can get duo 2FA sign-off from LDAP user identified by <email>.

As an added benefit it also supports proxying requests directly, which should work neatly, if you only need to do a single request.

API Surface

hostname: sudo.taskcluster.net

GET /v1/credentials/<email>
scopes:    sudo:<email>:<scope>
signature: sudo.credentials(email)
response:
{
  clientId: '...',
  accessToken: '...',
  certificate: '...',
}

PUT /v1/proxy/<email>/<hostname>/<path>?<query>
signature: sudo.put(email, hostname, path, {query...})
payload:
  {...}  // binary pass-through
response:
  {...}  // binary pass-through

// any other method we might support...

Implementation Notes

scope translation, notice that the <email> is passed as URL parameter and it is present in scopes. That's because when you specify <email> in the URL, all scopes on the form sudo:<email>:<scope> are translated to <scope>. Hence, sudo:* allows you to specify any email, and get a * scope if you can convince anyone to sign-off on the 2FA request (obviously, sudo:* is a bad scope we don't give out).

scope restriction, original proposal in bug 1312915 suggests that the request should specify exactly which scopes it wants to expand. This complicates usage at lot, and is very inconvenient, without adding much security. If you desire this I propose that you specify the set of scopes you want in authorizedScopes. For example, I might have sudo:jojensen@mozilla.com:* but I make the request to sudo using authorizedScopes: ['sudo:jojensen@mozilla.com:auth:*']. So that way we use the same scope restriction mechanism we use elsewhere.

Timeouts/retries, since methods should block waiting for 2FA to come through. We should probably cache the requestIp, clientId and email, and if a similar request comes in again within a timeout of say 2 min (or 5 min), we just let it through without 2FA. So scopes on the form sudo:<email>:<scope> are locked by 2FA until they get unlocked, and then they work for a specific source IP, for a short period of time.

Example usages:

Obtain temp creds from the sudo service as follows:

  let sudo = new taskcluster.Sudo();
  // Get temp creds from sudo, blocks until 2FA is confirmed, may still timeout
  // after 30s.
  // NOTE: We will permit a retry through if 2FA have been confirmed for the
  // same requestIp, clientId and email within the past 2min. This ensures that
  // retries will work, and timeouts will be a minor concern.
  let creds = await sudo.credentials('<email>');
  let queue = new taskcluster.Queue({credetials: creds});
  await queue.createTask(...)  // This will proxy the request through sudo service.

Proxy request through the sudo service:

  let queue = new taskcluster.Queue({
    baseUrl: 'https://sudo.taskcluster.net/v1/proxy/<email>/queue.taskcluster.net/v1'
  });
  // This will proxy the request through sudo service, this will block until 2FA
  // is confirmed, again it will permit the request if 2FA has been confirmed
  // for the same requestIp, clientId and email with the past 2min.
  await queue.createTask(...)
djmitche commented 7 years ago

As background, when I first brought up the "sudo" idea it was thoroughly rejected in favor of accounts. Things have changed since then!

djmitche commented 7 years ago

await queue.createTask(...) // This will proxy the request through sudo service.

Will it? I think that will use the temp creds just generated directly with the queue service, no?

A few other notes:

  1. Will the proxy form work for any method?
  2. I suspect we would want to have configurable controls for different scopes here. Some might require MFA on every access, some might allow cached MFA, some might only send notifications, and some might require a second-person sign-off.
  3. How would this work with scopes granted to roles, for example repository roles?
jonasfj commented 7 years ago

Will the proxy form work for any method?

Yes,

I suspect we would want to have configurable controls for different scopes here.

I suggest we don't. For important scopes we care about we want the most possible protection. We can keep making more classes of scopes, this is just going to cause us pain.

How would this work with scopes granted to roles, for example repository roles?

It would not. Or well, it wouldn't work unless a task then deliberately calls the sudo service or proxies through the sudo service, in which case you would get a push notification.


Alternative scope format. It's possible that taskcluster-sudo requires scopes on the following format as proposed initially in this issue: A) sudo:<email>:<scope> Or we could do something like: B) sudo:<scope> and assume:mozilla-user:<email>

If we use (A) then sudo-scopes can't be given by role, and passing it to another clientId won't work. In fact you can't give someone else a sudo-scope without having sudo:* which is a bad scope to give people (for obvious reasons, if not see initial discussion).

Hence, it might be smart to do (B), in which case I have pass a sudo scope to another role or clientId without doing the sudo-step, but then I don't need sudo:* to do so... downside is that an attacker can change the MFA token to be used without doing the sudoing-up, but transferring the scope to another clientId that has assume:mozilla-user:<email> for a different email.

hmm, in short neither of these is ideal. (B) is perhaps the best, but it's less secure.

Another option: C) sudo:email:<email>:<scope> and sudo:scope:<email>:<scope> That is to require both scopes for <scope>. That way we can give sudo:email:* to anyone who should be allowed to grant sudo scopes. Hmm, this is not fully thought through yet.

jonasfj commented 7 years ago

okay, from my previous comment it looks a lot like this won't work...

@djmitche and I are playing with ideas for a different proposal.

djmitche commented 7 years ago

Summarizing what we came up with, that might work, but we think it's not actually needed.

The login.oidcCredentials call would take optional sudo and totpToken arguments. If those are present and validate for the user_id corresponding to the supplied access_token, then the sudo scopes are evaluated if they match a policy configured in the login service.

jonasfj commented 7 years ago

I just recognized another thing that's wrong with this design... The format sudo:<scope> is rather bad...

A better design would be to let the scope sudo:<roleId> be transformed to assume:sudo:<roleId> by 2FA confirmation.

This way, when you search for a scope that is sudo protected in the scope-explorer, it'll show up under the sudo role it was given to. And finding out who can sudo into that role is then a matter of looking up the sudo:<roleId> scope.

Just saying, that letting sudo point to a role, rather than a scope, is probably a trail of thought to consider. Since it would make generic scope reasoning tools more useful.

djmitche commented 7 years ago

Why not sudo:<roleId> -> assume:<roleId>?

jonasfj commented 7 years ago

Because then sudo would need assume:*

Also this way when you lookup in scope explorer it'll show sudo in the list of roles that has the scope...

On 30 Oct 2017 07.44, "Dustin J. Mitchell" notifications@github.com wrote:

Why not sudo: -> assume:?

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/taskcluster/taskcluster-rfcs/issues/98#issuecomment-340465991, or mute the thread https://github.com/notifications/unsubscribe-auth/AAJI5JtGb40_MWndWCIojngmb3J2XXpLks5sxeDngaJpZM4QFeRl .