From a04edab75c37a70003bf7dc06c5bdf0fea4222c7 Mon Sep 17 00:00:00 2001 From: mywrites Date: Fri, 16 May 2025 02:11:37 -0400 Subject: [PATCH] Update login-policy.md Updated Login Policy --- docs/concepts/policy/login-policy.md | 166 ++++++++++++++++----------- 1 file changed, 96 insertions(+), 70 deletions(-) diff --git a/docs/concepts/policy/login-policy.md b/docs/concepts/policy/login-policy.md index 4a8c8cfa3..3adb50020 100644 --- a/docs/concepts/policy/login-policy.md +++ b/docs/concepts/policy/login-policy.md @@ -2,38 +2,46 @@ ## Purpose -!!! info - Please note, we currently don't support importing rego.v1 +

Do not use import modules, such as import rego.v1. Instead, you must write policies using standard Rego syntax.
+ The Open Policy Agent (OPA) will then read and evaluate these policies to make decisions.

-Login policies can allow users to log in to the account, and optionally give them admin privileges, too. Unlike all other policy types, login policies are global and can't be attached to individual stacks. They take effect immediately once they're created and affect all future login attempts. +Login policies dictate who can access a Spacelift account, how access is granted, and the extent of this access (which might include admin privileges). Login policies take effect immediately and impact subsequent login attempts. Unlike other policy types, login policies are global; you cannot attach them to individual stacks. -!!! info - API Keys are essentially virtual users so they get evaluated with login policy except for ones in the "root" space set with admin key. +API keys, similar to virtual users, function according to login policies unless you create them in the "root" space with an admin key. -!!! warning - Login policies don't affect GitHub organization or [SSO](../../integrations/single-sign-on/README.md) admins and private account owners who always get admin access to their respective Spacelift accounts. This is to avoid a situation where a bad login policy locks out everyone from the account. +Login policies **do not apply** to: +* GitHub organizations or [SSO](../../integrations/single-sign-on/README.md) admins +* Private account owners + * Private account owners have admin access to their respective Spacelift accounts. Admin access prevents an all-user account lockout if, for example, there is a poorly written policy. -!!! danger - Any change made (create, update or delete) to a login policy will invalidate all active sessions, except the session making the change. +!!! warning + When you create, update, or delete a login policy, all active sessions are logged out with the exception of the session where you initiated the create/update/delete changes. -A login policy can define the following types of boolean rules: +A login policy can define the true-false, allow-deny (Boolean) rules: -- **allow** - allows the user to log in as a _non-admin_; -- **admin** - allows the user to log in as an account-wide _admin_ - note that you don't need to explicitly **allow** admin users; -- **deny** - denies login attempt, no matter the result of other (**allow** and **admin**) rules; -- **deny_admin** - denies the current user **admin** access to the stack, no matter the outcome of other rules; -- **space_admin/space_write/space_read** - manages access levels to spaces. More on that in [Spaces Access Control](../spaces/access-control.md); +- **allow** – permits the user to log in as a _non-admin_. +- **admin** – allows the user to log in as an _account-wide admin_; you do not need to explicitly **allow** admin users. +- **deny** – prevents login access despite the result of other (**allow** and **admin**) rules. +- **deny_admin** – denies the current user **admin** access to the stack despite the outcome of other rules. +- **space_admin/space_write/space_read** – manages access levels to _spaces_. For additional information, read: [Spaces – Access Control](../spaces/access-control.md). -If no rules match, the default action will be to deny a login attempt. +If there is not a policy that applies to a particular login action, the system denies login access by default. -Note that giving folks admin access is a big thing. Admins can do pretty much everything in Spacelift - create and delete stacks, trigger runs or tasks, create, delete and attach contexts and policies, etc. Instead, you can give users limited admin access using the **space_admin** rule. +Granting admin rights is a significant decision, as these users will have full control in Spacelift. They will be able to perform a range of tasks to include creating and deleting stacks, triggering runs or tasks, and creating, deleting, and attaching contexts and policies. If this control is not necessary, consider applying the **space_admin** rule because it provides a more limited and restricted admin access. !!! danger - In practice, any time you define an **allow** or **admin** rule, you should probably think of restricting access using a **deny** rule, too. Please see the examples below to get a better feeling for it. + When you define an **allow** or **admin** rule, consider adding a **deny** rule to restrict access where necessary. For additional information, read: **Examples** (below) + +## Data Input + +Spacelift collects data during each login attempt. This data includes: +* The user’s IP address and login attempt time. +* User information, such as the username and any associated team or group name. +* The user who created the session and from what location. +* Account _spaces_ and their descriptions. -## Data input +Below is the data input schema that each policy request receives: -This is the schema of the data input that each policy request will receive: ```json { @@ -58,49 +66,61 @@ This is the schema of the data input that each policy request will receive: } ``` -!!! tip - OPA string comparisons are case-sensitive so make sure to use the proper case, as defined in your Identity Provider when comparing values. +Open Policy Agent (OPA) is the engine that reads or evaluates policies to make yes/no (allow/deny) decisions. Without OPA, this evaluation cannot occur. OPA string comparisons are case-sensitive, requiring that you use the exact case that your identity provider (GitHub, for example) defines when comparing these values. For guidance, access [Sampling Policy Inputs](./README.md#sampling-policy-inputs) to view the exact values that the identity provider (IdP) passes. - It might be helpful to [enable sampling on the policy](./README.md#sampling-policy-inputs) to see the exact values passed by the Identity Provider. +Two fields in the _session object_, data passed to a login policy, may require further explanation: _member_ and _team_. -Two fields in the session object may require further explanation: _member_ and _teams_. +### Account Membership -### Account membership +When you initially log in to Spacelift, our team uses GitHub as your IdP – retrieving details such as your login username. Each Spacelift account is linked to one specific GitHub account. During log in, Spacelift checks to determine if you are a member of that linked GitHub account. -When you first log in to Spacelift, we use GitHub as the identity provider and thus we're able to get some of your details from there with username (login) being the most important one. However, each Spacelift account is linked to one and only one GitHub account. Thus, when you log in to a Spacelift account, we're checking if you're a member of that GitHub account. +**GitHub Organization Account** +* Spacelift checks to determine if you are a member of that GitHub org. +* If yes, member: true +* If no, member: false -When that GitHub account is an organization, we can explicitly query for your organization membership. If you're a member, you get the member field set to _true_. If you're not - it's _false_. For private accounts it's different - they can only have one member, so the check is even simpler - if your login is the same as the name of the linked GitHub account, you get the member field set to _true_. If it isn't - it's _false_. +**Private GitHub Account** +* These accounts can only have one member. +* If your GitHub username matches the account name, member: true +* If no match, member: false -When using Single Sign-On with SAML, every successful login attempt will necessarily require that the _member_ field is set to _true -_ if the linked IdP could verify you, you **must** be a member. +**Security Assertion Markup Language (SAML)/SSO** +* If you are using SAML SSO, a successful log in indicates that the IdP verified the user. +* **member** is always true when valid login credentials entered. -!!! warning - Watch this field very closely - it may be _very_ useful for your **deny** rules. +The **member** field helps Spacelift determine account access, based on login policies. + +**This field can be extremely useful for your _deny_ rules.** #### Teams -When using the default identity provider (GitHub), Teams are only queried for organization accounts - if you're a member of the GitHub organization linked to a Spacelift account, Spacelift will query GitHub API for the full list of teams you're a member of. This list will be available in the `session.teams` field. For private accounts and non-members, this list will be empty. +Teams are only queried for organization accounts when using the default GitHub IdP. If you are a member of the GitHub organization linked to a Spacelift account, Spacelift will query the GitHub API for the full list of teams that include you as a member. This list will be available in the **session.teams** field. For private accounts and non-members, this list will be empty. -Note that Spacelift treats GitHub team membership as transitive - for example let's assume Charlie is a member of the _Badass_ team, which is a child of team _Awesome_. Charlie's list of teams includes both _Awesome_ and _Badass_, even though he's not a **direct** member of the team _Awesome_. +Spacelift treats GitHub team membership as transitive such that a _child_ team is part of a _parent_ team. For example, Charlie is a member of team _Brother_, which is a child of team _Family_. Charlie's list of teams includes both _Brother_ and _Family_, even though he is not a direct member of team _Family_. -For Single Sign-On, the list of teams is pretty much arbitrary and depends on how the SAML assertion attribute is mapped to your user record on the IdP end. Please see the [relevant article](../../integrations/single-sign-on/README.md#setting-up-the-integration) for more details. +For SSO, the list of teams is arbitrary and depends on how the SAML assertion attribute is mapped to your user record specific to the IdP. For additional information, read [SAML Setup Guides](../../integrations/single-sign-on/README.md#setting-up-the-integration). -!!! warning - Watch this field very closely - it may be _very_ useful for your **allow** and **admin** rules. +**The _team_ field can be extremely useful for your _allow_ and _admin_ rules.** ## Examples -!!! tip - We maintain a [library of example policies](https://github.com/spacelift-io/spacelift-policies-example-library/tree/main/examples/login){: rel="nofollow"} that are ready to use or that you could tweak to meet your specific needs. +Our team maintains a [library of example policies](https://github.com/spacelift-io/spacelift-policies-example-library/tree/main/examples/login){: rel="nofollow"} that you can use _as-is_ or customize to meet your needs. If you cannot find what you need below or in the library, contact [Support](../../product/support/README.md#contact-support). This team will create a policy per your specifications. - If you cannot find what you are looking for below or in the library, please reach out to [our support](../../product/support/README.md#contact-support) and we will craft a policy to do exactly what you need. +!!! tip + We recommend a single login policy, as combining policies can yield unexpected results when you merge their decisions. - We recommend having only one login policy as what sounds reasonable within a policy may not yield the expected results when the decisions are merged. +Below are use cases for login policies: +1. Managing access levels within an organization +1. Granting access to unauthorized org users +1. Restricting access to specific circumstances +1. Granting limited admin access +1. Rewriting teams -There are three possible use cases for login policies - granting access to folks in your org who would otherwise not have it, managing access for external contributors or restricting access to specific circumstances. Let's look into these use cases one by one. +Let's review each use case. -### Managing access levels within an organization +### Managing Access Levels within an Organization -In high-security environments where the principle of least access is applied, it's quite possible that nobody on the infra team gets admin access to _GitHub_. Still, it would be pretty useful for those people to be in charge of your _Spacelift_ account. Let's create a login policy that will allow every member of the DevOps team to get admin access, and everyone in Engineering to get regular access - we'll give them more granular access to individual stacks later using [stack access policies](stack-access-policy.md). While at it, let's also explicitly deny access to all non-members just to be on the safe side. +In high-security environments where the principle of least access is applied, it is possible that no one on the Infrastructure team gets admin access to GitHub. Still, it would be useful for these team members to oversee your Spacelift account. Let's create a login policy that grants **admin access** to the DevOps team, allows **regular access** for the Engineering team, and **denies access** to anyone who is not a member of this Spacelift account. We will give these non-members more granular access to individual stacks later using [Stack Access Policies](stack-access-policy.md). ```opa package spacelift @@ -114,20 +134,23 @@ allow { teams[_] == "Engineering" } deny { not input.session.member } ``` -Here's a [minimal example to play with](https://play.openpolicyagent.org/p/LpzDekpDOU){: rel="nofollow"}. +You can experiment with this [example](https://play.openpolicyagent.org/p/LpzDekpDOU){: rel="nofollow"}. -This is also important for Single Sign-On integrations: only the [integration creator](../../integrations/single-sign-on/README.md#setting-up-the-integration) gets administrative permissions by default, so all other administrators must be granted their access using a login policy. +Managing access levels within an organization is also important for SSO integrations. Only the [integration creator](../../integrations/single-sign-on/README.md#setting-up-the-integration) gets administrative permissions by default. All other administrators must be granted their access using a login policy. -### Granting access to external contributors +### Granting Access to Unauthorized Org Users -!!! danger - This feature is not available when using [Single Sign-On](../../integrations/single-sign-on/README.md) - your identity provider **must** be able to successfully validate each user trying to log in to Spacelift. +**This feature is not available when using [SSO](../../integrations/single-sign-on/README.md). Your IdP _must_ be able to successfully validate each user trying to log in to Spacelift.** -Sometimes you have folks (short-term consultants, most likely) who are not members of your organization but need access to your Spacelift account - either as regular members or perhaps even as admins. There's also the situation where a bunch of friends is working on a hobby project in a personal GitHub account and they could use access to Spacelift. Here are examples of a policy that allows a bunch of whitelisted folks to get regular access and one to get admin privileges: +On occasion, you might need to grant Spacelift account access to people who are not part of your GitHub organization, such as short-term consultants. These users might need **regular access** or even **admin access**. Similarly, you might have friends working on a hobby project in a personal GitHub account that could benefit from Spacelift access. + +Below are example policies: +* One that **whitelists specific users** for **regular access**. +* One that gives **admin access** to a single user. === "GitHub" - This example uses GitHub usernames to grant access to Spacelift. +This example uses GitHub usernames to grant access to Spacelift. More specifically, this example grants **admin access** to _alice_, **regular access** to _bob_, _charlie_, and _danny_, and denies access to all others. ```opa package spacelift @@ -140,11 +163,11 @@ Sometimes you have folks (short-term consultants, most likely) who are not membe allow { allowed[login] } deny { not admins[login]; not allowed[login] } ``` - - Here's a [minimal example to play with](https://play.openpolicyagent.org/p/ZsOJayumFw){: rel="nofollow"}. +You can experiment with this [example](https://play.openpolicyagent.org/p/ZsOJayumFw){: rel="nofollow"}. === "Google" - This example uses email addresses managed by Google to grant access to Spacelift. + +This example uses email addresses that Google manages to grant access to Spacelift. ```rego package spacelift @@ -158,13 +181,14 @@ Sometimes you have folks (short-term consultants, most likely) who are not membe ``` !!! warning - Note that granting access to individuals is less safe than granting access to teams and restricting access to account members. In the latter case, when they lose access to your GitHub org, they automatically lose access to Spacelift. But when whitelisting individuals and not restricting access to members only, you'll need to remember to explicitly remove them from your Spacelift login policy, too. + Granting access to individuals is less secure than both granting access to teams _and_ restricting access to account members. In the latter case, when they lose access to your GitHub org, they automatically lose access to Spacelift. However, if you whitelist individuals and do not restrict access to members, you must remove them from your Spacelift login policy when their access changes. + +### Restricting Access to Specific Circumstances -### Restricting access to specific circumstances +Stable and secure infrastructure is crucial to your business continuity. Changes to your infrastructure carry some risks, so you may want to restrict access to it. The example below is extreme, but it shows a comprehensive policy where you restrict Spacelift access to users logging in from the office IP solely to business hours. This example policy denies login access on weekends, before 9:00AM or after 5:00PM PT/LA, and if the user’s IP address is not within a specific range (which is specific to the office location). You may want to use elements of this policy to create your own version, or you might prefer to maintain this policy to support everyone's work-life balance. -Stable and secure infrastructure is crucial to your business continuity. And all changes to your infrastructure carry some risk, so you may want to somehow restrict access to it. The example below is pretty extreme but it shows a very comprehensive policy where you restrict Spacelift access to users logging in from the office IP during business hours. You may want to use elements of this policy to create your own - less draconian - version, or keep it this way to support everyone's work-life balance. +This example only defines the **deny** rule. You might want to add **allow** and **admin** rules, either in this policy or in a separate one. -Note that this example only defines deny rules so you'll likely want to add some allow and admin rules, too - either in this policy or in a separate one. ```opa package spacelift @@ -181,19 +205,20 @@ deny { clock[0] > 17 } deny { not net.cidr_contains("12.34.56.0/24", ip) } ``` -There's a lot to digest here, so a [playground example](https://play.openpolicyagent.org/p/4J3Nz6pYgC){: rel="nofollow"} may be helpful. +The [playground example](https://play.openpolicyagent.org/p/4J3Nz6pYgC){: rel="nofollow"} might be helpful. -### Granting limited admin access +### Granting Limited Admin Access -Very often you'd like to give a user admin access limited to a certain set of resources, so that they can manage them without having access to all other resources in that account. You can find more on that use case in [Spaces](../spaces/README.md). +You might need to grant users admin access that is limited to specific resources, allowing them to manage only those resources and nothing else in the account. For additional information, read: [Spaces](../spaces/README.md). -### Rewriting teams +### Rewriting Teams -In addition to boolean rules regulating access to your Spacelift account, the login policy exposes the **team** rule, which allows one to dynamically rewrite the list of teams received from the identity provider. This operation allows one to define Spacelift roles independent of the identity provider. To illustrate this use case, let's imagine you want to define a `Superwriter` role for someone who's: +In addition to Boolean rules regulating access to your Spacelift account, the login policy exposes the **team** rule. This rule allows you to dynamically rewrite the list of teams received from the IdP. This operation allows you to define Spacelift roles independent of the IdP. To demonstrate this use case, imagine you want to define a Superwriter role that meets this criteria: + +* User logs in from an office VPN. +* User is a member of the DevOps team, as defined by your IdP. +* User is not a member of the Contractors team, as defined by your IdP. -- logging in from an office VPN; -- is a member of the DevOps team, as defined by your IdP; -- is not a member of the Contractors team, as defined by your IdP; ```opa package spacelift @@ -209,9 +234,10 @@ devops { input.session.teams[_] == "DevOps" } office_vpn { net.cidr_contains("12.34.56.0/24", input.request.remote_ip) } ``` -What's important here is that the **team** rule overwrites the original list of teams, meaning that if it evaluates to a non-empty collection, it will **replace** the original list of teams in the session. In the above example, the `Superwriter` role will become the only team for the evaluated user session. +The **team** rule overwrites the original list of teams. If it evaluates to a non-empty collection, it will **replace** the original list of teams in the session. In the above example, the Superwriter role will become the only team for the evaluated user session. + +If you do not prefer the above example but, instead, want to retain the original list of teams, you can modify the above example as follows: -If the above is not what you want, and you still would like to retain the original list of teams, you can modify the above example the following way: ```opa package spacelift @@ -233,12 +259,12 @@ office_vpn { net.cidr_contains("12.34.56.0/24", input.request.remote_ip) } A playground example of the above is available [here](https://play.openpolicyagent.org/p/dM8P83sk4l){: rel="nofollow"}. -!!! hint - Because the user session is updated, the rewritten teams are available in the data input provided to the policy types that receive user information. For example, the rewritten teams can be used in [Access policies](./stack-access-policy.md). +Since the user session is updated, the modified team list is included in the input data for any policy that uses user information. +For example, [Access Policies](./stack-access-policy.md) can use these updated team values. -## Default login policy +## Default Login Policy -If no login policies are defined on the account, Spacelift behaves as if it had this policy: +If the account does not have any defined login policies, the system will allow access to account members: ```opa package spacelift