Authorization Architecture
Ingress involves the following components:
- Clouddriver.
- Front50 to query apps and service accounts.
- Gate signs users in with externally provided roles (e.g. OpenID Connect, SAML). These roles are then merged with provider sourced roles (if any), tagged with the source, and cached in Redis.
- Igor gets the list of build systems and roles required to access them.
Egress involves the following components:
- Clouddriver gets known accounts.
- Front50 gets known apps.
Fiat can be scaled by adding replicas. fiat.writeMode.enabled
dictates if the Fiat instance will try to sync roles. Fiat instances coordinate around locks (in Redis) to ensure that only one instance synchronizes roles at a time.
- a user ID (= a real one or <code>__unrestricted_user__</code> )
- Accounts permission = list of { name + cloudProvider + Permissions)
- Apps permissions = list of { name + permissions }
- Service accounts = list of service accounts the user belongs to
- Roles: list of roles the user has
- build services: list of build services the user has access to
Sync
Every 30 seconds, Fiat checks if it needs to sync roles. Every 10 minutes (by default), it will sync user ↔ roles. It may mean querying the provider for all the roles of all the users that Fiat knows about (= are cached in Redis).
Unrestricted User
- At any point, the unrestricted virtual user should have
UserPermission
in the permission repository (Redis for now) for all accounts, apps, service accounts, build services that have not been restricted (no permission specified). - The unrestricted user’s permissions is updated on every sync to account for permission changes and for new apps, accounts, etc.
- When returning a user’s permission is returned, it is merged with the unrestricted user. By having the account, app, service account, build service in the
UserPermission
of the user, it is known and the default access for unrestricted app/.. should apply.
Note:
During the sync while reading apps permissions from Front50 (in the app definition), Fiat checks if the app has roles defined for EXECUTE
. If not, Fiat copies the list of roles defined on the app for fiat.executeFallback
(which can be READ
or WRITE
) to the EXECUTE
permission list. This is done to ensure that at least some roles can execute a pipeline as that role has been introduced recently.
A service checks if user userId
has permission P
on resource R
of type T
(apps, account, build service). The following steps take place in the service calling Fiat. The response is detailed thereafter.
- Check the local cache first (which expires after
services.fiat.cache.expiresAfterWriteSeconds
and defaults to 20) - If the request fails, retry will back off. If it keeps failing:
- if
services.fiat.legacyFallback = true
:- If
T == account
: if the account has at least oneWRITE
permission, Yes (TO BE CONFIRMED), otherwise No - if
T == app
: Yes (note: viaallowAccessToUnknownApplications
) - if
T == buildService
: Yes
- If
- if
- if the request succeeds, permissions are returned by Fiat:
- If the user is admin → Yes
- If
T = account
: check that the permission has been returned by Fiat (= permissionP
is found forR
in mapT
) - if
T = app
:- if the user has access to the account with the right permission → Yes
- Else if the user does not have any permission set for this app and
permission.allowAccessToUnknownApplications == true
→ Yes - Else reject
- if
T = buildService
: check that the user has the right permission for the build service
Permissions returned by Fiat
Fiat can be asked to return all permissions to a user U
. These permissions are stored in Redis under the following keys spinnaker:fiat:permissions:<user ID>:<resource type>
and store a hash with the following info:
key
= name of the resource (e.g. name of the app)value
={"name": <name repeated>, "permissions": { <Permission>: [list of roles] } }
Example
Fiat will look up permissions in Redis:
- If the user is not known to Fiat (edge case, should not happen under normal circumstances but will happen if you try to
curl
to Fiat directly) or if there was a communication issue/bug with Redis- if : treat as the unrestricted user
- else 404 → the request has failed
- Merge the permission of the user with the unrestricted user’s permission
- As a reminder: the unrestricted user only has permissions for resources not constrained.
- Set
permission.isAdmin
to true if the user has been defined as an admin- This is true when the user has a role defined in
fiat.admin.roles
- This is true when the user has a role defined in