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 one WRITE permission, Yes (TO BE CONFIRMED), otherwise No
        • if T == app: Yes (note: via allowAccessToUnknownApplications)
        • if T == buildService: Yes
    • 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 (= permission P is found for R in map T)
      • 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

    Summary of available options in a local config file