From 7befc4cbfd374df19acc1d8f17e18c5953a8ed95 Mon Sep 17 00:00:00 2001 From: Abdul Azeez Date: Mon, 10 Jan 2022 13:03:42 +0530 Subject: [PATCH] Abdul/infra project owner (#6512) * project owner wip Signed-off-by: Abdul-Az * project owner actions Signed-off-by: Abdul-Az * fix Signed-off-by: Abdul-Az * doc update Signed-off-by: Abdul-Az * pipeline fix Signed-off-by: Abdul-Az --- .../sql/80_infra_project_owner.up.sql | 45 +++ .../storage/postgres/migration/sql/README.md | 3 + dev-docs/infra-server-iam.md | 88 ++++++ .../iam/infra_project_owner_actions.spec.ts | 260 ++++++++++++++++++ 4 files changed, 396 insertions(+) create mode 100644 components/authz-service/storage/postgres/migration/sql/80_infra_project_owner.up.sql create mode 100644 e2e/cypress/integration/api/iam/infra_project_owner_actions.spec.ts diff --git a/components/authz-service/storage/postgres/migration/sql/80_infra_project_owner.up.sql b/components/authz-service/storage/postgres/migration/sql/80_infra_project_owner.up.sql new file mode 100644 index 00000000000..8736f8c0fab --- /dev/null +++ b/components/authz-service/storage/postgres/migration/sql/80_infra_project_owner.up.sql @@ -0,0 +1,45 @@ +BEGIN; + +UPDATE iam_roles + SET + actions = '{ + infra:*:list, + infra:*:get, + infra:infraServersOrgsRoles:create, + infra:infraServersOrgsRoles:update, + infra:infraServersOrgsRoles:delete, + infra:infraServersOrgsClient:create, + infra:infraServersOrgsClient:update, + infra:infraServersOrgsClient:delete, + infra:infraServersOrgsDataBags:create, + infra:infraServersOrgsDataBags:delete, + infra:infraServersOrgsDataBagsItem:create, + infra:infraServersOrgsDataBagsItem:update, + infra:infraServersOrgsDataBagsItem:delete, + infra:infraServersOrgsEnvironments:create, + infra:infraServersOrgsEnvironments:update, + infra:infraServersOrgsEnvironments:delete, + infra:infraServersOrgsNodes:update, + infra:infraServersOrgsNodes:delete, + infra:infraServersOrgsPolicyFiles:delete, + compliance:*, + event:*, + ingest:*, + secrets:*, + iam:projects:list, + iam:projects:get, + iam:projects:assign, + iam:policies:list, + iam:policies:get, + iam:policyMembers:*, + iam:teams:list, + iam:teams:get, + iam:teamUsers:*, + iam:users:get, + iam:users:list, + applications:* + }' + WHERE + id = 'project-owner'; + +COMMIT; \ No newline at end of file diff --git a/components/authz-service/storage/postgres/migration/sql/README.md b/components/authz-service/storage/postgres/migration/sql/README.md index c2802cd9e30..6fdc47c66f4 100644 --- a/components/authz-service/storage/postgres/migration/sql/README.md +++ b/components/authz-service/storage/postgres/migration/sql/README.md @@ -77,3 +77,6 @@ - [`75_update_roles_pols.up.sql`](75_update_roles_pols.up.sql) - [`76_compliance_roles_pols.up.sql`](76_compliance_roles_pols.up.sql) - [`77_update_infra_service_policies.up.sql`](77_update_infra_service_policies.up.sql) +- [`78_update_viewer_role_pols.up.sql`](78_update_viewer_role_pols.up.sql) +- [`79_infra_editor_update.up.sql`](79_infra_editor_update.up.sql) +- [`80_infra_project_owner.up.sql`](80_infra_project_owner.up.sql) \ No newline at end of file diff --git a/dev-docs/infra-server-iam.md b/dev-docs/infra-server-iam.md index 5207a30f437..0066bd4fb38 100644 --- a/dev-docs/infra-server-iam.md +++ b/dev-docs/infra-server-iam.md @@ -77,3 +77,91 @@ Specify the action to restrict user access to the specific action | Get Node | GET | infra:infraServersOrgsNodes:get | /api/v0/infra/servers/{server_id}/orgs/{org_id}/nodes| https://{{< example_fqdn "automate" >}}/api/v0/infra/servers/{server_id}/orgs/{org_id}/nodes | | Update Node | POST | infra:infraServersOrgsNodes:update | /api/v0/infra/servers/{server_id}/orgs/{org_id}/nodes| https://{{< example_fqdn "automate" >}}/api/v0/infra/servers/{server_id}/orgs/{org_id}/nodes | | Delete Node | DELETE | infra:infraServersOrgsNodes:delete | /api/v0/infra/servers/{server_id}/orgs/{org_id}/nodes/{name}| https://{{< example_fqdn "automate" >}}/api/v0/infra/servers/{server_id}/orgs/{org_id}/nodes/{name} | + + +## Three types of user policies automatically gets created with creation of every project + + +Infra Viewer Policy Actions + +``` + secrets:*:get, + secrets:*:list, + infra:*:get, + infra:*:list, + compliance:*:get, + compliance:*:list, + event:*:get, + event:*:list, + ingest:*:get, + ingest:*:list, + iam:projects:list, + iam:projects:get, + applications:*:get, + applications:*:list +``` + +Infra Editor Policy Actions + +``` + infra:*:list, + infra:*:get, + infra:infraServersOrgsRoles:create, + infra:infraServersOrgsRoles:update, + infra:infraServersOrgsClient:create, + infra:infraServersOrgsClient:update, + infra:infraServersOrgsDataBags:create, + infra:infraServersOrgsDataBagsItem:create, + infra:infraServersOrgsDataBagsItem:update, + infra:infraServersOrgsEnvironments:create, + infra:infraServersOrgsEnvironments:update, + infra:infraServersOrgsNodes:update, + compliance:*, + event:*, + ingest:*, + secrets:*, + iam:projects:list, + iam:projects:get, + iam:projects:assign, + applications:* +``` + +Infra Project Owner Policy Actions + +``` + infra:*:list, + infra:*:get, + infra:infraServersOrgsRoles:create, + infra:infraServersOrgsRoles:update, + infra:infraServersOrgsRoles:delete, + infra:infraServersOrgsClient:create, + infra:infraServersOrgsClient:update, + infra:infraServersOrgsClient:delete, + infra:infraServersOrgsDataBags:create, + infra:infraServersOrgsDataBags:delete, + infra:infraServersOrgsDataBagsItem:create, + infra:infraServersOrgsDataBagsItem:update, + infra:infraServersOrgsDataBagsItem:delete, + infra:infraServersOrgsEnvironments:create, + infra:infraServersOrgsEnvironments:update, + infra:infraServersOrgsEnvironments:delete, + infra:infraServersOrgsNodes:update, + infra:infraServersOrgsNodes:delete, + infra:infraServersOrgsPolicyFiles:delete, + compliance:*, + event:*, + ingest:*, + secrets:*, + iam:projects:list, + iam:projects:get, + iam:projects:assign, + iam:policies:list, + iam:policies:get, + iam:policyMembers:*, + iam:teams:list, + iam:teams:get, + iam:teamUsers:*, + iam:users:get, + iam:users:list, + applications:* +``` \ No newline at end of file diff --git a/e2e/cypress/integration/api/iam/infra_project_owner_actions.spec.ts b/e2e/cypress/integration/api/iam/infra_project_owner_actions.spec.ts new file mode 100644 index 00000000000..f4b50562750 --- /dev/null +++ b/e2e/cypress/integration/api/iam/infra_project_owner_actions.spec.ts @@ -0,0 +1,260 @@ +describe('Infra Project Owner Policy', () => { + let withInfraProjectOwnerActionToken = ''; + let withoutInfraProjectOwnerActionToken = ''; + + const cypressPrefix = 'infra-project-owner'; + const policyId1 = `${cypressPrefix}-pol-1-${Cypress.moment().format('MMDDYYhhmm')}`; + const policyId2 = `${cypressPrefix}-pol-2-${Cypress.moment().format('MMDDYYhhmm')}`; + const tokenId1 = `${cypressPrefix}-token-1-${Cypress.moment().format('MMDDYYhhmm')}`; + const tokenId2 = `${cypressPrefix}-token-2-${Cypress.moment().format('MMDDYYhhmm')}`; + const objectsToCleanUp = ['tokens', 'policies']; + + const allowInfraProjectOwnerPolicy = { + id: policyId1, + name: tokenId1, + projects: [], + members: [`token:${tokenId1}`], + statements: [ + { + effect: 'ALLOW', + actions: [ + 'infra:*:list', + 'infra:*:get', + 'infra:infraServersOrgsRoles:create', + 'infra:infraServersOrgsRoles:update', + 'infra:infraServersOrgsRoles:delete', + 'infra:infraServersOrgsClient:create', + 'infra:infraServersOrgsClient:update', + 'infra:infraServersOrgsClient:delete', + 'infra:infraServersOrgsDataBags:create', + 'infra:infraServersOrgsDataBags:delete', + 'infra:infraServersOrgsDataBagsItem:create', + 'infra:infraServersOrgsDataBagsItem:update', + 'infra:infraServersOrgsDataBagsItem:delete', + 'infra:infraServersOrgsEnvironments:create', + 'infra:infraServersOrgsEnvironments:update', + 'infra:infraServersOrgsEnvironments:delete', + 'infra:infraServersOrgsNodes:update', + 'infra:infraServersOrgsNodes:delete', + 'infra:infraServersOrgsPolicyFiles:delete', + 'compliance:*', + 'event:*', + 'ingest:*', + 'secrets:*', + 'iam:projects:list', + 'iam:projects:get', + 'iam:projects:assign', + 'iam:policies:list', + 'iam:policies:get', + 'iam:policyMembers:*', + 'iam:teams:list', + 'iam:teams:get', + 'iam:teamUsers:*', + 'iam:users:get', + 'iam:users:list', + 'applications:*' + ], + projects: ['*'] + }] + }; + + + const denyInfraProjectOwnerPolicy = { + id: policyId2, + name: tokenId2, + projects: [], + members: [`token:${tokenId2}`], + statements: [ + { + effect: 'DENY', + actions: [ + 'infra:*:list', + 'infra:*:get', + 'infra:infraServersOrgsRoles:create', + 'infra:infraServersOrgsRoles:update', + 'infra:infraServersOrgsRoles:delete', + 'infra:infraServersOrgsClient:create', + 'infra:infraServersOrgsClient:update', + 'infra:infraServersOrgsClient:delete', + 'infra:infraServersOrgsDataBags:create', + 'infra:infraServersOrgsDataBags:delete', + 'infra:infraServersOrgsDataBagsItem:create', + 'infra:infraServersOrgsDataBagsItem:update', + 'infra:infraServersOrgsDataBagsItem:delete', + 'infra:infraServersOrgsEnvironments:create', + 'infra:infraServersOrgsEnvironments:update', + 'infra:infraServersOrgsEnvironments:delete', + 'infra:infraServersOrgsNodes:update', + 'infra:infraServersOrgsNodes:delete', + 'infra:infraServersOrgsPolicyFiles:delete', + 'compliance:*', + 'event:*', + 'ingest:*', + 'secrets:*', + 'iam:projects:list', + 'iam:projects:get', + 'iam:projects:assign', + 'iam:policies:list', + 'iam:policies:get', + 'iam:policyMembers:*', + 'iam:teams:list', + 'iam:teams:get', + 'iam:teamUsers:*', + 'iam:users:get', + 'iam:users:list', + 'applications:*' + ], + projects: ['*'] + }] + }; + + before(() => { + cy.cleanupIAMObjectsByIDPrefixes(cypressPrefix, objectsToCleanUp); + + cy.request({ + headers: { 'api-token': Cypress.env('ADMIN_TOKEN') }, + method: 'POST', + url: '/apis/iam/v2/tokens', + body: { + id: tokenId1, + name: tokenId1 + } + }).then((resp) => { + withInfraProjectOwnerActionToken = resp.body.token.value; + }); + + cy.request({ + headers: { 'api-token': Cypress.env('ADMIN_TOKEN') }, + method: 'POST', + url: '/apis/iam/v2/policies', + body: allowInfraProjectOwnerPolicy + }).then((resp) => { + expect(resp.status).to.equal(200); + }); + + cy.request({ + headers: { 'api-token': Cypress.env('ADMIN_TOKEN') }, + method: 'POST', + url: '/apis/iam/v2/tokens', + body: { + id: tokenId2, + name: tokenId2 + } + }).then((resp) => { + withoutInfraProjectOwnerActionToken = resp.body.token.value; + }); + + cy.request({ + headers: { 'api-token': Cypress.env('ADMIN_TOKEN') }, + method: 'POST', + url: '/apis/iam/v2/policies', + body: denyInfraProjectOwnerPolicy + }).then((resp) => { + expect(resp.status).to.equal(200); + }); + }); + + after(() => { + cy.cleanupIAMObjectsByIDPrefixes(cypressPrefix, objectsToCleanUp); + }); + + it('cookbooks get returns 200 when InfraProjectOwner policy is allowed', () => { + cy.request({ + headers: { 'api-token': withInfraProjectOwnerActionToken }, + method: 'GET', + url: '/api/v0/infra/servers/local-dev/orgs/test-org/cookbooks' + }).then((resp) => { + assert.equal(resp.status, 200); + }); + }); + + it('cookbooks get returns 403 when InfraProjectOwner policy actions is denied', () => { + cy.request({ + headers: { 'api-token': withoutInfraProjectOwnerActionToken }, + method: 'GET', + url: '/api/v0/infra/servers/local-dev/orgs/test-org/cookbooks', + failOnStatusCode: false + }).then((resp) => { + assert.equal(resp.status, 403); + }); + }); + + it('Create Env request returns 200 when InfraProjectOwner policy is allowed', () => { + cy.request({ + headers: { 'api-token': withInfraProjectOwnerActionToken }, + method: 'POST', + body: { + org_id: 'test-org', + server_id: 'local-dev', + name: 'test2', + description: 'cypress testing' + }, + url: '/api/v0/infra/servers/local-dev/orgs/test-org/environments', + }).then((resp) => { + assert.equal(resp.status, 200); + }); + }); + + it('Roles get returns 200 when InfraProjectOwner policy is allowed', () => { + cy.request({ + headers: { 'api-token': withInfraProjectOwnerActionToken }, + method: 'GET', + url: '/api/v0/infra/servers/local-dev/orgs/test-org/roles' + }).then((resp) => { + assert.equal(resp.status, 200); + }); + }); + + it('Roles get returns 403 when InfraProjectOwner policy is denied', () => { + cy.request({ + headers: { 'api-token': withoutInfraProjectOwnerActionToken }, + method: 'GET', + url: '/api/v0/infra/servers/local-dev/orgs/test-org/roles', + failOnStatusCode: false + }).then((resp) => { + assert.equal(resp.status, 403); + }); + }); + + it('Nodes delete returns 403 when infraServersOrgsNodes delete actions is denied', () => { + cy.request({ + headers: { 'api-token': withoutInfraProjectOwnerActionToken }, + method: 'DELETE', + url: '/api/v0/infra/servers/local-dev/orgs/test-org/nodes/test-admin', + failOnStatusCode: false + }).then((resp) => { + assert.equal(resp.status, 403); + }); + }); + + it('Nodes delete returns 200 when infraServersOrgsNodes delete actions is allowed', () => { + cy.request({ + headers: { 'api-token': withInfraProjectOwnerActionToken }, + method: 'DELETE', + url: '/api/v0/infra/servers/local-dev/orgs/test-org/nodes/test-admin' + }).then((resp) => { + assert.equal(resp.status, 200); + }); + }); + + it('policyfiles delete returns 403 when delete actions is denied', () => { + cy.request({ + headers: { 'api-token': withoutInfraProjectOwnerActionToken }, + method: 'DELETE', + url: '/api/v0/infra/servers/local-dev/orgs/test-org/policyfiles/examplecb', + failOnStatusCode: false + }).then((resp) => { + assert.equal(resp.status, 403); + }); + }); + + it('policyfiles delete returns 200 when delete actions is allowed', () => { + cy.request({ + headers: { 'api-token': withInfraProjectOwnerActionToken }, + method: 'DELETE', + url: '/api/v0/infra/servers/local-dev/orgs/test-org/policyfiles/examplecb' + }).then((resp) => { + assert.equal(resp.status, 200); + }); + }); +}); \ No newline at end of file