diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index 1146a9280ac4e..2155565a63ef2 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -207,3 +207,6 @@ xpack.ml.compatibleModuleType: 'observability' # Disable the embedded Dev Console console.ui.embeddedEnabled: false + +# Disable role management (custom roles) +xpack.security.roleManagementEnabled: false diff --git a/config/serverless.yml b/config/serverless.yml index 4249d8ff786ec..ec857577f1863 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -155,7 +155,7 @@ server.versioned.versionResolution: newest server.versioned.strictClientVersionCheck: false # Enforce single "default" space and disable feature visibility controls -xpack.spaces.maxSpaces: 1 +xpack.spaces.maxSpaces: 100 xpack.spaces.allowFeatureVisibility: false xpack.spaces.allowSolutionVisibility: false diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index fd41029331181..a11d7afd89b3b 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -40993,6 +40993,697 @@ ] } }, + "/api/security/role": { + "get": { + "operationId": "%2Fapi%2Fsecurity%2Frole#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "in": "query", + "name": "replaceDeprecatedPrivileges", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "responses": {}, + "summary": "Get all roles", + "tags": [ + "roles" + ] + } + }, + "/api/security/role/{name}": { + "delete": { + "operationId": "%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#1", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "required": true, + "schema": { + "minLength": 1, + "type": "string" + } + } + ], + "responses": {}, + "summary": "Delete a role", + "tags": [ + "roles" + ] + }, + "get": { + "operationId": "%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "required": true, + "schema": { + "minLength": 1, + "type": "string" + } + }, + { + "in": "query", + "name": "replaceDeprecatedPrivileges", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "responses": {}, + "summary": "Get a role", + "tags": [ + "roles" + ] + }, + "put": { + "operationId": "%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#2", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "required": true, + "schema": { + "maxLength": 1024, + "minLength": 1, + "type": "string" + } + }, + { + "in": "query", + "name": "createOnly", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "description": { + "maxLength": 2048, + "type": "string" + }, + "elasticsearch": { + "additionalProperties": false, + "properties": { + "cluster": { + "items": { + "type": "string" + }, + "type": "array" + }, + "indices": { + "items": { + "additionalProperties": false, + "properties": { + "allow_restricted_indices": { + "type": "boolean" + }, + "field_security": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "names": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "query": { + "type": "string" + } + }, + "required": [ + "names", + "privileges" + ], + "type": "object" + }, + "type": "array" + }, + "remote_cluster": { + "items": { + "additionalProperties": false, + "properties": { + "clusters": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "privileges", + "clusters" + ], + "type": "object" + }, + "type": "array" + }, + "remote_indices": { + "items": { + "additionalProperties": false, + "properties": { + "allow_restricted_indices": { + "type": "boolean" + }, + "clusters": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "field_security": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "names": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "query": { + "type": "string" + } + }, + "required": [ + "clusters", + "names", + "privileges" + ], + "type": "object" + }, + "type": "array" + }, + "run_as": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "kibana": { + "items": { + "additionalProperties": false, + "properties": { + "base": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "object" + }, + { + "type": "string" + } + ], + "nullable": true, + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "feature": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "spaces": { + "anyOf": [ + { + "items": { + "enum": [ + "*" + ], + "type": "string" + }, + "maxItems": 1, + "minItems": 1, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "default": [ + "*" + ] + } + }, + "required": [ + "base" + ], + "type": "object" + }, + "type": "array" + }, + "metadata": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "elasticsearch" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "Create or update a role", + "tags": [ + "roles" + ] + } + }, + "/api/security/roles": { + "post": { + "operationId": "%2Fapi%2Fsecurity%2Froles#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "roles": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "maxLength": 2048, + "type": "string" + }, + "elasticsearch": { + "additionalProperties": false, + "properties": { + "cluster": { + "items": { + "type": "string" + }, + "type": "array" + }, + "indices": { + "items": { + "additionalProperties": false, + "properties": { + "allow_restricted_indices": { + "type": "boolean" + }, + "field_security": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "names": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "query": { + "type": "string" + } + }, + "required": [ + "names", + "privileges" + ], + "type": "object" + }, + "type": "array" + }, + "remote_cluster": { + "items": { + "additionalProperties": false, + "properties": { + "clusters": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "privileges", + "clusters" + ], + "type": "object" + }, + "type": "array" + }, + "remote_indices": { + "items": { + "additionalProperties": false, + "properties": { + "allow_restricted_indices": { + "type": "boolean" + }, + "clusters": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "field_security": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "names": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "query": { + "type": "string" + } + }, + "required": [ + "clusters", + "names", + "privileges" + ], + "type": "object" + }, + "type": "array" + }, + "run_as": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "kibana": { + "items": { + "additionalProperties": false, + "properties": { + "base": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "object" + }, + { + "type": "string" + } + ], + "nullable": true, + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "feature": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "spaces": { + "anyOf": [ + { + "items": { + "enum": [ + "*" + ], + "type": "string" + }, + "maxItems": 1, + "minItems": 1, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "default": [ + "*" + ] + } + }, + "required": [ + "base" + ], + "type": "object" + }, + "type": "array" + }, + "metadata": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "elasticsearch" + ], + "type": "object" + }, + "type": "object" + } + }, + "required": [ + "roles" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "Create or update roles", + "tags": [ + "roles" + ] + } + }, "/api/spaces/space": { "get": { "operationId": "%2Fapi%2Fspaces%2Fspace#0", @@ -41493,6 +42184,9 @@ { "name": "Message Signing Service" }, + { + "name": "roles" + }, { "name": "spaces" }, diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml index acbbdd7bfca43..d1ca7e16557e8 100644 --- a/oas_docs/output/kibana.serverless.staging.yaml +++ b/oas_docs/output/kibana.serverless.staging.yaml @@ -36556,6 +36556,456 @@ paths: tags: - Security AI Assistant API - Prompts API + /api/security/role: + get: + operationId: '%2Fapi%2Fsecurity%2Frole#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: query + name: replaceDeprecatedPrivileges + required: false + schema: + type: boolean + responses: {} + summary: Get all roles + tags: + - roles + /api/security/role/{name}: + delete: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: name + required: true + schema: + minLength: 1 + type: string + responses: {} + summary: Delete a role + tags: + - roles + get: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: path + name: name + required: true + schema: + minLength: 1 + type: string + - in: query + name: replaceDeprecatedPrivileges + required: false + schema: + type: boolean + responses: {} + summary: Get a role + tags: + - roles + put: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#2' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: name + required: true + schema: + maxLength: 1024 + minLength: 1 + type: string + - in: query + name: createOnly + required: false + schema: + default: false + type: boolean + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + description: + maxLength: 2048 + type: string + elasticsearch: + additionalProperties: false + type: object + properties: + cluster: + items: + type: string + type: array + indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - names + - privileges + type: array + remote_cluster: + items: + additionalProperties: false + type: object + properties: + clusters: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + required: + - privileges + - clusters + type: array + remote_indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + clusters: + items: + type: string + minItems: 1 + type: array + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - clusters + - names + - privileges + type: array + run_as: + items: + type: string + type: array + kibana: + items: + additionalProperties: false + type: object + properties: + base: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - items: + type: string + type: array + - items: + type: string + type: array + feature: + additionalProperties: + items: + type: string + type: array + type: object + spaces: + anyOf: + - items: + enum: + - '*' + type: string + maxItems: 1 + minItems: 1 + type: array + - items: + type: string + type: array + default: + - '*' + required: + - base + type: array + metadata: + additionalProperties: {} + type: object + required: + - elasticsearch + responses: {} + summary: Create or update a role + tags: + - roles + /api/security/roles: + post: + operationId: '%2Fapi%2Fsecurity%2Froles#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + roles: + additionalProperties: + additionalProperties: false + type: object + properties: + description: + maxLength: 2048 + type: string + elasticsearch: + additionalProperties: false + type: object + properties: + cluster: + items: + type: string + type: array + indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - names + - privileges + type: array + remote_cluster: + items: + additionalProperties: false + type: object + properties: + clusters: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + required: + - privileges + - clusters + type: array + remote_indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + clusters: + items: + type: string + minItems: 1 + type: array + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - clusters + - names + - privileges + type: array + run_as: + items: + type: string + type: array + kibana: + items: + additionalProperties: false + type: object + properties: + base: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - items: + type: string + type: array + - items: + type: string + type: array + feature: + additionalProperties: + items: + type: string + type: array + type: object + spaces: + anyOf: + - items: + enum: + - '*' + type: string + maxItems: 1 + minItems: 1 + type: array + - items: + type: string + type: array + default: + - '*' + required: + - base + type: array + metadata: + additionalProperties: {} + type: object + required: + - elasticsearch + type: object + required: + - roles + responses: {} + summary: Create or update roles + tags: + - roles /api/spaces/space: get: operationId: '%2Fapi%2Fspaces%2Fspace#0' @@ -51504,6 +51954,7 @@ tags: - name: Message Signing Service - description: Machine learning name: ml + - name: roles - description: > Export sets of saved objects that you want to import into {kib}, resolve import errors, and rotate an encryption key for encrypted saved objects diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index acbbdd7bfca43..d1ca7e16557e8 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -36556,6 +36556,456 @@ paths: tags: - Security AI Assistant API - Prompts API + /api/security/role: + get: + operationId: '%2Fapi%2Fsecurity%2Frole#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: query + name: replaceDeprecatedPrivileges + required: false + schema: + type: boolean + responses: {} + summary: Get all roles + tags: + - roles + /api/security/role/{name}: + delete: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: name + required: true + schema: + minLength: 1 + type: string + responses: {} + summary: Delete a role + tags: + - roles + get: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: path + name: name + required: true + schema: + minLength: 1 + type: string + - in: query + name: replaceDeprecatedPrivileges + required: false + schema: + type: boolean + responses: {} + summary: Get a role + tags: + - roles + put: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#2' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: name + required: true + schema: + maxLength: 1024 + minLength: 1 + type: string + - in: query + name: createOnly + required: false + schema: + default: false + type: boolean + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + description: + maxLength: 2048 + type: string + elasticsearch: + additionalProperties: false + type: object + properties: + cluster: + items: + type: string + type: array + indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - names + - privileges + type: array + remote_cluster: + items: + additionalProperties: false + type: object + properties: + clusters: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + required: + - privileges + - clusters + type: array + remote_indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + clusters: + items: + type: string + minItems: 1 + type: array + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - clusters + - names + - privileges + type: array + run_as: + items: + type: string + type: array + kibana: + items: + additionalProperties: false + type: object + properties: + base: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - items: + type: string + type: array + - items: + type: string + type: array + feature: + additionalProperties: + items: + type: string + type: array + type: object + spaces: + anyOf: + - items: + enum: + - '*' + type: string + maxItems: 1 + minItems: 1 + type: array + - items: + type: string + type: array + default: + - '*' + required: + - base + type: array + metadata: + additionalProperties: {} + type: object + required: + - elasticsearch + responses: {} + summary: Create or update a role + tags: + - roles + /api/security/roles: + post: + operationId: '%2Fapi%2Fsecurity%2Froles#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + roles: + additionalProperties: + additionalProperties: false + type: object + properties: + description: + maxLength: 2048 + type: string + elasticsearch: + additionalProperties: false + type: object + properties: + cluster: + items: + type: string + type: array + indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - names + - privileges + type: array + remote_cluster: + items: + additionalProperties: false + type: object + properties: + clusters: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + required: + - privileges + - clusters + type: array + remote_indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + clusters: + items: + type: string + minItems: 1 + type: array + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - clusters + - names + - privileges + type: array + run_as: + items: + type: string + type: array + kibana: + items: + additionalProperties: false + type: object + properties: + base: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - items: + type: string + type: array + - items: + type: string + type: array + feature: + additionalProperties: + items: + type: string + type: array + type: object + spaces: + anyOf: + - items: + enum: + - '*' + type: string + maxItems: 1 + minItems: 1 + type: array + - items: + type: string + type: array + default: + - '*' + required: + - base + type: array + metadata: + additionalProperties: {} + type: object + required: + - elasticsearch + type: object + required: + - roles + responses: {} + summary: Create or update roles + tags: + - roles /api/spaces/space: get: operationId: '%2Fapi%2Fspaces%2Fspace#0' @@ -51504,6 +51954,7 @@ tags: - name: Message Signing Service - description: Machine learning name: ml + - name: roles - description: > Export sets of saved objects that you want to import into {kib}, resolve import errors, and rotate an encryption key for encrypted saved objects diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index f5a735fdfe8b7..2e2199ff850a1 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -247,7 +247,7 @@ describe('config schema', () => { }, "loginAssistanceMessage": "", "public": Object {}, - "roleManagementEnabled": false, + "roleManagementEnabled": true, "secureCookies": false, "session": Object { "cleanupInterval": "PT1H", diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 5618186459566..8be1500bdccf1 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -303,8 +303,9 @@ export const ConfigSchema = schema.object({ ), }), + // config/serverless.oblt.yml contains an override to false for OBLT projects roleManagementEnabled: offeringBasedSchema({ - serverless: schema.boolean({ defaultValue: false }), + serverless: schema.boolean({ defaultValue: true }), }), // Setting only allowed in the Serverless offering diff --git a/x-pack/plugins/serverless/public/plugin.tsx b/x-pack/plugins/serverless/public/plugin.tsx index ee2b32e4d97a5..82578123452e7 100644 --- a/x-pack/plugins/serverless/public/plugin.tsx +++ b/x-pack/plugins/serverless/public/plugin.tsx @@ -125,7 +125,7 @@ export class ServerlessPlugin getNavigationCards: (roleManagementEnabled, extendCardNavDefinitions) => { if (!roleManagementEnabled) return extendCardNavDefinitions; - const manageOrgMembersNavCard = generateManageOrgMembersNavCard(cloud.organizationUrl); + const manageOrgMembersNavCard = generateManageOrgMembersNavCard(cloud.usersAndRolesUrl); if (extendCardNavDefinitions) { extendCardNavDefinitions[manageOrgMembersNavCardName] = manageOrgMembersNavCard; return extendCardNavDefinitions; diff --git a/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts b/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts index f73af3a6d4bf7..e7df37f5aa312 100644 --- a/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts +++ b/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts @@ -113,10 +113,6 @@ export function createServerlessTestConfig { - supertestAdminWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('admin', { - withCommonHeaders: true, - }); - supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( - 'admin', - { - useCookieHeader: true, - withInternalHeaders: true, - } - ); - }); - after(async () => { - // delete any lingering spaces - const { body } = await supertestAdminWithApiKey.get('/api/spaces/space').send().expect(200); - - const toDelete = (body as Array<{ id: string }>).filter((f) => f.id !== 'default'); - - await asyncForEach(toDelete, async (space) => { - await deleteSpace(space.id); - }); - - await supertestAdminWithApiKey.destroy(); - }); - - describe('Create (POST /api/spaces/space)', () => { - it('should allow us to create a space', async () => { - await supertestAdminWithApiKey - .post('/api/spaces/space') - .send({ - id: 'custom_space_1', - name: 'custom_space_1', - disabledFeatures: [], - }) - .expect(200); - }); - - it('should not allow us to create a space with disabled features', async () => { - await supertestAdminWithApiKey - .post('/api/spaces/space') - .send({ - id: 'custom_space_2', - name: 'custom_space_2', - disabledFeatures: ['discover'], - }) - .expect(400); - }); - }); - - describe('Read (GET /api/spaces/space)', () => { - before(async () => { - await createSpace('space_to_get_1'); - await createSpace('space_to_get_2'); - await createSpace('space_to_get_3'); - }); - - after(async () => { - await deleteSpace('space_to_get_1'); - await deleteSpace('space_to_get_2'); - await deleteSpace('space_to_get_3'); - }); - - it('should allow us to get a space', async () => { - await supertestAdminWithApiKey.get('/api/spaces/space/space_to_get_1').send().expect(200, { - id: 'space_to_get_1', - name: 'space_to_get_1', - disabledFeatures: [], - }); - }); - - it('should allow us to get all spaces', async () => { - const { body } = await supertestAdminWithApiKey.get('/api/spaces/space').send().expect(200); - - expect(body).toEqual( - expect.arrayContaining([ - { - _reserved: true, - color: '#00bfb3', - description: 'This is your default space!', - disabledFeatures: [], - id: 'default', - name: 'Default', - }, - { id: 'space_to_get_1', name: 'space_to_get_1', disabledFeatures: [] }, - { id: 'space_to_get_2', name: 'space_to_get_2', disabledFeatures: [] }, - { id: 'space_to_get_3', name: 'space_to_get_3', disabledFeatures: [] }, - ]) - ); - }); - }); - - describe('Update (PUT /api/spaces/space)', () => { - before(async () => { - await createSpace('space_to_update'); - }); - - after(async () => { - await deleteSpace('space_to_update'); - }); - - it('should allow us to update a space', async () => { - await supertestAdminWithApiKey - .put('/api/spaces/space/space_to_update') - .send({ - id: 'space_to_update', - name: 'some new name', - initials: 'SN', - disabledFeatures: [], - }) - .expect(200); - - await supertestAdminWithApiKey.get('/api/spaces/space/space_to_update').send().expect(200, { - id: 'space_to_update', - name: 'some new name', - initials: 'SN', - disabledFeatures: [], - }); - }); - - it('should not allow us to update a space with disabled features', async () => { - await supertestAdminWithApiKey - .put('/api/spaces/space/space_to_update') - .send({ - id: 'space_to_update', - name: 'some new name', - initials: 'SN', - disabledFeatures: ['discover'], - }) - .expect(400); - }); - }); - - describe('Delete (DELETE /api/spaces/space)', () => { - it('should allow us to delete a space', async () => { - await createSpace('space_to_delete'); - - await supertestAdminWithApiKey.delete(`/api/spaces/space/space_to_delete`).expect(204); - }); - }); - - describe('Get active space (GET /internal/spaces/_active_space)', () => { - before(async () => { - await createSpace('foo-space'); - }); - - after(async () => { - await deleteSpace('foo-space'); - }); - - it('returns the default space', async () => { - const response = await supertestAdminWithCookieCredentials - .get('/internal/spaces/_active_space') - .expect(200); - - const { id, name, _reserved } = response.body; - expect({ id, name, _reserved }).toEqual({ - id: 'default', - name: 'Default', - _reserved: true, - }); - }); - - it('returns the default space when explicitly referenced', async () => { - const response = await supertestAdminWithCookieCredentials - .get('/s/default/internal/spaces/_active_space') - .expect(200); - - const { id, name, _reserved } = response.body; - expect({ id, name, _reserved }).toEqual({ - id: 'default', - name: 'Default', - _reserved: true, - }); - }); - - it('returns the foo space', async () => { - await supertestAdminWithCookieCredentials - .get('/s/foo-space/internal/spaces/_active_space') - .expect(200, { - id: 'foo-space', - name: 'foo-space', - disabledFeatures: [], - }); - }); - - it('returns 404 when the space is not found', async () => { - await supertestAdminWithCookieCredentials - .get('/s/not-found-space/internal/spaces/_active_space') - .expect(404, { - statusCode: 404, - error: 'Not Found', - message: 'Saved object [space/not-found-space] not found', - }); - }); - }); - - // These tests just test access to API endpoints, in this case - // when accessed without internal headers they will return 400 - // They will be included in deployment agnostic testing once spaces - // are enabled in production. - describe(`Access`, () => { - describe(`internal`, () => { - it('#getActiveSpace requires internal header', async () => { - let body: any; - let status: number; - - ({ body, status } = await supertestAdminWithApiKey - .get('/internal/spaces/_active_space') - .set(samlAuth.getCommonRequestHeader())); - // expect a rejection because we're not using the internal header - expect(body).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: expect.stringContaining( - 'method [get] exists but is not available with the current configuration' - ), - }); - expect(status).toBe(400); - - ({ body, status } = await supertestAdminWithApiKey - .get('/internal/spaces/_active_space') - .set(samlAuth.getInternalRequestHeader())); - // expect success because we're using the internal header - expect(body).toEqual( - expect.objectContaining({ - id: 'default', - }) - ); - expect(status).toBe(200); - }); - - it('#copyToSpace requires internal header', async () => { - let body: any; - let status: number; - - ({ body, status } = await supertestAdminWithApiKey.post( - '/api/spaces/_copy_saved_objects' - )); - // expect a rejection because we're not using the internal header - expect(body).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: expect.stringContaining( - 'method [post] exists but is not available with the current configuration' - ), - }); - - ({ body, status } = await supertestAdminWithApiKey - .post('/api/spaces/_copy_saved_objects') - .set(samlAuth.getInternalRequestHeader())); - - svlCommonApi.assertResponseStatusCode(400, status, body); - - // expect 400 for missing body - expect(body).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: '[request body]: expected a plain object value, but found [null] instead.', - }); - }); - - it('#resolveCopyToSpaceErrors requires internal header', async () => { - let body: any; - let status: number; - - ({ body, status } = await supertestAdminWithApiKey.post( - '/api/spaces/_resolve_copy_saved_objects_errors' - )); - // expect a rejection because we're not using the internal header - expect(body).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: expect.stringContaining( - 'method [post] exists but is not available with the current configuration' - ), - }); - - ({ body, status } = await supertestAdminWithApiKey - .post('/api/spaces/_resolve_copy_saved_objects_errors') - .set(samlAuth.getInternalRequestHeader())); - - svlCommonApi.assertResponseStatusCode(400, status, body); - - // expect 400 for missing body - expect(body).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: '[request body]: expected a plain object value, but found [null] instead.', - }); - }); - - it('#updateObjectsSpaces requires internal header', async () => { - let body: any; - let status: number; - - ({ body, status } = await supertestAdminWithApiKey.post( - '/api/spaces/_update_objects_spaces' - )); - // expect a rejection because we're not using the internal header - expect(body).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: expect.stringContaining( - 'method [post] exists but is not available with the current configuration' - ), - }); - - ({ body, status } = await supertestAdminWithApiKey - .post('/api/spaces/_update_objects_spaces') - .set(samlAuth.getInternalRequestHeader())); - - svlCommonApi.assertResponseStatusCode(400, status, body); - - // expect 400 for missing body - expect(body).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: '[request body]: expected a plain object value, but found [null] instead.', - }); - }); - - it('#getShareableReferences requires internal header', async () => { - let body: any; - let status: number; - - ({ body, status } = await supertestAdminWithApiKey.post( - '/api/spaces/_get_shareable_references' - )); - // expect a rejection because we're not using the internal header - expect(body).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: expect.stringContaining( - 'method [post] exists but is not available with the current configuration' - ), - }); - - ({ body, status } = await supertestAdminWithApiKey - .post('/api/spaces/_get_shareable_references') - .set(samlAuth.getInternalRequestHeader()) - .send({ - objects: [{ type: 'a', id: 'a' }], - })); - - svlCommonApi.assertResponseStatusCode(200, status, body); - }); - }); - - describe(`disabled`, () => { - it('#disableLegacyUrlAliases', async () => { - const { body, status } = await supertestAdminWithApiKey.post( - '/api/spaces/_disable_legacy_url_aliases' - ); - // without a request body we would normally a 400 bad request if the endpoint was registered - svlCommonApi.assertApiNotFound(body, status); - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/management/spaces.ts b/x-pack/test_serverless/api_integration/test_suites/common/management/spaces.ts index 4bde897e714af..000848fd37e5f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/management/spaces.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/management/spaces.ts @@ -7,6 +7,7 @@ import expect from 'expect'; import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services'; +import { asyncForEach } from '@kbn/std'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -16,6 +17,21 @@ export default function ({ getService }: FtrProviderContext) { let supertestAdminWithApiKey: SupertestWithRoleScopeType; let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType; + async function createSpace(id: string) { + await supertestAdminWithApiKey + .post('/api/spaces/space') + .send({ + id, + name: id, + disabledFeatures: [], + }) + .expect(200); + } + + async function deleteSpace(id: string) { + await supertestAdminWithApiKey.delete(`/api/spaces/space/${id}`).expect(204); + } + describe('spaces', function () { before(async () => { // admin is the only predefined role that will work for all 3 solutions @@ -34,76 +50,212 @@ export default function ({ getService }: FtrProviderContext) { await supertestAdminWithApiKey.destroy(); }); - describe('route access', () => { - describe('public (CRUD)', () => { - // Skipped due to change in QA environment for role management and spaces - // TODO: revisit once the change is rolled out to all environments - it.skip('#create', async () => { - const { body, status } = await supertestAdminWithApiKey.post('/api/spaces/space').send({ - id: 'custom', - name: 'Custom', - disabledFeatures: [], - }); + // The create and update test cases are unique to serverless because + // setting feature visibility is not possible in serverless + describe('CRUD', () => { + after(async () => { + // delete any lingering spaces + const { body } = await supertestAdminWithApiKey.get('/api/spaces/space').send().expect(200); - svlCommonApi.assertResponseStatusCode(400, status, body); + const toDelete = (body as Array<{ id: string }>).filter((f) => f.id !== 'default'); - // Should fail due to maximum spaces limit, not because of lacking internal header - expect(body).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: - 'Unable to create Space, this exceeds the maximum number of spaces set by the xpack.spaces.maxSpaces setting', - }); + await asyncForEach(toDelete, async (space) => { + await deleteSpace(space.id); }); + }); - it('#get', async () => { - const { body, status } = await supertestAdminWithApiKey.get('/api/spaces/space/default'); - // expect success because we're using the internal header - expect(body).toEqual(expect.objectContaining({ id: 'default' })); - expect(status).toBe(200); + describe('Create (POST /api/spaces/space)', () => { + it('should allow us to create a space', async () => { + await supertestAdminWithApiKey + .post('/api/spaces/space') + .send({ + id: 'custom_space_1', + name: 'custom_space_1', + disabledFeatures: [], + }) + .expect(200); }); - it('#getAll', async () => { - const { body, status } = await supertestAdminWithApiKey.get('/api/spaces/space'); - // expect success because we're using the internal header + it('should not allow us to create a space with disabled features', async () => { + await supertestAdminWithApiKey + .post('/api/spaces/space') + .send({ + id: 'custom_space_2', + name: 'custom_space_2', + disabledFeatures: ['discover'], + }) + .expect(400); + }); + }); + + describe('Read (GET /api/spaces/space)', () => { + before(async () => { + await createSpace('space_to_get_1'); + await createSpace('space_to_get_2'); + await createSpace('space_to_get_3'); + }); + + after(async () => { + await deleteSpace('space_to_get_1'); + await deleteSpace('space_to_get_2'); + await deleteSpace('space_to_get_3'); + }); + + it('should allow us to get a space', async () => { + await supertestAdminWithApiKey + .get('/api/spaces/space/space_to_get_1') + .send() + .expect(200, { + id: 'space_to_get_1', + name: 'space_to_get_1', + disabledFeatures: [], + }); + }); + + it('should allow us to get all spaces', async () => { + const { body } = await supertestAdminWithApiKey + .get('/api/spaces/space') + .send() + .expect(200); + expect(body).toEqual( expect.arrayContaining([ - expect.objectContaining({ + { + _reserved: true, + color: '#00bfb3', + description: 'This is your default space!', + disabledFeatures: [], id: 'default', - }), + name: 'Default', + }, + { id: 'space_to_get_1', name: 'space_to_get_1', disabledFeatures: [] }, + { id: 'space_to_get_2', name: 'space_to_get_2', disabledFeatures: [] }, + { id: 'space_to_get_3', name: 'space_to_get_3', disabledFeatures: [] }, ]) ); - expect(status).toBe(200); }); + }); - it('#update', async () => { - const { body, status } = await supertestAdminWithApiKey - .put('/api/spaces/space/default') + describe('Update (PUT /api/spaces/space)', () => { + before(async () => { + await createSpace('space_to_update'); + }); + + after(async () => { + await deleteSpace('space_to_update'); + }); + + it('should allow us to update a space', async () => { + await supertestAdminWithApiKey + .put('/api/spaces/space/space_to_update') .send({ - id: 'default', - name: 'UPDATED!', + id: 'space_to_update', + name: 'some new name', + initials: 'SN', + disabledFeatures: [], + }) + .expect(200); + + await supertestAdminWithApiKey + .get('/api/spaces/space/space_to_update') + .send() + .expect(200, { + id: 'space_to_update', + name: 'some new name', + initials: 'SN', disabledFeatures: [], }); + }); - svlCommonApi.assertResponseStatusCode(200, status, body); + it('should not allow us to update a space with disabled features', async () => { + await supertestAdminWithApiKey + .put('/api/spaces/space/space_to_update') + .send({ + id: 'space_to_update', + name: 'some new name', + initials: 'SN', + disabledFeatures: ['discover'], + }) + .expect(400); }); + }); - it('#delete', async () => { - const { body, status } = await supertestAdminWithApiKey.delete( - '/api/spaces/space/default' - ); + describe('Delete (DELETE /api/spaces/space)', () => { + it('should allow us to delete a space', async () => { + await createSpace('space_to_delete'); - svlCommonApi.assertResponseStatusCode(400, status, body); + await supertestAdminWithApiKey.delete(`/api/spaces/space/space_to_delete`).expect(204); + }); + }); - // 400 with specific reason - cannot delete the default space - expect(body).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: 'The default space cannot be deleted because it is reserved.', + describe('Get active space (GET /internal/spaces/_active_space)', () => { + before(async () => { + await createSpace('foo-space'); + }); + + after(async () => { + await deleteSpace('foo-space'); + }); + + it('returns the default space', async () => { + const response = await supertestAdminWithCookieCredentials + .get('/internal/spaces/_active_space') + .set(samlAuth.getInternalRequestHeader()) + .expect(200); + + const { id, name, _reserved } = response.body; + expect({ id, name, _reserved }).toEqual({ + id: 'default', + name: 'Default', + _reserved: true, }); }); + + it('returns the default space when explicitly referenced', async () => { + const response = await supertestAdminWithCookieCredentials + .get('/s/default/internal/spaces/_active_space') + .set(samlAuth.getInternalRequestHeader()) + .expect(200); + + const { id, name, _reserved } = response.body; + expect({ id, name, _reserved }).toEqual({ + id: 'default', + name: 'Default', + _reserved: true, + }); + }); + + it('returns the foo space', async () => { + await supertestAdminWithCookieCredentials + .get('/s/foo-space/internal/spaces/_active_space') + .set(samlAuth.getInternalRequestHeader()) + .expect(200, { + id: 'foo-space', + name: 'foo-space', + disabledFeatures: [], + }); + }); + + it('returns 404 when the space is not found', async () => { + await supertestAdminWithCookieCredentials + .get('/s/not-found-space/internal/spaces/_active_space') + .set(samlAuth.getInternalRequestHeader()) + .expect(404, { + statusCode: 404, + error: 'Not Found', + message: 'Saved object [space/not-found-space] not found', + }); + }); }); + }); + describe('route access', () => { + // The 'internal route access' tests check that the internal header + // is needed for these specific endpoints. + // When accessed without internal headers they will return 400. + // They could be moved to deployment agnostic testing if there is + // a way to specify which tests to run when stateful vs serverles, + // as internal vs disabled is different in serverless. describe('internal', () => { it('#getActiveSpace requires internal header', async () => { let body: any; @@ -251,6 +403,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); + // Disabled in serverless, but public in stateful describe('disabled', () => { it('#disableLegacyUrlAliases', async () => { const { body, status } = await supertestAdminWithApiKey @@ -262,26 +415,5 @@ export default function ({ getService }: FtrProviderContext) { }); }); }); - - // TODO: Re-enable test-suite once users can create and update spaces in the Serverless offering. - // it('rejects request to update a space with disabledFeatures', async () => { - // const { body, status } = await supertest - // .put('/api/spaces/space/default') - // .set(svlCommonApi.getInternalRequestHeader()) - // .send({ - // id: 'custom', - // name: 'Custom', - // disabledFeatures: ['some-feature'], - // }); - // - // // in a non-serverless environment this would succeed with a 200 - // expect(body).toEqual({ - // statusCode: 400, - // error: 'Bad Request', - // message: - // 'Unable to update Space, the disabledFeatures array must be empty when xpack.spaces.allowFeatureVisibility setting is disabled', - // }); - // expect(status).toBe(400); - // }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts index fab1a6ef6b265..187efa4e860a8 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts @@ -8,8 +8,21 @@ import expect from 'expect'; import { KibanaFeatureConfig, SubFeaturePrivilegeConfig } from '@kbn/features-plugin/common'; import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services'; +import type { Role } from '@kbn/security-plugin-types-common'; import { FtrProviderContext } from '../../../ftr_provider_context'; +/* + * This file contains authorization tests that... + * - are applicable to all peroject types + * - are applicable to only search and security projects, where custom roles are enabled (role CRUD endpoints are enabled): + * - security/authorization/Roles + * - security/authorization/route access/custom roles + * - are applicable to only observability projects, where custom roles are not enabled (role CRUD endpoints are disabled): + * - security/authorization/route access/disabled/oblt only + * + * The test blocks use skip tags to run only the relevant tests per project type. + */ + function collectSubFeaturesPrivileges(feature: KibanaFeatureConfig) { return new Map( feature.subFeatures?.flatMap((subFeature) => @@ -26,13 +39,12 @@ export default function ({ getService }: FtrProviderContext) { const log = getService('log'); const svlCommonApi = getService('svlCommonApi'); const roleScopedSupertest = getService('roleScopedSupertest'); + const es = getService('es'); let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType; let supertestAdminWithApiKey: SupertestWithRoleScopeType; describe('security/authorization', function () { - // see details: https://github.com/elastic/kibana/issues/192282 - this.tags(['failsOnMKI']); - before(async () => { + before(async function () { supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( 'admin', { @@ -43,76 +55,1004 @@ export default function ({ getService }: FtrProviderContext) { withCommonHeaders: true, }); }); - after(async () => { + after(async function () { await supertestAdminWithApiKey.destroy(); }); - describe('route access', () => { - // skipped, see https://github.com/elastic/kibana/issues/194933 - describe.skip('disabled', () => { - // Skipped due to change in QA environment for role management and spaces - // TODO: revisit once the change is rolled out to all environments - it.skip('get all privileges', async () => { - const { body, status } = await supertestAdminWithApiKey.get('/api/security/privileges'); - svlCommonApi.assertApiNotFound(body, status); + + describe('Roles', function () { + // custom roles are not enabled for observability projects + this.tags(['skipSvlOblt']); + + describe('Create Role', function () { + it('should allow us to create an empty role', async function () { + await supertestAdminWithApiKey.put('/api/security/role/empty_role').send({}).expect(204); }); - // Skipped due to change in QA environment for role management and spaces - // TODO: revisit once the change is rolled out to all environments - it.skip('get built-in elasticsearch privileges', async () => { - const { body, status } = await supertestAdminWithCookieCredentials.get( - '/internal/security/esPrivileges/builtin' - ); - svlCommonApi.assertApiNotFound(body, status); + it('should create a role with kibana and elasticsearch privileges', async function () { + await supertestAdminWithApiKey + .put('/api/security/role/role_with_privileges') + .send({ + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + }, + kibana: [ + { + base: ['read'], + }, + { + feature: { + dashboard: ['read'], + discover: ['all'], + ml: ['all'], + }, + spaces: ['marketing', 'sales'], + }, + ], + }) + .expect(204); + + const role = await es.security.getRole({ name: 'role_with_privileges' }); + expect(role).toEqual({ + role_with_privileges: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + allow_restricted_indices: false, + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'kibana-.kibana', + privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], + resources: ['space:marketing', 'space:sales'], + }, + ], + metadata: { + foo: 'test-metadata', + }, + run_as: [], + transient_metadata: { + enabled: true, + }, + }, + }); }); - // Role CRUD APIs are gated behind the xpack.security.roleManagementEnabled config - // setting. This setting is false by default on serverless. When the custom roles - // feature is enabled, this setting will be true, and the tests from - // roles_routes_feature_flag.ts can be moved here to replace these. - it('create/update roleAuthc', async () => { - const { body, status } = await supertestAdminWithApiKey.put('/api/security/role/test'); - svlCommonApi.assertApiNotFound(body, status); + it(`should create a role with kibana and FLS/DLS elasticsearch privileges`, async function () { + await supertestAdminWithApiKey + .put('/api/security/role/role_with_privileges_dls_fls') + .send({ + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + field_security: { + grant: ['*'], + except: ['geo.*'], + }, + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + query: `{ "match": { "geo.src": "CN" } }`, + }, + ], + }, + }) + .expect(204); + }); + + // serverless only (stateful will allow) + it(`should not create a role with 'run as' privileges`, async function () { + await supertestAdminWithApiKey + .put('/api/security/role/role_with_privileges') + .send({ + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + run_as: ['admin'], + }, + kibana: [ + { + base: ['read'], + }, + { + feature: { + dashboard: ['read'], + discover: ['all'], + ml: ['all'], + }, + spaces: ['marketing', 'sales'], + }, + ], + }) + .expect(400); + }); + + // serverless only (stateful will allow) + it(`should not create a role with remote cluster privileges`, async function () { + await supertestAdminWithApiKey + .put('/api/security/role/role_with_privileges') + .send({ + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + remote_cluster: [ + { + clusters: ['remote_cluster1'], + privileges: ['monitor_enrich'], + }, + ], + }, + kibana: [ + { + base: ['read'], + }, + { + feature: { + dashboard: ['read'], + discover: ['all'], + ml: ['all'], + }, + spaces: ['marketing', 'sales'], + }, + ], + }) + .expect(400); }); - it('get role', async () => { - const { body, status } = await supertestAdminWithApiKey.get( - '/api/security/role/someRole' // mame of the role doesn't matter, we're checking the endpoint doesn't exist + // serverless only (stateful will allow) + it(`should not create a role with remote index privileges`, async function () { + await supertestAdminWithApiKey + .put('/api/security/role/role_with_privileges') + .send({ + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + remote_indices: [ + { + clusters: ['remote_cluster1'], + names: ['remote_index1', 'remote_index2'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + base: ['read'], + }, + { + feature: { + dashboard: ['read'], + discover: ['all'], + ml: ['all'], + }, + spaces: ['marketing', 'sales'], + }, + ], + }) + .expect(400); + }); + + describe('with the createOnly option enabled', function () { + it('should fail when role already exists', async function () { + await es.security.putRole({ + name: 'test_role', + body: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + }, + ], + }, + }); + + await supertestAdminWithApiKey + .put('/api/security/role/test_role?createOnly=true') + .send({}) + .expect(409); + }); + + it('should succeed when role does not exist', async function () { + await supertestAdminWithApiKey + .put('/api/security/role/new_role?createOnly=true') + .send({}) + .expect(204); + }); + }); + }); + + describe('Read Role', function () { + it('should get roles', async function () { + await es.security.putRole({ + name: 'role_to_get', + body: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'kibana-.kibana', + privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], + resources: ['space:marketing', 'space:sales'], + }, + { + application: 'apm', + privileges: ['apm-privilege'], + resources: ['*'], + }, + ], + metadata: { + foo: 'test-metadata', + }, + transient_metadata: { + enabled: true, + }, + }, + }); + + await supertestAdminWithApiKey.get('/api/security/role/role_to_get').expect(200, { + name: 'role_to_get', + metadata: { + foo: 'test-metadata', + }, + transient_metadata: { enabled: true }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + allow_restricted_indices: false, + }, + ], + run_as: [], + }, + kibana: [ + { + base: ['read'], + feature: {}, + spaces: ['*'], + }, + { + base: [], + feature: { + dashboard: ['read'], + discover: ['all'], + ml: ['all'], + }, + spaces: ['marketing', 'sales'], + }, + ], + + _transform_error: [], + _unrecognized_applications: ['apm'], + }); + }); + + it('should get roles by space id', async function () { + await es.security.putRole({ + name: 'space_role_not_to_get', + body: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], + resources: ['space:marketing', 'space:sales'], + }, + ], + metadata: { + foo: 'test-metadata', + }, + transient_metadata: { + enabled: true, + }, + }, + }); + + await es.security.putRole({ + name: 'space_role_to_get', + body: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], + resources: ['space:engineering', 'space:sales'], + }, + ], + metadata: { + foo: 'test-metadata', + }, + transient_metadata: { + enabled: true, + }, + }, + }); + + await supertestAdminWithCookieCredentials + .get('/internal/security/roles/engineering') + .set(svlCommonApi.getInternalRequestHeader()) + .expect(200) + .expect((res: { body: Role[] }) => { + const roles = res.body; + + const success = roles.every((role) => { + return ( + role.name !== 'space_role_not_to_get' && + role.kibana.some((privilege) => { + return ( + privilege.spaces.includes('*') || privilege.spaces.includes('engineering') + ); + }) + ); + }); + + const expectedRole = roles.find((role) => role.name === 'space_role_to_get'); + + expect(success).toBe(true); + expect(expectedRole).toBeTruthy(); + }); + }); + }); + + describe('Update Role', function () { + it('should update a role with elasticsearch, kibana and other applications privileges', async function () { + await es.security.putRole({ + name: 'role_to_update', + body: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'apm', + privileges: ['apm-privilege'], + resources: ['*'], + }, + ], + metadata: { + bar: 'old-metadata', + }, + }, + }); + + await supertestAdminWithApiKey + .put('/api/security/role/role_to_update') + .send({ + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + }, + kibana: [ + { + feature: { + dashboard: ['read'], + dev_tools: ['all'], + }, + spaces: ['*'], + }, + { + base: ['all'], + spaces: ['marketing', 'sales'], + }, + ], + }) + .expect(204); + + const role = await es.security.getRole({ name: 'role_to_update' }); + expect(role).toEqual({ + role_to_update: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + allow_restricted_indices: false, + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_dashboard.read', 'feature_dev_tools.all'], + resources: ['*'], + }, + { + application: 'kibana-.kibana', + privileges: ['space_all'], + resources: ['space:marketing', 'space:sales'], + }, + { + application: 'apm', + privileges: ['apm-privilege'], + resources: ['*'], + }, + ], + metadata: { + foo: 'test-metadata', + }, + run_as: [], + transient_metadata: { + enabled: true, + }, + }, + }); + }); + + it(`should update a role adding DLS and FLS privileges`, async function () { + await es.security.putRole({ + name: 'role_to_update_with_dls_fls', + body: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + }, + ], + }, + }); + + await supertestAdminWithApiKey + .put('/api/security/role/role_to_update_with_dls_fls') + .send({ + elasticsearch: { + cluster: ['manage'], + indices: [ + { + field_security: { + grant: ['*'], + except: ['geo.*'], + }, + names: ['logstash-*'], + privileges: ['read'], + query: `{ "match": { "geo.src": "CN" } }`, + }, + ], + }, + }) + .expect(204); + + const role = await es.security.getRole({ name: 'role_to_update_with_dls_fls' }); + + expect(role.role_to_update_with_dls_fls.cluster).toEqual(['manage']); + expect(role.role_to_update_with_dls_fls.indices[0].names).toEqual(['logstash-*']); + expect(role.role_to_update_with_dls_fls.indices[0].query).toEqual( + `{ "match": { "geo.src": "CN" } }` ); - svlCommonApi.assertApiNotFound(body, status); }); - it('get all roles', async () => { - const { body, status } = await supertestAdminWithApiKey.get('/api/security/role'); - svlCommonApi.assertApiNotFound(body, status); + // serverless only (stateful will allow) + it(`should not update a role with 'run as' privileges`, async function () { + await es.security.putRole({ + name: 'role_to_update', + body: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'apm', + privileges: ['apm-privilege'], + resources: ['*'], + }, + ], + metadata: { + bar: 'old-metadata', + }, + }, + }); + + await supertestAdminWithApiKey + .put('/api/security/role/role_to_update') + .send({ + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + run_as: ['admin'], + }, + kibana: [ + { + feature: { + dashboard: ['read'], + dev_tools: ['all'], + }, + spaces: ['*'], + }, + { + base: ['all'], + spaces: ['marketing', 'sales'], + }, + ], + }) + .expect(400); + + const role = await es.security.getRole({ name: 'role_to_update' }); + expect(role).toEqual({ + role_to_update: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + allow_restricted_indices: false, + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'apm', + privileges: ['apm-privilege'], + resources: ['*'], + }, + ], + metadata: { + bar: 'old-metadata', + }, + run_as: [], + transient_metadata: { + enabled: true, + }, + }, + }); + }); + + // serverless only (stateful will allow) + it(`should not update a role with remote cluster privileges`, async function () { + await es.security.putRole({ + name: 'role_to_update', + body: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'apm', + privileges: ['apm-privilege'], + resources: ['*'], + }, + ], + metadata: { + bar: 'old-metadata', + }, + }, + }); + + await supertestAdminWithApiKey + .put('/api/security/role/role_to_update') + .send({ + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + remote_cluster: [ + { + clusters: ['remote_cluster1'], + privileges: ['monitor_enrich'], + }, + ], + }, + kibana: [ + { + feature: { + dashboard: ['read'], + dev_tools: ['all'], + }, + spaces: ['*'], + }, + { + base: ['all'], + spaces: ['marketing', 'sales'], + }, + ], + }) + .expect(400); + + const role = await es.security.getRole({ name: 'role_to_update' }); + expect(role).toEqual({ + role_to_update: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + allow_restricted_indices: false, + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'apm', + privileges: ['apm-privilege'], + resources: ['*'], + }, + ], + metadata: { + bar: 'old-metadata', + }, + run_as: [], + transient_metadata: { + enabled: true, + }, + }, + }); }); - it('delete role', async () => { - const { body, status } = await supertestAdminWithApiKey.delete( - '/api/security/role/someRole' // mame of the role doesn't matter, we're checking the endpoint doesn't exist + // serverless only (stateful will allow) + it(`should not update a role with remote index privileges`, async function () { + await es.security.putRole({ + name: 'role_to_update', + body: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'apm', + privileges: ['apm-privilege'], + resources: ['*'], + }, + ], + metadata: { + bar: 'old-metadata', + }, + }, + }); + + await supertestAdminWithApiKey + .put('/api/security/role/role_to_update') + .send({ + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + remote_indices: [ + { + clusters: ['remote_cluster1'], + names: ['remote_index1', 'remote_index2'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + feature: { + dashboard: ['read'], + dev_tools: ['all'], + }, + spaces: ['*'], + }, + { + base: ['all'], + spaces: ['marketing', 'sales'], + }, + ], + }) + .expect(400); + + const role = await es.security.getRole({ name: 'role_to_update' }); + expect(role).toEqual({ + role_to_update: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + allow_restricted_indices: false, + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'apm', + privileges: ['apm-privilege'], + resources: ['*'], + }, + ], + metadata: { + bar: 'old-metadata', + }, + run_as: [], + transient_metadata: { + enabled: true, + }, + }, + }); + }); + }); + + describe('Delete Role', function () { + it('should delete an existing role', async function () { + await es.security.putRole({ + name: 'role_to_delete', + body: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'apm', + privileges: ['apm-privilege'], + resources: ['*'], + }, + ], + metadata: { + bar: 'old-metadata', + }, + }, + }); + await supertestAdminWithApiKey.delete('/api/security/role/role_to_delete').expect(204); + + const deletedRole = await es.security.getRole( + { name: 'role_to_delete' }, + { ignore: [404] } ); - svlCommonApi.assertApiNotFound(body, status); + expect(deletedRole).toEqual({}); }); + }); + }); - it('get shared saved object permissions', async () => { + describe('route access', function () { + describe('disabled', function () { + it('get shared saved object permissions', async function () { const { body, status } = await supertestAdminWithCookieCredentials.get( '/internal/security/_share_saved_object_permissions' ); svlCommonApi.assertApiNotFound(body, status); }); + + describe('oblt only', function () { + // custom roles are not enabled for observability projects + this.tags(['skipSvlSearch', 'skipSvlSec']); + + it('create/update role', async function () { + const { body, status } = await supertestAdminWithApiKey.put('/api/security/role/test'); + svlCommonApi.assertApiNotFound(body, status); + }); + + it('get role', async function () { + const { body, status } = await supertestAdminWithApiKey.get( + '/api/security/role/superuser' + ); + svlCommonApi.assertApiNotFound(body, status); + }); + + it('get all roles', async function () { + const { body, status } = await supertestAdminWithApiKey.get('/api/security/role'); + svlCommonApi.assertApiNotFound(body, status); + }); + + it('delete role', async function () { + const { body, status } = await supertestAdminWithApiKey.delete( + '/api/security/role/superuser' + ); + svlCommonApi.assertApiNotFound(body, status); + }); + + it('get all privileges', async function () { + const { body, status } = await supertestAdminWithApiKey.get('/api/security/privileges'); + svlCommonApi.assertApiNotFound(body, status); + }); + + it('get built-in elasticsearch privileges', async function () { + const { body, status } = await supertestAdminWithCookieCredentials.get( + '/internal/security/esPrivileges/builtin' + ); + svlCommonApi.assertApiNotFound(body, status); + }); + }); }); - describe('public', () => { - it('reset session page', async () => { + describe('public', function () { + // Public but undocumented, hence 'internal' in path + it('reset session page', async function () { const { status } = await supertestAdminWithCookieCredentials.get( '/internal/security/reset_session_page.js' ); expect(status).toBe(200); }); }); + + describe('custom roles', function () { + // custom roles are not enabled for observability projects + this.tags(['skipSvlOblt']); + + describe('internal', function () { + it('get built-in elasticsearch privileges', async function () { + let body: any; + let status: number; + + ({ body, status } = await supertestAdminWithCookieCredentials + .get('/internal/security/esPrivileges/builtin') + .set(svlCommonApi.getCommonRequestHeader())); + // expect a rejection because we're not using the internal header + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: expect.stringContaining( + 'method [get] exists but is not available with the current configuration' + ), + }); + expect(status).toBe(400); + + ({ status } = await supertestAdminWithCookieCredentials.get( + '/internal/security/esPrivileges/builtin' + )); + expect(status).toBe(400); + + // expect success when using the internal header + ({ body, status } = await supertestAdminWithCookieCredentials + .get('/internal/security/esPrivileges/builtin') + .set(svlCommonApi.getInternalRequestHeader())); + expect(status).toBe(200); + }); + }); + + describe('public', function () { + it('get all privileges', async function () { + const { status } = await supertestAdminWithApiKey + .get('/api/security/privileges') + .set(svlCommonApi.getInternalRequestHeader()); + expect(status).toBe(200); + }); + }); + }); }); - describe('available features', () => { - it('all Dashboard and Discover sub-feature privileges are disabled', async () => { + describe('available features', function () { + it('all Dashboard and Discover sub-feature privileges are disabled', async function () { const { body } = await supertestAdminWithCookieCredentials.get('/api/features').expect(200); // We should make sure that neither Discover nor Dashboard displays any sub-feature privileges in Serverless. diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/roles_routes_feature_flag.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/roles_routes_feature_flag.ts deleted file mode 100644 index 7f2237eda4b44..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/roles_routes_feature_flag.ts +++ /dev/null @@ -1,951 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from 'expect'; -import type { Role } from '@kbn/security-plugin-types-common'; -import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -// Notes: -// Test coverage comes from stateful test suite: x-pack/test/api_integration/apis/security/roles.ts -// It has been modified to work for serverless by removing invalid options (run_as, allow_restricted_indices, etc). -// -// Note: this suite is currently only called from the feature flags test configs, e.g. -// x-pack/test_serverless/api_integration/test_suites/search/config.feature_flags.ts -// -// This suite should be converted into a deployment agnostic suite when the native roles -// feature flags are enabled permanently in serverless. Additionally, the route access tests -// for the roles APIs in authorization.ts should also get updated at that time. -// kbnServerArgs: ['--xpack.security.roleManagementEnabled=true'], -// esServerArgs: ['xpack.security.authc.native_roles.enabled=true'], - -export default function ({ getService }: FtrProviderContext) { - const platformSecurityUtils = getService('platformSecurityUtils'); - const roleScopedSupertest = getService('roleScopedSupertest'); - const svlCommonApi = getService('svlCommonApi'); - let supertestAdminWithApiKey: SupertestWithRoleScopeType; - let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType; - const es = getService('es'); - - describe('security', function () { - describe('Roles', () => { - before(async () => { - supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( - 'admin', - { - useCookieHeader: true, - withInternalHeaders: true, - } - ); - supertestAdminWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('admin', { - withCommonHeaders: true, - }); - }); - after(async () => { - await platformSecurityUtils.clearAllRoles(); - }); - - describe('Create Role', () => { - it('should allow us to create an empty role', async () => { - await supertestAdminWithApiKey.put('/api/security/role/empty_role').send({}).expect(204); - }); - - it('should create a role with kibana and elasticsearch privileges', async () => { - await supertestAdminWithApiKey - .put('/api/security/role/role_with_privileges') - .send({ - metadata: { - foo: 'test-metadata', - }, - elasticsearch: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - }, - kibana: [ - { - base: ['read'], - }, - { - feature: { - dashboard: ['read'], - discover: ['all'], - ml: ['all'], - }, - spaces: ['marketing', 'sales'], - }, - ], - }) - .expect(204); - - const role = await es.security.getRole({ name: 'role_with_privileges' }); - expect(role).toEqual({ - role_with_privileges: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - allow_restricted_indices: false, - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read'], - resources: ['*'], - }, - { - application: 'kibana-.kibana', - privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], - resources: ['space:marketing', 'space:sales'], - }, - ], - metadata: { - foo: 'test-metadata', - }, - run_as: [], - transient_metadata: { - enabled: true, - }, - }, - }); - }); - - it(`should create a role with kibana and FLS/DLS elasticsearch privileges`, async () => { - await supertestAdminWithApiKey - .put('/api/security/role/role_with_privileges_dls_fls') - .send({ - metadata: { - foo: 'test-metadata', - }, - elasticsearch: { - cluster: ['manage'], - indices: [ - { - field_security: { - grant: ['*'], - except: ['geo.*'], - }, - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - query: `{ "match": { "geo.src": "CN" } }`, - }, - ], - }, - }) - .expect(204); - }); - - // serverless only (stateful will allow) - it(`should not create a role with 'run as' privileges`, async () => { - await supertestAdminWithApiKey - .put('/api/security/role/role_with_privileges') - .send({ - metadata: { - foo: 'test-metadata', - }, - elasticsearch: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - run_as: ['admin'], - }, - kibana: [ - { - base: ['read'], - }, - { - feature: { - dashboard: ['read'], - discover: ['all'], - ml: ['all'], - }, - spaces: ['marketing', 'sales'], - }, - ], - }) - .expect(400); - }); - - // serverless only (stateful will allow) - it(`should not create a role with remote cluster privileges`, async () => { - await supertestAdminWithApiKey - .put('/api/security/role/role_with_privileges') - .send({ - metadata: { - foo: 'test-metadata', - }, - elasticsearch: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - remote_cluster: [ - { - clusters: ['remote_cluster1'], - privileges: ['monitor_enrich'], - }, - ], - }, - kibana: [ - { - base: ['read'], - }, - { - feature: { - dashboard: ['read'], - discover: ['all'], - ml: ['all'], - }, - spaces: ['marketing', 'sales'], - }, - ], - }) - .expect(400); - }); - - // serverless only (stateful will allow) - it(`should not create a role with remote index privileges`, async () => { - await supertestAdminWithApiKey - .put('/api/security/role/role_with_privileges') - .send({ - metadata: { - foo: 'test-metadata', - }, - elasticsearch: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - remote_indices: [ - { - clusters: ['remote_cluster1'], - names: ['remote_index1', 'remote_index2'], - privileges: ['all'], - }, - ], - }, - kibana: [ - { - base: ['read'], - }, - { - feature: { - dashboard: ['read'], - discover: ['all'], - ml: ['all'], - }, - spaces: ['marketing', 'sales'], - }, - ], - }) - .expect(400); - }); - - describe('with the createOnly option enabled', () => { - it('should fail when role already exists', async () => { - await es.security.putRole({ - name: 'test_role', - body: { - cluster: ['monitor'], - indices: [ - { - names: ['beats-*'], - privileges: ['write'], - }, - ], - }, - }); - - await supertestAdminWithApiKey - .put('/api/security/role/test_role?createOnly=true') - .send({}) - .expect(409); - }); - - it('should succeed when role does not exist', async () => { - await supertestAdminWithApiKey - .put('/api/security/role/new_role?createOnly=true') - .send({}) - .expect(204); - }); - }); - }); - - describe('Read Role', () => { - it('should get roles', async () => { - await es.security.putRole({ - name: 'role_to_get', - body: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read'], - resources: ['*'], - }, - { - application: 'kibana-.kibana', - privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], - resources: ['space:marketing', 'space:sales'], - }, - { - application: 'apm', - privileges: ['apm-privilege'], - resources: ['*'], - }, - ], - metadata: { - foo: 'test-metadata', - }, - transient_metadata: { - enabled: true, - }, - }, - }); - - await supertestAdminWithApiKey.get('/api/security/role/role_to_get').expect(200, { - name: 'role_to_get', - metadata: { - foo: 'test-metadata', - }, - transient_metadata: { enabled: true }, - elasticsearch: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - allow_restricted_indices: false, - }, - ], - run_as: [], - }, - kibana: [ - { - base: ['read'], - feature: {}, - spaces: ['*'], - }, - { - base: [], - feature: { - dashboard: ['read'], - discover: ['all'], - ml: ['all'], - }, - spaces: ['marketing', 'sales'], - }, - ], - - _transform_error: [], - _unrecognized_applications: ['apm'], - }); - }); - - it('should get roles by space id', async () => { - await es.security.putRole({ - name: 'space_role_not_to_get', - body: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], - resources: ['space:marketing', 'space:sales'], - }, - ], - metadata: { - foo: 'test-metadata', - }, - transient_metadata: { - enabled: true, - }, - }, - }); - - await es.security.putRole({ - name: 'space_role_to_get', - body: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], - resources: ['space:engineering', 'space:sales'], - }, - ], - metadata: { - foo: 'test-metadata', - }, - transient_metadata: { - enabled: true, - }, - }, - }); - - await supertestAdminWithCookieCredentials - .get('/internal/security/roles/engineering') - .expect(200) - .expect((res: { body: Role[] }) => { - const roles = res.body; - - const success = roles.every((role) => { - return ( - role.name !== 'space_role_not_to_get' && - role.kibana.some((privilege) => { - return ( - privilege.spaces.includes('*') || privilege.spaces.includes('engineering') - ); - }) - ); - }); - - const expectedRole = roles.find((role) => role.name === 'space_role_to_get'); - - expect(success).toBe(true); - expect(expectedRole).toBeTruthy(); - }); - }); - }); - - describe('Update Role', () => { - it('should update a role with elasticsearch, kibana and other applications privileges', async () => { - await es.security.putRole({ - name: 'role_to_update', - body: { - cluster: ['monitor'], - indices: [ - { - names: ['beats-*'], - privileges: ['write'], - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read'], - resources: ['*'], - }, - { - application: 'apm', - privileges: ['apm-privilege'], - resources: ['*'], - }, - ], - metadata: { - bar: 'old-metadata', - }, - }, - }); - - await supertestAdminWithApiKey - .put('/api/security/role/role_to_update') - .send({ - metadata: { - foo: 'test-metadata', - }, - elasticsearch: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - }, - kibana: [ - { - feature: { - dashboard: ['read'], - dev_tools: ['all'], - }, - spaces: ['*'], - }, - { - base: ['all'], - spaces: ['marketing', 'sales'], - }, - ], - }) - .expect(204); - - const role = await es.security.getRole({ name: 'role_to_update' }); - expect(role).toEqual({ - role_to_update: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - allow_restricted_indices: false, - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['feature_dashboard.read', 'feature_dev_tools.all'], - resources: ['*'], - }, - { - application: 'kibana-.kibana', - privileges: ['space_all'], - resources: ['space:marketing', 'space:sales'], - }, - { - application: 'apm', - privileges: ['apm-privilege'], - resources: ['*'], - }, - ], - metadata: { - foo: 'test-metadata', - }, - run_as: [], - transient_metadata: { - enabled: true, - }, - }, - }); - }); - - it(`should update a role adding DLS and FLS privileges`, async () => { - await es.security.putRole({ - name: 'role_to_update_with_dls_fls', - body: { - cluster: ['monitor'], - indices: [ - { - names: ['beats-*'], - privileges: ['write'], - }, - ], - }, - }); - - await supertestAdminWithApiKey - .put('/api/security/role/role_to_update_with_dls_fls') - .send({ - elasticsearch: { - cluster: ['manage'], - indices: [ - { - field_security: { - grant: ['*'], - except: ['geo.*'], - }, - names: ['logstash-*'], - privileges: ['read'], - query: `{ "match": { "geo.src": "CN" } }`, - }, - ], - }, - }) - .expect(204); - - const role = await es.security.getRole({ name: 'role_to_update_with_dls_fls' }); - - expect(role.role_to_update_with_dls_fls.cluster).toEqual(['manage']); - expect(role.role_to_update_with_dls_fls.indices[0].names).toEqual(['logstash-*']); - expect(role.role_to_update_with_dls_fls.indices[0].query).toEqual( - `{ "match": { "geo.src": "CN" } }` - ); - }); - - // serverless only (stateful will allow) - it(`should not update a role with 'run as' privileges`, async () => { - await es.security.putRole({ - name: 'role_to_update', - body: { - cluster: ['monitor'], - indices: [ - { - names: ['beats-*'], - privileges: ['write'], - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read'], - resources: ['*'], - }, - { - application: 'apm', - privileges: ['apm-privilege'], - resources: ['*'], - }, - ], - metadata: { - bar: 'old-metadata', - }, - }, - }); - - await supertestAdminWithApiKey - .put('/api/security/role/role_to_update') - .send({ - metadata: { - foo: 'test-metadata', - }, - elasticsearch: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - run_as: ['admin'], - }, - kibana: [ - { - feature: { - dashboard: ['read'], - dev_tools: ['all'], - }, - spaces: ['*'], - }, - { - base: ['all'], - spaces: ['marketing', 'sales'], - }, - ], - }) - .expect(400); - - const role = await es.security.getRole({ name: 'role_to_update' }); - expect(role).toEqual({ - role_to_update: { - cluster: ['monitor'], - indices: [ - { - names: ['beats-*'], - privileges: ['write'], - allow_restricted_indices: false, - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read'], - resources: ['*'], - }, - { - application: 'apm', - privileges: ['apm-privilege'], - resources: ['*'], - }, - ], - metadata: { - bar: 'old-metadata', - }, - run_as: [], - transient_metadata: { - enabled: true, - }, - }, - }); - }); - - // serverless only (stateful will allow) - it(`should not update a role with remote cluster privileges`, async () => { - await es.security.putRole({ - name: 'role_to_update', - body: { - cluster: ['monitor'], - indices: [ - { - names: ['beats-*'], - privileges: ['write'], - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read'], - resources: ['*'], - }, - { - application: 'apm', - privileges: ['apm-privilege'], - resources: ['*'], - }, - ], - metadata: { - bar: 'old-metadata', - }, - }, - }); - - await supertestAdminWithApiKey - .put('/api/security/role/role_to_update') - .send({ - metadata: { - foo: 'test-metadata', - }, - elasticsearch: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - remote_cluster: [ - { - clusters: ['remote_cluster1'], - privileges: ['monitor_enrich'], - }, - ], - }, - kibana: [ - { - feature: { - dashboard: ['read'], - dev_tools: ['all'], - }, - spaces: ['*'], - }, - { - base: ['all'], - spaces: ['marketing', 'sales'], - }, - ], - }) - .expect(400); - - const role = await es.security.getRole({ name: 'role_to_update' }); - expect(role).toEqual({ - role_to_update: { - cluster: ['monitor'], - indices: [ - { - names: ['beats-*'], - privileges: ['write'], - allow_restricted_indices: false, - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read'], - resources: ['*'], - }, - { - application: 'apm', - privileges: ['apm-privilege'], - resources: ['*'], - }, - ], - metadata: { - bar: 'old-metadata', - }, - run_as: [], - transient_metadata: { - enabled: true, - }, - }, - }); - }); - - // serverless only (stateful will allow) - it(`should not update a role with remote index privileges`, async () => { - await es.security.putRole({ - name: 'role_to_update', - body: { - cluster: ['monitor'], - indices: [ - { - names: ['beats-*'], - privileges: ['write'], - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read'], - resources: ['*'], - }, - { - application: 'apm', - privileges: ['apm-privilege'], - resources: ['*'], - }, - ], - metadata: { - bar: 'old-metadata', - }, - }, - }); - - await supertestAdminWithApiKey - .put('/api/security/role/role_to_update') - .send({ - metadata: { - foo: 'test-metadata', - }, - elasticsearch: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - remote_indices: [ - { - clusters: ['remote_cluster1'], - names: ['remote_index1', 'remote_index2'], - privileges: ['all'], - }, - ], - }, - kibana: [ - { - feature: { - dashboard: ['read'], - dev_tools: ['all'], - }, - spaces: ['*'], - }, - { - base: ['all'], - spaces: ['marketing', 'sales'], - }, - ], - }) - .expect(400); - - const role = await es.security.getRole({ name: 'role_to_update' }); - expect(role).toEqual({ - role_to_update: { - cluster: ['monitor'], - indices: [ - { - names: ['beats-*'], - privileges: ['write'], - allow_restricted_indices: false, - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read'], - resources: ['*'], - }, - { - application: 'apm', - privileges: ['apm-privilege'], - resources: ['*'], - }, - ], - metadata: { - bar: 'old-metadata', - }, - run_as: [], - transient_metadata: { - enabled: true, - }, - }, - }); - }); - }); - - describe('Delete Role', () => { - it('should delete an existing role', async () => { - await es.security.putRole({ - name: 'role_to_delete', - body: { - cluster: ['monitor'], - indices: [ - { - names: ['beats-*'], - privileges: ['write'], - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read'], - resources: ['*'], - }, - { - application: 'apm', - privileges: ['apm-privilege'], - resources: ['*'], - }, - ], - metadata: { - bar: 'old-metadata', - }, - }, - }); - await supertestAdminWithApiKey.delete('/api/security/role/role_to_delete').expect(204); - - const deletedRole = await es.security.getRole( - { name: 'role_to_delete' }, - { ignore: [404] } - ); - expect(deletedRole).toEqual({}); - }); - }); - - describe('Access', () => { - describe('public', () => { - it('reset session page', async () => { - const { status } = await supertestAdminWithCookieCredentials.get( - '/internal/security/reset_session_page.js' - ); - expect(status).toBe(200); - }); - }); - describe('Disabled', () => { - it('get shared saved object permissions', async () => { - const { body, status } = await supertestAdminWithCookieCredentials.get( - '/internal/security/_share_saved_object_permissions' - ); - svlCommonApi.assertApiNotFound(body, status); - }); - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/config.feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/observability/config.feature_flags.ts index 20724f4f9ae16..3668b6742c828 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/config.feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/config.feature_flags.ts @@ -22,13 +22,12 @@ export default createTestConfig({ // add feature flags kbnServerArgs: [ '--xpack.infra.enabled=true', - '--xpack.security.roleManagementEnabled=true', // enables custom roles - `--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities + '--xpack.security.roleManagementEnabled=true', // needed to check composite feautures in /observability/platform_security/authorization.ts ], // load tests in the index file testFiles: [require.resolve('./index.feature_flags.ts')], // include settings from project controller // https://github.com/elastic/project-controller/blob/main/internal/project/observability/config/elasticsearch.yml - esServerArgs: ['xpack.ml.dfa.enabled=false', 'xpack.security.authc.native_roles.enabled=true'], + esServerArgs: ['xpack.ml.dfa.enabled=false'], }); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts index 44ac3675266f7..3cd47636831f3 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts @@ -12,7 +12,5 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./custom_threshold_rule')); loadTestFile(require.resolve('./infra')); loadTestFile(require.resolve('./platform_security')); - loadTestFile(require.resolve('../common/platform_security/roles_routes_feature_flag.ts')); - loadTestFile(require.resolve('../common/management/multiple_spaces_enabled.ts')); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/search/config.feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/search/config.feature_flags.ts index 09de0f17e6b63..9dac5bfb595c9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/search/config.feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/search/config.feature_flags.ts @@ -19,8 +19,6 @@ export default createTestConfig({ suiteTags: { exclude: ['skipSvlSearch'] }, // add feature flags kbnServerArgs: [ - '--xpack.security.roleManagementEnabled=true', // enables custom roles - `--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities `--xpack.searchIndices.enabled=true`, // global empty state FF ], // load tests in the index file @@ -28,5 +26,5 @@ export default createTestConfig({ // include settings from project controller // https://github.com/elastic/project-controller/blob/main/internal/project/esproject/config/elasticsearch.yml - esServerArgs: ['xpack.security.authc.native_roles.enabled=true'], + esServerArgs: [], }); diff --git a/x-pack/test_serverless/api_integration/test_suites/search/index.feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/search/index.feature_flags.ts index acd435d9d29ae..bbfbc47fe9184 100644 --- a/x-pack/test_serverless/api_integration/test_suites/search/index.feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/search/index.feature_flags.ts @@ -10,8 +10,5 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Serverless search API - feature flags', function () { loadTestFile(require.resolve('./search_indices')); - loadTestFile(require.resolve('./platform_security')); - loadTestFile(require.resolve('../common/platform_security/roles_routes_feature_flag.ts')); - loadTestFile(require.resolve('../common/management/multiple_spaces_enabled.ts')); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/search/index.ts b/x-pack/test_serverless/api_integration/test_suites/search/index.ts index b568e75960951..42b8d0dd90435 100644 --- a/x-pack/test_serverless/api_integration/test_suites/search/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/search/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./cases/find_cases')); loadTestFile(require.resolve('./cases/post_case')); loadTestFile(require.resolve('./serverless_search')); + loadTestFile(require.resolve('./platform_security')); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/security/config.feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/security/config.feature_flags.ts index eb6270bab7ce1..6f6404ad497cf 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/config.feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/config.feature_flags.ts @@ -18,14 +18,11 @@ export default createTestConfig({ }, suiteTags: { exclude: ['skipSvlSec'] }, // add feature flags - kbnServerArgs: [ - '--xpack.security.roleManagementEnabled=true', // enables custom roles - `--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities - ], + kbnServerArgs: [], // load tests in the index file testFiles: [require.resolve('./index.feature_flags.ts')], // include settings from project controller // https://github.com/elastic/project-controller/blob/main/internal/project/security/config/elasticsearch.yml - esServerArgs: ['xpack.ml.nlp.enabled=true', 'xpack.security.authc.native_roles.enabled=true'], + esServerArgs: ['xpack.ml.nlp.enabled=true'], }); diff --git a/x-pack/test_serverless/api_integration/test_suites/security/index.feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/security/index.feature_flags.ts index 5c591f3213149..de4c823dbbb62 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/index.feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/index.feature_flags.ts @@ -8,9 +8,5 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('Serverless security API - feature flags', function () { - loadTestFile(require.resolve('./platform_security')); - loadTestFile(require.resolve('../common/platform_security/roles_routes_feature_flag.ts')); - loadTestFile(require.resolve('../common/management/multiple_spaces_enabled.ts')); - }); + describe('Serverless security API - feature flags', function () {}); } diff --git a/x-pack/test_serverless/api_integration/test_suites/security/index.ts b/x-pack/test_serverless/api_integration/test_suites/security/index.ts index a7cb3cea71049..98dbf046bac94 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/index.ts @@ -13,5 +13,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./cases')); loadTestFile(require.resolve('./cloud_security_posture')); + loadTestFile(require.resolve('./platform_security')); }); } diff --git a/x-pack/test_serverless/functional/config.base.ts b/x-pack/test_serverless/functional/config.base.ts index 1a3cd2ffd6a5b..f904c53195dcc 100644 --- a/x-pack/test_serverless/functional/config.base.ts +++ b/x-pack/test_serverless/functional/config.base.ts @@ -41,10 +41,6 @@ export function createTestConfig(options: CreateTestConfigOptions) { `--xpack.trigger_actions_ui.enableExperimental=${JSON.stringify([ 'isUsingRuleCreateFlyout', ])}`, - // custom native roles are enabled only for search and security projects - ...(options.serverlessProject !== 'oblt' - ? ['--xpack.security.roleManagementEnabled=true'] - : []), ...(options.kbnServerArgs ?? []), ], }, diff --git a/x-pack/test_serverless/functional/test_suites/common/platform_security/index.ts b/x-pack/test_serverless/functional/test_suites/common/platform_security/index.ts index 062aef5b9acfb..5f96ea70aee73 100644 --- a/x-pack/test_serverless/functional/test_suites/common/platform_security/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/platform_security/index.ts @@ -13,6 +13,8 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./api_keys')); loadTestFile(require.resolve('./navigation/avatar_menu')); + loadTestFile(require.resolve('./navigation/management_nav_cards')); loadTestFile(require.resolve('./user_profiles/user_profiles')); + loadTestFile(require.resolve('./roles.ts')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/common/platform_security/navigation/management_nav_cards.ts b/x-pack/test_serverless/functional/test_suites/common/platform_security/navigation/management_nav_cards.ts index c9aa658cdc44f..1dfa1a5dff79c 100644 --- a/x-pack/test_serverless/functional/test_suites/common/platform_security/navigation/management_nav_cards.ts +++ b/x-pack/test_serverless/functional/test_suites/common/platform_security/navigation/management_nav_cards.ts @@ -46,16 +46,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(url).to.contain('/management/security/api_keys'); }); - it('displays the roles management card, and will navigate to the Roles UI', async () => { - await pageObjects.svlManagementPage.assertRoleManagementCardExists(); - await pageObjects.svlManagementPage.clickRoleManagementCard(); + describe('custom roles', function () { + this.tags('skipSvlOblt'); // Observability will not support custom roles - const url = await browser.getCurrentUrl(); - expect(url).to.contain('/management/security/roles'); - }); + it('displays the roles management card, and will navigate to the Roles UI', async () => { + await pageObjects.svlManagementPage.assertRoleManagementCardExists(); + await pageObjects.svlManagementPage.clickRoleManagementCard(); + + const url = await browser.getCurrentUrl(); + expect(url).to.contain('/management/security/roles'); + }); - describe('Organization members', function () { - this.tags('skipSvlOblt'); // Observability will not support custom roles it('displays the Organization members management card, and will navigate to the cloud organization URL', async () => { await pageObjects.svlManagementPage.assertOrgMembersManagementCardExists(); await pageObjects.svlManagementPage.clickOrgMembersManagementCard(); diff --git a/x-pack/test_serverless/functional/test_suites/common/platform_security/roles.ts b/x-pack/test_serverless/functional/test_suites/common/platform_security/roles.ts index a57f82f158ebf..c402ad42f4fca 100644 --- a/x-pack/test_serverless/functional/test_suites/common/platform_security/roles.ts +++ b/x-pack/test_serverless/functional/test_suites/common/platform_security/roles.ts @@ -19,6 +19,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const platformSecurityUtils = getService('platformSecurityUtils'); describe('Roles', function () { + // custom roles are not enabled for observability projects + this.tags(['skipSvlOblt']); + describe('as Viewer', () => { before(async () => { await pageObjects.svlCommonPage.loginAsViewer(); diff --git a/x-pack/test_serverless/functional/test_suites/common/spaces/index.ts b/x-pack/test_serverless/functional/test_suites/common/spaces/index.ts index e9648b0339ac3..48dcebf486618 100644 --- a/x-pack/test_serverless/functional/test_suites/common/spaces/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/spaces/index.ts @@ -11,6 +11,7 @@ export default ({ loadTestFile }: FtrProviderContext) => { describe('Spaces', function () { this.tags(['esGate']); + loadTestFile(require.resolve('./spaces_management.ts')); loadTestFile(require.resolve('./spaces_selection.ts')); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/common/spaces/multiple_spaces_enabled.ts b/x-pack/test_serverless/functional/test_suites/common/spaces/spaces_management.ts similarity index 58% rename from x-pack/test_serverless/functional/test_suites/common/spaces/multiple_spaces_enabled.ts rename to x-pack/test_serverless/functional/test_suites/common/spaces/spaces_management.ts index 84c7291e6e7ad..49ca03e5861e7 100644 --- a/x-pack/test_serverless/functional/test_suites/common/spaces/multiple_spaces_enabled.ts +++ b/x-pack/test_serverless/functional/test_suites/common/spaces/spaces_management.ts @@ -15,51 +15,9 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObject, getService }: FtrProviderContext) { const svlCommon = getPageObject('common'); const svlCommonPage = getPageObject('svlCommonPage'); - const svlCommonNavigation = getService('svlCommonNavigation'); const testSubjects = getService('testSubjects'); describe('spaces', function () { - describe('selection', function () { - describe('as Viewer', function () { - before(async () => { - await svlCommonPage.loginAsViewer(); - }); - - it('displays the space selection menu in header', async () => { - await svlCommonNavigation.navigateToKibanaHome(); - await svlCommonPage.assertProjectHeaderExists(); - - await testSubjects.existOrFail('spacesNavSelector'); - }); - - it(`does not display the manage button in the space selection menu`, async () => { - await svlCommonNavigation.navigateToKibanaHome(); - await svlCommonPage.assertProjectHeaderExists(); - await testSubjects.click('spacesNavSelector'); - await testSubjects.missingOrFail('manageSpaces'); - }); - }); - - describe('as Admin', function () { - before(async () => { - await svlCommonPage.loginAsAdmin(); - }); - - it('displays the space selection menu in header', async () => { - await svlCommonNavigation.navigateToKibanaHome(); - await svlCommonPage.assertProjectHeaderExists(); - await testSubjects.existOrFail('spacesNavSelector'); - }); - - it(`displays the manage button in the space selection menu`, async () => { - await svlCommonNavigation.navigateToKibanaHome(); - await svlCommonPage.assertProjectHeaderExists(); - await testSubjects.click('spacesNavSelector'); - await testSubjects.existOrFail('manageSpaces'); - }); - }); - }); - describe('management', function () { describe('as Viewer', function () { before(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/common/spaces/spaces_selection.ts b/x-pack/test_serverless/functional/test_suites/common/spaces/spaces_selection.ts index 526d0b3db5a41..a903d8778b7d8 100644 --- a/x-pack/test_serverless/functional/test_suites/common/spaces/spaces_selection.ts +++ b/x-pack/test_serverless/functional/test_suites/common/spaces/spaces_selection.ts @@ -12,18 +12,46 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const svlCommonNavigation = getService('svlCommonNavigation'); const testSubjects = getService('testSubjects'); - // Skipped due to change in QA environment for role management and spaces - // TODO: revisit once the change is rolled out to all environments - describe.skip('space selection', function () { - before(async () => { - await svlCommonPage.loginAsViewer(); - }); + describe('spaces', function () { + describe('selection', function () { + describe('as Viewer', function () { + before(async () => { + await svlCommonPage.loginAsViewer(); + }); + + it('displays the space selection menu in header', async () => { + await svlCommonNavigation.navigateToKibanaHome(); + await svlCommonPage.assertProjectHeaderExists(); + + await testSubjects.existOrFail('spacesNavSelector'); + }); + + it(`does not display the manage button in the space selection menu`, async () => { + await svlCommonNavigation.navigateToKibanaHome(); + await svlCommonPage.assertProjectHeaderExists(); + await testSubjects.click('spacesNavSelector'); + await testSubjects.missingOrFail('manageSpaces'); + }); + }); + + describe('as Admin', function () { + before(async () => { + await svlCommonPage.loginAsAdmin(); + }); - it('does not have the space selection menu in header', async () => { - await svlCommonNavigation.navigateToKibanaHome(); - await svlCommonPage.assertProjectHeaderExists(); + it('displays the space selection menu in header', async () => { + await svlCommonNavigation.navigateToKibanaHome(); + await svlCommonPage.assertProjectHeaderExists(); + await testSubjects.existOrFail('spacesNavSelector'); + }); - await testSubjects.missingOrFail('spacesNavSelector'); + it(`displays the manage button in the space selection menu`, async () => { + await svlCommonNavigation.navigateToKibanaHome(); + await svlCommonPage.assertProjectHeaderExists(); + await testSubjects.click('spacesNavSelector'); + await testSubjects.existOrFail('manageSpaces'); + }); + }); }); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts index 8d85455f4588a..ba07c22e6ab01 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts @@ -22,10 +22,6 @@ export default createTestConfig({ '--xpack.infra.enabled=true', '--xpack.infra.featureFlags.customThresholdAlertsEnabled=true', '--xpack.security.roleManagementEnabled=true', - `--xpack.cloud.serverless.project_id='fakeprojectid'`, - `--xpack.cloud.base_url='https://cloud.elastic.co'`, - `--xpack.cloud.organization_url='/account/members'`, - `--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities ], // load tests in the index file testFiles: [require.resolve('./index.feature_flags.ts')], diff --git a/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts index 1f087233b52e9..df3c826c20813 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts @@ -12,8 +12,5 @@ export default function ({ loadTestFile }: FtrProviderContext) { // add tests that require feature flags, defined in config.feature_flags.ts loadTestFile(require.resolve('./role_management')); loadTestFile(require.resolve('./infra')); - loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts')); - loadTestFile(require.resolve('../common/platform_security/roles.ts')); - loadTestFile(require.resolve('../common/spaces/multiple_spaces_enabled.ts')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts index 592da3d368c0d..b7c818821d36c 100644 --- a/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts @@ -20,19 +20,13 @@ export default createTestConfig({ // add feature flags kbnServerArgs: [ `--xpack.cloud.id=ES3_FTR_TESTS:ZmFrZS1kb21haW4uY2xkLmVsc3RjLmNvJGZha2Vwcm9qZWN0aWQuZXMkZmFrZXByb2plY3RpZC5rYg==`, - `--xpack.cloud.serverless.project_id=fakeprojectid`, - `--xpack.cloud.base_url=https://fake-cloud.elastic.co`, - `--xpack.cloud.projects_url=/projects/`, - `--xpack.cloud.organization_url=/account/members`, - `--xpack.security.roleManagementEnabled=true`, - `--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities ], // load tests in the index file testFiles: [require.resolve('./index.feature_flags.ts')], // include settings from project controller // https://github.com/elastic/project-controller/blob/main/internal/project/esproject/config/elasticsearch.yml - esServerArgs: ['xpack.security.authc.native_roles.enabled=true'], + esServerArgs: [], apps: { serverlessElasticsearch: { pathname: '/app/elasticsearch/getting_started', diff --git a/x-pack/test_serverless/functional/test_suites/search/config.ts b/x-pack/test_serverless/functional/test_suites/search/config.ts index b01c80ec2dbb7..f330546373525 100644 --- a/x-pack/test_serverless/functional/test_suites/search/config.ts +++ b/x-pack/test_serverless/functional/test_suites/search/config.ts @@ -20,16 +20,8 @@ export default createTestConfig({ esServerArgs: [], kbnServerArgs: [ `--xpack.cloud.id=ES3_FTR_TESTS:ZmFrZS1kb21haW4uY2xkLmVsc3RjLmNvJGZha2Vwcm9qZWN0aWQuZXMkZmFrZXByb2plY3RpZC5rYg==`, - `--xpack.cloud.serverless.project_id=fakeprojectid`, `--xpack.cloud.serverless.project_name=ES3_FTR_TESTS`, - `--xpack.cloud.base_url=https://fake-cloud.elastic.co`, - `--xpack.cloud.profile_url=/user/settings/`, - `--xpack.cloud.billing_url=/billing/overview/`, - `--xpack.cloud.deployments_url=/deployments`, `--xpack.cloud.deployment_url=/projects/elasticsearch/fakeprojectid`, - `--xpack.cloud.users_and_roles_url=/account/members/`, - `--xpack.cloud.projects_url=/projects/`, - `--xpack.cloud.organization_url=/account/`, ], apps: { serverlessElasticsearch: { diff --git a/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts index bc9e19f2ae71f..f33776926bd26 100644 --- a/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts @@ -10,9 +10,5 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('serverless search UI - feature flags', function () { // add tests that require feature flags, defined in config.feature_flags.ts - - loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts')); - loadTestFile(require.resolve('../common/platform_security/roles.ts')); - loadTestFile(require.resolve('../common/spaces/multiple_spaces_enabled.ts')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts index 84e80f154bee9..081887cae2380 100644 --- a/x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts @@ -18,16 +18,11 @@ export default createTestConfig({ }, suiteTags: { exclude: ['skipSvlSec'] }, // add feature flags - kbnServerArgs: [ - `--xpack.security.roleManagementEnabled=true`, - `--xpack.cloud.base_url='https://cloud.elastic.co'`, - `--xpack.cloud.organization_url='/account/members'`, - `--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities - ], + kbnServerArgs: [], // load tests in the index file testFiles: [require.resolve('./index.feature_flags.ts')], // include settings from project controller // https://github.com/elastic/project-controller/blob/main/internal/project/security/config/elasticsearch.yml - esServerArgs: ['xpack.ml.nlp.enabled=true', 'xpack.security.authc.native_roles.enabled=true'], + esServerArgs: ['xpack.ml.nlp.enabled=true'], }); diff --git a/x-pack/test_serverless/functional/test_suites/security/index.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/security/index.feature_flags.ts index 212632be442d3..45849d6063231 100644 --- a/x-pack/test_serverless/functional/test_suites/security/index.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/security/index.feature_flags.ts @@ -10,8 +10,5 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('serverless security UI - feature flags', function () { // add tests that require feature flags, defined in config.feature_flags.ts - loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts')); - loadTestFile(require.resolve('../common/platform_security/roles.ts')); - loadTestFile(require.resolve('../common/spaces/multiple_spaces_enabled.ts')); }); } diff --git a/x-pack/test_serverless/shared/config.base.ts b/x-pack/test_serverless/shared/config.base.ts index 1c702f02cff28..8b0be99b58ae4 100644 --- a/x-pack/test_serverless/shared/config.base.ts +++ b/x-pack/test_serverless/shared/config.base.ts @@ -151,7 +151,6 @@ export default async () => { // This ensures that we register the Security SAML API endpoints. // In the real world the SAML config is injected by control plane. `--plugin-path=${samlIdPPlugin}`, - '--xpack.cloud.id=ftr_fake_cloud_id', // Ensure that SAML is used as the default authentication method whenever a user navigates to Kibana. In other // words, Kibana should attempt to authenticate the user using the provider with the lowest order if the Login // Selector is disabled (which is how Serverless Kibana is configured). By declaring `cloud-basic` with a higher @@ -167,6 +166,16 @@ export default async () => { // configure security reponse header report-to settings to mimic MKI configuration `--csp.report_to=${JSON.stringify(['violations-endpoint'])}`, `--permissionsPolicy.report_to=${JSON.stringify(['violations-endpoint'])}`, + // normally below is injected by control plane + '--xpack.cloud.id=ftr_fake_cloud_id', + `--xpack.cloud.serverless.project_id=fakeprojectid`, + `--xpack.cloud.base_url=https://fake-cloud.elastic.co`, + `--xpack.cloud.projects_url=/projects/`, + `--xpack.cloud.profile_url=/user/settings/`, + `--xpack.cloud.billing_url=/billing/overview/`, + `--xpack.cloud.deployments_url=/deployments`, + `--xpack.cloud.organization_url=/account/`, + `--xpack.cloud.users_and_roles_url=/account/members/`, ], }, diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index d61c5c19a63db..92048160cb622 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -97,8 +97,8 @@ "@kbn/test-suites-src", "@kbn/console-plugin", "@kbn/cloud-security-posture-common", - "@kbn/security-plugin-types-common", "@kbn/core-saved-objects-import-export-server-internal", + "@kbn/security-plugin-types-common", "@kbn/ai-assistant-common", ] }