Skip to content

Commit

Permalink
Add ability to autoprovision service account rbac (#853)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljguarino authored Jan 11, 2023
1 parent 5e55877 commit d211799
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 1 deletion.
3 changes: 2 additions & 1 deletion apps/core/lib/core/schema/account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule Core.Schema.Account do
field :user_count, :integer, default: 0
field :cluster_count, :integer, default: 0
field :usage_updated, :boolean
field :sa_provisioned, :boolean

belongs_to :root_user, User
has_many :domain_mappings, DomainMapping, on_replace: :delete
Expand All @@ -39,7 +40,7 @@ defmodule Core.Schema.Account do
)
end

@valid ~w(name workos_connection_id)a
@valid ~w(name workos_connection_id sa_provisioned)a
@payment ~w(billing_customer_id)a

def changeset(model, attrs \\ %{}) do
Expand Down
1 change: 1 addition & 0 deletions apps/core/lib/core/schema/platform_plan.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ defmodule Core.Schema.PlatformPlan do
schema "platform_plans" do
field :name, :string
field :visible, :boolean, default: true
field :enterprise, :boolean
field :cost, :integer
field :period, Period
field :external_id, :string
Expand Down
49 changes: 49 additions & 0 deletions apps/core/lib/core/services/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ defmodule Core.Services.Accounts do

def get_group!(id), do: Core.Repo.get!(Group, id)

def get_group_by_name(aid, name), do: Core.Repo.get_by(Group, name: name, account_id: aid)

def get_invite!(id), do: Core.Repo.get_by!(Invite, secure_id: id)

def get_invite(id), do: Core.Repo.get_by(Invite, secure_id: id)
Expand Down Expand Up @@ -173,6 +175,11 @@ defmodule Core.Services.Accounts do
|> allow(user, :create)
|> when_ok(:insert)
end)
|> add_operation(:account, fn %{sa: %{account_id: aid}} -> {:ok, get_account!(aid)} end)
|> add_operation(:provision, fn
%{account: %Account{sa_provisioned: true} = account, sa: sa} -> setup_sa_group(account, sa)
%{account: account, sa: sa} -> provision_service_account_role(account, sa)
end)
|> add_operation(:validate, fn %{sa: %{account_id: aid} = sa} ->
case Core.Repo.preload(sa, impersonation_policy: :bindings) do
%{impersonation_policy: %{bindings: bindings}} -> validate_bindings(aid, bindings)
Expand All @@ -183,6 +190,48 @@ defmodule Core.Services.Accounts do
|> Users.notify(:create)
end

@sa_group "service-accounts"

defp provision_service_account_role(%Account{id: aid} = account, %User{} = sa) do
start_transaction()
|> add_operation(:group, fn _ -> setup_sa_group(account, sa) end)
|> add_operation(:role, fn %{group: %{group: g}} ->
%Role{account_id: aid}
|> Role.changeset(%{
name: @sa_group,
description: "permissions given to service accounts in your account",
repositories: ["*"],
permissions: %{install: true},
role_bindings: [%{group_id: g.id}]
})
|> Core.Repo.insert()
end)
|> add_operation(:account, fn _ ->
Account.changeset(account, %{sa_provisioned: true})
|> Core.Repo.update()
end)
|> execute()
end

defp setup_sa_group(%Account{id: aid}, %User{id: user_id}) do
start_transaction()
|> add_operation(:group, fn _ ->
case get_group_by_name(aid, @sa_group) do
%Group{} = g -> {:ok, g}
_ ->
%Group{account_id: aid}
|> Group.changeset(%{name: @sa_group, description: "group managed automatically for all service accounts"})
|> Core.Repo.insert()
end
end)
|> add_operation(:group_member, fn %{group: group} ->
%GroupMember{group_id: group.id}
|> GroupMember.changeset(%{user_id: user_id})
|> Core.Repo.insert()
end)
|> execute()
end

@doc """
Updates a service account
"""
Expand Down
2 changes: 2 additions & 0 deletions apps/core/lib/core/services/payments.ex
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ defmodule Core.Services.Payments do
{false, _, _, _} -> true
{_, true, _, _} -> false
{_, _, true, _} -> true
{_, _, _, %User{account: %Account{subscription: %PlatformSubscription{plan: %PlatformPlan{enterprise: true}}}}} -> true
{_, _, _, %User{account: %Account{subscription: %PlatformSubscription{plan: %PlatformPlan{features: %{^feature => true}}}}}} -> true
_ -> false
end
Expand Down Expand Up @@ -333,6 +334,7 @@ defmodule Core.Services.Payments do
name: "Enterprise",
period: :monthly,
visible: true,
enterprise: true,
features: %{vpn: true, user_management: true},
line_items: [
%{name: "User", dimension: :user, period: :monthly, cost: 4900}, # these costs are arbitrary, as won't be billed through stripe
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Core.Repo.Migrations.AddRoleProvisionedBool do
use Ecto.Migration

def change do
alter table(:accounts) do
add :sa_provisioned, :boolean
end

alter table(:platform_plans) do
add :enterprise, :boolean
end
end
end
8 changes: 8 additions & 0 deletions apps/core/priv/repo/seeds/012_enterprise_plan.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Botanist

seed do
enterprise = Core.Services.Payments.get_platform_plan_by_name!("Enterprise")

Ecto.Changeset.change(enterprise, %{enterprise: true})
|> Core.Repo.update!()
end
9 changes: 9 additions & 0 deletions apps/core/test/services/accounts_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ defmodule Core.Services.AccountsTest do

assert binding.group_id == group.id

%{groups: [group], group_role_bindings: [%{role: role}]} = Core.Services.Rbac.preload(srv_acct)

assert group.name == "service-accounts"
assert group.account_id == srv_acct.account_id
assert role.name == "service-accounts"
assert role.permissions.install

assert refetch(user.account).sa_provisioned

assert_receive {:event, %PubSub.UserCreated{item: ^srv_acct}}
end

Expand Down
7 changes: 7 additions & 0 deletions apps/core/test/services/payments_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,13 @@ defmodule Core.Services.PaymentsTest do
assert Payments.has_feature?(user, :user_management)
end

test "if a user's plan is enterprise, it get's any feature" do
account = insert(:account)
insert(:platform_subscription, account: account, plan: build(:platform_plan, enterprise: true, features: %{user_management: false}))
user = insert(:user, account: account)
assert Payments.has_feature?(user, :user_management)
end

test "if a user's account is grandfathered, then it returns true" do
account = insert(:account, grandfathered_until: Timex.now() |> Timex.shift(days: 1))
insert(:platform_subscription, account: account, plan: build(:platform_plan, features: %{user_management: false}))
Expand Down
1 change: 1 addition & 0 deletions apps/graphql/lib/graphql/schema/payments.ex
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ defmodule GraphQl.Schema.Payments do
field :visible, non_null(:boolean)
field :cost, non_null(:integer)
field :period, non_null(:payment_period)
field :enterprise, :boolean
field :features, :plan_features
field :line_items, list_of(:platform_plan_item)

Expand Down

0 comments on commit d211799

Please sign in to comment.