Skip to content

Commit

Permalink
WIP - allow org-admins to modify organizations
Browse files Browse the repository at this point in the history
In addition to give org-admins permissions to CRUD organizations,
this removes the requirement that in order to modify an organization,
the actor must be a member of the organization.

However, they must still have appropriate permissions to perform any CRUD action
related to an organization.

This supports the multi-tenancy case where a customer has many organizations to manage
but does not necessarily need to admins to be a part of those organizations. The primary
use case is SaaS offering , in which customers have full control over a chef
server installation but do not have local/chef-server-ctl access, and
must keep the pivotal key locked down for security purposes.

This functionality is already available using the pivotal/superuser key, but the
pivotal key should not be widely distributed. This functionality was also originally
intended to be available to org-admins but the completion of that work was never
prioritized.

Signed-off-by: Marc A. Paradise <marc.paradise@progress.com>
  • Loading branch information
marcparadise committed Oct 17, 2024
1 parent bfcc20d commit 5fc71b4
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def bootstrap
@superuser_authz_id = create_actor_in_authz(bifrost_superuser_id)
users_authz_id = create_container_in_authz(superuser_authz_id)
orgs_authz_id = create_container_in_authz(superuser_authz_id)
create_server_admins_global_group_in_bifrost(users_authz_id)
create_server_admins_global_group_in_bifrost(orgs_authz_id, users_authz_id)

# put pivotal in server-admins global group
insert_authz_actor_into_group(server_admins_authz_id, superuser_authz_id)
Expand All @@ -61,13 +61,19 @@ def bootstrap
private

# Create and set up permissions for the server admins group.
def create_server_admins_global_group_in_bifrost(users_authz_id)
def create_server_admins_global_group_in_bifrost(orgs_container_authz_id, users_container_authz_id)
@server_admins_authz_id = create_group_in_authz(bifrost_superuser_id)
%w(create read update delete).each do |permission|
# grant server admins group permission on the users container,
# as the erchef superuser.
grant_authz_object_permission(permission, 'groups', 'containers', users_authz_id,
grant_authz_object_permission(permission, 'groups', 'containers', users_container_authz_id,
server_admins_authz_id, superuser_authz_id)

# grant server admins group permission on the organizations container,
# as the erchef superuser.
grant_authz_object_permission(permission, 'groups', 'containers', orgs_container_authz_id,
server_admins_authz_id, superuser_authz_id)

# grant superuser actor permissions on the server admin group,
# as the bifrost superuser
grant_authz_object_permission(permission, 'actors', 'groups', server_admins_authz_id,
Expand Down
20 changes: 19 additions & 1 deletion src/oc_erchef/apps/oc_chef_wm/src/oc_chef_wm_base.erl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
content_types_provided/2,
finish_request/2,
forbidden/2,
is_authorized_no_membership/2, %% verify request signature only
is_authorized_no_membership/3, %% verify request signature only with custom extractor
is_authorized/2, %% verify request signature and org membership if appropriate
is_authorized/3, %% verify request signature and org membership if appropriate, with custom extractor fun
malformed_request/2, %% verify request headers and size requirements, call module verify_request
Expand Down Expand Up @@ -441,7 +443,23 @@ is_authorized(Req, State, Extractor) ->
end;
{false, ReqOther, StateOther} ->
%% FIXME: the supported version is determined by the chef_authn application
%% also, see: https://wiki.corp.chef.io/display/CORP/RFC+Authentication+Version+Negotiation
{"X-Ops-Sign version=\"1.0\" version=\"1.1\"", ReqOther, StateOther};
{{halt, _Code}, _Req, _State} = Halt -> Halt
end.

is_authorized_no_membership(Req, State) ->
is_authorized_no_membership(Req, State, fun authorization_data_extractor/3).

%% Verify request signature but do not verify org membership. Modules
%% can invoke this directly instead of mixing in the default is_verified/2
%% if they rely only on permissions for authorization and not org membership.
-spec is_authorized_no_membership(wm_req(), #base_state{}, extractor()) -> any().
is_authorized_no_membership(Req, State, Extractor) ->
case verify_request_signature(Req, State, Extractor) of
{true, _, _} = Result ->
Result;
{false, ReqOther, StateOther} ->
%% FIXME: the supported version is determined by the chef_authn application
{"X-Ops-Sign version=\"1.0\" version=\"1.1\"", ReqOther, StateOther};
{{halt, _Code}, _Req, _State} = Halt -> Halt
end.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
malformed_request/2,
ping/2,
forbidden/2,
is_authorized/2,
service_available/2]}]).

-export([allowed_methods/2,
Expand All @@ -28,6 +27,7 @@
-export([auth_info/2,
init/1,
init_resource_state/1,
is_authorized/2,
malformed_request_message/3,
request_type/0,
validate_request/3]).
Expand All @@ -47,6 +47,13 @@ request_type() ->
allowed_methods(Req, State) ->
{['GET', 'PUT', 'DELETE'], Req, State}.


is_authorized(Req, State) ->
%% To support management of multi-tenant installations by users w/ appropriate permissions
%% who are not the superuser, the user does not need to be a member of the organization
%% to operate on the organization.
oc_chef_wm_base:is_authorized_no_membership(Req, State).

-spec validate_request(chef_wm:http_verb(), wm_req(), chef_wm:base_state()) ->
{wm_req(), chef_wm:base_state()}.
validate_request(Method, Req, State = #base_state{organization_guid = OrgId}) when Method == 'GET';
Expand Down
25 changes: 16 additions & 9 deletions src/oc_erchef/apps/oc_chef_wm/src/oc_chef_wm_org_associations.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
malformed_request/2,
ping/2,
forbidden/2,
is_authorized/2,
service_available/2]}]).

-export([allowed_methods/2,
Expand All @@ -43,6 +42,7 @@
-export([auth_info/2,
init/1,
init_resource_state/1,
is_authorized/2,
malformed_request_message/3,
create_path/2,
request_type/0,
Expand All @@ -67,6 +67,13 @@ allowed_methods(Req, State) ->
end,
{Allowed, Req, State}.

is_authorized(Req, State) ->
% To support management of multi-tenant installations by users w/ appropriate permissions
% who are not superusers, a user with appropriate ACLs does not need to be a member of
% the organization to operate on the organization - that includes the operations of this endpoint,
% CRD against org members.
chef_wm_base:is_authorized_no_membership(Req, State).

-spec validate_request(chef_wm:http_verb(), wm_req(), chef_wm:base_state()) ->
{wm_req(), chef_wm:base_state()}.
validate_request('POST', Req, #base_state{chef_db_context = DbContext} = State) ->
Expand Down Expand Up @@ -117,14 +124,14 @@ auth_info(Req, #base_state{organization_name = OrgName,
auth_info(Req, #base_state{requestor_id = RequestorAuthzId,
organization_authz_id = OrgAuthzId,
resource_state = #association_state{user = User} } = State) ->
case wrq:method(Req) of
'POST' ->
% Only the superuser can force-create an org-user association
{superuser_only, Req, State};
Method ->
{auth_type_for_method(Method, User, OrgAuthzId, RequestorAuthzId), Req, State}
end.

{auth_type_for_method(wrq:method(req), User, OrgAuthzId, RequestorAuthzId), Req, State}.

auth_type_for_method('POST', #chef_user{ authz_id = UserAuthzId }, OrgAuthzId, RequestorAuthzId) ->
%% requestor must have:
%% - update permissions in org (should not require membership in org)
%% - read permissions on user
%% - update permissions on org_user_association
[{object, OrgAuthzId, update}, {actor, UserAuthzId, read}, {object, OrgAuthzId, update}];
auth_type_for_method('DELETE', #chef_user{ authz_id = UserAuthzId }, _OrgAuthzId, UserAuthzId) ->
%% permissions-wise, user can always disassociate his or her own org association
%% though we'll have additional safety checks below as well.
Expand Down
14 changes: 10 additions & 4 deletions src/oc_erchef/habitat/config/chef_server_data_bootstrap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def self.with_connection(database = 'template1', opts = {})
postgres['db_superuser']="{{cfg.sql_user}}"
postgres['db_superuser_password']="{{cfg.sql_password}}"
{{/if}}
connection = nil
# Some callers expect failure - this gives the option to suppress
Expand Down Expand Up @@ -118,7 +118,7 @@ def bootstrap

users_authz_id = create_container_in_authz(superuser_authz_id)
orgs_authz_id = create_container_in_authz(superuser_authz_id)
create_server_admins_global_group_in_bifrost(users_authz_id)
create_server_admins_global_group_in_bifrost(orgs_authz_id, users_authz_id)

# put pivotal in server-admins global group
insert_authz_actor_into_group(server_admins_authz_id, superuser_authz_id)
Expand All @@ -142,13 +142,19 @@ def bootstrap
private

# Create and set up permissions for the server admins group.
def create_server_admins_global_group_in_bifrost(users_authz_id)
def create_server_admins_global_group_in_bifrost(orgs_container_authz_id, users_container_authz_id)
@server_admins_authz_id = create_group_in_authz(bifrost_superuser_id)
%w{create read update delete}.each do |permission|
# grant server admins group permission on the users container,
# as the erchef superuser.
grant_authz_object_permission(permission, "groups", "containers", users_authz_id,
server_admins_authz_id, superuser_authz_id)

# grant server admins group permission on the organizations container,
# as the erchef superuser.
grant_authz_object_permission(permission, 'groups', 'containers', orgs_container_authz_id,
server_admins_authz_id, superuser_authz_id)

# grant superuser actor permissions on the server admin group,
# as the bifrost superuser
grant_authz_object_permission(permission, "actors", "groups", server_admins_authz_id,
Expand Down Expand Up @@ -381,7 +387,7 @@ def load_superuser_public_key()
@superuser_public_key = "DUMMY KEY FROM BOOTSTRAP"
{{/if}}
end
def load_bifrost()
bifrost={}
{{#if bind.oc_bifrost}}
Expand Down

0 comments on commit 5fc71b4

Please sign in to comment.