mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Fleet] Add SSL options to fleet server hosts settings (#208091)
Fixes https://github.com/elastic/kibana/issues/207322 ## Summary Show SSL options for fleet server host in Fleet server settings section and in add fleet server host flyout - Registered fleet server host as a encrypted save object and the new mappings added under `ssl` property, mirroring what's already existing for `logstash` and `kafka` outputs - The new options are displayed in the UI, both when adding a new fleet server host from the flyout and when editing an existing one. - The values are then added to the full agent policy - The values for `ssh.key` and `ssh.es_key` can additionally be saved as secrets but for now this option is not enabled until [fleet server supports it](https://github.com/elastic/fleet-server/issues/4470) - I used the feature flag `enableSSLSecrets` <details> <summary>Screenshots</summary> <img width="803" alt="Screenshot 2025-02-14 at 10 23 41" src="https://github.com/user-attachments/assets/e1bf8c93-e8c0-4351-b86b-a7f8a8b0ec72" /> <img width="801" alt="Screenshot 2025-02-14 at 10 23 36" src="https://github.com/user-attachments/assets/f96d2a5c-0285-41d1-953b-e662ccdcd514" /> <img width="780" alt="Screenshot 2025-02-04 at 14 34 52" src="https://github.com/user-attachments/assets/e854fc28-d4aa-4b01-8634-e1f37f70419b" /> <img width="804" alt="Screenshot 2025-02-04 at 14 35 00" src="https://github.com/user-attachments/assets/f507c34a-774e-4aa1-94b2-b912539d6143" /> <img width="791" alt="Screenshot 2025-02-04 at 09 25 28" src="https://github.com/user-attachments/assets/82c1f761-7ee5-42d0-8b8f-23848cfc0391" /> Generated policy: <img width="795" alt="Screenshot 2025-02-24 at 16 43 58" src="https://github.com/user-attachments/assets/5ef4e34f-5850-4449-8a70-7de10750bb84" /> <img width="796" alt="Screenshot 2025-02-24 at 16 44 15" src="https://github.com/user-attachments/assets/bdcf70fe-72f0-4df0-9a9e-40346407a1df" /> </details> ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
3fcd11ce4d
commit
151fa26a5f
55 changed files with 5052 additions and 947 deletions
|
@ -25956,12 +25956,101 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"host_urls"
|
||||
"host_urls",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
@ -26069,6 +26158,95 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -26117,12 +26295,101 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"host_urls"
|
||||
"host_urls",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
|
@ -26295,12 +26562,101 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"host_urls"
|
||||
"host_urls",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
|
@ -26394,6 +26750,95 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -26441,12 +26886,101 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"host_urls"
|
||||
"host_urls",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
|
|
|
@ -25956,12 +25956,101 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"host_urls"
|
||||
"host_urls",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
@ -26069,6 +26158,95 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -26117,12 +26295,101 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"host_urls"
|
||||
"host_urls",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
|
@ -26295,12 +26562,101 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"host_urls"
|
||||
"host_urls",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
|
@ -26394,6 +26750,95 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -26441,12 +26886,101 @@
|
|||
"proxy_id": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"es_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"client_auth": {
|
||||
"enum": [
|
||||
"optional",
|
||||
"required",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"es_certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"es_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"host_urls"
|
||||
"host_urls",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
|
|
|
@ -24602,10 +24602,65 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- host_urls
|
||||
- id
|
||||
type: array
|
||||
page:
|
||||
type: number
|
||||
|
@ -24678,6 +24733,61 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- host_urls
|
||||
|
@ -24713,10 +24823,65 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- host_urls
|
||||
- id
|
||||
required:
|
||||
- item
|
||||
'400':
|
||||
|
@ -24833,10 +24998,65 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- host_urls
|
||||
- id
|
||||
required:
|
||||
- item
|
||||
'400':
|
||||
|
@ -24898,6 +25118,61 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- proxy_id
|
||||
responses:
|
||||
|
@ -24932,10 +25207,65 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- host_urls
|
||||
- id
|
||||
required:
|
||||
- item
|
||||
'400':
|
||||
|
|
|
@ -26675,10 +26675,65 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- host_urls
|
||||
- id
|
||||
type: array
|
||||
page:
|
||||
type: number
|
||||
|
@ -26750,6 +26805,61 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- host_urls
|
||||
|
@ -26785,10 +26895,65 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- host_urls
|
||||
- id
|
||||
required:
|
||||
- item
|
||||
'400':
|
||||
|
@ -26903,10 +27068,65 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- host_urls
|
||||
- id
|
||||
required:
|
||||
- item
|
||||
'400':
|
||||
|
@ -26967,6 +27187,61 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- proxy_id
|
||||
responses:
|
||||
|
@ -27001,10 +27276,65 @@ paths:
|
|||
proxy_id:
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
es_key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_auth:
|
||||
enum:
|
||||
- optional
|
||||
- required
|
||||
- none
|
||||
type: string
|
||||
es_certificate:
|
||||
type: string
|
||||
es_certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
es_key:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- host_urls
|
||||
- id
|
||||
required:
|
||||
- item
|
||||
'400':
|
||||
|
|
|
@ -28,6 +28,7 @@ module.exports = {
|
|||
/x-pack[\/\\]platform[\/\\]packages[\/\\]shared[\/\\]kbn-elastic-assistant[\/\\]impl[\/\\]data_anonymization_editor[\/\\]context_editor[\/\\]get_columns[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]platform[\/\\]plugins[\/\\]shared[\/\\]fleet[\/\\]public[\/\\]applications[\/\\]fleet[\/\\]components[\/\\]fleet_server_instructions[\/\\]components[\/\\]fleet_server_hosts_form.tsx/,
|
||||
/x-pack[\/\\]platform[\/\\]plugins[\/\\]shared[\/\\]fleet[\/\\]public[\/\\]applications[\/\\]fleet[\/\\]components[\/\\]fleet_server_instructions[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]platform[\/\\]plugins[\/\\]shared[\/\\]fleet[\/\\]public[\/\\]applications[\/\\]fleet[\/\\]components[\/\\]fleet_server_instructions[\/\\]steps[\/\\]add_fleet_server_host.tsx/,
|
||||
/x-pack[\/\\]platform[\/\\]plugins[\/\\]shared[\/\\]fleet[\/\\]public[\/\\]applications[\/\\]fleet[\/\\]components[\/\\]fleet_server_instructions[\/\\]steps[\/\\]create_service_token.tsx/,
|
||||
/x-pack[\/\\]platform[\/\\]plugins[\/\\]shared[\/\\]fleet[\/\\]public[\/\\]applications[\/\\]fleet[\/\\]components[\/\\]generate_service_token.tsx/,
|
||||
/x-pack[\/\\]platform[\/\\]plugins[\/\\]shared[\/\\]fleet[\/\\]public[\/\\]applications[\/\\]fleet[\/\\]components[\/\\]search_bar.tsx/,
|
||||
|
|
|
@ -1713,6 +1713,7 @@
|
|||
}
|
||||
},
|
||||
"fleet-fleet-server-host": {
|
||||
"dynamic": false,
|
||||
"properties": {
|
||||
"host_urls": {
|
||||
"index": false,
|
||||
|
|
|
@ -109,7 +109,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"file-upload-usage-collection-telemetry": "06e0a8c04f991e744e09d03ab2bd7f86b2088200",
|
||||
"fileShare": "5be52de1747d249a221b5241af2838264e19aaa1",
|
||||
"fleet-agent-policies": "f69f7c5639f4cf9e85077c904e161f3574ac3ca2",
|
||||
"fleet-fleet-server-host": "69be15f6b6f2a2875ad3c7050ddea7a87f505417",
|
||||
"fleet-fleet-server-host": "232d98738d5321b86edc426e21a9ca2f607da999",
|
||||
"fleet-message-signing-keys": "93421f43fed2526b59092a4e3c65d64bc2266c0f",
|
||||
"fleet-package-policies": "b1ded996118af658bc420a737ff3c4d784641fc7",
|
||||
"fleet-preconfiguration-deletion-record": "c52ea1e13c919afe8a5e8e3adbb7080980ecc08e",
|
||||
|
|
|
@ -10,8 +10,10 @@ import type { SecurityRoleDescriptor } from '@elastic/elasticsearch/lib/api/type
|
|||
import type { agentPolicyStatuses } from '../../constants';
|
||||
import type { MonitoringType, PolicySecretReference, ValueOf } from '..';
|
||||
|
||||
import type { SOSecret } from '..';
|
||||
|
||||
import type { PackagePolicy, PackagePolicyPackage } from './package_policy';
|
||||
import type { Output, OutputSecret } from './output';
|
||||
import type { Output } from './output';
|
||||
|
||||
export type AgentPolicyStatus = typeof agentPolicyStatuses;
|
||||
|
||||
|
@ -126,6 +128,7 @@ export interface FullAgentPolicyInput {
|
|||
};
|
||||
streams?: FullAgentPolicyInputStream[];
|
||||
processors?: FullAgentPolicyAddFields[];
|
||||
ssl?: BaseSSLConfig;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
@ -145,6 +148,7 @@ export type FullAgentPolicyOutputPermissions = Record<string, SecurityRoleDescri
|
|||
export type FullAgentPolicyOutput = Pick<Output, 'type' | 'hosts' | 'ca_sha256'> & {
|
||||
proxy_url?: string;
|
||||
proxy_headers?: any;
|
||||
ssl?: BaseSSLConfig;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
|
@ -221,19 +225,22 @@ export interface FullAgentPolicy {
|
|||
};
|
||||
}
|
||||
|
||||
export interface BaseSSLConfig {
|
||||
verification_mode?: string;
|
||||
certificate_authorities?: string[];
|
||||
renegotiation?: string;
|
||||
certificate?: string;
|
||||
key?: string;
|
||||
client_authentication?: string;
|
||||
}
|
||||
|
||||
export interface FullAgentPolicyFleetConfig {
|
||||
hosts: string[];
|
||||
proxy_url?: string;
|
||||
proxy_headers?: any;
|
||||
ssl?: {
|
||||
verification_mode?: string;
|
||||
certificate_authorities?: string[];
|
||||
renegotiation?: string;
|
||||
certificate?: string;
|
||||
key?: OutputSecret;
|
||||
};
|
||||
ssl?: BaseSSLConfig;
|
||||
secrets?: {
|
||||
ssl?: { key?: OutputSecret };
|
||||
ssl?: { key?: SOSecret };
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,17 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { ValueOf } from '..';
|
||||
|
||||
// SO definition for this type is declared in server/types/interfaces
|
||||
import type { SOSecret } from './secret';
|
||||
|
||||
export const clientAuth = {
|
||||
Optional: 'optional',
|
||||
Required: 'required',
|
||||
None: 'none',
|
||||
} as const;
|
||||
|
||||
export type ClientAuth = typeof clientAuth;
|
||||
export interface NewFleetServerHost {
|
||||
name: string;
|
||||
host_urls: string[];
|
||||
|
@ -13,6 +22,21 @@ export interface NewFleetServerHost {
|
|||
is_preconfigured: boolean;
|
||||
is_internal?: boolean;
|
||||
proxy_id?: string | null;
|
||||
ssl?: {
|
||||
certificate_authorities?: string[];
|
||||
certificate?: string;
|
||||
key?: string;
|
||||
es_certificate_authorities?: string[];
|
||||
es_certificate?: string;
|
||||
es_key?: string;
|
||||
client_auth?: ValueOf<ClientAuth>;
|
||||
} | null;
|
||||
secrets?: {
|
||||
ssl?: {
|
||||
key?: SOSecret;
|
||||
es_key?: SOSecret;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface FleetServerHost extends NewFleetServerHost {
|
||||
|
|
|
@ -13,6 +13,7 @@ import type { kafkaTopicWhenType } from '../../constants';
|
|||
import type { kafkaAcknowledgeReliabilityLevel } from '../../constants';
|
||||
import type { kafkaVerificationModes } from '../../constants';
|
||||
import type { kafkaConnectionType } from '../../constants';
|
||||
import type { SOSecret } from '..';
|
||||
|
||||
export type OutputType = typeof outputType;
|
||||
export type KafkaCompressionType = typeof kafkaCompressionType;
|
||||
|
@ -23,12 +24,6 @@ export type KafkaPartitionType = typeof kafkaPartitionType;
|
|||
export type KafkaTopicWhenType = typeof kafkaTopicWhenType;
|
||||
export type KafkaAcknowledgeReliabilityLevel = typeof kafkaAcknowledgeReliabilityLevel;
|
||||
export type KafkaVerificationMode = typeof kafkaVerificationModes;
|
||||
export type OutputSecret =
|
||||
| string
|
||||
| {
|
||||
id: string;
|
||||
hash?: string;
|
||||
};
|
||||
|
||||
export type OutputPreset = 'custom' | 'balanced' | 'throughput' | 'scale' | 'latency';
|
||||
|
||||
|
@ -54,7 +49,7 @@ interface NewBaseOutput {
|
|||
allow_edit?: string[];
|
||||
secrets?: {
|
||||
ssl?: {
|
||||
key?: OutputSecret;
|
||||
key?: SOSecret;
|
||||
};
|
||||
};
|
||||
preset?: OutputPreset;
|
||||
|
@ -68,10 +63,10 @@ export interface NewRemoteElasticsearchOutput extends NewBaseOutput {
|
|||
type: OutputType['RemoteElasticsearch'];
|
||||
service_token?: string | null;
|
||||
secrets?: {
|
||||
service_token?: OutputSecret;
|
||||
kibana_api_key?: OutputSecret;
|
||||
service_token?: SOSecret;
|
||||
kibana_api_key?: SOSecret;
|
||||
ssl?: {
|
||||
key?: OutputSecret;
|
||||
key?: SOSecret;
|
||||
};
|
||||
};
|
||||
sync_integrations?: boolean;
|
||||
|
@ -81,6 +76,11 @@ export interface NewRemoteElasticsearchOutput extends NewBaseOutput {
|
|||
|
||||
export interface NewLogstashOutput extends NewBaseOutput {
|
||||
type: OutputType['Logstash'];
|
||||
secrets?: {
|
||||
ssl?: {
|
||||
key?: SOSecret;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type NewOutput =
|
||||
|
@ -141,9 +141,9 @@ export interface KafkaOutput extends NewBaseOutput {
|
|||
broker_timeout?: number;
|
||||
required_acks?: ValueOf<KafkaAcknowledgeReliabilityLevel>;
|
||||
secrets?: {
|
||||
password?: OutputSecret;
|
||||
password?: SOSecret;
|
||||
ssl?: {
|
||||
key?: OutputSecret;
|
||||
key?: SOSecret;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -21,10 +21,18 @@ export interface SecretPath {
|
|||
path: string[];
|
||||
value: PackagePolicyConfigRecordEntry;
|
||||
}
|
||||
export interface OutputSecretPath {
|
||||
export interface SOSecretPath {
|
||||
path: string;
|
||||
value: string | { id: string };
|
||||
}
|
||||
|
||||
export type SOSecret =
|
||||
| string
|
||||
| {
|
||||
id: string;
|
||||
hash?: string;
|
||||
};
|
||||
|
||||
// this is used in the top level secret_refs array on package and agent policies
|
||||
export interface PolicySecretReference {
|
||||
id: string;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { FleetServerHost } from '../models';
|
||||
import type { FleetServerHost, NewFleetServerHost } from '../models';
|
||||
|
||||
import type { ListResult } from './common';
|
||||
|
||||
|
@ -15,24 +15,11 @@ export interface PutFleetServerHostsRequest {
|
|||
params: {
|
||||
itemId: string;
|
||||
};
|
||||
body: {
|
||||
name?: string;
|
||||
host_urls?: string[];
|
||||
is_default?: boolean;
|
||||
is_internal?: boolean;
|
||||
proxy_id?: string | null;
|
||||
};
|
||||
body: Partial<NewFleetServerHost>;
|
||||
}
|
||||
|
||||
export interface PostFleetServerHostsRequest {
|
||||
body: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
host_urls?: string[];
|
||||
is_default?: boolean;
|
||||
is_internal?: boolean;
|
||||
proxy_id?: string | null;
|
||||
};
|
||||
body: Partial<FleetServerHost>;
|
||||
}
|
||||
|
||||
export interface PostFleetServerHostsResponse {
|
||||
|
|
|
@ -73,7 +73,7 @@ export const AdvancedTab: React.FunctionComponent<AdvancedTabProps> = ({
|
|||
getInstallFleetServerStep({
|
||||
isFleetServerReady,
|
||||
serviceToken,
|
||||
fleetServerHost: fleetServerHostForm.fleetServerHost?.host_urls[0],
|
||||
fleetServerHost: fleetServerHostForm.fleetServerHost,
|
||||
fleetServerPolicyId: fleetServerPolicyId || selectedPolicyId,
|
||||
deploymentMode,
|
||||
disabled: !Boolean(serviceToken),
|
||||
|
|
|
@ -13,13 +13,18 @@ import {
|
|||
useInput,
|
||||
useSwitchInput,
|
||||
validateInputs,
|
||||
useSecretInput,
|
||||
useRadioInput,
|
||||
} from '../../../hooks';
|
||||
import type { FleetServerHost } from '../../../types';
|
||||
|
||||
import type { FleetServerHostSSLInputsType } from '../../../sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form';
|
||||
import {
|
||||
validateName,
|
||||
validateFleetServerHosts,
|
||||
} from '../../../sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form';
|
||||
import type { ClientAuth, NewFleetServerHost, ValueOf } from '../../../../../../common/types';
|
||||
import { clientAuth } from '../../../../../../common/types';
|
||||
|
||||
export interface FleetServerHostForm {
|
||||
fleetServerHosts: FleetServerHost[];
|
||||
|
@ -28,11 +33,7 @@ export interface FleetServerHostForm {
|
|||
fleetServerHost?: FleetServerHost | null;
|
||||
setFleetServerHost: React.Dispatch<React.SetStateAction<FleetServerHost | undefined | null>>;
|
||||
error?: string;
|
||||
inputs: {
|
||||
hostUrlsInput: ReturnType<typeof useComboInput>;
|
||||
nameInput: ReturnType<typeof useInput>;
|
||||
isDefaultInput: ReturnType<typeof useSwitchInput>;
|
||||
};
|
||||
inputs: FleetServerHostSSLInputsType;
|
||||
}
|
||||
|
||||
export const useFleetServerHost = (): FleetServerHostForm => {
|
||||
|
@ -44,16 +45,82 @@ export const useFleetServerHost = (): FleetServerHostForm => {
|
|||
const isDefaultInput = useSwitchInput(false, isPreconfigured || fleetServerHost?.is_default);
|
||||
const hostUrlsInput = useComboInput('hostUrls', [], validateFleetServerHosts, isPreconfigured);
|
||||
|
||||
const inputs = useMemo(
|
||||
const sslCertificateAuthoritiesInput = useComboInput(
|
||||
'sslCertificateAuthoritiesComboxBox',
|
||||
fleetServerHost?.ssl?.certificate_authorities ?? [],
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
const sslCertificateInput = useInput(
|
||||
fleetServerHost?.ssl?.certificate ?? '',
|
||||
() => undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
const sslEsCertificateAuthoritiesInput = useComboInput(
|
||||
'sslEsCertificateAuthoritiesComboxBox',
|
||||
fleetServerHost?.ssl?.es_certificate_authorities ?? [],
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
const sslEsCertificateInput = useInput(
|
||||
fleetServerHost?.ssl?.es_certificate ?? '',
|
||||
() => undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
const sslClientAuthInput = useRadioInput(
|
||||
fleetServerHost?.ssl?.client_auth ?? clientAuth.None,
|
||||
undefined
|
||||
);
|
||||
|
||||
const sslKeyInput = useInput(fleetServerHost?.ssl?.key ?? '', undefined, undefined);
|
||||
const sslESKeyInput = useInput(fleetServerHost?.ssl?.es_key ?? '', undefined, undefined);
|
||||
|
||||
const sslKeySecretInput = useSecretInput(
|
||||
(fleetServerHost as FleetServerHost)?.secrets?.ssl?.key,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
const sslESKeySecretInput = useSecretInput(
|
||||
(fleetServerHost as FleetServerHost)?.secrets?.ssl?.es_key,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
const inputs: FleetServerHostSSLInputsType = useMemo(
|
||||
() => ({
|
||||
nameInput,
|
||||
isDefaultInput,
|
||||
hostUrlsInput,
|
||||
sslCertificateAuthoritiesInput,
|
||||
sslCertificateInput,
|
||||
sslEsCertificateAuthoritiesInput,
|
||||
sslEsCertificateInput,
|
||||
sslKeyInput,
|
||||
sslESKeyInput,
|
||||
sslKeySecretInput,
|
||||
sslESKeySecretInput,
|
||||
sslClientAuthInput,
|
||||
}),
|
||||
[nameInput, isDefaultInput, hostUrlsInput]
|
||||
[
|
||||
nameInput,
|
||||
isDefaultInput,
|
||||
hostUrlsInput,
|
||||
sslCertificateAuthoritiesInput,
|
||||
sslCertificateInput,
|
||||
sslEsCertificateAuthoritiesInput,
|
||||
sslEsCertificateInput,
|
||||
sslKeyInput,
|
||||
sslESKeyInput,
|
||||
sslKeySecretInput,
|
||||
sslESKeySecretInput,
|
||||
sslClientAuthInput,
|
||||
]
|
||||
);
|
||||
|
||||
const validate = useCallback(() => validateInputs(inputs), [inputs]);
|
||||
const validate = useCallback(() => validateInputs({ ...inputs }), [inputs]);
|
||||
|
||||
const { data, resendRequest: refreshGetFleetServerHosts } = useGetFleetServerHosts();
|
||||
|
||||
|
@ -76,12 +143,33 @@ export const useFleetServerHost = (): FleetServerHostForm => {
|
|||
return;
|
||||
}
|
||||
setIsFleetServerHostSubmitted(false);
|
||||
const newFleetServerHost = {
|
||||
name: inputs.nameInput.value,
|
||||
host_urls: inputs.hostUrlsInput.value,
|
||||
is_default: inputs.isDefaultInput.value,
|
||||
const newFleetServerHost: Partial<NewFleetServerHost> = {
|
||||
name: nameInput.value,
|
||||
host_urls: hostUrlsInput.value,
|
||||
is_default: isDefaultInput.value,
|
||||
ssl: {
|
||||
certificate: sslCertificateInput.value,
|
||||
key: sslKeyInput.value || undefined,
|
||||
certificate_authorities: sslCertificateAuthoritiesInput.value.filter((val) => val !== ''),
|
||||
es_certificate: sslEsCertificateInput.value,
|
||||
es_key: sslESKeyInput.value || undefined,
|
||||
es_certificate_authorities: sslEsCertificateAuthoritiesInput.value.filter(
|
||||
(val) => val !== ''
|
||||
),
|
||||
...(sslClientAuthInput.value !== clientAuth.None && {
|
||||
client_auth: sslClientAuthInput.value as ValueOf<ClientAuth>,
|
||||
}),
|
||||
},
|
||||
...(((!sslKeyInput.value && sslKeySecretInput.value) ||
|
||||
(!sslESKeyInput.value && sslESKeySecretInput.value)) && {
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: sslKeySecretInput.value || undefined,
|
||||
es_key: sslESKeySecretInput.value || undefined,
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const res = await sendPostFleetServerHost(newFleetServerHost);
|
||||
if (res.error) {
|
||||
throw res.error;
|
||||
|
@ -97,10 +185,19 @@ export const useFleetServerHost = (): FleetServerHostForm => {
|
|||
return res.data.item;
|
||||
}, [
|
||||
validate,
|
||||
nameInput.value,
|
||||
hostUrlsInput.value,
|
||||
isDefaultInput.value,
|
||||
sslCertificateInput.value,
|
||||
sslKeyInput.value,
|
||||
sslCertificateAuthoritiesInput.value,
|
||||
sslEsCertificateInput.value,
|
||||
sslESKeyInput.value,
|
||||
sslEsCertificateAuthoritiesInput.value,
|
||||
sslClientAuthInput.value,
|
||||
sslKeySecretInput.value,
|
||||
sslESKeySecretInput.value,
|
||||
refreshGetFleetServerHosts,
|
||||
inputs.nameInput.value,
|
||||
inputs.hostUrlsInput.value,
|
||||
inputs.isDefaultInput.value,
|
||||
]);
|
||||
|
||||
return {
|
||||
|
|
|
@ -53,7 +53,7 @@ export const QuickStartTab: React.FunctionComponent<Props> = ({ onClose }) => {
|
|||
}),
|
||||
getInstallFleetServerStep({
|
||||
isFleetServerReady,
|
||||
fleetServerHost: fleetServerHost?.host_urls[0],
|
||||
fleetServerHost,
|
||||
fleetServerPolicyId,
|
||||
serviceToken,
|
||||
deploymentMode: 'quickstart',
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import type { EuiStepProps } from '@elastic/eui';
|
||||
import { EuiIconTip } from '@elastic/eui';
|
||||
import { EuiAccordion, EuiIconTip } from '@elastic/eui';
|
||||
import {
|
||||
EuiSwitch,
|
||||
EuiButton,
|
||||
|
@ -24,12 +24,22 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import type { FleetServerHost } from '../../../types';
|
||||
|
||||
import { useStartServices, useLink } from '../../../hooks';
|
||||
import { useStartServices, useLink, useFleetStatus } from '../../../hooks';
|
||||
import type { FleetServerHostForm } from '../hooks';
|
||||
import { MultiRowInput } from '../../../sections/settings/components/multi_row_input';
|
||||
import { FleetServerHostSelect } from '../components';
|
||||
import { SSLFormSection } from '../../../sections/settings/components/fleet_server_hosts_flyout/ssl_form_section';
|
||||
import { ExperimentalFeaturesService } from '../../../services';
|
||||
|
||||
const StyledEuiAccordion = styled(EuiAccordion)`
|
||||
.ingest-active-button {
|
||||
color: ${(props) => props.theme.eui.euiColorPrimary};
|
||||
}
|
||||
`;
|
||||
|
||||
export const getAddFleetServerHostStep = ({
|
||||
fleetServerHostForm,
|
||||
|
@ -70,6 +80,69 @@ export const AddFleetServerHostStepContent = ({
|
|||
const [submittedFleetServerHost, setSubmittedFleetServerHost] = useState<FleetServerHost>();
|
||||
const { notifications } = useStartServices();
|
||||
const { getHref } = useLink();
|
||||
const { enableSSLSecrets } = ExperimentalFeaturesService.get();
|
||||
|
||||
const [isFirstLoad, setIsFirstLoad] = React.useState(true);
|
||||
const [isConvertedToSecret, setIsConvertedToSecret] = React.useState({
|
||||
sslKey: false,
|
||||
sslESKey: false,
|
||||
});
|
||||
const [secretsToggleState, setSecretsToggleState] = useState<'disabled' | true | false>(true);
|
||||
const useSecretsStorage = secretsToggleState === true;
|
||||
|
||||
const fleetStatus = useFleetStatus();
|
||||
if (fleetStatus.isSecretsStorageEnabled !== undefined && secretsToggleState === 'disabled') {
|
||||
setSecretsToggleState(fleetStatus.isSecretsStorageEnabled);
|
||||
}
|
||||
|
||||
const onToggleSecretStorage = (secretEnabled: boolean) => {
|
||||
if (secretsToggleState === 'disabled') {
|
||||
return;
|
||||
}
|
||||
|
||||
setSecretsToggleState(secretEnabled);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFirstLoad) return;
|
||||
setIsFirstLoad(false);
|
||||
// populate the secret input with the value of the plain input in order to re-save the key with secret storage
|
||||
if (useSecretsStorage && enableSSLSecrets) {
|
||||
if (inputs.sslKeyInput.value && !inputs.sslKeySecretInput.value) {
|
||||
inputs.sslKeySecretInput.setValue(inputs.sslKeyInput.value);
|
||||
inputs.sslKeyInput.clear();
|
||||
setIsConvertedToSecret({ ...isConvertedToSecret, sslKey: true });
|
||||
}
|
||||
if (inputs.sslESKeyInput.value && !inputs.sslESKeySecretInput.value) {
|
||||
inputs.sslESKeySecretInput.setValue(inputs.sslESKeyInput.value);
|
||||
inputs.sslESKeyInput.clear();
|
||||
setIsConvertedToSecret({ ...isConvertedToSecret, sslESKey: true });
|
||||
}
|
||||
}
|
||||
}, [
|
||||
inputs.sslKeyInput,
|
||||
inputs.sslKeySecretInput,
|
||||
isFirstLoad,
|
||||
setIsFirstLoad,
|
||||
isConvertedToSecret,
|
||||
inputs.sslESKeyInput,
|
||||
inputs.sslESKeySecretInput,
|
||||
secretsToggleState,
|
||||
useSecretsStorage,
|
||||
enableSSLSecrets,
|
||||
]);
|
||||
|
||||
const onToggleSecretAndClearValue = (secretEnabled: boolean) => {
|
||||
if (secretEnabled) {
|
||||
inputs.sslKeyInput.clear();
|
||||
inputs.sslESKeyInput.clear();
|
||||
} else {
|
||||
inputs.sslKeySecretInput.setValue('');
|
||||
inputs.sslESKeySecretInput.setValue('');
|
||||
}
|
||||
setIsConvertedToSecret({ ...isConvertedToSecret, sslKey: false, sslESKey: false });
|
||||
onToggleSecretStorage(secretEnabled);
|
||||
};
|
||||
|
||||
const onSubmit = useCallback(async () => {
|
||||
try {
|
||||
|
@ -170,6 +243,27 @@ export const AddFleetServerHostStepContent = ({
|
|||
{error && <EuiFormErrorText>{error}</EuiFormErrorText>}
|
||||
</>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
<StyledEuiAccordion
|
||||
id="advancedSSLOptions"
|
||||
data-test-subj="advancedSSLOptionsButton"
|
||||
buttonContent={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerSetup.SSLOptionsToggleLabel"
|
||||
defaultMessage="SSL options"
|
||||
/>
|
||||
}
|
||||
buttonClassName="ingest-active-button"
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<SSLFormSection
|
||||
inputs={inputs}
|
||||
useSecretsStorage={enableSSLSecrets && useSecretsStorage}
|
||||
onToggleSecretAndClearValue={onToggleSecretAndClearValue}
|
||||
isConvertedToSecret={isConvertedToSecret}
|
||||
/>
|
||||
</StyledEuiAccordion>
|
||||
<EuiSpacer size="m" />
|
||||
{fleetServerHosts.length > 0 ? (
|
||||
<EuiFormRow fullWidth {...inputs.isDefaultInput.formRowProps}>
|
||||
<EuiSwitch
|
||||
|
|
|
@ -20,6 +20,8 @@ import { PlatformSelector } from '../..';
|
|||
|
||||
import { getInstallCommandForPlatform } from '../utils';
|
||||
|
||||
import type { FleetServerHost } from '../../../types';
|
||||
|
||||
import type { DeploymentMode } from './set_deployment_mode';
|
||||
|
||||
export function getInstallFleetServerStep({
|
||||
|
@ -33,7 +35,7 @@ export function getInstallFleetServerStep({
|
|||
isFleetServerReady: boolean;
|
||||
disabled: boolean;
|
||||
serviceToken?: string;
|
||||
fleetServerHost?: string;
|
||||
fleetServerHost?: FleetServerHost | null;
|
||||
fleetServerPolicyId?: string;
|
||||
deploymentMode: DeploymentMode;
|
||||
}): EuiStepProps {
|
||||
|
@ -55,7 +57,7 @@ export function getInstallFleetServerStep({
|
|||
|
||||
const InstallFleetServerStepContent: React.FunctionComponent<{
|
||||
serviceToken?: string;
|
||||
fleetServerHost?: string;
|
||||
fleetServerHost?: FleetServerHost | null;
|
||||
fleetServerPolicyId?: string;
|
||||
deploymentMode: DeploymentMode;
|
||||
}> = ({ serviceToken, fleetServerHost, fleetServerPolicyId, deploymentMode }) => {
|
||||
|
|
|
@ -8,6 +8,14 @@
|
|||
import { getInstallCommandForPlatform } from './install_command_utils';
|
||||
|
||||
describe('getInstallCommandForPlatform', () => {
|
||||
const fleetServerHost = {
|
||||
id: 'host-id1',
|
||||
name: 'host',
|
||||
host_urls: ['http://fleetserver:8220'],
|
||||
is_default: false,
|
||||
is_preconfigured: false,
|
||||
};
|
||||
|
||||
describe('without policy id', () => {
|
||||
it('should return the correct command if the the policyId is not set for linux', () => {
|
||||
expect(
|
||||
|
@ -441,7 +449,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
|
@ -467,7 +475,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
|
@ -495,7 +503,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
|
@ -521,7 +529,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
|
@ -547,7 +555,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
});
|
||||
|
||||
|
@ -577,7 +585,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
|
@ -604,7 +612,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
|
@ -633,7 +641,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
|
@ -660,7 +668,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
|
@ -680,6 +688,78 @@ describe('getInstallCommandForPlatform', () => {
|
|||
--install-servers"
|
||||
`);
|
||||
});
|
||||
|
||||
describe('with full fleet server hosts settings', () => {
|
||||
it('should return the command with correct SSL options', () => {
|
||||
const fullFleetServerHost = {
|
||||
...fleetServerHost,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
},
|
||||
};
|
||||
const res = getInstallCommandForPlatform({
|
||||
platform: 'deb_x86_64',
|
||||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: fullFleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
});
|
||||
|
||||
expect(res).toMatchInlineSnapshot(`
|
||||
"curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--amd64.deb
|
||||
sudo dpkg -i elastic-agent--amd64.deb
|
||||
sudo systemctl enable elastic-agent
|
||||
sudo systemctl start elastic-agent
|
||||
sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\
|
||||
--fleet-server-es=http://elasticsearch:9200 \\\\
|
||||
--fleet-server-service-token=service-token-1 \\\\
|
||||
--fleet-server-policy=policy-1 \\\\
|
||||
--certificate-authorities='cert authorities' \\\\
|
||||
--fleet-server-es-ca='path/to/EScert' \\\\
|
||||
--fleet-server-cert='path/to/cert' \\\\
|
||||
--fleet-server-cert-key=<PATH_TO_FLEET_SERVER_CERT_KEY> \\\\
|
||||
--fleet-server-port=8220 \\\\
|
||||
--install-servers"
|
||||
`);
|
||||
});
|
||||
it('should return the command with SSL options and placeholders', () => {
|
||||
const fullFleetServerHost = {
|
||||
...fleetServerHost,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
},
|
||||
};
|
||||
const res = getInstallCommandForPlatform({
|
||||
platform: 'deb_x86_64',
|
||||
esOutputHost: 'http://elasticsearch:9200',
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: fullFleetServerHost,
|
||||
isProductionDeployment: true,
|
||||
});
|
||||
|
||||
expect(res).toMatchInlineSnapshot(`
|
||||
"curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--amd64.deb
|
||||
sudo dpkg -i elastic-agent--amd64.deb
|
||||
sudo systemctl enable elastic-agent
|
||||
sudo systemctl start elastic-agent
|
||||
sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\
|
||||
--fleet-server-es=http://elasticsearch:9200 \\\\
|
||||
--fleet-server-service-token=service-token-1 \\\\
|
||||
--fleet-server-policy=policy-1 \\\\
|
||||
--certificate-authorities='cert authorities' \\\\
|
||||
--fleet-server-es-ca=<PATH_TO_ES_CERT> \\\\
|
||||
--fleet-server-cert='path/to/cert' \\\\
|
||||
--fleet-server-cert-key=<PATH_TO_FLEET_SERVER_CERT_KEY> \\\\
|
||||
--fleet-server-port=8220 \\\\
|
||||
--install-servers"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with simple proxy settings', () => {
|
||||
|
@ -697,7 +777,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -738,7 +818,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -781,7 +861,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -822,7 +902,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -863,7 +943,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -908,7 +988,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -950,7 +1030,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -994,7 +1074,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -1036,7 +1116,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -1083,7 +1163,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -1161,7 +1241,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -1240,7 +1320,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -1292,7 +1372,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
@ -1372,7 +1452,7 @@ describe('getInstallCommandForPlatform', () => {
|
|||
},
|
||||
serviceToken: 'service-token-1',
|
||||
policyId: 'policy-1',
|
||||
fleetServerHost: 'http://fleetserver:8220',
|
||||
fleetServerHost,
|
||||
downloadSource: {
|
||||
id: 'download-src',
|
||||
name: 'download-src',
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { DownloadSource, FleetProxy } from '../../../../../../common/types';
|
||||
import type { DownloadSource, FleetProxy, FleetServerHost } from '../../../../../../common/types';
|
||||
import {
|
||||
getDownloadBaseUrl,
|
||||
getDownloadSourceProxyArgs,
|
||||
|
@ -120,7 +120,7 @@ export function getInstallCommandForPlatform({
|
|||
esOutputProxy?: FleetProxy | undefined;
|
||||
serviceToken: string;
|
||||
policyId?: string;
|
||||
fleetServerHost?: string;
|
||||
fleetServerHost?: FleetServerHost | null;
|
||||
isProductionDeployment?: boolean;
|
||||
sslCATrustedFingerprint?: string;
|
||||
kibanaVersion?: string;
|
||||
|
@ -134,7 +134,7 @@ export function getInstallCommandForPlatform({
|
|||
const commandArguments = [];
|
||||
|
||||
if (isProductionDeployment && fleetServerHost) {
|
||||
commandArguments.push(['url', fleetServerHost]);
|
||||
commandArguments.push(['url', fleetServerHost?.host_urls[0]]);
|
||||
}
|
||||
|
||||
commandArguments.push(['fleet-server-es', esOutputHost]);
|
||||
|
@ -148,11 +148,22 @@ export function getInstallCommandForPlatform({
|
|||
}
|
||||
|
||||
if (isProductionDeployment) {
|
||||
commandArguments.push(['certificate-authorities', '<PATH_TO_CA>']);
|
||||
const certificateAuthorities = fleetServerHost?.ssl?.certificate_authorities
|
||||
? `'${fleetServerHost?.ssl?.certificate_authorities}'`
|
||||
: '<PATH_TO_CA>';
|
||||
const fleetServerCert = fleetServerHost?.ssl?.certificate
|
||||
? `'${fleetServerHost?.ssl?.certificate}'`
|
||||
: '<PATH_TO_FLEET_SERVER_CERT>';
|
||||
|
||||
commandArguments.push(['certificate-authorities', certificateAuthorities]);
|
||||
|
||||
if (!sslCATrustedFingerprint) {
|
||||
commandArguments.push(['fleet-server-es-ca', '<PATH_TO_ES_CERT>']);
|
||||
const esCert = fleetServerHost?.ssl?.es_certificate
|
||||
? `'${fleetServerHost?.ssl?.es_certificate}'`
|
||||
: '<PATH_TO_ES_CERT>';
|
||||
commandArguments.push(['fleet-server-es-ca', esCert]);
|
||||
}
|
||||
commandArguments.push(['fleet-server-cert', '<PATH_TO_FLEET_SERVER_CERT>']);
|
||||
commandArguments.push(['fleet-server-cert', fleetServerCert]);
|
||||
commandArguments.push(['fleet-server-cert-key', '<PATH_TO_FLEET_SERVER_CERT_KEY>']);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
|
@ -30,11 +30,14 @@ import {
|
|||
|
||||
import { MultiRowInput } from '../multi_row_input';
|
||||
import { MAX_FLYOUT_WIDTH } from '../../../../constants';
|
||||
import { useStartServices } from '../../../../hooks';
|
||||
import { useFleetStatus, useStartServices } from '../../../../hooks';
|
||||
import type { FleetServerHost, FleetProxy } from '../../../../types';
|
||||
import { TextInput } from '../form';
|
||||
import { ProxyWarning } from '../fleet_proxies_table/proxy_warning';
|
||||
|
||||
import { ExperimentalFeaturesService } from '../../../../services';
|
||||
|
||||
import { SSLFormSection } from './ssl_form_section';
|
||||
import { useFleetServerHostsForm } from './use_fleet_server_host_form';
|
||||
|
||||
export interface FleetServerHostsFlyoutProps {
|
||||
|
@ -60,6 +63,70 @@ export const FleetServerHostsFlyout: React.FunctionComponent<FleetServerHostsFly
|
|||
[proxies]
|
||||
);
|
||||
|
||||
const { enableSSLSecrets } = ExperimentalFeaturesService.get();
|
||||
const [isFirstLoad, setIsFirstLoad] = React.useState(true);
|
||||
const [isConvertedToSecret, setIsConvertedToSecret] = React.useState({
|
||||
sslKey: false,
|
||||
sslESKey: false,
|
||||
});
|
||||
const [secretsToggleState, setSecretsToggleState] = useState<'disabled' | true | false>(true);
|
||||
|
||||
const useSecretsStorage = secretsToggleState === true;
|
||||
|
||||
const fleetStatus = useFleetStatus();
|
||||
if (fleetStatus.isSecretsStorageEnabled !== undefined && secretsToggleState === 'disabled') {
|
||||
setSecretsToggleState(fleetStatus.isSecretsStorageEnabled);
|
||||
}
|
||||
|
||||
const onToggleSecretStorage = (secretEnabled: boolean) => {
|
||||
if (secretsToggleState === 'disabled') {
|
||||
return;
|
||||
}
|
||||
|
||||
setSecretsToggleState(secretEnabled);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFirstLoad) return;
|
||||
setIsFirstLoad(false);
|
||||
// populate the secret input with the value of the plain input in order to re-save the key with secret storage
|
||||
if (useSecretsStorage && enableSSLSecrets) {
|
||||
if (inputs.sslKeyInput.value && !inputs.sslKeySecretInput.value) {
|
||||
inputs.sslKeySecretInput.setValue(inputs.sslKeyInput.value);
|
||||
inputs.sslKeyInput.clear();
|
||||
setIsConvertedToSecret({ ...isConvertedToSecret, sslKey: true });
|
||||
}
|
||||
if (inputs.sslESKeyInput.value && !inputs.sslESKeySecretInput.value) {
|
||||
inputs.sslESKeySecretInput.setValue(inputs.sslESKeyInput.value);
|
||||
inputs.sslESKeyInput.clear();
|
||||
setIsConvertedToSecret({ ...isConvertedToSecret, sslESKey: true });
|
||||
}
|
||||
}
|
||||
}, [
|
||||
inputs.sslKeyInput,
|
||||
inputs.sslKeySecretInput,
|
||||
isFirstLoad,
|
||||
setIsFirstLoad,
|
||||
isConvertedToSecret,
|
||||
inputs.sslESKeyInput,
|
||||
inputs.sslESKeySecretInput,
|
||||
secretsToggleState,
|
||||
useSecretsStorage,
|
||||
enableSSLSecrets,
|
||||
]);
|
||||
|
||||
const onToggleSecretAndClearValue = (secretEnabled: boolean) => {
|
||||
if (secretEnabled) {
|
||||
inputs.sslKeyInput.clear();
|
||||
inputs.sslESKeyInput.clear();
|
||||
} else {
|
||||
inputs.sslKeySecretInput.setValue('');
|
||||
inputs.sslESKeySecretInput.setValue('');
|
||||
}
|
||||
setIsConvertedToSecret({ ...isConvertedToSecret, sslKey: false, sslESKey: false });
|
||||
onToggleSecretStorage(secretEnabled);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose} maxWidth={MAX_FLYOUT_WIDTH}>
|
||||
<EuiFlyoutHeader hasBorder={true}>
|
||||
|
@ -196,16 +263,16 @@ export const FleetServerHostsFlyout: React.FunctionComponent<FleetServerHostsFly
|
|||
<EuiComboBox
|
||||
fullWidth
|
||||
data-test-subj="fleetServerHostsFlyout.proxyIdInput"
|
||||
{...inputs.proxyIdInput.props}
|
||||
onChange={(options) => inputs.proxyIdInput.setValue(options?.[0]?.value ?? '')}
|
||||
{...inputs.proxyIdInput?.props}
|
||||
onChange={(options) => inputs?.proxyIdInput?.setValue(options?.[0]?.value ?? '')}
|
||||
selectedOptions={
|
||||
inputs.proxyIdInput.value !== ''
|
||||
? proxiesOptions.filter((option) => option.value === inputs.proxyIdInput.value)
|
||||
inputs?.proxyIdInput?.value !== ''
|
||||
? proxiesOptions.filter((option) => option.value === inputs.proxyIdInput?.value)
|
||||
: []
|
||||
}
|
||||
options={proxiesOptions}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
isDisabled={inputs.proxyIdInput.props.disabled}
|
||||
isDisabled={inputs.proxyIdInput?.props.disabled}
|
||||
isClearable={true}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHostsFlyout.proxyIdPlaceholder',
|
||||
|
@ -230,6 +297,13 @@ export const FleetServerHostsFlyout: React.FunctionComponent<FleetServerHostsFly
|
|||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="l" />
|
||||
<SSLFormSection
|
||||
inputs={inputs}
|
||||
useSecretsStorage={enableSSLSecrets && useSecretsStorage}
|
||||
onToggleSecretAndClearValue={onToggleSecretAndClearValue}
|
||||
isConvertedToSecret={isConvertedToSecret}
|
||||
/>
|
||||
</EuiForm>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
@ -249,6 +323,7 @@ export const FleetServerHostsFlyout: React.FunctionComponent<FleetServerHostsFly
|
|||
isDisabled={form.isDisabled}
|
||||
onClick={form.submit}
|
||||
data-test-subj="saveApplySettingsBtn"
|
||||
aria-label="Save and apply settings"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.fleetServerHostsFlyout.saveButton"
|
||||
|
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiTextArea, EuiFormRow, EuiRadioGroup, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { MultiRowInput } from '../multi_row_input';
|
||||
|
||||
import { SecretFormRow } from '../edit_output_flyout/output_form_secret_form_row';
|
||||
|
||||
import { clientAuth } from '../../../../../../../common/types';
|
||||
|
||||
import type { FleetServerHostSSLInputsType } from './use_fleet_server_host_form';
|
||||
|
||||
interface Props {
|
||||
inputs: FleetServerHostSSLInputsType;
|
||||
useSecretsStorage: boolean;
|
||||
isConvertedToSecret: {
|
||||
sslKey: boolean;
|
||||
sslESKey: boolean;
|
||||
};
|
||||
onToggleSecretAndClearValue: (secretEnabled: boolean) => void;
|
||||
}
|
||||
|
||||
export const SSLFormSection: React.FunctionComponent<Props> = (props) => {
|
||||
const { inputs, useSecretsStorage, isConvertedToSecret, onToggleSecretAndClearValue } = props;
|
||||
|
||||
const clientAuthenticationsOptions = [
|
||||
{
|
||||
id: clientAuth.None,
|
||||
label: 'None',
|
||||
'data-test-subj': 'clientAuthNoneRadioButton',
|
||||
},
|
||||
{
|
||||
id: clientAuth.Required,
|
||||
label: 'Required',
|
||||
'data-test-subj': 'clientAuthUsernamePasswordRadioButton',
|
||||
},
|
||||
{
|
||||
id: clientAuth.Optional,
|
||||
label: 'Optional',
|
||||
'data-test-subj': 'clientAuthSSLRadioButton',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<MultiRowInput
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHosts.sslCertificateAuthoritiesInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify certificate authority',
|
||||
}
|
||||
)}
|
||||
label={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHosts.sslCertificateAuthoritiesInputLabel',
|
||||
{
|
||||
defaultMessage: 'Server SSL certificate authorities',
|
||||
}
|
||||
)}
|
||||
multiline={true}
|
||||
sortable={false}
|
||||
{...inputs.sslCertificateAuthoritiesInput.props}
|
||||
/>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.fleetServerHosts.sslCertificateInputLabel"
|
||||
defaultMessage="Client SSL certificate"
|
||||
/>
|
||||
}
|
||||
{...inputs.sslCertificateInput.formRowProps}
|
||||
>
|
||||
<EuiTextArea
|
||||
fullWidth
|
||||
rows={5}
|
||||
{...inputs.sslCertificateInput.props}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHosts.sslCertificateInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify SSL certificate',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{!useSecretsStorage ? (
|
||||
<SecretFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.fleetServerHosts.sslKeyInputLabel"
|
||||
defaultMessage="Client SSL certificate key"
|
||||
/>
|
||||
}
|
||||
{...inputs.sslKeyInput.formRowProps}
|
||||
useSecretsStorage={useSecretsStorage}
|
||||
onToggleSecretStorage={onToggleSecretAndClearValue}
|
||||
disabled={!useSecretsStorage}
|
||||
>
|
||||
<EuiTextArea
|
||||
fullWidth
|
||||
rows={5}
|
||||
{...inputs.sslKeyInput.props}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHosts.sslKeyInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify certificate key',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</SecretFormRow>
|
||||
) : (
|
||||
<SecretFormRow
|
||||
fullWidth
|
||||
title={i18n.translate('xpack.fleet.settings.fleetServerHosts.sslKeySecretInputTitle', {
|
||||
defaultMessage: 'Client SSL certificate key',
|
||||
})}
|
||||
{...inputs.sslKeySecretInput.formRowProps}
|
||||
useSecretsStorage={useSecretsStorage}
|
||||
isConvertedToSecret={isConvertedToSecret.sslKey}
|
||||
onToggleSecretStorage={onToggleSecretAndClearValue}
|
||||
cancelEdit={inputs.sslKeySecretInput.cancelEdit}
|
||||
>
|
||||
<EuiTextArea
|
||||
fullWidth
|
||||
rows={5}
|
||||
{...inputs.sslKeySecretInput.props}
|
||||
data-test-subj="sslKeySecretInput"
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHosts.sslKeySecretInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify certificate key',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</SecretFormRow>
|
||||
)}
|
||||
|
||||
<MultiRowInput
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHosts.sslEsCertificateAuthoritiesInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify Elasticsearch certificate authority',
|
||||
}
|
||||
)}
|
||||
label={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHosts.sslEsCertificateAuthoritiesInputLabel',
|
||||
{
|
||||
defaultMessage: 'Elasticsearch certificate authorities',
|
||||
}
|
||||
)}
|
||||
multiline={true}
|
||||
sortable={false}
|
||||
{...inputs.sslEsCertificateAuthoritiesInput.props}
|
||||
/>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.fleetServerHosts.sslEsCertificateInputLabel"
|
||||
defaultMessage="SSL certificate for Elasticsearch"
|
||||
/>
|
||||
}
|
||||
{...inputs.sslEsCertificateInput.formRowProps}
|
||||
>
|
||||
<EuiTextArea
|
||||
fullWidth
|
||||
rows={5}
|
||||
{...inputs.sslEsCertificateInput.props}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHosts.sslEsCertificateInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify Elasticsearch SSL certificate',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{!useSecretsStorage ? (
|
||||
<SecretFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.fleetServerHosts.sslEsKeyInputLabel"
|
||||
defaultMessage="SSL certificate key for Elasticsearch"
|
||||
/>
|
||||
}
|
||||
{...inputs.sslESKeyInput.formRowProps}
|
||||
useSecretsStorage={useSecretsStorage}
|
||||
onToggleSecretStorage={onToggleSecretAndClearValue}
|
||||
disabled={!useSecretsStorage}
|
||||
>
|
||||
<EuiTextArea
|
||||
fullWidth
|
||||
rows={5}
|
||||
{...inputs.sslESKeyInput.props}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHosts.sslKeyInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify certificate key',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</SecretFormRow>
|
||||
) : (
|
||||
<SecretFormRow
|
||||
fullWidth
|
||||
title={i18n.translate('xpack.fleet.settings.fleetServerHosts.sslEsKeySecretInputTitle', {
|
||||
defaultMessage: 'SSL certificate key for Elasticsearch',
|
||||
})}
|
||||
{...inputs.sslESKeySecretInput.formRowProps}
|
||||
useSecretsStorage={useSecretsStorage}
|
||||
isConvertedToSecret={isConvertedToSecret.sslKey}
|
||||
onToggleSecretStorage={onToggleSecretAndClearValue}
|
||||
cancelEdit={inputs.sslESKeySecretInput.cancelEdit}
|
||||
>
|
||||
<EuiTextArea
|
||||
fullWidth
|
||||
rows={5}
|
||||
{...inputs.sslESKeySecretInput.props}
|
||||
data-test-subj="sslESKeySecretInput"
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.fleetServerHosts.sslESKeySecretInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify certificate key',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</SecretFormRow>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.fleetServerHosts.clientAuthenticationInputLabel"
|
||||
defaultMessage="Client auth"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiRadioGroup
|
||||
style={{ flexDirection: 'row', flexWrap: 'wrap', columnGap: 30 }}
|
||||
data-test-subj={'fleetServerHosts.clientAuthenticationRadioInput'}
|
||||
options={clientAuthenticationsOptions}
|
||||
compressed
|
||||
{...inputs.sslClientAuthInput.props}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -70,7 +70,37 @@ describe('useFleetServerHostsForm', () => {
|
|||
await testRenderer.waitFor(() => expect(onSuccess).toBeCalled());
|
||||
});
|
||||
|
||||
it('should allow the user to correct and submit a invalid form', async () => {
|
||||
it('should submit a valid form with SSL options', async () => {
|
||||
const testRenderer = createFleetTestRendererMock();
|
||||
const onSuccess = jest.fn();
|
||||
testRenderer.startServices.http.post.mockResolvedValue({});
|
||||
const { result } = testRenderer.renderHook(() =>
|
||||
useFleetServerHostsForm(
|
||||
{
|
||||
id: 'id1',
|
||||
name: 'fleet server 1',
|
||||
host_urls: [],
|
||||
is_default: false,
|
||||
is_preconfigured: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
es_certificate_authorities: ['ES cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
},
|
||||
},
|
||||
onSuccess
|
||||
)
|
||||
);
|
||||
|
||||
act(() => result.current.inputs.hostUrlsInput.props.onChange(['https://test.fr']));
|
||||
|
||||
await act(() => result.current.submit());
|
||||
|
||||
await testRenderer.waitFor(() => expect(onSuccess).toBeCalled());
|
||||
});
|
||||
|
||||
it('should allow the user to correct and submit an invalid form', async () => {
|
||||
const testRenderer = createFleetTestRendererMock();
|
||||
const onSuccess = jest.fn();
|
||||
testRenderer.startServices.http.post.mockResolvedValue({});
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
// copy this one
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { useRadioInput } from '../../../../hooks';
|
||||
import {
|
||||
sendPostFleetServerHost,
|
||||
sendPutFleetServerHost,
|
||||
|
@ -19,13 +19,32 @@ import {
|
|||
useStartServices,
|
||||
useSwitchInput,
|
||||
validateInputs,
|
||||
useSecretInput,
|
||||
} from '../../../../hooks';
|
||||
import { isDiffPathProtocol } from '../../../../../../../common/services';
|
||||
import { useConfirmModal } from '../../hooks/use_confirm_modal';
|
||||
import type { FleetServerHost } from '../../../../types';
|
||||
import type { ClientAuth, NewFleetServerHost, ValueOf } from '../../../../../../../common/types';
|
||||
import { clientAuth } from '../../../../../../../common/types';
|
||||
|
||||
const URL_REGEX = /^(https):\/\/[^\s$.?#].[^\s]*$/gm;
|
||||
|
||||
export interface FleetServerHostSSLInputsType {
|
||||
nameInput: ReturnType<typeof useInput>;
|
||||
hostUrlsInput: ReturnType<typeof useComboInput>;
|
||||
isDefaultInput: ReturnType<typeof useSwitchInput>;
|
||||
proxyIdInput?: ReturnType<typeof useInput>;
|
||||
sslCertificateInput: ReturnType<typeof useInput>;
|
||||
sslKeyInput: ReturnType<typeof useInput>;
|
||||
sslKeySecretInput: ReturnType<typeof useSecretInput>;
|
||||
sslCertificateAuthoritiesInput: ReturnType<typeof useComboInput>;
|
||||
sslEsCertificateInput: ReturnType<typeof useInput>;
|
||||
sslESKeyInput: ReturnType<typeof useInput>;
|
||||
sslESKeySecretInput: ReturnType<typeof useSecretInput>;
|
||||
sslEsCertificateAuthoritiesInput: ReturnType<typeof useComboInput>;
|
||||
sslClientAuthInput: ReturnType<typeof useRadioInput>;
|
||||
}
|
||||
|
||||
const ConfirmTitle = () => (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.fleetServerHostsFlyout.confirmModalTitle"
|
||||
|
@ -146,17 +165,82 @@ export function useFleetServerHostsForm(
|
|||
);
|
||||
const proxyIdInput = useInput(fleetServerHost?.proxy_id ?? '', () => undefined, isEditDisabled);
|
||||
|
||||
const inputs = useMemo(
|
||||
const sslCertificateAuthoritiesInput = useComboInput(
|
||||
'sslCertificateAuthoritiesComboxBox',
|
||||
fleetServerHost?.ssl?.certificate_authorities ?? [],
|
||||
undefined,
|
||||
isEditDisabled
|
||||
);
|
||||
const sslCertificateInput = useInput(
|
||||
fleetServerHost?.ssl?.certificate ?? '',
|
||||
() => undefined,
|
||||
isEditDisabled
|
||||
);
|
||||
|
||||
const sslEsCertificateAuthoritiesInput = useComboInput(
|
||||
'sslEsCertificateAuthoritiesComboxBox',
|
||||
fleetServerHost?.ssl?.es_certificate_authorities ?? [],
|
||||
undefined,
|
||||
isEditDisabled
|
||||
);
|
||||
const sslEsCertificateInput = useInput(
|
||||
fleetServerHost?.ssl?.es_certificate ?? '',
|
||||
() => undefined,
|
||||
isEditDisabled
|
||||
);
|
||||
const sslKeyInput = useInput(fleetServerHost?.ssl?.key ?? '', undefined, isEditDisabled);
|
||||
const sslESKeyInput = useInput(fleetServerHost?.ssl?.es_key ?? '', undefined, isEditDisabled);
|
||||
|
||||
const sslKeySecretInput = useSecretInput(
|
||||
(fleetServerHost as FleetServerHost)?.secrets?.ssl?.key,
|
||||
undefined,
|
||||
isEditDisabled
|
||||
);
|
||||
|
||||
const sslESKeySecretInput = useSecretInput(
|
||||
(fleetServerHost as FleetServerHost)?.secrets?.ssl?.es_key,
|
||||
undefined,
|
||||
isEditDisabled
|
||||
);
|
||||
|
||||
const sslClientAuthInput = useRadioInput(
|
||||
fleetServerHost?.ssl?.client_auth ?? clientAuth.None,
|
||||
isEditDisabled
|
||||
);
|
||||
|
||||
const inputs: FleetServerHostSSLInputsType = useMemo(
|
||||
() => ({
|
||||
nameInput,
|
||||
isDefaultInput,
|
||||
hostUrlsInput,
|
||||
proxyIdInput,
|
||||
sslCertificateAuthoritiesInput,
|
||||
sslCertificateInput,
|
||||
sslEsCertificateAuthoritiesInput,
|
||||
sslEsCertificateInput,
|
||||
sslKeyInput,
|
||||
sslESKeyInput,
|
||||
sslKeySecretInput,
|
||||
sslESKeySecretInput,
|
||||
sslClientAuthInput,
|
||||
}),
|
||||
[nameInput, isDefaultInput, hostUrlsInput, proxyIdInput]
|
||||
[
|
||||
nameInput,
|
||||
isDefaultInput,
|
||||
hostUrlsInput,
|
||||
proxyIdInput,
|
||||
sslCertificateAuthoritiesInput,
|
||||
sslCertificateInput,
|
||||
sslEsCertificateAuthoritiesInput,
|
||||
sslEsCertificateInput,
|
||||
sslKeyInput,
|
||||
sslESKeyInput,
|
||||
sslKeySecretInput,
|
||||
sslESKeySecretInput,
|
||||
sslClientAuthInput,
|
||||
]
|
||||
);
|
||||
|
||||
const validate = useCallback(() => validateInputs(inputs), [inputs]);
|
||||
const validate = useCallback(() => validateInputs({ ...inputs }), [inputs]);
|
||||
|
||||
const submit = useCallback(async () => {
|
||||
try {
|
||||
|
@ -167,12 +251,35 @@ export function useFleetServerHostsForm(
|
|||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
const data = {
|
||||
const data: Partial<NewFleetServerHost> = {
|
||||
name: nameInput.value,
|
||||
host_urls: hostUrlsInput.value,
|
||||
is_default: isDefaultInput.value,
|
||||
proxy_id: proxyIdInput.value !== '' ? proxyIdInput.value : null,
|
||||
ssl: {
|
||||
certificate: sslCertificateInput.value,
|
||||
key: sslKeyInput.value || undefined,
|
||||
certificate_authorities: sslCertificateAuthoritiesInput.value.filter((val) => val !== ''),
|
||||
es_certificate: sslEsCertificateInput.value,
|
||||
es_key: sslESKeyInput.value || undefined,
|
||||
es_certificate_authorities: sslEsCertificateAuthoritiesInput.value.filter(
|
||||
(val) => val !== ''
|
||||
),
|
||||
...(sslClientAuthInput.value !== clientAuth.None && {
|
||||
client_auth: sslClientAuthInput.value as ValueOf<ClientAuth>,
|
||||
}),
|
||||
},
|
||||
...(((!sslKeyInput.value && sslKeySecretInput.value) ||
|
||||
(!sslESKeyInput.value && sslESKeySecretInput.value)) && {
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: sslKeySecretInput.value || undefined,
|
||||
es_key: sslESKeySecretInput.value || undefined,
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
if (fleetServerHost) {
|
||||
const res = await sendPutFleetServerHost(fleetServerHost.id, data);
|
||||
if (res.error) {
|
||||
|
@ -200,24 +307,32 @@ export function useFleetServerHostsForm(
|
|||
});
|
||||
}
|
||||
}, [
|
||||
fleetServerHost,
|
||||
validate,
|
||||
confirm,
|
||||
nameInput.value,
|
||||
hostUrlsInput.value,
|
||||
isDefaultInput.value,
|
||||
proxyIdInput.value,
|
||||
validate,
|
||||
notifications,
|
||||
confirm,
|
||||
sslCertificateInput.value,
|
||||
sslKeyInput.value,
|
||||
sslCertificateAuthoritiesInput.value,
|
||||
sslEsCertificateInput.value,
|
||||
sslESKeyInput.value,
|
||||
sslEsCertificateAuthoritiesInput.value,
|
||||
sslClientAuthInput.value,
|
||||
sslKeySecretInput.value,
|
||||
sslESKeySecretInput.value,
|
||||
fleetServerHost,
|
||||
notifications.toasts,
|
||||
onSuccess,
|
||||
]);
|
||||
|
||||
const hasChanged = Object.values(inputs).some((input) => input.hasChanged);
|
||||
|
||||
const isDisabled =
|
||||
isEditDisabled ||
|
||||
isLoading ||
|
||||
(!hostUrlsInput.hasChanged &&
|
||||
!isDefaultInput.hasChanged &&
|
||||
!nameInput.hasChanged &&
|
||||
!proxyIdInput.hasChanged) ||
|
||||
!hasChanged ||
|
||||
hostUrlsInput.props.isInvalid ||
|
||||
nameInput.props.isInvalid;
|
||||
|
||||
|
|
|
@ -173,6 +173,7 @@ export function useRadioInput(defaultValue: string, disabled = false) {
|
|||
setValue,
|
||||
value,
|
||||
hasChanged,
|
||||
validate: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../constants'
|
|||
|
||||
import { packagePolicyService } from '../services';
|
||||
import { getAgentStatusForAgentPolicy } from '../services/agents';
|
||||
import { listFleetServerHosts } from '../services/fleet_server_host';
|
||||
import { fleetServerHostService } from '../services/fleet_server_host';
|
||||
|
||||
const DEFAULT_USAGE = {
|
||||
total_all_statuses: 0,
|
||||
|
@ -45,7 +45,7 @@ export const getFleetServerUsage = async (
|
|||
return DEFAULT_USAGE;
|
||||
}
|
||||
|
||||
const fleetServerHosts = await listFleetServerHosts(soClient);
|
||||
const fleetServerHosts = await fleetServerHostService.list(soClient);
|
||||
const numHostsUrls = fleetServerHosts.items.flatMap((host) => host.host_urls).length;
|
||||
|
||||
// Find all policies with Fleet server than query agent status
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { SERVERLESS_DEFAULT_FLEET_SERVER_HOST_ID } from '../../constants';
|
||||
import { agentPolicyService, appContextService } from '../../services';
|
||||
import * as fleetServerService from '../../services/fleet_server_host';
|
||||
import { fleetServerHostService } from '../../services/fleet_server_host';
|
||||
import { withDefaultErrorHandler } from '../../services/security/fleet_router';
|
||||
|
||||
import { postFleetServerHost, putFleetServerHostHandler } from './handler';
|
||||
|
@ -32,13 +32,9 @@ describe('fleet server hosts handler', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(appContextService, 'getLogger').mockReturnValue({ error: jest.fn() } as any);
|
||||
jest
|
||||
.spyOn(fleetServerService, 'createFleetServerHost')
|
||||
.mockResolvedValue({ id: 'host1' } as any);
|
||||
jest
|
||||
.spyOn(fleetServerService, 'updateFleetServerHost')
|
||||
.mockResolvedValue({ id: 'host1' } as any);
|
||||
jest.spyOn(fleetServerService, 'getFleetServerHost').mockResolvedValue({
|
||||
jest.spyOn(fleetServerHostService, 'create').mockResolvedValue({ id: 'host1' } as any);
|
||||
jest.spyOn(fleetServerHostService, 'update').mockResolvedValue({ id: 'host1' } as any);
|
||||
jest.spyOn(fleetServerHostService, 'get').mockResolvedValue({
|
||||
id: SERVERLESS_DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
host_urls: ['http://elasticsearch:9200'],
|
||||
} as any);
|
||||
|
@ -90,6 +86,54 @@ describe('fleet server hosts handler', () => {
|
|||
expect(res).toEqual({ body: { item: { id: 'host1' } } });
|
||||
});
|
||||
|
||||
it('should return error if both ssl.key and secrets.ssl.key are provided', async () => {
|
||||
jest
|
||||
.spyOn(appContextService, 'getCloud')
|
||||
.mockReturnValue({ isServerlessEnabled: false } as any);
|
||||
|
||||
const res = await postFleetServerHostWithErrorHandler(
|
||||
mockContext,
|
||||
{
|
||||
body: {
|
||||
id: 'host1',
|
||||
host_urls: ['http://localhost:8080'],
|
||||
ssl: { key: 'token1' },
|
||||
secrets: { ssl: { key: 'token1' } },
|
||||
},
|
||||
} as any,
|
||||
mockResponse as any
|
||||
);
|
||||
|
||||
expect(res).toEqual({
|
||||
body: { message: 'Cannot specify both ssl.key and secrets.ssl.key' },
|
||||
statusCode: 400,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error if both ssl.es_key and secrets.ssl.es_key are provided', async () => {
|
||||
jest
|
||||
.spyOn(appContextService, 'getCloud')
|
||||
.mockReturnValue({ isServerlessEnabled: false } as any);
|
||||
|
||||
const res = await postFleetServerHostWithErrorHandler(
|
||||
mockContext,
|
||||
{
|
||||
body: {
|
||||
id: 'host1',
|
||||
host_urls: ['http://localhost:8080'],
|
||||
ssl: { es_key: 'token1' },
|
||||
secrets: { ssl: { es_key: 'token1' } },
|
||||
},
|
||||
} as any,
|
||||
mockResponse as any
|
||||
);
|
||||
|
||||
expect(res).toEqual({
|
||||
body: { message: 'Cannot specify both ssl.es_key and secrets.ssl.es_key' },
|
||||
statusCode: 400,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error on put in serverless if host url is different from default', async () => {
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isServerlessEnabled: true } as any);
|
||||
|
||||
|
|
|
@ -10,24 +10,29 @@ import type { RequestHandler, SavedObjectsClientContract } from '@kbn/core/serve
|
|||
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
|
||||
import { SERVERLESS_DEFAULT_FLEET_SERVER_HOST_ID } from '../../constants';
|
||||
|
||||
import { FleetServerHostUnauthorizedError } from '../../errors';
|
||||
import { agentPolicyService, appContextService } from '../../services';
|
||||
import { agentPolicyService, appContextService, fleetServerHostService } from '../../services';
|
||||
|
||||
import {
|
||||
createFleetServerHost,
|
||||
deleteFleetServerHost,
|
||||
getFleetServerHost,
|
||||
listFleetServerHosts,
|
||||
updateFleetServerHost,
|
||||
} from '../../services/fleet_server_host';
|
||||
import type {
|
||||
FleetServerHost,
|
||||
GetOneFleetServerHostRequestSchema,
|
||||
PostFleetServerHostRequestSchema,
|
||||
PutFleetServerHostRequestSchema,
|
||||
} from '../../types';
|
||||
|
||||
function ensureNoDuplicateSecrets(fleetServerHost: Partial<FleetServerHost>) {
|
||||
if (fleetServerHost.ssl?.key && fleetServerHost.secrets?.ssl?.key) {
|
||||
throw Boom.badRequest('Cannot specify both ssl.key and secrets.ssl.key');
|
||||
}
|
||||
if (fleetServerHost.ssl?.es_key && fleetServerHost.secrets?.ssl?.es_key) {
|
||||
throw Boom.badRequest('Cannot specify both ssl.es_key and secrets.ssl.es_key');
|
||||
}
|
||||
}
|
||||
|
||||
async function checkFleetServerHostsWriteAPIsAllowed(
|
||||
soClient: SavedObjectsClientContract,
|
||||
hostUrls: string[]
|
||||
|
@ -38,7 +43,7 @@ async function checkFleetServerHostsWriteAPIsAllowed(
|
|||
}
|
||||
|
||||
// Fleet Server hosts must have the default host URL in serverless.
|
||||
const serverlessDefaultFleetServerHost = await getFleetServerHost(
|
||||
const serverlessDefaultFleetServerHost = await fleetServerHostService.get(
|
||||
soClient,
|
||||
SERVERLESS_DEFAULT_FLEET_SERVER_HOST_ID
|
||||
);
|
||||
|
@ -62,8 +67,11 @@ export const postFleetServerHost: RequestHandler<
|
|||
await checkFleetServerHostsWriteAPIsAllowed(soClient, request.body.host_urls);
|
||||
|
||||
const { id, ...data } = request.body;
|
||||
const FleetServerHost = await createFleetServerHost(
|
||||
ensureNoDuplicateSecrets(data);
|
||||
|
||||
const FleetServerHost = await fleetServerHostService.create(
|
||||
soClient,
|
||||
esClient,
|
||||
{ ...data, is_preconfigured: false },
|
||||
{ id }
|
||||
);
|
||||
|
@ -83,7 +91,7 @@ export const getFleetServerHostHandler: RequestHandler<
|
|||
> = async (context, request, response) => {
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
try {
|
||||
const item = await getFleetServerHost(soClient, request.params.itemId);
|
||||
const item = await fleetServerHostService.get(soClient, request.params.itemId);
|
||||
const body = {
|
||||
item,
|
||||
};
|
||||
|
@ -108,7 +116,7 @@ export const deleteFleetServerHostHandler: RequestHandler<
|
|||
const soClient = coreContext.savedObjects.client;
|
||||
const esClient = coreContext.elasticsearch.client.asInternalUser;
|
||||
|
||||
await deleteFleetServerHost(soClient, esClient, request.params.itemId);
|
||||
await fleetServerHostService.delete(soClient, esClient, request.params.itemId);
|
||||
const body = {
|
||||
id: request.params.itemId,
|
||||
};
|
||||
|
@ -139,8 +147,14 @@ export const putFleetServerHostHandler: RequestHandler<
|
|||
if (request.body.host_urls) {
|
||||
await checkFleetServerHostsWriteAPIsAllowed(soClient, request.body.host_urls);
|
||||
}
|
||||
ensureNoDuplicateSecrets(request.body);
|
||||
|
||||
const item = await updateFleetServerHost(soClient, request.params.itemId, request.body);
|
||||
const item = await fleetServerHostService.update(
|
||||
soClient,
|
||||
esClient,
|
||||
request.params.itemId,
|
||||
request.body
|
||||
);
|
||||
const body = {
|
||||
item,
|
||||
};
|
||||
|
@ -165,7 +179,7 @@ export const putFleetServerHostHandler: RequestHandler<
|
|||
|
||||
export const getAllFleetServerHostsHandler: RequestHandler = async (context, request, response) => {
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
const res = await listFleetServerHosts(soClient);
|
||||
const res = await fleetServerHostService.list(soClient);
|
||||
const body = {
|
||||
items: res.items,
|
||||
page: res.page,
|
||||
|
|
|
@ -11,12 +11,7 @@ import type { FleetRequestHandlerContext } from '../..';
|
|||
import { xpackMocks } from '../../mocks';
|
||||
import { ListResponseSchema } from '../schema/utils';
|
||||
import { FleetServerHostSchema, FleetServerHostResponseSchema } from '../../types';
|
||||
import {
|
||||
createFleetServerHost,
|
||||
getFleetServerHost,
|
||||
listFleetServerHosts,
|
||||
updateFleetServerHost,
|
||||
} from '../../services/fleet_server_host';
|
||||
import { fleetServerHostService } from '../../services';
|
||||
|
||||
import {
|
||||
getAllFleetServerHostsHandler,
|
||||
|
@ -33,13 +28,13 @@ jest.mock('../../services', () => ({
|
|||
agentPolicyService: {
|
||||
bumpAllAgentPolicies: jest.fn().mockResolvedValue({}),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../services/fleet_server_host', () => ({
|
||||
listFleetServerHosts: jest.fn(),
|
||||
createFleetServerHost: jest.fn(),
|
||||
updateFleetServerHost: jest.fn(),
|
||||
getFleetServerHost: jest.fn(),
|
||||
fleetServerHostService: {
|
||||
list: jest.fn(),
|
||||
get: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn().mockResolvedValue({}),
|
||||
delete: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('schema validation', () => {
|
||||
|
@ -68,7 +63,7 @@ describe('schema validation', () => {
|
|||
page: 1,
|
||||
perPage: 20,
|
||||
};
|
||||
(listFleetServerHosts as jest.Mock).mockResolvedValue(expectedResponse);
|
||||
(fleetServerHostService.list as jest.Mock).mockResolvedValue(expectedResponse);
|
||||
await getAllFleetServerHostsHandler(context, {} as any, response);
|
||||
|
||||
expect(response.ok).toHaveBeenCalledWith({
|
||||
|
@ -89,7 +84,7 @@ describe('schema validation', () => {
|
|||
proxy_id: 'proxy1',
|
||||
},
|
||||
};
|
||||
(createFleetServerHost as jest.Mock).mockResolvedValue(expectedResponse.item);
|
||||
(fleetServerHostService.create as jest.Mock).mockResolvedValue(expectedResponse.item);
|
||||
await postFleetServerHost(
|
||||
context,
|
||||
{
|
||||
|
@ -119,7 +114,7 @@ describe('schema validation', () => {
|
|||
proxy_id: null,
|
||||
},
|
||||
};
|
||||
(updateFleetServerHost as jest.Mock).mockResolvedValue(expectedResponse.item);
|
||||
(fleetServerHostService.update as jest.Mock).mockResolvedValue(expectedResponse.item);
|
||||
await putFleetServerHostHandler(
|
||||
context,
|
||||
{
|
||||
|
@ -150,7 +145,7 @@ describe('schema validation', () => {
|
|||
proxy_id: null,
|
||||
},
|
||||
};
|
||||
(getFleetServerHost as jest.Mock).mockResolvedValue(expectedResponse.item);
|
||||
(fleetServerHostService.get as jest.Mock).mockResolvedValue(expectedResponse.item);
|
||||
await getFleetServerHostHandler(
|
||||
context,
|
||||
{ body: {}, params: { itemId: 'host1' } } as any,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import * as fleetServerService from '../../services/fleet_server_host';
|
||||
import { fleetServerHostService } from '../../services/fleet_server_host';
|
||||
|
||||
import { PostHealthCheckResponseSchema } from '../../types';
|
||||
|
||||
|
@ -42,7 +42,7 @@ describe('Fleet server health_check handler', () => {
|
|||
});
|
||||
|
||||
it('should return a bad request error if the requested fleet server host has no host_urls', async () => {
|
||||
jest.spyOn(fleetServerService, 'getFleetServerHost').mockResolvedValue({
|
||||
jest.spyOn(fleetServerHostService, 'get').mockResolvedValue({
|
||||
id: 'default-fleet-server',
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
|
@ -70,7 +70,7 @@ describe('Fleet server health_check handler', () => {
|
|||
name: 'Default',
|
||||
};
|
||||
|
||||
jest.spyOn(fleetServerService, 'getFleetServerHost').mockResolvedValue({
|
||||
jest.spyOn(fleetServerHostService, 'get').mockResolvedValue({
|
||||
id: 'default-fleet-server',
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
|
@ -105,7 +105,7 @@ describe('Fleet server health_check handler', () => {
|
|||
|
||||
it('should return an error when host id is not found', async () => {
|
||||
jest
|
||||
.spyOn(fleetServerService, 'getFleetServerHost')
|
||||
.spyOn(fleetServerHostService, 'get')
|
||||
.mockRejectedValue({ output: { statusCode: 404 }, isBoom: true });
|
||||
|
||||
const res = await postHealthCheckHandler(
|
||||
|
@ -123,7 +123,7 @@ describe('Fleet server health_check handler', () => {
|
|||
});
|
||||
|
||||
it('should return status `offline` when fetch request gets aborted', async () => {
|
||||
jest.spyOn(fleetServerService, 'getFleetServerHost').mockResolvedValue({
|
||||
jest.spyOn(fleetServerHostService, 'get').mockResolvedValue({
|
||||
id: 'default-fleet-server',
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import { getFleetServerHost } from '../../services/fleet_server_host';
|
||||
import { fleetServerHostService } from '../../services/fleet_server_host';
|
||||
|
||||
import type { FleetRequestHandler, PostHealthCheckRequestSchema } from '../../types';
|
||||
|
||||
|
@ -23,7 +23,7 @@ export const postHealthCheckHandler: FleetRequestHandler<
|
|||
const soClient = coreContext.savedObjects.client;
|
||||
|
||||
try {
|
||||
const fleetServerHost = await getFleetServerHost(soClient, id);
|
||||
const fleetServerHost = await fleetServerHostService.get(soClient, id);
|
||||
|
||||
if (
|
||||
!fleetServerHost ||
|
||||
|
|
|
@ -1147,6 +1147,7 @@ export const getSavedObjectTypes = (
|
|||
importableAndExportable: false,
|
||||
},
|
||||
mappings: {
|
||||
dynamic: false,
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
is_default: { type: 'boolean' },
|
||||
|
@ -1167,6 +1168,14 @@ export const getSavedObjectTypes = (
|
|||
},
|
||||
],
|
||||
},
|
||||
'2': {
|
||||
changes: [
|
||||
{
|
||||
type: 'mappings_addition',
|
||||
addedMappings: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[FLEET_PROXY_SAVED_OBJECT_TYPE]: {
|
||||
|
@ -1253,11 +1262,16 @@ export const OUTPUT_INCLUDE_AAD_FIELDS = new Set([
|
|||
'channel_buffer_size',
|
||||
]);
|
||||
|
||||
// dangerouslyExposeValue added to allow the user with access to the SO to see and edit these values through the UI
|
||||
export const OUTPUT_ENCRYPTED_FIELDS = new Set([
|
||||
{ key: 'ssl', dangerouslyExposeValue: true },
|
||||
{ key: 'password', dangerouslyExposeValue: true },
|
||||
]);
|
||||
|
||||
export const FLEET_SERVER_HOST_ENCRYPTED_FIELDS = new Set([
|
||||
{ key: 'ssl', dangerouslyExposeValue: true },
|
||||
]);
|
||||
|
||||
export function registerEncryptedSavedObjects(
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup
|
||||
) {
|
||||
|
@ -1277,4 +1291,10 @@ export function registerEncryptedSavedObjects(
|
|||
attributesToEncrypt: new Set(['token']),
|
||||
attributesToIncludeInAAD: new Set(['policy_id', 'token_plain']),
|
||||
});
|
||||
encryptedSavedObjects.registerType({
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
attributesToEncrypt: FLEET_SERVER_HOST_ENCRYPTED_FIELDS,
|
||||
// enforceRandomId allows to create an SO with an arbitrary id
|
||||
enforceRandomId: false,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { agentPolicyService } from '../agent_policy';
|
|||
import { agentPolicyUpdateEventHandler } from '../agent_policy_update';
|
||||
import { appContextService } from '../app_context';
|
||||
import { getPackageInfo } from '../epm/packages';
|
||||
import { getFleetServerHostsForAgentPolicy } from '../fleet_server_host';
|
||||
|
||||
import {
|
||||
generateFleetConfig,
|
||||
|
@ -26,6 +27,7 @@ import {
|
|||
import { getMonitoringPermissions } from './monitoring_permissions';
|
||||
|
||||
jest.mock('../epm/packages');
|
||||
jest.mock('../fleet_server_host');
|
||||
|
||||
const mockedGetElasticAgentMonitoringPermissions = getMonitoringPermissions as jest.Mock<
|
||||
ReturnType<typeof getMonitoringPermissions>
|
||||
|
@ -34,6 +36,9 @@ const mockedAgentPolicyService = agentPolicyService as jest.Mocked<typeof agentP
|
|||
|
||||
const soClientMock = savedObjectsClientMock.create();
|
||||
const mockedGetPackageInfo = getPackageInfo as jest.Mock<ReturnType<typeof getPackageInfo>>;
|
||||
const mockedGetFleetServerHostsForAgentPolicy = getFleetServerHostsForAgentPolicy as jest.Mock<
|
||||
ReturnType<typeof getFleetServerHostsForAgentPolicy>
|
||||
>;
|
||||
|
||||
function mockAgentPolicy(data: Partial<AgentPolicy>) {
|
||||
mockedAgentPolicyService.get.mockResolvedValue({
|
||||
|
@ -51,18 +56,6 @@ function mockAgentPolicy(data: Partial<AgentPolicy>) {
|
|||
});
|
||||
}
|
||||
|
||||
jest.mock('../fleet_server_host', () => {
|
||||
return {
|
||||
getFleetServerHostsForAgentPolicy: async () => {
|
||||
return {
|
||||
id: '93f74c0-e876-11ea-b7d3-8b2acec6f75c',
|
||||
is_default: true,
|
||||
host_urls: ['http://fleetserver:8220'],
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../agent_policy');
|
||||
|
||||
jest.mock('../output', () => {
|
||||
|
@ -154,6 +147,14 @@ function getAgentPolicyUpdateMock() {
|
|||
|
||||
describe('getFullAgentPolicy', () => {
|
||||
beforeEach(() => {
|
||||
mockedGetFleetServerHostsForAgentPolicy.mockResolvedValue({
|
||||
name: 'default Fleet Server',
|
||||
id: '93f74c0-e876-11ea-b7d3-8b2acec6f75c',
|
||||
is_default: true,
|
||||
host_urls: ['http://fleetserver:8220'],
|
||||
is_preconfigured: false,
|
||||
});
|
||||
|
||||
getAgentPolicyUpdateMock().mockClear();
|
||||
mockedAgentPolicyService.get.mockReset();
|
||||
mockedGetElasticAgentMonitoringPermissions.mockReset();
|
||||
|
@ -1028,6 +1029,42 @@ describe('getFullAgentPolicy', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should have ssl options in outputs when fleet server host has es ssl options', async () => {
|
||||
mockedGetFleetServerHostsForAgentPolicy.mockResolvedValue({
|
||||
name: 'default Fleet Server',
|
||||
id: '93f74c0-e876-11ea-b7d3-8b2acec6f75c',
|
||||
is_default: true,
|
||||
host_urls: ['http://fleetserver:8220'],
|
||||
is_preconfigured: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['/tmp/ssl/ca.crt'],
|
||||
certificate: 'my-cert',
|
||||
key: 'my-key',
|
||||
es_certificate_authorities: ['/tmp/ssl/es-ca.crt'],
|
||||
es_certificate: 'my-es-cert',
|
||||
es_key: 'my-es-key',
|
||||
},
|
||||
});
|
||||
|
||||
mockAgentPolicy({});
|
||||
const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy');
|
||||
expect(agentPolicy?.outputs).toMatchObject({
|
||||
default: {
|
||||
hosts: ['http://127.0.0.1:9201'],
|
||||
preset: 'balanced',
|
||||
type: 'elasticsearch',
|
||||
},
|
||||
'fleetserver-output-93f74c0-e876-11ea-b7d3-8b2acec6f75c': {
|
||||
ssl: {
|
||||
certificate: 'my-es-cert',
|
||||
certificate_authorities: ['/tmp/ssl/es-ca.crt'],
|
||||
key: 'my-es-key',
|
||||
},
|
||||
type: 'elasticsearch',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFullMonitoringSettings', () => {
|
||||
|
@ -1309,7 +1346,7 @@ ssl.test: 123
|
|||
type: 'logstash',
|
||||
},
|
||||
undefined,
|
||||
true
|
||||
false
|
||||
);
|
||||
|
||||
expect(policyOutput).toMatchInlineSnapshot(`
|
||||
|
|
|
@ -28,6 +28,7 @@ import type {
|
|||
AgentPolicy,
|
||||
} from '../../types';
|
||||
import type {
|
||||
FullAgentPolicyInput,
|
||||
FullAgentPolicyMonitoring,
|
||||
FullAgentPolicyOutputPermissions,
|
||||
PackageInfo,
|
||||
|
@ -44,7 +45,7 @@ import { getPackageInfo } from '../epm/packages';
|
|||
import { pkgToPkgKey, splitPkgKey } from '../epm/registry';
|
||||
import { appContextService } from '../app_context';
|
||||
|
||||
import { getOutputSecretReferences } from '../secrets';
|
||||
import { getFleetServerHostsSecretReferences, getOutputSecretReferences } from '../secrets';
|
||||
|
||||
import { getMonitoringPermissions } from './monitoring_permissions';
|
||||
import { storedPackagePoliciesToAgentInputs } from '.';
|
||||
|
@ -87,7 +88,7 @@ export async function getFullAgentPolicy(
|
|||
outputs,
|
||||
proxies,
|
||||
dataOutput,
|
||||
fleetServerHosts,
|
||||
fleetServerHost,
|
||||
monitoringOutput,
|
||||
downloadSourceUri,
|
||||
downloadSourceProxyUri,
|
||||
|
@ -119,6 +120,7 @@ export async function getFullAgentPolicy(
|
|||
packageInfoCache.set(pkgKey, packageInfo);
|
||||
})
|
||||
);
|
||||
const bootstrapOutputConfig = generateFleetServerOutputSSLConfig(fleetServerHost);
|
||||
|
||||
const inputs = (
|
||||
await storedPackagePoliciesToAgentInputs(
|
||||
|
@ -134,7 +136,18 @@ export async function getFullAgentPolicy(
|
|||
if (output) {
|
||||
input.use_output = getOutputIdForAgentPolicy(output);
|
||||
}
|
||||
|
||||
if (input.type === 'fleet-server' && fleetServerHost) {
|
||||
const sslInputConfig = generateSSLConfigForFleetServerInput(fleetServerHost);
|
||||
if (sslInputConfig) {
|
||||
input = {
|
||||
...input,
|
||||
...sslInputConfig,
|
||||
...(bootstrapOutputConfig
|
||||
? { use_output: `fleetserver-output-${fleetServerHost.id}` }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
return input;
|
||||
});
|
||||
const features = (agentPolicy.agent_features || []).reduce((acc, { name, ...featureConfig }) => {
|
||||
|
@ -143,6 +156,10 @@ export async function getFullAgentPolicy(
|
|||
}, {} as NonNullable<FullAgentPolicy['agent']>['features']);
|
||||
|
||||
const outputSecretReferences = outputs.flatMap((output) => getOutputSecretReferences(output));
|
||||
const fleetserverHostSecretReferences = fleetServerHost
|
||||
? getFleetServerHostsSecretReferences(fleetServerHost)
|
||||
: [];
|
||||
|
||||
const packagePolicySecretReferences = (agentPolicy?.package_policies || []).flatMap(
|
||||
(policy) => policy.secret_references || []
|
||||
);
|
||||
|
@ -150,18 +167,22 @@ export async function getFullAgentPolicy(
|
|||
const fullAgentPolicy: FullAgentPolicy = {
|
||||
id: agentPolicy.id,
|
||||
outputs: {
|
||||
...(bootstrapOutputConfig ? bootstrapOutputConfig : {}),
|
||||
...outputs.reduce<FullAgentPolicy['outputs']>((acc, output) => {
|
||||
acc[getOutputIdForAgentPolicy(output)] = transformOutputToFullPolicyOutput(
|
||||
output,
|
||||
output.proxy_id ? proxies.find((proxy) => output.proxy_id === proxy.id) : undefined,
|
||||
standalone
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
},
|
||||
inputs,
|
||||
secret_references: [...outputSecretReferences, ...packagePolicySecretReferences],
|
||||
secret_references: [
|
||||
...outputSecretReferences,
|
||||
...fleetserverHostSecretReferences,
|
||||
...packagePolicySecretReferences,
|
||||
],
|
||||
revision: agentPolicy.revision,
|
||||
agent: {
|
||||
download: {
|
||||
|
@ -267,8 +288,8 @@ export async function getFullAgentPolicy(
|
|||
}, {});
|
||||
|
||||
// only add fleet server hosts if not in standalone
|
||||
if (!standalone && fleetServerHosts) {
|
||||
fullAgentPolicy.fleet = generateFleetConfig(agentPolicy, fleetServerHosts, proxies, outputs);
|
||||
if (!standalone && fleetServerHost) {
|
||||
fullAgentPolicy.fleet = generateFleetConfig(agentPolicy, fleetServerHost, proxies, outputs);
|
||||
}
|
||||
|
||||
const settingsValues = getSettingsValuesForAgentPolicy(
|
||||
|
@ -318,18 +339,17 @@ export async function getFullAgentPolicy(
|
|||
if (agentPolicy.overrides) {
|
||||
return deepMerge<FullAgentPolicy>(fullAgentPolicy, agentPolicy.overrides);
|
||||
}
|
||||
|
||||
return fullAgentPolicy;
|
||||
}
|
||||
|
||||
export function generateFleetConfig(
|
||||
agentPolicy: AgentPolicy,
|
||||
fleetServerHosts: FleetServerHost,
|
||||
fleetServerHost: FleetServerHost,
|
||||
proxies: FleetProxy[],
|
||||
outputs: Output[]
|
||||
): FullAgentPolicy['fleet'] {
|
||||
const config: FullAgentPolicy['fleet'] = {
|
||||
hosts: fleetServerHosts.host_urls,
|
||||
hosts: fleetServerHost.host_urls,
|
||||
};
|
||||
|
||||
// generating the ssl configs for checking into Fleet
|
||||
|
@ -370,8 +390,8 @@ export function generateFleetConfig(
|
|||
}
|
||||
}
|
||||
|
||||
const fleetServerHostproxy = fleetServerHosts.proxy_id
|
||||
? proxies.find((proxy) => proxy.id === fleetServerHosts.proxy_id)
|
||||
const fleetServerHostproxy = fleetServerHost.proxy_id
|
||||
? proxies.find((proxy) => proxy.id === fleetServerHost.proxy_id)
|
||||
: null;
|
||||
if (fleetServerHostproxy) {
|
||||
config.proxy_url = fleetServerHostproxy.url;
|
||||
|
@ -398,6 +418,40 @@ export function generateFleetConfig(
|
|||
return config;
|
||||
}
|
||||
|
||||
// Generate the SSL configs for fleet-server type input
|
||||
// Corresponding to --fleet-server-cert, --fleet-server-cert-key, --certificate-authorites cli options
|
||||
function generateSSLConfigForFleetServerInput(fleetServerHost: FleetServerHost) {
|
||||
const inputConfig: Partial<FullAgentPolicyInput> = {};
|
||||
|
||||
if (fleetServerHost?.ssl) {
|
||||
inputConfig.ssl = {
|
||||
...(fleetServerHost.ssl.certificate_authorities && {
|
||||
certificate_authorities: fleetServerHost.ssl.certificate_authorities,
|
||||
}),
|
||||
...(fleetServerHost.ssl.certificate && {
|
||||
certificate: fleetServerHost.ssl.certificate,
|
||||
}),
|
||||
...(fleetServerHost.ssl.key &&
|
||||
!fleetServerHost?.secrets?.ssl?.key && {
|
||||
key: fleetServerHost.ssl.key,
|
||||
}),
|
||||
...(fleetServerHost.ssl.client_auth && {
|
||||
client_authentication: fleetServerHost.ssl.client_auth,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (fleetServerHost?.secrets) {
|
||||
inputConfig.secrets = {
|
||||
...(fleetServerHost?.secrets?.ssl?.key &&
|
||||
!fleetServerHost?.ssl?.key && {
|
||||
ssl: { key: fleetServerHost.secrets?.ssl?.key },
|
||||
}),
|
||||
};
|
||||
}
|
||||
return inputConfig;
|
||||
}
|
||||
|
||||
export function transformOutputToFullPolicyOutput(
|
||||
output: Output,
|
||||
proxy?: FleetProxy,
|
||||
|
@ -576,6 +630,45 @@ export function transformOutputToFullPolicyOutput(
|
|||
return newOutput;
|
||||
}
|
||||
|
||||
// Generate the SSL configs for fleet server connection to ES
|
||||
// Corresponding to --fleet-server-es-ca, --fleet-server-es-cert, --fleet-server-es-cert-key cli options
|
||||
// This function generates a `bootstrap output` to be sent directly to elastic-agent
|
||||
function generateFleetServerOutputSSLConfig(fleetServerHost: FleetServerHost | undefined):
|
||||
| {
|
||||
[key: string]: FullAgentPolicyOutput;
|
||||
}
|
||||
| undefined {
|
||||
if (!fleetServerHost || (!fleetServerHost?.ssl && !fleetServerHost.secrets)) return undefined;
|
||||
|
||||
const outputConfig: FullAgentPolicyOutput = { type: 'elasticsearch' };
|
||||
if (fleetServerHost?.ssl) {
|
||||
outputConfig.ssl = {
|
||||
...(fleetServerHost.ssl.es_certificate_authorities && {
|
||||
certificate_authorities: fleetServerHost.ssl.es_certificate_authorities,
|
||||
}),
|
||||
...(fleetServerHost.ssl.es_certificate && {
|
||||
certificate: fleetServerHost.ssl.es_certificate,
|
||||
}),
|
||||
...(fleetServerHost.ssl.es_key &&
|
||||
!fleetServerHost?.secrets?.ssl?.es_key && {
|
||||
key: fleetServerHost.ssl.es_key,
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (fleetServerHost?.secrets) {
|
||||
outputConfig.secrets = {
|
||||
...(fleetServerHost?.secrets?.ssl?.es_key &&
|
||||
!fleetServerHost?.ssl?.es_key && {
|
||||
ssl: { key: fleetServerHost.secrets?.ssl?.es_key },
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
[`fleetserver-output-${fleetServerHost?.id}`]: outputConfig,
|
||||
};
|
||||
}
|
||||
|
||||
export function getFullMonitoringSettings(
|
||||
agentPolicy: Pick<
|
||||
AgentPolicy,
|
||||
|
|
|
@ -55,7 +55,7 @@ export async function fetchRelatedSavedObjects(
|
|||
.getLogger()
|
||||
?.warn(`Unable to get fleet server hosts for policy ${agentPolicy?.id}: ${err.message}`);
|
||||
|
||||
return;
|
||||
return undefined;
|
||||
}),
|
||||
]);
|
||||
|
||||
|
@ -94,6 +94,6 @@ export async function fetchRelatedSavedObjects(
|
|||
monitoringOutput,
|
||||
downloadSourceUri,
|
||||
downloadSourceProxyUri,
|
||||
fleetServerHosts,
|
||||
fleetServerHost: fleetServerHosts,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import type { AgentPolicy, NewAgentPolicy } from '../../types';
|
|||
|
||||
import { appContextService } from '../app_context';
|
||||
import { listEnrollmentApiKeys } from '../api_keys';
|
||||
import { listFleetServerHosts } from '../fleet_server_host';
|
||||
import { fleetServerHostService } from '../fleet_server_host';
|
||||
|
||||
import { agentlessAgentService } from './agentless_agent';
|
||||
|
||||
|
@ -48,8 +48,8 @@ mockedAppContextService.getSecuritySetup.mockImplementation(() => ({
|
|||
const mockedListEnrollmentApiKeys = listEnrollmentApiKeys as jest.Mock<
|
||||
ReturnType<typeof listEnrollmentApiKeys>
|
||||
>;
|
||||
const mockedListFleetServerHosts = listFleetServerHosts as jest.Mock<
|
||||
ReturnType<typeof listFleetServerHosts>
|
||||
const mockedFleetServerHostService = fleetServerHostService as jest.Mocked<
|
||||
typeof fleetServerHostService
|
||||
>;
|
||||
|
||||
function getAgentPolicyCreateMock() {
|
||||
|
@ -115,7 +115,7 @@ describe('Agentless Agent service', () => {
|
|||
jest
|
||||
.spyOn(appContextService, 'getKibanaVersion')
|
||||
.mockReturnValue('mocked-kibana-version-infinite');
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -215,7 +215,7 @@ describe('Agentless Agent service', () => {
|
|||
jest
|
||||
.spyOn(appContextService, 'getKibanaVersion')
|
||||
.mockReturnValue('mocked-kibana-version-infinite');
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -314,7 +314,7 @@ describe('Agentless Agent service', () => {
|
|||
jest
|
||||
.spyOn(appContextService, 'getKibanaVersion')
|
||||
.mockReturnValue('mocked-kibana-version-infinite');
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -425,7 +425,7 @@ describe('Agentless Agent service', () => {
|
|||
jest
|
||||
.spyOn(appContextService, 'getKibanaVersion')
|
||||
.mockReturnValue('mocked-kibana-version-infinite');
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -620,7 +620,7 @@ describe('Agentless Agent service', () => {
|
|||
.spyOn(appContextService, 'getKibanaVersion')
|
||||
.mockReturnValue('mocked-kibana-version-infinite');
|
||||
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -681,7 +681,7 @@ describe('Agentless Agent service', () => {
|
|||
.spyOn(appContextService, 'getKibanaVersion')
|
||||
.mockReturnValue('mocked-kibana-version-infinite');
|
||||
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -737,7 +737,7 @@ describe('Agentless Agent service', () => {
|
|||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -806,7 +806,7 @@ describe('Agentless Agent service', () => {
|
|||
jest
|
||||
.spyOn(appContextService, 'getKibanaVersion')
|
||||
.mockReturnValue('mocked-kibana-version-infinite');
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -946,7 +946,7 @@ describe('Agentless Agent service', () => {
|
|||
},
|
||||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
mockedListFleetServerHosts.mockResolvedValue({ items: [] } as any);
|
||||
mockedFleetServerHostService.list.mockResolvedValue({ items: [] } as any);
|
||||
mockedListEnrollmentApiKeys.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
|
@ -984,7 +984,7 @@ describe('Agentless Agent service', () => {
|
|||
},
|
||||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked',
|
||||
|
@ -1025,7 +1025,7 @@ describe('Agentless Agent service', () => {
|
|||
},
|
||||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -1085,7 +1085,7 @@ describe('Agentless Agent service', () => {
|
|||
},
|
||||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -1145,7 +1145,7 @@ describe('Agentless Agent service', () => {
|
|||
},
|
||||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -1205,7 +1205,7 @@ describe('Agentless Agent service', () => {
|
|||
},
|
||||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -1265,7 +1265,7 @@ describe('Agentless Agent service', () => {
|
|||
},
|
||||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -1325,7 +1325,7 @@ describe('Agentless Agent service', () => {
|
|||
},
|
||||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -1385,7 +1385,7 @@ describe('Agentless Agent service', () => {
|
|||
},
|
||||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
@ -1445,7 +1445,7 @@ describe('Agentless Agent service', () => {
|
|||
},
|
||||
} as any);
|
||||
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
|
||||
mockedListFleetServerHosts.mockResolvedValue({
|
||||
mockedFleetServerHostService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 'mocked-fleet-server-id',
|
||||
|
|
|
@ -34,7 +34,7 @@ import {
|
|||
import { appContextService } from '../app_context';
|
||||
|
||||
import { listEnrollmentApiKeys } from '../api_keys';
|
||||
import { listFleetServerHosts } from '../fleet_server_host';
|
||||
import { fleetServerHostService } from '../fleet_server_host';
|
||||
import type { AgentlessConfig } from '../utils/agentless';
|
||||
import { prependAgentlessApiBasePathToEndpoint, isAgentlessEnabled } from '../utils/agentless';
|
||||
import {
|
||||
|
@ -336,7 +336,7 @@ class AgentlessAgentService {
|
|||
kuery: `policy_id:"${policyId}"`,
|
||||
});
|
||||
|
||||
const { items: fleetHosts } = await listFleetServerHosts(soClient);
|
||||
const { items: fleetHosts } = await fleetServerHostService.list(soClient);
|
||||
// Tech Debt: change this when we add the internal fleet server config to use the internal fleet server host
|
||||
// https://github.com/elastic/security-team/issues/9695
|
||||
const defaultFleetHost =
|
||||
|
|
|
@ -15,7 +15,7 @@ import { FLEET_PROXY_SAVED_OBJECT_TYPE } from '../constants';
|
|||
import { appContextService } from './app_context';
|
||||
|
||||
import { deleteFleetProxy } from './fleet_proxies';
|
||||
import { listFleetServerHostsForProxyId, updateFleetServerHost } from './fleet_server_host';
|
||||
import { fleetServerHostService } from './fleet_server_host';
|
||||
import { outputService } from './output';
|
||||
import { downloadSourceService } from './download_source';
|
||||
|
||||
|
@ -24,14 +24,9 @@ jest.mock('./download_source');
|
|||
jest.mock('./fleet_server_host');
|
||||
jest.mock('./app_context');
|
||||
|
||||
const mockedListFleetServerHostsForProxyId = listFleetServerHostsForProxyId as jest.MockedFunction<
|
||||
typeof listFleetServerHostsForProxyId
|
||||
const mockedFleetServerHostService = fleetServerHostService as jest.Mocked<
|
||||
typeof fleetServerHostService
|
||||
>;
|
||||
|
||||
const mockedUpdateFleetServerHost = updateFleetServerHost as jest.MockedFunction<
|
||||
typeof updateFleetServerHost
|
||||
>;
|
||||
|
||||
const mockedOutputService = outputService as jest.Mocked<typeof outputService>;
|
||||
const mockedDownloadSourceService = downloadSourceService as jest.Mocked<
|
||||
typeof downloadSourceService
|
||||
|
@ -61,7 +56,8 @@ describe('Fleet proxies service', () => {
|
|||
mockedDownloadSourceService.listAllForProxyId.mockReset();
|
||||
mockedOutputService.update.mockReset();
|
||||
soClientMock.delete.mockReset();
|
||||
mockedUpdateFleetServerHost.mockReset();
|
||||
mockedFleetServerHostService.update.mockReset();
|
||||
mockedFleetServerHostService.listAllForProxyId.mockReset();
|
||||
mockedDownloadSourceService.listAllForProxyId.mockImplementation(async () => ({
|
||||
items: [],
|
||||
total: 0,
|
||||
|
@ -93,7 +89,7 @@ describe('Fleet proxies service', () => {
|
|||
perPage: 10,
|
||||
};
|
||||
});
|
||||
mockedListFleetServerHostsForProxyId.mockImplementation(async (_, proxyId) => {
|
||||
mockedFleetServerHostService.listAllForProxyId.mockImplementation(async (_, proxyId) => {
|
||||
if (proxyId === PROXY_IDS.RELATED_PRECONFIGURED) {
|
||||
return {
|
||||
items: [
|
||||
|
@ -178,7 +174,7 @@ describe('Fleet proxies service', () => {
|
|||
fromPreconfiguration: true,
|
||||
});
|
||||
expect(mockedOutputService.update).toBeCalled();
|
||||
expect(mockedUpdateFleetServerHost).toBeCalled();
|
||||
expect(mockedFleetServerHostService.update).toBeCalled();
|
||||
expect(soClientMock.delete).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@ import type {
|
|||
|
||||
import { appContextService } from './app_context';
|
||||
|
||||
import { listFleetServerHostsForProxyId, updateFleetServerHost } from './fleet_server_host';
|
||||
import { fleetServerHostService } from './fleet_server_host';
|
||||
import { outputService } from './output';
|
||||
import { downloadSourceService } from './download_source';
|
||||
|
||||
|
@ -206,7 +206,7 @@ async function updateRelatedSavedObject(
|
|||
await pMap(
|
||||
fleetServerHosts,
|
||||
(fleetServerHost) =>
|
||||
updateFleetServerHost(soClient, fleetServerHost.id, {
|
||||
fleetServerHostService.update(soClient, esClient, fleetServerHost.id, {
|
||||
...omit(fleetServerHost, 'id'),
|
||||
proxy_id: null,
|
||||
}),
|
||||
|
@ -237,7 +237,7 @@ export async function getFleetProxyRelatedSavedObjects(
|
|||
) {
|
||||
const [{ items: fleetServerHosts }, { items: outputs }, { items: downloadSources }] =
|
||||
await Promise.all([
|
||||
listFleetServerHostsForProxyId(soClient, proxyId),
|
||||
fleetServerHostService.listAllForProxyId(soClient, proxyId),
|
||||
outputService.listAllForProxyId(soClient, proxyId),
|
||||
downloadSourceService.listAllForProxyId(soClient, proxyId),
|
||||
]);
|
||||
|
|
|
@ -15,15 +15,18 @@ import {
|
|||
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
} from '../constants';
|
||||
|
||||
import { appContextService } from './app_context';
|
||||
|
||||
import { deleteFleetServerHost, migrateSettingsToFleetServerHost } from './fleet_server_host';
|
||||
import { fleetServerHostService, migrateSettingsToFleetServerHost } from './fleet_server_host';
|
||||
import { agentPolicyService } from './agent_policy';
|
||||
import { getAgentsByKuery } from './agents';
|
||||
|
||||
jest.mock('./app_context');
|
||||
jest.mock('./agent_policy');
|
||||
jest.mock('./agents');
|
||||
|
||||
const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>;
|
||||
mockedAppContextService.getSecuritySetup.mockImplementation(() => ({
|
||||
|
@ -31,63 +34,46 @@ mockedAppContextService.getSecuritySetup.mockImplementation(() => ({
|
|||
}));
|
||||
|
||||
mockedAppContextService.getExperimentalFeatures.mockReturnValue({} as any);
|
||||
|
||||
let mockedLogger: jest.Mocked<Logger>;
|
||||
const mockedGetAgentsByKuery = getAgentsByKuery as jest.MockedFunction<typeof getAgentsByKuery>;
|
||||
|
||||
describe('migrateSettingsToFleetServerHost', () => {
|
||||
beforeEach(() => {
|
||||
mockedLogger = loggerMock.create();
|
||||
mockedAppContextService.getLogger.mockReturnValue(mockedLogger);
|
||||
function getMockedSoClient(options?: { id?: string; findHosts?: boolean; findSettings?: boolean }) {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
mockedAppContextService.getInternalUserSOClient.mockReturnValue(soClient);
|
||||
|
||||
soClient.get.mockImplementation(async (t: string, id: string) => {
|
||||
return {
|
||||
id: 'test1',
|
||||
attributes: {},
|
||||
} as any;
|
||||
});
|
||||
|
||||
it('should not migrate settings if a default fleet server policy config exists', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
soClient.find.mockImplementation(({ type }) => {
|
||||
if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) {
|
||||
return { saved_objects: [{ id: 'test123' }] } as any;
|
||||
}
|
||||
|
||||
throw new Error('Not mocked');
|
||||
});
|
||||
|
||||
await migrateSettingsToFleetServerHost(soClient);
|
||||
|
||||
expect(soClient.create).not.toBeCalled();
|
||||
soClient.create.mockImplementation(async (type, data, createOptions) => {
|
||||
return {
|
||||
id: createOptions?.id || 'generated-id',
|
||||
type,
|
||||
attributes: {},
|
||||
references: [],
|
||||
};
|
||||
});
|
||||
|
||||
it('should not migrate settings if there is not old settings', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
soClient.find.mockImplementation(({ type }) => {
|
||||
if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) {
|
||||
return { saved_objects: [] } as any;
|
||||
}
|
||||
|
||||
if (type === GLOBAL_SETTINGS_SAVED_OBJECT_TYPE) {
|
||||
soClient.find.mockImplementation(({ type }) => {
|
||||
if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) {
|
||||
if (options?.findHosts) {
|
||||
return {
|
||||
saved_objects: [],
|
||||
saved_objects: [
|
||||
{
|
||||
id: 'test123',
|
||||
attributes: { name: 'fleetServerHost', host_urls: [], is_default: true },
|
||||
},
|
||||
],
|
||||
} as any;
|
||||
}
|
||||
return { saved_objects: [] } as any;
|
||||
}
|
||||
|
||||
throw new Error('Not mocked');
|
||||
});
|
||||
|
||||
soClient.create.mockResolvedValue({
|
||||
id: DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
attributes: {},
|
||||
} as any);
|
||||
|
||||
await migrateSettingsToFleetServerHost(soClient);
|
||||
expect(soClient.create).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should migrate settings to new saved object', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
soClient.find.mockImplementation(({ type }) => {
|
||||
if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) {
|
||||
return { saved_objects: [] } as any;
|
||||
}
|
||||
|
||||
if (type === GLOBAL_SETTINGS_SAVED_OBJECT_TYPE) {
|
||||
if (type === GLOBAL_SETTINGS_SAVED_OBJECT_TYPE) {
|
||||
if (options?.findSettings) {
|
||||
return {
|
||||
saved_objects: [
|
||||
{
|
||||
|
@ -99,15 +85,99 @@ describe('migrateSettingsToFleetServerHost', () => {
|
|||
} as any;
|
||||
}
|
||||
|
||||
throw new Error('Not mocked');
|
||||
});
|
||||
return {
|
||||
saved_objects: [],
|
||||
} as any;
|
||||
}
|
||||
|
||||
soClient.create.mockResolvedValue({
|
||||
id: DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
attributes: {},
|
||||
if (type === PACKAGE_POLICY_SAVED_OBJECT_TYPE) {
|
||||
return {
|
||||
saved_objects: [
|
||||
{
|
||||
id: 'existing-package-policy',
|
||||
type: 'ingest-package-policies',
|
||||
score: 1,
|
||||
references: [],
|
||||
version: '1.0.0',
|
||||
attributes: {
|
||||
name: 'fleet-server',
|
||||
description: '',
|
||||
namespace: 'default',
|
||||
enabled: true,
|
||||
policy_id: 'fleet-server-id-1',
|
||||
policy_ids: ['fleet-server-id-1'],
|
||||
package: {
|
||||
name: 'fleet-server',
|
||||
title: 'Fleet Server',
|
||||
version: '0.9.0',
|
||||
},
|
||||
inputs: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as any;
|
||||
}
|
||||
throw new Error('Not mocked');
|
||||
});
|
||||
|
||||
return soClient;
|
||||
}
|
||||
|
||||
describe('migrateSettingsToFleetServerHost', () => {
|
||||
beforeEach(() => {
|
||||
mockedLogger = loggerMock.create();
|
||||
mockedAppContextService.getLogger.mockReturnValue(mockedLogger);
|
||||
mockedAppContextService.getEncryptedSavedObjectsSetup.mockReturnValue({
|
||||
canEncrypt: true,
|
||||
} as any);
|
||||
});
|
||||
const esMock = elasticsearchServiceMock.createInternalClient();
|
||||
|
||||
it('should not migrate settings if a default fleet server policy config exists', async () => {
|
||||
const soClient = getMockedSoClient({ id: DEFAULT_FLEET_SERVER_HOST_ID, findHosts: true });
|
||||
await migrateSettingsToFleetServerHost(soClient, esMock);
|
||||
|
||||
expect(soClient.create).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not migrate settings if there is no old settings', async () => {
|
||||
const soClient = getMockedSoClient({ id: DEFAULT_FLEET_SERVER_HOST_ID });
|
||||
mockedGetAgentsByKuery.mockResolvedValueOnce({ agents: [] } as any);
|
||||
|
||||
await migrateSettingsToFleetServerHost(soClient, esMock);
|
||||
expect(soClient.create).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should migrate settings to new saved object', async () => {
|
||||
const soClient = getMockedSoClient({ findSettings: true });
|
||||
|
||||
mockedGetAgentsByKuery.mockResolvedValueOnce({
|
||||
agents: [
|
||||
{
|
||||
id: '1',
|
||||
local_metadata: {
|
||||
elastic: {
|
||||
agent: {
|
||||
version: '10.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
local_metadata: {
|
||||
elastic: {
|
||||
agent: {
|
||||
version: '10.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
|
||||
await migrateSettingsToFleetServerHost(soClient);
|
||||
await migrateSettingsToFleetServerHost(soClient, esMock);
|
||||
|
||||
expect(soClient.create).toBeCalledWith(
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
expect.objectContaining({
|
||||
|
@ -119,18 +189,67 @@ describe('migrateSettingsToFleetServerHost', () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not work if getEncryptedSavedObjectsSetup is not set', async () => {
|
||||
const soClient = getMockedSoClient({ findSettings: true });
|
||||
|
||||
mockedAppContextService.getEncryptedSavedObjectsSetup.mockReturnValue({
|
||||
canEncrypt: false,
|
||||
} as any);
|
||||
await expect(() => migrateSettingsToFleetServerHost(soClient, esMock)).rejects.toThrow(
|
||||
'Fleet server host needs encrypted saved object api key to be set'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteFleetServerHost', () => {
|
||||
it('should removeFleetServerHostFromAll agent policies without force if not deleted from preconfiguration', async () => {
|
||||
const soMock = savedObjectsClientMock.create();
|
||||
|
||||
soMock.get.mockResolvedValue({
|
||||
id: 'test1',
|
||||
attributes: {},
|
||||
describe('create', () => {
|
||||
beforeEach(() => {
|
||||
mockedLogger = loggerMock.create();
|
||||
mockedAppContextService.getLogger.mockReturnValue(mockedLogger);
|
||||
mockedAppContextService.getEncryptedSavedObjectsSetup.mockReturnValue({
|
||||
canEncrypt: true,
|
||||
} as any);
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should throw if encryptedSavedObject is not configured', async () => {
|
||||
const soMock = getMockedSoClient();
|
||||
const esMock = elasticsearchServiceMock.createInternalClient();
|
||||
await deleteFleetServerHost(soMock, esMock, 'test1', {});
|
||||
mockedAppContextService.getEncryptedSavedObjectsSetup.mockReturnValue({
|
||||
canEncrypt: false,
|
||||
} as any);
|
||||
|
||||
await expect(
|
||||
fleetServerHostService.create(
|
||||
soMock,
|
||||
esMock,
|
||||
{
|
||||
name: 'Test',
|
||||
host_urls: [],
|
||||
is_default: false,
|
||||
is_preconfigured: false,
|
||||
},
|
||||
{ id: 'output-test' }
|
||||
)
|
||||
).rejects.toThrow(`Fleet server host needs encrypted saved object api key to be set`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete fleetServerHost', () => {
|
||||
beforeEach(() => {
|
||||
mockedLogger = loggerMock.create();
|
||||
mockedAppContextService.getLogger.mockReturnValue(mockedLogger);
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should removeFleetServerHostFromAll agent policies without force if not deleted from preconfiguration', async () => {
|
||||
const soMock = getMockedSoClient();
|
||||
const esMock = elasticsearchServiceMock.createInternalClient();
|
||||
await fleetServerHostService.delete(soMock, esMock, 'test1', {});
|
||||
|
||||
expect(jest.mocked(agentPolicyService.removeFleetServerHostFromAll)).toBeCalledWith(
|
||||
esMock,
|
||||
|
@ -141,14 +260,10 @@ describe('deleteFleetServerHost', () => {
|
|||
);
|
||||
});
|
||||
it('should removeFleetServerHostFromAll agent policies with force if deleted from preconfiguration', async () => {
|
||||
const soMock = savedObjectsClientMock.create();
|
||||
const soMock = getMockedSoClient();
|
||||
|
||||
soMock.get.mockResolvedValue({
|
||||
id: 'test1',
|
||||
attributes: {},
|
||||
} as any);
|
||||
const esMock = elasticsearchServiceMock.createInternalClient();
|
||||
await deleteFleetServerHost(soMock, esMock, 'test1', {
|
||||
await (fleetServerHostService.delete as jest.Mock)(soMock, esMock, 'test1', {
|
||||
fromPreconfiguration: true,
|
||||
});
|
||||
|
||||
|
|
|
@ -5,12 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { omit } from 'lodash';
|
||||
|
||||
import type {
|
||||
ElasticsearchClient,
|
||||
SavedObjectsClientContract,
|
||||
SavedObject,
|
||||
KibanaRequest,
|
||||
} from '@kbn/core/server';
|
||||
|
||||
import type { Nullable } from 'tough-cookie';
|
||||
|
||||
import { normalizeHostsForAgents } from '../../common/services';
|
||||
import {
|
||||
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
|
@ -25,215 +30,360 @@ import type {
|
|||
FleetServerHost,
|
||||
NewFleetServerHost,
|
||||
AgentPolicy,
|
||||
PolicySecretReference,
|
||||
} from '../types';
|
||||
import { FleetServerHostUnauthorizedError, FleetServerHostNotFoundError } from '../errors';
|
||||
import {
|
||||
FleetServerHostUnauthorizedError,
|
||||
FleetServerHostNotFoundError,
|
||||
FleetEncryptedSavedObjectEncryptionKeyRequired,
|
||||
} from '../errors';
|
||||
|
||||
import { appContextService } from './app_context';
|
||||
|
||||
import { agentPolicyService } from './agent_policy';
|
||||
import { escapeSearchQueryPhrase } from './saved_object';
|
||||
import {
|
||||
deleteFleetServerHostsSecrets,
|
||||
deleteSecrets,
|
||||
extractAndUpdateFleetServerHostsSecrets,
|
||||
extractAndWriteFleetServerHostsSecrets,
|
||||
isSecretStorageEnabled,
|
||||
} from './secrets';
|
||||
|
||||
function savedObjectToFleetServerHost(so: SavedObject<FleetServerHostSOAttributes>) {
|
||||
const data = { ...so.attributes };
|
||||
function savedObjectToFleetServerHost(
|
||||
so: SavedObject<FleetServerHostSOAttributes>
|
||||
): FleetServerHost {
|
||||
const { ssl, proxy_id: proxyId, ...attributes } = so.attributes;
|
||||
|
||||
if (data.proxy_id === null) {
|
||||
delete data.proxy_id;
|
||||
}
|
||||
|
||||
return { id: so.id, ...data };
|
||||
return {
|
||||
id: so.id,
|
||||
...attributes,
|
||||
...(ssl ? { ssl: JSON.parse(ssl as string) } : {}),
|
||||
...(proxyId ? { proxy_id: proxyId } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createFleetServerHost(
|
||||
soClient: SavedObjectsClientContract,
|
||||
data: NewFleetServerHost,
|
||||
options?: { id?: string; overwrite?: boolean; fromPreconfiguration?: boolean }
|
||||
): Promise<FleetServerHost> {
|
||||
const logger = appContextService.getLogger();
|
||||
if (data.is_default) {
|
||||
const defaultItem = await getDefaultFleetServerHost(soClient);
|
||||
if (defaultItem && defaultItem.id !== options?.id) {
|
||||
await updateFleetServerHost(
|
||||
soClient,
|
||||
defaultItem.id,
|
||||
{ is_default: false },
|
||||
{ fromPreconfiguration: options?.fromPreconfiguration }
|
||||
const fakeRequest = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
} as unknown as KibanaRequest;
|
||||
|
||||
class FleetServerHostService {
|
||||
private get encryptedSoClient() {
|
||||
return appContextService.getInternalUserSOClient(fakeRequest);
|
||||
}
|
||||
|
||||
public async create(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
fleetServerHost: NewFleetServerHost,
|
||||
options?: {
|
||||
id?: string;
|
||||
overwrite?: boolean;
|
||||
fromPreconfiguration?: boolean;
|
||||
secretHashes?: Record<string, any>;
|
||||
}
|
||||
): Promise<FleetServerHost> {
|
||||
const logger = appContextService.getLogger();
|
||||
const data: FleetServerHostSOAttributes = { ...omit(fleetServerHost, ['ssl', 'secrets']) };
|
||||
|
||||
if (!appContextService.getEncryptedSavedObjectsSetup()?.canEncrypt) {
|
||||
throw new FleetEncryptedSavedObjectEncryptionKeyRequired(
|
||||
`Fleet server host needs encrypted saved object api key to be set`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.host_urls) {
|
||||
data.host_urls = data.host_urls.map(normalizeHostsForAgents);
|
||||
}
|
||||
logger.debug(`Creating fleet server host with ${data}`);
|
||||
const res = await soClient.create<FleetServerHostSOAttributes>(
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
data,
|
||||
{ id: options?.id, overwrite: options?.overwrite }
|
||||
);
|
||||
logger.debug(`Created fleet server host ${options?.id}`);
|
||||
return savedObjectToFleetServerHost(res);
|
||||
}
|
||||
|
||||
export async function getFleetServerHost(
|
||||
soClient: SavedObjectsClientContract,
|
||||
id: string
|
||||
): Promise<FleetServerHost> {
|
||||
const res = await soClient.get<FleetServerHostSOAttributes>(
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
id
|
||||
);
|
||||
|
||||
return savedObjectToFleetServerHost(res);
|
||||
}
|
||||
|
||||
export async function listFleetServerHosts(soClient: SavedObjectsClientContract) {
|
||||
const res = await soClient.find<FleetServerHostSOAttributes>({
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
perPage: SO_SEARCH_LIMIT,
|
||||
});
|
||||
|
||||
return {
|
||||
items: res.saved_objects.map<FleetServerHost>(savedObjectToFleetServerHost),
|
||||
total: res.total,
|
||||
page: res.page,
|
||||
perPage: res.per_page,
|
||||
};
|
||||
}
|
||||
|
||||
export async function listFleetServerHostsForProxyId(
|
||||
soClient: SavedObjectsClientContract,
|
||||
proxyId: string
|
||||
) {
|
||||
const res = await soClient.find<FleetServerHostSOAttributes>({
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
perPage: SO_SEARCH_LIMIT,
|
||||
searchFields: ['proxy_id'],
|
||||
search: escapeSearchQueryPhrase(proxyId),
|
||||
});
|
||||
|
||||
return {
|
||||
items: res.saved_objects.map<FleetServerHost>(savedObjectToFleetServerHost),
|
||||
total: res.total,
|
||||
page: res.page,
|
||||
perPage: res.per_page,
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteFleetServerHost(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
options?: { fromPreconfiguration?: boolean }
|
||||
) {
|
||||
const logger = appContextService.getLogger();
|
||||
logger.debug(`Deleting fleet server host ${id}`);
|
||||
|
||||
const fleetServerHost = await getFleetServerHost(soClient, id);
|
||||
|
||||
if (fleetServerHost.is_preconfigured && !options?.fromPreconfiguration) {
|
||||
throw new FleetServerHostUnauthorizedError(
|
||||
`Cannot delete ${id} preconfigured fleet server host`
|
||||
);
|
||||
}
|
||||
|
||||
if (fleetServerHost.is_default) {
|
||||
throw new FleetServerHostUnauthorizedError(
|
||||
`Default Fleet Server hosts ${id} cannot be deleted.`
|
||||
);
|
||||
}
|
||||
|
||||
await agentPolicyService.removeFleetServerHostFromAll(esClient, id, {
|
||||
force: options?.fromPreconfiguration,
|
||||
});
|
||||
|
||||
return await soClient.delete(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id);
|
||||
}
|
||||
|
||||
export async function updateFleetServerHost(
|
||||
soClient: SavedObjectsClientContract,
|
||||
id: string,
|
||||
data: Partial<FleetServerHost>,
|
||||
options?: { fromPreconfiguration?: boolean }
|
||||
) {
|
||||
const logger = appContextService.getLogger();
|
||||
logger.debug(`Updating fleet server host ${id}`);
|
||||
|
||||
const originalItem = await getFleetServerHost(soClient, id);
|
||||
|
||||
if (data.is_preconfigured && !options?.fromPreconfiguration) {
|
||||
throw new FleetServerHostUnauthorizedError(
|
||||
`Cannot update ${id} preconfigured fleet server host`
|
||||
);
|
||||
}
|
||||
|
||||
if (data.is_default) {
|
||||
const defaultItem = await getDefaultFleetServerHost(soClient);
|
||||
if (defaultItem && defaultItem.id !== id) {
|
||||
await updateFleetServerHost(
|
||||
soClient,
|
||||
defaultItem.id,
|
||||
{
|
||||
is_default: false,
|
||||
},
|
||||
{ fromPreconfiguration: options?.fromPreconfiguration }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.host_urls) {
|
||||
data.host_urls = data.host_urls.map(normalizeHostsForAgents);
|
||||
}
|
||||
|
||||
await soClient.update<FleetServerHostSOAttributes>(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id, data);
|
||||
logger.debug(`Updated fleet server host ${id}`);
|
||||
return {
|
||||
...originalItem,
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
export async function bulkGetFleetServerHosts(
|
||||
soClient: SavedObjectsClientContract,
|
||||
ids: string[],
|
||||
{ ignoreNotFound = false } = { ignoreNotFound: true }
|
||||
) {
|
||||
if (ids.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const res = await soClient.bulkGet<FleetServerHostSOAttributes>(
|
||||
ids.map((id) => ({
|
||||
id,
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
}))
|
||||
);
|
||||
|
||||
return res.saved_objects
|
||||
.map((so) => {
|
||||
if (so.error) {
|
||||
if (!ignoreNotFound || so.error.statusCode !== 404) {
|
||||
throw so.error;
|
||||
}
|
||||
return undefined;
|
||||
if (fleetServerHost.is_default) {
|
||||
const defaultItem = await this.getDefaultFleetServerHost(soClient);
|
||||
if (defaultItem && defaultItem.id !== options?.id) {
|
||||
await this.update(
|
||||
soClient,
|
||||
esClient,
|
||||
defaultItem.id,
|
||||
{ is_default: false },
|
||||
{ fromPreconfiguration: options?.fromPreconfiguration }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return savedObjectToFleetServerHost(so);
|
||||
})
|
||||
.filter(
|
||||
(fleetServerHostOrUndefined): fleetServerHostOrUndefined is FleetServerHost =>
|
||||
typeof fleetServerHostOrUndefined !== 'undefined'
|
||||
if (fleetServerHost.host_urls) {
|
||||
data.host_urls = fleetServerHost.host_urls.map(normalizeHostsForAgents);
|
||||
}
|
||||
if (fleetServerHost.ssl) {
|
||||
data.ssl = JSON.stringify(fleetServerHost.ssl);
|
||||
}
|
||||
|
||||
// Store secret values if enabled; if not, store plain text values
|
||||
if (await isSecretStorageEnabled(esClient, soClient)) {
|
||||
const { fleetServerHost: fleetServerHostWithSecrets } =
|
||||
await extractAndWriteFleetServerHostsSecrets({
|
||||
fleetServerHost,
|
||||
esClient,
|
||||
secretHashes: fleetServerHost.is_preconfigured ? options?.secretHashes : undefined,
|
||||
});
|
||||
if (fleetServerHostWithSecrets.secrets)
|
||||
data.secrets = fleetServerHostWithSecrets.secrets as FleetServerHostSOAttributes['secrets'];
|
||||
} else {
|
||||
if (
|
||||
(!fleetServerHost.ssl?.key && fleetServerHost.secrets?.ssl?.key) ||
|
||||
(!fleetServerHost.ssl?.es_key && fleetServerHost.secrets?.ssl?.es_key)
|
||||
) {
|
||||
data.ssl = JSON.stringify({ ...fleetServerHost.ssl, ...fleetServerHost.secrets.ssl });
|
||||
}
|
||||
}
|
||||
const res = await this.encryptedSoClient.create<FleetServerHostSOAttributes>(
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
data,
|
||||
{ id: options?.id, overwrite: options?.overwrite }
|
||||
);
|
||||
logger.debug(`Created fleet server host ${options?.id}`);
|
||||
return savedObjectToFleetServerHost(res);
|
||||
}
|
||||
|
||||
public async get(soClient: SavedObjectsClientContract, id: string): Promise<FleetServerHost> {
|
||||
// add code to retrieve encrypted fields
|
||||
const res = await this.encryptedSoClient.get<FleetServerHostSOAttributes>(
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
id
|
||||
);
|
||||
|
||||
return savedObjectToFleetServerHost(res);
|
||||
}
|
||||
|
||||
public async list(soClient: SavedObjectsClientContract) {
|
||||
const res = await this.encryptedSoClient.find<FleetServerHostSOAttributes>({
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
perPage: SO_SEARCH_LIMIT,
|
||||
});
|
||||
|
||||
return {
|
||||
items: res.saved_objects.map<FleetServerHost>(savedObjectToFleetServerHost),
|
||||
total: res.total,
|
||||
page: res.page,
|
||||
perPage: res.per_page,
|
||||
};
|
||||
}
|
||||
|
||||
public async listAllForProxyId(soClient: SavedObjectsClientContract, proxyId: string) {
|
||||
const res = await this.encryptedSoClient.find<FleetServerHostSOAttributes>({
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
perPage: SO_SEARCH_LIMIT,
|
||||
searchFields: ['proxy_id'],
|
||||
search: escapeSearchQueryPhrase(proxyId),
|
||||
});
|
||||
|
||||
return {
|
||||
items: res.saved_objects.map<FleetServerHost>(savedObjectToFleetServerHost),
|
||||
total: res.total,
|
||||
page: res.page,
|
||||
perPage: res.per_page,
|
||||
};
|
||||
}
|
||||
|
||||
// export async function deleteFleetServerHost(
|
||||
public async delete(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
options?: { fromPreconfiguration?: boolean }
|
||||
) {
|
||||
const logger = appContextService.getLogger();
|
||||
logger.debug(`Deleting fleet server host ${id}`);
|
||||
|
||||
const fleetServerHost = await this.get(soClient, id);
|
||||
|
||||
if (fleetServerHost.is_preconfigured && !options?.fromPreconfiguration) {
|
||||
throw new FleetServerHostUnauthorizedError(
|
||||
`Cannot delete ${id} preconfigured fleet server host`
|
||||
);
|
||||
}
|
||||
|
||||
if (fleetServerHost.is_default) {
|
||||
throw new FleetServerHostUnauthorizedError(
|
||||
`Default Fleet Server hosts ${id} cannot be deleted.`
|
||||
);
|
||||
}
|
||||
|
||||
await agentPolicyService.removeFleetServerHostFromAll(esClient, id, {
|
||||
force: options?.fromPreconfiguration,
|
||||
});
|
||||
|
||||
const soDeleteResult = await this.encryptedSoClient.delete(
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
id
|
||||
);
|
||||
await deleteFleetServerHostsSecrets({
|
||||
fleetServerHost,
|
||||
esClient: appContextService.getInternalUserESClient(),
|
||||
});
|
||||
|
||||
return soDeleteResult;
|
||||
}
|
||||
|
||||
public async update(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
data: Partial<FleetServerHost>,
|
||||
options?: { fromPreconfiguration?: boolean; secretHashes?: Record<string, any> }
|
||||
) {
|
||||
let secretsToDelete: PolicySecretReference[] = [];
|
||||
|
||||
const logger = appContextService.getLogger();
|
||||
logger.debug(`Updating fleet server host ${id}`);
|
||||
|
||||
const originalItem = await this.get(soClient, id);
|
||||
const updateData: Nullable<Partial<FleetServerHostSOAttributes>> = {
|
||||
...omit(data, ['ssl', 'secrets']),
|
||||
};
|
||||
|
||||
if (data.is_preconfigured && !options?.fromPreconfiguration) {
|
||||
throw new FleetServerHostUnauthorizedError(
|
||||
`Cannot update ${id} preconfigured fleet server host`
|
||||
);
|
||||
}
|
||||
|
||||
if (data.is_default) {
|
||||
const defaultItem = await this.getDefaultFleetServerHost(soClient);
|
||||
if (defaultItem && defaultItem.id !== id) {
|
||||
await this.update(
|
||||
soClient,
|
||||
esClient,
|
||||
defaultItem.id,
|
||||
{
|
||||
is_default: false,
|
||||
},
|
||||
{ fromPreconfiguration: options?.fromPreconfiguration }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.host_urls) {
|
||||
updateData.host_urls = data.host_urls.map(normalizeHostsForAgents);
|
||||
}
|
||||
|
||||
if (data.ssl) {
|
||||
updateData.ssl = JSON.stringify(data.ssl);
|
||||
} else if (data.ssl === null) {
|
||||
// Explicitly set to null to allow to delete the field
|
||||
updateData.ssl = null;
|
||||
}
|
||||
|
||||
// Store secret values if enabled; if not, store plain text values
|
||||
if (await isSecretStorageEnabled(esClient, soClient)) {
|
||||
const secretsRes = await extractAndUpdateFleetServerHostsSecrets({
|
||||
oldFleetServerHost: originalItem,
|
||||
fleetServerHostUpdate: data,
|
||||
esClient,
|
||||
secretHashes: data.is_preconfigured ? options?.secretHashes : undefined,
|
||||
});
|
||||
|
||||
updateData.secrets = secretsRes.fleetServerHostUpdate
|
||||
.secrets as FleetServerHostSOAttributes['secrets'];
|
||||
secretsToDelete = secretsRes.secretsToDelete;
|
||||
} else {
|
||||
if (
|
||||
(!data.ssl?.key && data.secrets?.ssl?.key) ||
|
||||
(!data.ssl?.es_key && data.secrets?.ssl?.es_key)
|
||||
) {
|
||||
updateData.ssl = JSON.stringify({ ...data.ssl, ...data.secrets.ssl });
|
||||
}
|
||||
}
|
||||
|
||||
await this.encryptedSoClient.update<FleetServerHostSOAttributes>(
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
id,
|
||||
updateData
|
||||
);
|
||||
|
||||
if (secretsToDelete.length) {
|
||||
try {
|
||||
await deleteSecrets({ esClient, ids: secretsToDelete.map((s) => s.id) });
|
||||
} catch (err) {
|
||||
logger.warn(`Error cleaning up secrets for output ${id}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Updated fleet server host ${id}`);
|
||||
return {
|
||||
...originalItem,
|
||||
...updateData,
|
||||
};
|
||||
}
|
||||
|
||||
public async bulkGet(
|
||||
soClient: SavedObjectsClientContract,
|
||||
ids: string[],
|
||||
{ ignoreNotFound = false } = { ignoreNotFound: true }
|
||||
) {
|
||||
if (ids.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const res = await this.encryptedSoClient.bulkGet<FleetServerHostSOAttributes>(
|
||||
ids.map((id) => ({
|
||||
id,
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
}))
|
||||
);
|
||||
|
||||
return res.saved_objects
|
||||
.map((so) => {
|
||||
if (so.error) {
|
||||
if (!ignoreNotFound || so.error.statusCode !== 404) {
|
||||
throw so.error;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return savedObjectToFleetServerHost(so);
|
||||
})
|
||||
.filter(
|
||||
(fleetServerHostOrUndefined): fleetServerHostOrUndefined is FleetServerHost =>
|
||||
typeof fleetServerHostOrUndefined !== 'undefined'
|
||||
);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Get the default Fleet server policy hosts or throw if it does not exists
|
||||
// */
|
||||
public async getDefaultFleetServerHost(
|
||||
soClient: SavedObjectsClientContract
|
||||
): Promise<FleetServerHost | null> {
|
||||
const res = await this.encryptedSoClient.find<FleetServerHostSOAttributes>({
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
filter: `${FLEET_SERVER_HOST_SAVED_OBJECT_TYPE}.attributes.is_default:true`,
|
||||
});
|
||||
|
||||
if (res.saved_objects.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return savedObjectToFleetServerHost(res.saved_objects[0]);
|
||||
}
|
||||
}
|
||||
|
||||
export const fleetServerHostService = new FleetServerHostService();
|
||||
|
||||
export async function getFleetServerHostsForAgentPolicy(
|
||||
soClient: SavedObjectsClientContract,
|
||||
agentPolicy: Pick<AgentPolicy, 'fleet_server_host_id'>
|
||||
) {
|
||||
if (agentPolicy.fleet_server_host_id) {
|
||||
return getFleetServerHost(soClient, agentPolicy.fleet_server_host_id);
|
||||
return fleetServerHostService.get(soClient, agentPolicy.fleet_server_host_id);
|
||||
}
|
||||
|
||||
const defaultFleetServerHost = await getDefaultFleetServerHost(soClient);
|
||||
const defaultFleetServerHost = await fleetServerHostService.getDefaultFleetServerHost(soClient);
|
||||
if (!defaultFleetServerHost) {
|
||||
throw new FleetServerHostNotFoundError('Default Fleet Server host is not setup');
|
||||
}
|
||||
|
@ -241,29 +391,14 @@ export async function getFleetServerHostsForAgentPolicy(
|
|||
return defaultFleetServerHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default Fleet server policy hosts or throw if it does not exists
|
||||
*/
|
||||
export async function getDefaultFleetServerHost(
|
||||
soClient: SavedObjectsClientContract
|
||||
): Promise<FleetServerHost | null> {
|
||||
const res = await soClient.find<FleetServerHostSOAttributes>({
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
filter: `${FLEET_SERVER_HOST_SAVED_OBJECT_TYPE}.attributes.is_default:true`,
|
||||
});
|
||||
|
||||
if (res.saved_objects.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return savedObjectToFleetServerHost(res.saved_objects[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate Global setting fleet server hosts to their own saved object
|
||||
*/
|
||||
export async function migrateSettingsToFleetServerHost(soClient: SavedObjectsClientContract) {
|
||||
const defaultFleetServerHost = await getDefaultFleetServerHost(soClient);
|
||||
export async function migrateSettingsToFleetServerHost(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient
|
||||
) {
|
||||
const defaultFleetServerHost = await fleetServerHostService.getDefaultFleetServerHost(soClient);
|
||||
if (defaultFleetServerHost) {
|
||||
return;
|
||||
}
|
||||
|
@ -282,8 +417,9 @@ export async function migrateSettingsToFleetServerHost(soClient: SavedObjectsCli
|
|||
}
|
||||
|
||||
// Migrate
|
||||
await createFleetServerHost(
|
||||
await fleetServerHostService.create(
|
||||
soClient,
|
||||
esClient,
|
||||
{
|
||||
name: 'Default',
|
||||
host_urls: oldSettings.attributes.fleet_server_hosts,
|
||||
|
|
|
@ -38,6 +38,7 @@ export { outputService } from './output';
|
|||
export { downloadSourceService } from './download_source';
|
||||
export { settingsService };
|
||||
export { dataStreamService } from './data_streams';
|
||||
export { fleetServerHostService } from './fleet_server_host';
|
||||
|
||||
// Plugin services
|
||||
export { appContextService } from './app_context';
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
listFleetProxies,
|
||||
updateFleetProxy,
|
||||
} from '../fleet_proxies';
|
||||
import { listFleetServerHostsForProxyId } from '../fleet_server_host';
|
||||
import { fleetServerHostService } from '../fleet_server_host';
|
||||
import { agentPolicyService } from '../agent_policy';
|
||||
import { outputService } from '../output';
|
||||
|
||||
|
@ -96,7 +96,7 @@ async function createOrUpdatePreconfiguredFleetProxies(
|
|||
);
|
||||
// Bump all the agent policy that use that proxy
|
||||
const [{ items: fleetServerHosts }, { items: outputs }] = await Promise.all([
|
||||
listFleetServerHostsForProxyId(soClient, id),
|
||||
fleetServerHostService.listAllForProxyId(soClient, id),
|
||||
outputService.listAllForProxyId(soClient, id),
|
||||
]);
|
||||
if (
|
||||
|
@ -146,7 +146,7 @@ async function cleanPreconfiguredFleetProxies(
|
|||
}
|
||||
|
||||
const [{ items: fleetServerHosts }, { items: outputs }] = await Promise.all([
|
||||
listFleetServerHostsForProxyId(soClient, existingFleetProxy.id),
|
||||
fleetServerHostService.listAllForProxyId(soClient, existingFleetProxy.id),
|
||||
outputService.listAllForProxyId(soClient, existingFleetProxy.id),
|
||||
]);
|
||||
const isUsed = fleetServerHosts.length > 0 || outputs.length > 0;
|
||||
|
|
|
@ -8,12 +8,9 @@ import { savedObjectsClientMock, elasticsearchServiceMock } from '@kbn/core/serv
|
|||
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
||||
|
||||
import { appContextService } from '../app_context';
|
||||
import {
|
||||
getDefaultFleetServerHost,
|
||||
createFleetServerHost,
|
||||
bulkGetFleetServerHosts,
|
||||
updateFleetServerHost,
|
||||
} from '../fleet_server_host';
|
||||
import { fleetServerHostService } from '../fleet_server_host';
|
||||
|
||||
import type { FleetServerHost } from '../../../common/types';
|
||||
|
||||
import {
|
||||
createCloudFleetServerHostIfNeeded,
|
||||
|
@ -21,8 +18,7 @@ import {
|
|||
getPreconfiguredFleetServerHostFromConfig,
|
||||
createOrUpdatePreconfiguredFleetServerHosts,
|
||||
} from './fleet_server_host';
|
||||
|
||||
import type { FleetServerHost } from '../../../common/types';
|
||||
import { hashSecret } from './outputs';
|
||||
|
||||
jest.mock('../fleet_server_host');
|
||||
jest.mock('../app_context');
|
||||
|
@ -33,17 +29,8 @@ mockedAppContextService.getSecuritySetup.mockImplementation(() => ({
|
|||
...securityMock.createSetup(),
|
||||
}));
|
||||
|
||||
const mockedGetDefaultFleetServerHost = getDefaultFleetServerHost as jest.MockedFunction<
|
||||
typeof getDefaultFleetServerHost
|
||||
>;
|
||||
const mockedCreateFleetServerHost = createFleetServerHost as jest.MockedFunction<
|
||||
typeof createFleetServerHost
|
||||
>;
|
||||
const mockedUpdateFleetServerHost = updateFleetServerHost as jest.MockedFunction<
|
||||
typeof updateFleetServerHost
|
||||
>;
|
||||
const mockedBulkGetFleetServerHosts = bulkGetFleetServerHosts as jest.MockedFunction<
|
||||
typeof bulkGetFleetServerHosts
|
||||
const mockedFleetServerHostService = fleetServerHostService as jest.Mocked<
|
||||
typeof fleetServerHostService
|
||||
>;
|
||||
|
||||
describe('getPreconfiguredFleetServerHostFromConfig', () => {
|
||||
|
@ -64,6 +51,30 @@ describe('getPreconfiguredFleetServerHostFromConfig', () => {
|
|||
expect(res).toEqual(config.fleetServerHosts);
|
||||
});
|
||||
|
||||
it('should work with preconfigured fleetServerHosts that have SSL options', () => {
|
||||
const config = {
|
||||
fleetServerHosts: [
|
||||
{
|
||||
id: 'id1',
|
||||
name: 'fleet server 1',
|
||||
host_urls: [],
|
||||
is_default: false,
|
||||
is_preconfigured: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
es_certificate_authorities: ['es cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = getPreconfiguredFleetServerHostFromConfig(config);
|
||||
|
||||
expect(res).toEqual(config.fleetServerHosts);
|
||||
});
|
||||
|
||||
it('should work with agents.fleet_server.hosts', () => {
|
||||
const config = {
|
||||
agents: { fleet_server: { hosts: ['http://test.fr'] } },
|
||||
|
@ -217,19 +228,22 @@ describe('getCloudFleetServersHosts', () => {
|
|||
|
||||
describe('createCloudFleetServerHostIfNeeded', () => {
|
||||
afterEach(() => {
|
||||
mockedCreateFleetServerHost.mockReset();
|
||||
mockedFleetServerHostService.create.mockReset();
|
||||
mockedAppContextService.getCloud.mockReset();
|
||||
});
|
||||
it('should do nothing if there is no cloud fleet server hosts', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createCloudFleetServerHostIfNeeded(soClient);
|
||||
await createCloudFleetServerHostIfNeeded(soClient, esClient);
|
||||
|
||||
expect(mockedCreateFleetServerHost).not.toBeCalled();
|
||||
expect(mockedFleetServerHostService.create).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should do nothing if there is already an host configured', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
mockedAppContextService.getCloud.mockReturnValue({
|
||||
cloudId:
|
||||
'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
|
||||
|
@ -242,17 +256,19 @@ describe('createCloudFleetServerHostIfNeeded', () => {
|
|||
projectId: undefined,
|
||||
},
|
||||
});
|
||||
mockedGetDefaultFleetServerHost.mockResolvedValue({
|
||||
mockedFleetServerHostService.get.mockResolvedValue({
|
||||
id: 'test',
|
||||
} as any);
|
||||
|
||||
await createCloudFleetServerHostIfNeeded(soClient);
|
||||
await createCloudFleetServerHostIfNeeded(soClient, esClient);
|
||||
|
||||
expect(mockedCreateFleetServerHost).not.toBeCalled();
|
||||
expect(mockedFleetServerHostService.create).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should create a new fleet server hosts if there is no host configured', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
mockedAppContextService.getCloud.mockReturnValue({
|
||||
cloudId:
|
||||
'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
|
||||
|
@ -266,16 +282,17 @@ describe('createCloudFleetServerHostIfNeeded', () => {
|
|||
projectId: undefined,
|
||||
},
|
||||
});
|
||||
mockedGetDefaultFleetServerHost.mockResolvedValue(null);
|
||||
mockedFleetServerHostService.get.mockResolvedValue(null as any);
|
||||
soClient.create.mockResolvedValue({
|
||||
id: 'test-id',
|
||||
attributes: {},
|
||||
} as any);
|
||||
|
||||
await createCloudFleetServerHostIfNeeded(soClient);
|
||||
await createCloudFleetServerHostIfNeeded(soClient, esClient);
|
||||
|
||||
expect(mockedCreateFleetServerHost).toBeCalledTimes(1);
|
||||
expect(mockedCreateFleetServerHost).toBeCalledWith(
|
||||
expect(mockedFleetServerHostService.create).toBeCalledTimes(1);
|
||||
expect(mockedFleetServerHostService.create).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
host_urls: ['https://deployment-id-1.fleet.us-east-1.aws.found.io'],
|
||||
|
@ -287,8 +304,10 @@ describe('createCloudFleetServerHostIfNeeded', () => {
|
|||
});
|
||||
|
||||
describe('createOrUpdatePreconfiguredFleetServerHosts', () => {
|
||||
beforeEach(() => {
|
||||
mockedBulkGetFleetServerHosts.mockResolvedValue([
|
||||
let secretHash: string;
|
||||
beforeEach(async () => {
|
||||
secretHash = await hashSecret('secretKey');
|
||||
mockedFleetServerHostService.bulkGet.mockResolvedValue([
|
||||
{
|
||||
id: 'fleet-123',
|
||||
name: 'TEST',
|
||||
|
@ -301,11 +320,88 @@ describe('createOrUpdatePreconfiguredFleetServerHosts', () => {
|
|||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: true,
|
||||
},
|
||||
{
|
||||
id: 'fleet-with-secrets',
|
||||
name: 'TEST_SECRETS',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: true,
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'test123', hash: secretHash },
|
||||
},
|
||||
},
|
||||
},
|
||||
] as FleetServerHost[]);
|
||||
});
|
||||
afterEach(() => {
|
||||
mockedBulkGetFleetServerHosts.mockReset();
|
||||
mockedFleetServerHostService.bulkGet.mockReset();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should create a preconfigured fleet server host that does not exist', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, esClient, [
|
||||
{
|
||||
id: 'new-fleet-server-host',
|
||||
name: 'TEST_1',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: true,
|
||||
},
|
||||
]);
|
||||
expect(mockedFleetServerHostService.create).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
name: 'TEST_1',
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
expect(mockedFleetServerHostService.update).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should create a preconfigured fleet server host with secrets that does not exist', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, esClient, [
|
||||
{
|
||||
id: 'new-fleet-server-host',
|
||||
name: 'TEST_1',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: true,
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'unsecureKey1',
|
||||
es_key: 'unsecureKey2',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(mockedFleetServerHostService.create).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
name: 'TEST_1',
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'unsecureKey1',
|
||||
es_key: 'unsecureKey2',
|
||||
},
|
||||
},
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
expect(mockedFleetServerHostService.update).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should update preconfigured fleet server hosts if is_internal flag changes', async () => {
|
||||
|
@ -319,11 +415,224 @@ describe('createOrUpdatePreconfiguredFleetServerHosts', () => {
|
|||
is_default: false,
|
||||
is_internal: true,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: false,
|
||||
is_preconfigured: true,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(mockedCreateFleetServerHost).not.toBeCalled();
|
||||
expect(mockedUpdateFleetServerHost).toBeCalled();
|
||||
expect(mockedFleetServerHostService.create).not.toBeCalled();
|
||||
expect(mockedFleetServerHostService.update).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
'fleet-internal',
|
||||
expect.objectContaining({
|
||||
is_internal: true,
|
||||
}),
|
||||
{ fromPreconfiguration: true, secretHashes: {} }
|
||||
);
|
||||
});
|
||||
|
||||
it('should update preconfigured fleet server hosts if host_urls change', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, esClient, [
|
||||
{
|
||||
id: 'fleet-internal',
|
||||
name: 'TEST_INTERNAL',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr', 'http://test.fr'],
|
||||
is_preconfigured: true,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(mockedFleetServerHostService.create).not.toBeCalled();
|
||||
expect(mockedFleetServerHostService.update).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
'fleet-internal',
|
||||
expect.objectContaining({
|
||||
host_urls: ['http://test-internal.fr', 'http://test.fr'],
|
||||
}),
|
||||
{ fromPreconfiguration: true, secretHashes: {} }
|
||||
);
|
||||
});
|
||||
|
||||
it('should update preconfigured fleet server hosts if proxy_id change', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, esClient, [
|
||||
{
|
||||
id: 'fleet-internal',
|
||||
name: 'TEST_INTERNAL',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: true,
|
||||
proxy_id: 'proxy-test',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(mockedFleetServerHostService.create).not.toBeCalled();
|
||||
expect(mockedFleetServerHostService.update).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
'fleet-internal',
|
||||
expect.objectContaining({
|
||||
proxy_id: 'proxy-test',
|
||||
}),
|
||||
{ fromPreconfiguration: true, secretHashes: {} }
|
||||
);
|
||||
});
|
||||
|
||||
it('should update preconfigured fleet server hosts if preconfigured host exists and changed to have ssl', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, esClient, [
|
||||
{
|
||||
id: 'fleet-internal',
|
||||
name: 'TEST_INTERNAL',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: true,
|
||||
ssl: {
|
||||
key: 'unsecureKey1',
|
||||
es_key: 'unsecureKey2',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(mockedFleetServerHostService.create).not.toBeCalled();
|
||||
expect(mockedFleetServerHostService.update).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
'fleet-internal',
|
||||
expect.objectContaining({
|
||||
ssl: {
|
||||
key: 'unsecureKey1',
|
||||
es_key: 'unsecureKey2',
|
||||
},
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('should update preconfigured fleet server hosts if preconfigured host exists and changed to have secrets', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, esClient, [
|
||||
{
|
||||
id: 'fleet-internal',
|
||||
name: 'TEST_INTERNAL',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: true,
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'unsecureKey1',
|
||||
es_key: 'unsecureKey2',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(mockedFleetServerHostService.create).not.toBeCalled();
|
||||
expect(mockedFleetServerHostService.update).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
'fleet-internal',
|
||||
expect.objectContaining({
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'unsecureKey1',
|
||||
es_key: 'unsecureKey2',
|
||||
},
|
||||
},
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
it('should update preconfigured fleet server hosts if preconfigured host with secrets exists and changes', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, esClient, [
|
||||
{
|
||||
id: 'fleet-with-secrets',
|
||||
name: 'TEST_SECRETS',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: true,
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'secretKey',
|
||||
es_key: 'secretKey2',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(mockedFleetServerHostService.create).not.toBeCalled();
|
||||
expect(mockedFleetServerHostService.update).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
'fleet-with-secrets',
|
||||
expect.objectContaining({
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'secretKey',
|
||||
es_key: 'secretKey2',
|
||||
},
|
||||
},
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('should not update preconfigured fleet server hosts if no fields changed', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, esClient, [
|
||||
{
|
||||
id: 'fleet-internal',
|
||||
name: 'TEST_INTERNAL',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: true,
|
||||
},
|
||||
]);
|
||||
expect(mockedFleetServerHostService.create).not.toBeCalled();
|
||||
expect(mockedFleetServerHostService.update).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not update preconfigured fleet server hosts with secrets if no fields changed', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, esClient, [
|
||||
{
|
||||
id: 'fleet-with-secrets',
|
||||
name: 'TEST_SECRETS',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: true,
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'secretKey',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(mockedFleetServerHostService.create).not.toBeCalled();
|
||||
expect(mockedFleetServerHostService.update).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,17 +15,12 @@ import { FleetError } from '../../errors';
|
|||
|
||||
import type { FleetServerHost } from '../../types';
|
||||
import { appContextService } from '../app_context';
|
||||
import {
|
||||
bulkGetFleetServerHosts,
|
||||
createFleetServerHost,
|
||||
deleteFleetServerHost,
|
||||
listFleetServerHosts,
|
||||
updateFleetServerHost,
|
||||
getDefaultFleetServerHost,
|
||||
} from '../fleet_server_host';
|
||||
import { fleetServerHostService } from '../fleet_server_host';
|
||||
|
||||
import { agentPolicyService } from '../agent_policy';
|
||||
|
||||
import { isDifferent } from './utils';
|
||||
import { hashSecret, isSecretDifferent } from './outputs';
|
||||
|
||||
export function getCloudFleetServersHosts() {
|
||||
const cloudSetup = appContextService.getCloud();
|
||||
|
@ -81,7 +76,7 @@ export async function ensurePreconfiguredFleetServerHosts(
|
|||
esClient,
|
||||
preconfiguredFleetServerHosts
|
||||
);
|
||||
await createCloudFleetServerHostIfNeeded(soClient);
|
||||
await createCloudFleetServerHostIfNeeded(soClient, esClient);
|
||||
await cleanPreconfiguredFleetServerHosts(soClient, esClient, preconfiguredFleetServerHosts);
|
||||
}
|
||||
|
||||
|
@ -90,7 +85,7 @@ export async function createOrUpdatePreconfiguredFleetServerHosts(
|
|||
esClient: ElasticsearchClient,
|
||||
preconfiguredFleetServerHosts: FleetServerHost[]
|
||||
) {
|
||||
const existingFleetServerHosts = await bulkGetFleetServerHosts(
|
||||
const existingFleetServerHosts = await fleetServerHostService.bulkGet(
|
||||
soClient,
|
||||
preconfiguredFleetServerHosts.map(({ id }) => id),
|
||||
{ ignoreNotFound: true }
|
||||
|
@ -105,36 +100,37 @@ export async function createOrUpdatePreconfiguredFleetServerHosts(
|
|||
const { id, ...data } = preconfiguredFleetServerHost;
|
||||
|
||||
const isCreate = !existingHost;
|
||||
|
||||
const isUpdateWithNewData =
|
||||
(existingHost &&
|
||||
(!existingHost.is_preconfigured ||
|
||||
existingHost.is_default !== preconfiguredFleetServerHost.is_default ||
|
||||
existingHost.name !== preconfiguredFleetServerHost.name ||
|
||||
isDifferent(existingHost.is_internal, preconfiguredFleetServerHost.is_internal) ||
|
||||
isDifferent(
|
||||
existingHost.host_urls.map(normalizeHostsForAgents),
|
||||
preconfiguredFleetServerHost.host_urls.map(normalizeHostsForAgents)
|
||||
))) ||
|
||||
isDifferent(existingHost?.proxy_id, preconfiguredFleetServerHost.proxy_id);
|
||||
existingHost &&
|
||||
(!existingHost.is_preconfigured ||
|
||||
(await isPreconfiguredFleetServerHostDifferentFromCurrent(
|
||||
existingHost,
|
||||
preconfiguredFleetServerHost
|
||||
)));
|
||||
|
||||
const secretHashes = await hashSecrets(preconfiguredFleetServerHost);
|
||||
|
||||
if (isCreate) {
|
||||
await createFleetServerHost(
|
||||
await fleetServerHostService.create(
|
||||
soClient,
|
||||
esClient,
|
||||
{
|
||||
...data,
|
||||
is_preconfigured: true,
|
||||
},
|
||||
{ id, overwrite: true, fromPreconfiguration: true }
|
||||
{ id, overwrite: true, fromPreconfiguration: true, secretHashes }
|
||||
);
|
||||
} else if (isUpdateWithNewData) {
|
||||
await updateFleetServerHost(
|
||||
await fleetServerHostService.update(
|
||||
soClient,
|
||||
esClient,
|
||||
id,
|
||||
{
|
||||
...data,
|
||||
is_preconfigured: true,
|
||||
},
|
||||
{ fromPreconfiguration: true }
|
||||
{ fromPreconfiguration: true, secretHashes }
|
||||
);
|
||||
if (data.is_default) {
|
||||
await agentPolicyService.bumpAllAgentPolicies(esClient);
|
||||
|
@ -146,16 +142,20 @@ export async function createOrUpdatePreconfiguredFleetServerHosts(
|
|||
);
|
||||
}
|
||||
|
||||
export async function createCloudFleetServerHostIfNeeded(soClient: SavedObjectsClientContract) {
|
||||
export async function createCloudFleetServerHostIfNeeded(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient
|
||||
) {
|
||||
const cloudServerHosts = getCloudFleetServersHosts();
|
||||
if (!cloudServerHosts || cloudServerHosts.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultFleetServerHost = await getDefaultFleetServerHost(soClient);
|
||||
const defaultFleetServerHost = await fleetServerHostService.getDefaultFleetServerHost(soClient);
|
||||
if (!defaultFleetServerHost) {
|
||||
await createFleetServerHost(
|
||||
await fleetServerHostService.create(
|
||||
soClient,
|
||||
esClient,
|
||||
{
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
|
@ -172,7 +172,7 @@ export async function cleanPreconfiguredFleetServerHosts(
|
|||
esClient: ElasticsearchClient,
|
||||
preconfiguredFleetServerHosts: FleetServerHost[]
|
||||
) {
|
||||
const existingFleetServerHosts = await listFleetServerHosts(soClient);
|
||||
const existingFleetServerHosts = await fleetServerHostService.list(soClient);
|
||||
const existingPreconfiguredHosts = existingFleetServerHosts.items.filter(
|
||||
(o) => o.is_preconfigured === true
|
||||
);
|
||||
|
@ -186,8 +186,9 @@ export async function cleanPreconfiguredFleetServerHosts(
|
|||
}
|
||||
|
||||
if (existingFleetServerHost.is_default) {
|
||||
await updateFleetServerHost(
|
||||
await fleetServerHostService.update(
|
||||
soClient,
|
||||
esClient,
|
||||
existingFleetServerHost.id,
|
||||
{ is_preconfigured: false },
|
||||
{
|
||||
|
@ -195,7 +196,7 @@ export async function cleanPreconfiguredFleetServerHosts(
|
|||
}
|
||||
);
|
||||
} else {
|
||||
await deleteFleetServerHost(soClient, esClient, existingFleetServerHost.id, {
|
||||
await fleetServerHostService.delete(soClient, esClient, existingFleetServerHost.id, {
|
||||
fromPreconfiguration: true,
|
||||
});
|
||||
}
|
||||
|
@ -207,3 +208,53 @@ function getConfigFleetServerHosts(config?: FleetConfigType) {
|
|||
? config?.agents?.fleet_server?.hosts
|
||||
: undefined;
|
||||
}
|
||||
|
||||
async function hashSecrets(preconfiguredFleetServerHost: FleetServerHost) {
|
||||
let secrets: Record<string, any> = {};
|
||||
if (typeof preconfiguredFleetServerHost.secrets?.ssl?.key === 'string') {
|
||||
const key = await hashSecret(preconfiguredFleetServerHost.secrets?.ssl?.key);
|
||||
secrets = {
|
||||
ssl: {
|
||||
key,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (typeof preconfiguredFleetServerHost.secrets?.ssl?.key === 'string') {
|
||||
const esKey = await hashSecret(preconfiguredFleetServerHost.secrets?.ssl?.key);
|
||||
secrets = {
|
||||
...(secrets ? secrets : {}),
|
||||
ssl: { es_key: esKey },
|
||||
};
|
||||
}
|
||||
return secrets;
|
||||
}
|
||||
|
||||
async function isPreconfiguredFleetServerHostDifferentFromCurrent(
|
||||
existingFleetServerHost: FleetServerHost,
|
||||
preconfiguredFleetServerHost: Partial<FleetServerHost>
|
||||
): Promise<boolean> {
|
||||
const secretFieldsAreDifferent = async (): Promise<boolean> => {
|
||||
const sslKeyHashIsDifferent = await isSecretDifferent(
|
||||
preconfiguredFleetServerHost.secrets?.ssl?.key,
|
||||
existingFleetServerHost.secrets?.ssl?.key
|
||||
);
|
||||
const sslESKeyHashIsDifferent = await isSecretDifferent(
|
||||
preconfiguredFleetServerHost.secrets?.ssl?.es_key,
|
||||
existingFleetServerHost.secrets?.ssl?.es_key
|
||||
);
|
||||
return sslKeyHashIsDifferent || sslESKeyHashIsDifferent;
|
||||
};
|
||||
|
||||
return (
|
||||
existingFleetServerHost.is_default !== preconfiguredFleetServerHost.is_default ||
|
||||
existingFleetServerHost.name !== preconfiguredFleetServerHost.name ||
|
||||
isDifferent(existingFleetServerHost.is_internal, preconfiguredFleetServerHost.is_internal) ||
|
||||
isDifferent(
|
||||
existingFleetServerHost.host_urls.map(normalizeHostsForAgents),
|
||||
preconfiguredFleetServerHost?.host_urls?.map(normalizeHostsForAgents)
|
||||
) ||
|
||||
isDifferent(existingFleetServerHost?.proxy_id, preconfiguredFleetServerHost.proxy_id) ||
|
||||
isDifferent(existingFleetServerHost?.ssl, preconfiguredFleetServerHost?.ssl) ||
|
||||
secretFieldsAreDifferent()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import type {
|
|||
PreconfiguredOutput,
|
||||
Output,
|
||||
NewOutput,
|
||||
OutputSecret,
|
||||
SOSecret,
|
||||
KafkaOutput,
|
||||
NewRemoteElasticsearchOutput,
|
||||
} from '../../../common/types';
|
||||
|
@ -255,13 +255,13 @@ export async function cleanPreconfiguredOutputs(
|
|||
}
|
||||
}
|
||||
|
||||
const hasHash = (secret?: OutputSecret): secret is { id: string; hash: string } => {
|
||||
const hasHash = (secret?: SOSecret): secret is { id: string; hash: string } => {
|
||||
return !!secret && typeof secret !== 'string' && !!secret.hash;
|
||||
};
|
||||
|
||||
async function isSecretDifferent(
|
||||
preconfiguredValue: OutputSecret | undefined,
|
||||
existingSecret: OutputSecret | undefined
|
||||
export async function isSecretDifferent(
|
||||
preconfiguredValue: SOSecret | undefined,
|
||||
existingSecret: SOSecret | undefined
|
||||
): Promise<boolean> {
|
||||
if (!existingSecret && preconfiguredValue) {
|
||||
return true;
|
||||
|
|
|
@ -24,10 +24,12 @@ import { appContextService } from './app_context';
|
|||
import {
|
||||
getPolicySecretPaths,
|
||||
diffSecretPaths,
|
||||
diffOutputSecretPaths,
|
||||
diffSOSecretPaths,
|
||||
extractAndWriteSecrets,
|
||||
extractAndUpdateSecrets,
|
||||
extractAndUpdateOutputSecrets,
|
||||
extractAndWriteFleetServerHostsSecrets,
|
||||
extractAndUpdateFleetServerHostsSecrets,
|
||||
} from './secrets';
|
||||
|
||||
describe('secrets', () => {
|
||||
|
@ -1440,11 +1442,152 @@ describe('secrets', () => {
|
|||
expect(result.secretsToDelete).toEqual([{ id: 'token' }]);
|
||||
});
|
||||
});
|
||||
describe('extractAndWriteFleetServerHostsSecrets', () => {
|
||||
const esClientMock = elasticsearchServiceMock.createInternalClient();
|
||||
|
||||
esClientMock.transport.request.mockImplementation(async (req) => {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
esClientMock.transport.request.mockClear();
|
||||
});
|
||||
|
||||
it('should create new secrets', async () => {
|
||||
const fleetServerHost = {
|
||||
id: 'id1',
|
||||
name: 'fleet server 1',
|
||||
host_urls: [],
|
||||
is_default: false,
|
||||
is_preconfigured: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
es_certificate_authorities: ['es cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'key1',
|
||||
es_key: 'key2',
|
||||
},
|
||||
},
|
||||
};
|
||||
const res = await extractAndWriteFleetServerHostsSecrets({
|
||||
fleetServerHost,
|
||||
esClient: esClientMock,
|
||||
});
|
||||
expect(res.fleetServerHost).toEqual({
|
||||
...fleetServerHost,
|
||||
secrets: {
|
||||
ssl: {
|
||||
es_key: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
key: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res.secretReferences).toEqual([{ id: expect.anything() }, { id: expect.anything() }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractAndUpdateFleetServerHostsSecrets', () => {
|
||||
const esClientMock = elasticsearchServiceMock.createInternalClient();
|
||||
|
||||
esClientMock.transport.request.mockImplementation(async (req) => {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
esClientMock.transport.request.mockClear();
|
||||
});
|
||||
|
||||
it('should update existing secrets', async () => {
|
||||
const fleetServerHost = {
|
||||
id: 'id1',
|
||||
name: 'fleet server 1',
|
||||
host_urls: [],
|
||||
is_default: false,
|
||||
is_preconfigured: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
es_certificate_authorities: ['es cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'key1',
|
||||
es_key: 'key2',
|
||||
},
|
||||
},
|
||||
};
|
||||
const updatedFleetServerHost = {
|
||||
...fleetServerHost,
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'newkey1',
|
||||
es_key: 'newkey2',
|
||||
},
|
||||
},
|
||||
};
|
||||
const res = await extractAndUpdateFleetServerHostsSecrets({
|
||||
oldFleetServerHost: fleetServerHost,
|
||||
fleetServerHostUpdate: updatedFleetServerHost,
|
||||
esClient: esClientMock,
|
||||
});
|
||||
expect(res.fleetServerHostUpdate).toEqual({
|
||||
...fleetServerHost,
|
||||
secrets: {
|
||||
ssl: {
|
||||
es_key: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
key: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res.secretReferences).toEqual([{ id: expect.anything() }, { id: expect.anything() }]);
|
||||
expect(res.secretsToDelete).toEqual([{ id: undefined }, { id: undefined }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('diffOutputSecretPaths', () => {
|
||||
describe('diffSOSecretPaths', () => {
|
||||
const paths1 = [
|
||||
{
|
||||
path: 'somepath1',
|
||||
value: {
|
||||
id: 'secret-1',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'somepath2',
|
||||
value: {
|
||||
id: 'secret-2',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const paths2 = [
|
||||
paths1[0],
|
||||
{
|
||||
path: 'somepath2',
|
||||
value: 'newvalue',
|
||||
},
|
||||
];
|
||||
|
||||
it('should return empty array if no secrets', () => {
|
||||
expect(diffOutputSecretPaths([], [])).toEqual({
|
||||
expect(diffSOSecretPaths([], [])).toEqual({
|
||||
toCreate: [],
|
||||
toDelete: [],
|
||||
noChange: [],
|
||||
|
@ -1459,7 +1602,7 @@ describe('diffOutputSecretPaths', () => {
|
|||
},
|
||||
},
|
||||
];
|
||||
expect(diffOutputSecretPaths(paths, paths)).toEqual({
|
||||
expect(diffSOSecretPaths(paths, paths)).toEqual({
|
||||
toCreate: [],
|
||||
toDelete: [],
|
||||
noChange: paths,
|
||||
|
@ -1487,37 +1630,14 @@ describe('diffOutputSecretPaths', () => {
|
|||
},
|
||||
];
|
||||
|
||||
expect(diffOutputSecretPaths(paths, paths.slice().reverse())).toEqual({
|
||||
expect(diffSOSecretPaths(paths, paths.slice().reverse())).toEqual({
|
||||
toCreate: [],
|
||||
toDelete: [],
|
||||
noChange: paths,
|
||||
});
|
||||
});
|
||||
it('single secret modified', () => {
|
||||
const paths1 = [
|
||||
{
|
||||
path: 'somepath1',
|
||||
value: {
|
||||
id: 'secret-1',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'somepath2',
|
||||
value: {
|
||||
id: 'secret-2',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const paths2 = [
|
||||
paths1[0],
|
||||
{
|
||||
path: 'somepath2',
|
||||
value: 'newvalue',
|
||||
},
|
||||
];
|
||||
|
||||
expect(diffOutputSecretPaths(paths1, paths2)).toEqual({
|
||||
expect(diffSOSecretPaths(paths1, paths2)).toEqual({
|
||||
toCreate: [
|
||||
{
|
||||
path: 'somepath2',
|
||||
|
@ -1536,7 +1656,7 @@ describe('diffOutputSecretPaths', () => {
|
|||
});
|
||||
});
|
||||
it('double secret modified', () => {
|
||||
const paths1 = [
|
||||
const pathsDouble1 = [
|
||||
{
|
||||
path: 'somepath1',
|
||||
value: {
|
||||
|
@ -1551,7 +1671,7 @@ describe('diffOutputSecretPaths', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const paths2 = [
|
||||
const pathsDouble2 = [
|
||||
{
|
||||
path: 'somepath1',
|
||||
value: 'newvalue1',
|
||||
|
@ -1562,7 +1682,7 @@ describe('diffOutputSecretPaths', () => {
|
|||
},
|
||||
];
|
||||
|
||||
expect(diffOutputSecretPaths(paths1, paths2)).toEqual({
|
||||
expect(diffSOSecretPaths(pathsDouble1, pathsDouble2)).toEqual({
|
||||
toCreate: [
|
||||
{
|
||||
path: 'somepath1',
|
||||
|
@ -1590,9 +1710,8 @@ describe('diffOutputSecretPaths', () => {
|
|||
noChange: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('single secret added', () => {
|
||||
const paths1 = [
|
||||
const pathsSingle1 = [
|
||||
{
|
||||
path: 'somepath1',
|
||||
value: {
|
||||
|
@ -1601,7 +1720,7 @@ describe('diffOutputSecretPaths', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const paths2 = [
|
||||
const pathsSingle2 = [
|
||||
paths1[0],
|
||||
{
|
||||
path: 'somepath2',
|
||||
|
@ -1609,7 +1728,7 @@ describe('diffOutputSecretPaths', () => {
|
|||
},
|
||||
];
|
||||
|
||||
expect(diffOutputSecretPaths(paths1, paths2)).toEqual({
|
||||
expect(diffSOSecretPaths(pathsSingle1, pathsSingle2)).toEqual({
|
||||
toCreate: [
|
||||
{
|
||||
path: 'somepath2',
|
||||
|
|
|
@ -11,10 +11,12 @@ import { get, keyBy } from 'lodash';
|
|||
import { set } from '@kbn/safer-lodash-set';
|
||||
|
||||
import type {
|
||||
FleetServerHost,
|
||||
SOSecretPath,
|
||||
KafkaOutput,
|
||||
NewFleetServerHost,
|
||||
NewRemoteElasticsearchOutput,
|
||||
Output,
|
||||
OutputSecretPath,
|
||||
} from '../../common/types';
|
||||
|
||||
import { packageHasNoPolicyTemplates } from '../../common/services/policy_template';
|
||||
|
@ -256,138 +258,6 @@ export async function extractAndWriteSecrets(opts: {
|
|||
};
|
||||
}
|
||||
|
||||
export async function extractAndWriteOutputSecrets(opts: {
|
||||
output: NewOutput;
|
||||
esClient: ElasticsearchClient;
|
||||
secretHashes?: Record<string, any>;
|
||||
}): Promise<{ output: NewOutput; secretReferences: PolicySecretReference[] }> {
|
||||
const { output, esClient, secretHashes = {} } = opts;
|
||||
|
||||
const secretPaths = getOutputSecretPaths(output.type, output).filter(
|
||||
(path) => typeof path.value === 'string'
|
||||
);
|
||||
|
||||
if (secretPaths.length === 0) {
|
||||
return { output, secretReferences: [] };
|
||||
}
|
||||
|
||||
const secrets = await createSecrets({
|
||||
esClient,
|
||||
values: secretPaths.map(({ value }) => value as string),
|
||||
});
|
||||
|
||||
const outputWithSecretRefs = JSON.parse(JSON.stringify(output));
|
||||
secretPaths.forEach((secretPath, i) => {
|
||||
const pathWithoutPrefix = secretPath.path.replace('secrets.', '');
|
||||
const maybeHash = get(secretHashes, pathWithoutPrefix);
|
||||
set(outputWithSecretRefs, secretPath.path, {
|
||||
id: secrets[i].id,
|
||||
...(typeof maybeHash === 'string' && { hash: maybeHash }),
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
output: outputWithSecretRefs,
|
||||
secretReferences: secrets.map(({ id }) => ({ id })),
|
||||
};
|
||||
}
|
||||
|
||||
function getOutputSecretPaths(
|
||||
outputType: NewOutput['type'],
|
||||
output: NewOutput | Partial<Output>
|
||||
): OutputSecretPath[] {
|
||||
const outputSecretPaths: OutputSecretPath[] = [];
|
||||
|
||||
if (outputType === 'kafka') {
|
||||
const kafkaOutput = output as KafkaOutput;
|
||||
if (kafkaOutput?.secrets?.password) {
|
||||
outputSecretPaths.push({
|
||||
path: 'secrets.password',
|
||||
value: kafkaOutput.secrets.password,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (outputType === 'remote_elasticsearch') {
|
||||
const remoteESOutput = output as NewRemoteElasticsearchOutput;
|
||||
if (remoteESOutput.secrets?.service_token) {
|
||||
outputSecretPaths.push({
|
||||
path: 'secrets.service_token',
|
||||
value: remoteESOutput.secrets.service_token,
|
||||
});
|
||||
}
|
||||
if (remoteESOutput.secrets?.kibana_api_key) {
|
||||
outputSecretPaths.push({
|
||||
path: 'secrets.kibana_api_key',
|
||||
value: remoteESOutput.secrets.kibana_api_key,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// common to all outputs
|
||||
if (output?.secrets?.ssl?.key) {
|
||||
outputSecretPaths.push({
|
||||
path: 'secrets.ssl.key',
|
||||
value: output.secrets.ssl.key,
|
||||
});
|
||||
}
|
||||
|
||||
return outputSecretPaths;
|
||||
}
|
||||
|
||||
export async function deleteOutputSecrets(opts: {
|
||||
output: Output;
|
||||
esClient: ElasticsearchClient;
|
||||
}): Promise<void> {
|
||||
const { output, esClient } = opts;
|
||||
|
||||
const outputType = output.type;
|
||||
const outputSecretPaths = getOutputSecretPaths(outputType, output);
|
||||
|
||||
if (outputSecretPaths.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const secretIds = outputSecretPaths.map(({ value }) => (value as { id: string }).id);
|
||||
|
||||
try {
|
||||
return deleteSecrets({ esClient, ids: secretIds });
|
||||
} catch (err) {
|
||||
appContextService.getLogger().warn(`Error deleting secrets: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getOutputSecretReferences(output: Output): PolicySecretReference[] {
|
||||
const outputSecretPaths: PolicySecretReference[] = [];
|
||||
|
||||
if (typeof output.secrets?.ssl?.key === 'object') {
|
||||
outputSecretPaths.push({
|
||||
id: output.secrets.ssl.key.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (output.type === 'kafka' && typeof output?.secrets?.password === 'object') {
|
||||
outputSecretPaths.push({
|
||||
id: output.secrets.password.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (output.type === 'remote_elasticsearch') {
|
||||
if (typeof output?.secrets?.service_token === 'object') {
|
||||
outputSecretPaths.push({
|
||||
id: output.secrets.service_token.id,
|
||||
});
|
||||
}
|
||||
if (typeof output?.secrets?.kibana_api_key === 'object') {
|
||||
outputSecretPaths.push({
|
||||
id: output.secrets.kibana_api_key.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return outputSecretPaths;
|
||||
}
|
||||
|
||||
export async function extractAndUpdateSecrets(opts: {
|
||||
oldPackagePolicy: PackagePolicy;
|
||||
packagePolicyUpdate: UpdatePackagePolicy;
|
||||
|
@ -443,59 +313,6 @@ export async function extractAndUpdateSecrets(opts: {
|
|||
secretsToDelete,
|
||||
};
|
||||
}
|
||||
export async function extractAndUpdateOutputSecrets(opts: {
|
||||
oldOutput: Output;
|
||||
outputUpdate: Partial<Output>;
|
||||
esClient: ElasticsearchClient;
|
||||
secretHashes?: Record<string, any>;
|
||||
}): Promise<{
|
||||
outputUpdate: Partial<Output>;
|
||||
secretReferences: PolicySecretReference[];
|
||||
secretsToDelete: PolicySecretReference[];
|
||||
}> {
|
||||
const { oldOutput, outputUpdate, esClient, secretHashes } = opts;
|
||||
const outputType = outputUpdate.type || oldOutput.type;
|
||||
const oldSecretPaths = getOutputSecretPaths(oldOutput.type, oldOutput);
|
||||
const updatedSecretPaths = getOutputSecretPaths(outputType, outputUpdate);
|
||||
|
||||
if (!oldSecretPaths.length && !updatedSecretPaths.length) {
|
||||
return { outputUpdate, secretReferences: [], secretsToDelete: [] };
|
||||
}
|
||||
|
||||
const { toCreate, toDelete, noChange } = diffOutputSecretPaths(
|
||||
oldSecretPaths,
|
||||
updatedSecretPaths
|
||||
);
|
||||
|
||||
const createdSecrets = await createSecrets({
|
||||
esClient,
|
||||
values: toCreate.map((secretPath) => secretPath.value as string),
|
||||
});
|
||||
|
||||
const outputWithSecretRefs = JSON.parse(JSON.stringify(outputUpdate));
|
||||
toCreate.forEach((secretPath, i) => {
|
||||
const pathWithoutPrefix = secretPath.path.replace('secrets.', '');
|
||||
const maybeHash = get(secretHashes, pathWithoutPrefix);
|
||||
|
||||
set(outputWithSecretRefs, secretPath.path, {
|
||||
id: createdSecrets[i].id,
|
||||
...(typeof maybeHash === 'string' && { hash: maybeHash }),
|
||||
});
|
||||
});
|
||||
|
||||
const secretReferences = [
|
||||
...noChange.map((secretPath) => ({ id: (secretPath.value as { id: string }).id })),
|
||||
...createdSecrets.map(({ id }) => ({ id })),
|
||||
];
|
||||
|
||||
return {
|
||||
outputUpdate: outputWithSecretRefs,
|
||||
secretReferences,
|
||||
secretsToDelete: toDelete.map((secretPath) => ({
|
||||
id: (secretPath.value as { id: string }).id,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function isSecretVar(varDef: RegistryVarsEntry) {
|
||||
return varDef.secret === true;
|
||||
|
@ -547,38 +364,6 @@ export function diffSecretPaths(
|
|||
return { toCreate: [...toCreate, ...remainingNewPaths], toDelete, noChange };
|
||||
}
|
||||
|
||||
export function diffOutputSecretPaths(
|
||||
oldPaths: OutputSecretPath[],
|
||||
newPaths: OutputSecretPath[]
|
||||
): { toCreate: OutputSecretPath[]; toDelete: OutputSecretPath[]; noChange: OutputSecretPath[] } {
|
||||
const toCreate: OutputSecretPath[] = [];
|
||||
const toDelete: OutputSecretPath[] = [];
|
||||
const noChange: OutputSecretPath[] = [];
|
||||
const newPathsByPath = keyBy(newPaths, 'path');
|
||||
|
||||
for (const oldPath of oldPaths) {
|
||||
if (!newPathsByPath[oldPath.path]) {
|
||||
toDelete.push(oldPath);
|
||||
}
|
||||
|
||||
const newPath = newPathsByPath[oldPath.path];
|
||||
if (newPath && newPath.value) {
|
||||
const newValue = newPath.value;
|
||||
if (typeof newValue === 'string') {
|
||||
toCreate.push(newPath);
|
||||
toDelete.push(oldPath);
|
||||
} else {
|
||||
noChange.push(newPath);
|
||||
}
|
||||
}
|
||||
delete newPathsByPath[oldPath.path];
|
||||
}
|
||||
|
||||
const remainingNewPaths = Object.values(newPathsByPath);
|
||||
|
||||
return { toCreate: [...toCreate, ...remainingNewPaths], toDelete, noChange };
|
||||
}
|
||||
|
||||
// Given a package policy and a package,
|
||||
// returns an array of lodash style paths to all secrets and their current values
|
||||
export function getPolicySecretPaths(
|
||||
|
@ -850,3 +635,393 @@ function getPolicyWithSecretReferences(
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common functions for SO objects
|
||||
* Currently used for outputs and fleet server hosts
|
||||
*/
|
||||
|
||||
/**
|
||||
* diffSOSecretPaths
|
||||
* Makes the diff betwwen old and new secrets paths
|
||||
*/
|
||||
export function diffSOSecretPaths(
|
||||
oldPaths: SOSecretPath[],
|
||||
newPaths: SOSecretPath[]
|
||||
): { toCreate: SOSecretPath[]; toDelete: SOSecretPath[]; noChange: SOSecretPath[] } {
|
||||
const toCreate: SOSecretPath[] = [];
|
||||
const toDelete: SOSecretPath[] = [];
|
||||
const noChange: SOSecretPath[] = [];
|
||||
const newPathsByPath = keyBy(newPaths, 'path');
|
||||
|
||||
for (const oldPath of oldPaths) {
|
||||
if (!newPathsByPath[oldPath.path]) {
|
||||
toDelete.push(oldPath);
|
||||
}
|
||||
|
||||
const newPath = newPathsByPath[oldPath.path];
|
||||
if (newPath && newPath.value) {
|
||||
const newValue = newPath.value;
|
||||
if (typeof newValue === 'string') {
|
||||
toCreate.push(newPath);
|
||||
toDelete.push(oldPath);
|
||||
} else {
|
||||
noChange.push(newPath);
|
||||
}
|
||||
}
|
||||
delete newPathsByPath[oldPath.path];
|
||||
}
|
||||
|
||||
const remainingNewPaths = Object.values(newPathsByPath);
|
||||
|
||||
return { toCreate: [...toCreate, ...remainingNewPaths], toDelete, noChange };
|
||||
}
|
||||
|
||||
/**
|
||||
* deleteSOSecrets
|
||||
* Given an array of secret paths, deletes the corresponding secrets
|
||||
*/
|
||||
export async function deleteSOSecrets(
|
||||
esClient: ElasticsearchClient,
|
||||
secretPaths: SOSecretPath[]
|
||||
): Promise<void> {
|
||||
if (secretPaths.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const secretIds = secretPaths.map(({ value }) => (value as { id: string }).id);
|
||||
|
||||
try {
|
||||
return deleteSecrets({ esClient, ids: secretIds });
|
||||
} catch (err) {
|
||||
appContextService.getLogger().warn(`Error deleting secrets: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* extractAndWriteSOSecrets
|
||||
* Takes a generic object T and its secret paths
|
||||
* Creates new secrets and returns the references
|
||||
*/
|
||||
async function extractAndWriteSOSecrets<T>(opts: {
|
||||
soObject: T;
|
||||
esClient: ElasticsearchClient;
|
||||
secretPaths: SOSecretPath[];
|
||||
secretHashes?: Record<string, any>;
|
||||
}): Promise<{ soObjectWithSecrets: T; secretReferences: PolicySecretReference[] }> {
|
||||
const { soObject, esClient, secretPaths, secretHashes = {} } = opts;
|
||||
|
||||
if (secretPaths.length === 0) {
|
||||
return { soObjectWithSecrets: soObject, secretReferences: [] };
|
||||
}
|
||||
|
||||
const secrets = await createSecrets({
|
||||
esClient,
|
||||
values: secretPaths.map(({ value }) => value as string),
|
||||
});
|
||||
|
||||
const objectWithSecretRefs = JSON.parse(JSON.stringify(soObject));
|
||||
secretPaths.forEach((secretPath, i) => {
|
||||
const pathWithoutPrefix = secretPath.path.replace('secrets.', '');
|
||||
const maybeHash = get(secretHashes, pathWithoutPrefix);
|
||||
set(objectWithSecretRefs, secretPath.path, {
|
||||
id: secrets[i].id,
|
||||
...(typeof maybeHash === 'string' && { hash: maybeHash }),
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
soObjectWithSecrets: objectWithSecretRefs,
|
||||
secretReferences: secrets.map(({ id }) => ({ id })),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* extractAndUpdateSOSecrets
|
||||
* Takes a generic object T to update and its old and new secret paths
|
||||
* Updates secrets and returns the references
|
||||
*/
|
||||
async function extractAndUpdateSOSecrets<T>(opts: {
|
||||
updatedSoObject: Partial<T>;
|
||||
oldSecretPaths: SOSecretPath[];
|
||||
updatedSecretPaths: SOSecretPath[];
|
||||
esClient: ElasticsearchClient;
|
||||
secretHashes?: Record<string, any>;
|
||||
}): Promise<{
|
||||
updatedSoObject: Partial<T>;
|
||||
secretReferences: PolicySecretReference[];
|
||||
secretsToDelete: PolicySecretReference[];
|
||||
}> {
|
||||
const { updatedSoObject, oldSecretPaths, updatedSecretPaths, esClient, secretHashes } = opts;
|
||||
|
||||
if (!oldSecretPaths.length && !updatedSecretPaths.length) {
|
||||
return { updatedSoObject, secretReferences: [], secretsToDelete: [] };
|
||||
}
|
||||
|
||||
const { toCreate, toDelete, noChange } = diffSOSecretPaths(oldSecretPaths, updatedSecretPaths);
|
||||
|
||||
const createdSecrets = await createSecrets({
|
||||
esClient,
|
||||
values: toCreate.map((secretPath) => secretPath.value as string),
|
||||
});
|
||||
|
||||
const soObjectWithSecretRefs = JSON.parse(JSON.stringify(updatedSoObject));
|
||||
toCreate.forEach((secretPath, i) => {
|
||||
const pathWithoutPrefix = secretPath.path.replace('secrets.', '');
|
||||
const maybeHash = get(secretHashes, pathWithoutPrefix);
|
||||
|
||||
set(soObjectWithSecretRefs, secretPath.path, {
|
||||
id: createdSecrets[i].id,
|
||||
...(typeof maybeHash === 'string' && { hash: maybeHash }),
|
||||
});
|
||||
});
|
||||
|
||||
const secretReferences = [
|
||||
...noChange.map((secretPath) => ({ id: (secretPath.value as { id: string }).id })),
|
||||
...createdSecrets.map(({ id }) => ({ id })),
|
||||
];
|
||||
|
||||
return {
|
||||
updatedSoObject: soObjectWithSecretRefs,
|
||||
secretReferences,
|
||||
secretsToDelete: toDelete.map((secretPath) => ({
|
||||
id: (secretPath.value as { id: string }).id,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
// Outputs functions
|
||||
export async function extractAndWriteOutputSecrets(opts: {
|
||||
output: NewOutput;
|
||||
esClient: ElasticsearchClient;
|
||||
secretHashes?: Record<string, any>;
|
||||
}): Promise<{ output: NewOutput; secretReferences: PolicySecretReference[] }> {
|
||||
const { output, esClient, secretHashes = {} } = opts;
|
||||
const secretPaths = getOutputSecretPaths(output.type, output).filter(
|
||||
(path) => typeof path.value === 'string'
|
||||
);
|
||||
const secretRes = await extractAndWriteSOSecrets<NewOutput>({
|
||||
soObject: output,
|
||||
secretPaths,
|
||||
esClient,
|
||||
secretHashes,
|
||||
});
|
||||
return { output: secretRes.soObjectWithSecrets, secretReferences: secretRes.secretReferences };
|
||||
}
|
||||
|
||||
export async function extractAndUpdateOutputSecrets(opts: {
|
||||
oldOutput: Output;
|
||||
outputUpdate: Partial<Output>;
|
||||
esClient: ElasticsearchClient;
|
||||
secretHashes?: Record<string, any>;
|
||||
}): Promise<{
|
||||
outputUpdate: Partial<Output>;
|
||||
secretReferences: PolicySecretReference[];
|
||||
secretsToDelete: PolicySecretReference[];
|
||||
}> {
|
||||
const { oldOutput, outputUpdate, esClient, secretHashes } = opts;
|
||||
const outputType = outputUpdate.type || oldOutput.type;
|
||||
const oldSecretPaths = getOutputSecretPaths(oldOutput.type, oldOutput);
|
||||
const updatedSecretPaths = getOutputSecretPaths(outputType, outputUpdate);
|
||||
|
||||
const secretRes = await extractAndUpdateSOSecrets<Output>({
|
||||
updatedSoObject: outputUpdate,
|
||||
oldSecretPaths,
|
||||
updatedSecretPaths,
|
||||
esClient,
|
||||
secretHashes: outputUpdate.is_preconfigured ? secretHashes : undefined,
|
||||
});
|
||||
return {
|
||||
outputUpdate: secretRes.updatedSoObject,
|
||||
secretReferences: secretRes.secretReferences,
|
||||
secretsToDelete: secretRes.secretsToDelete,
|
||||
};
|
||||
}
|
||||
|
||||
function getOutputSecretPaths(
|
||||
outputType: NewOutput['type'],
|
||||
output: NewOutput | Partial<Output>
|
||||
): SOSecretPath[] {
|
||||
const outputSecretPaths: SOSecretPath[] = [];
|
||||
|
||||
if (outputType === 'kafka') {
|
||||
const kafkaOutput = output as KafkaOutput;
|
||||
if (kafkaOutput?.secrets?.password) {
|
||||
outputSecretPaths.push({
|
||||
path: 'secrets.password',
|
||||
value: kafkaOutput.secrets.password,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (outputType === 'remote_elasticsearch') {
|
||||
const remoteESOutput = output as NewRemoteElasticsearchOutput;
|
||||
if (remoteESOutput.secrets?.service_token) {
|
||||
outputSecretPaths.push({
|
||||
path: 'secrets.service_token',
|
||||
value: remoteESOutput.secrets.service_token,
|
||||
});
|
||||
}
|
||||
if (remoteESOutput.secrets?.kibana_api_key) {
|
||||
outputSecretPaths.push({
|
||||
path: 'secrets.kibana_api_key',
|
||||
value: remoteESOutput.secrets.kibana_api_key,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// common to all outputs
|
||||
if (output?.secrets?.ssl?.key) {
|
||||
outputSecretPaths.push({
|
||||
path: 'secrets.ssl.key',
|
||||
value: output.secrets.ssl.key,
|
||||
});
|
||||
}
|
||||
|
||||
return outputSecretPaths;
|
||||
}
|
||||
|
||||
export async function deleteOutputSecrets(opts: {
|
||||
output: Output;
|
||||
esClient: ElasticsearchClient;
|
||||
}): Promise<void> {
|
||||
const { output, esClient } = opts;
|
||||
|
||||
const outputType = output.type;
|
||||
const outputSecretPaths = getOutputSecretPaths(outputType, output);
|
||||
|
||||
await deleteSOSecrets(esClient, outputSecretPaths);
|
||||
}
|
||||
|
||||
export function getOutputSecretReferences(output: Output): PolicySecretReference[] {
|
||||
const outputSecretPaths: PolicySecretReference[] = [];
|
||||
|
||||
if (typeof output.secrets?.ssl?.key === 'object') {
|
||||
outputSecretPaths.push({
|
||||
id: output.secrets.ssl.key.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (output.type === 'kafka' && typeof output?.secrets?.password === 'object') {
|
||||
outputSecretPaths.push({
|
||||
id: output.secrets.password.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (output.type === 'remote_elasticsearch') {
|
||||
if (typeof output?.secrets?.service_token === 'object') {
|
||||
outputSecretPaths.push({
|
||||
id: output.secrets.service_token.id,
|
||||
});
|
||||
}
|
||||
if (typeof output?.secrets?.kibana_api_key === 'object') {
|
||||
outputSecretPaths.push({
|
||||
id: output.secrets.kibana_api_key.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return outputSecretPaths;
|
||||
}
|
||||
// Fleet server hosts functions
|
||||
function getFleetServerHostsSecretPaths(
|
||||
fleetServerHost: NewFleetServerHost | Partial<FleetServerHost>
|
||||
): SOSecretPath[] {
|
||||
const secretPaths: SOSecretPath[] = [];
|
||||
|
||||
if (fleetServerHost?.secrets?.ssl?.key) {
|
||||
secretPaths.push({
|
||||
path: 'secrets.ssl.key',
|
||||
value: fleetServerHost.secrets.ssl.key,
|
||||
});
|
||||
}
|
||||
if (fleetServerHost?.secrets?.ssl?.es_key) {
|
||||
secretPaths.push({
|
||||
path: 'secrets.ssl.es_key',
|
||||
value: fleetServerHost.secrets.ssl.es_key,
|
||||
});
|
||||
}
|
||||
|
||||
return secretPaths;
|
||||
}
|
||||
|
||||
export async function extractAndWriteFleetServerHostsSecrets(opts: {
|
||||
fleetServerHost: NewFleetServerHost;
|
||||
esClient: ElasticsearchClient;
|
||||
secretHashes?: Record<string, any>;
|
||||
}): Promise<{ fleetServerHost: NewFleetServerHost; secretReferences: PolicySecretReference[] }> {
|
||||
const { fleetServerHost, esClient, secretHashes = {} } = opts;
|
||||
|
||||
const secretPaths = getFleetServerHostsSecretPaths(fleetServerHost).filter(
|
||||
(path) => typeof path.value === 'string'
|
||||
);
|
||||
const secretRes = await extractAndWriteSOSecrets<NewFleetServerHost>({
|
||||
soObject: fleetServerHost,
|
||||
secretPaths,
|
||||
esClient,
|
||||
secretHashes,
|
||||
});
|
||||
return {
|
||||
fleetServerHost: secretRes.soObjectWithSecrets,
|
||||
secretReferences: secretRes.secretReferences,
|
||||
};
|
||||
}
|
||||
|
||||
export async function extractAndUpdateFleetServerHostsSecrets(opts: {
|
||||
oldFleetServerHost: NewFleetServerHost;
|
||||
fleetServerHostUpdate: Partial<NewFleetServerHost>;
|
||||
esClient: ElasticsearchClient;
|
||||
secretHashes?: Record<string, any>;
|
||||
}): Promise<{
|
||||
fleetServerHostUpdate: Partial<NewFleetServerHost>;
|
||||
secretReferences: PolicySecretReference[];
|
||||
secretsToDelete: PolicySecretReference[];
|
||||
}> {
|
||||
const { oldFleetServerHost, fleetServerHostUpdate, esClient, secretHashes } = opts;
|
||||
const oldSecretPaths = getFleetServerHostsSecretPaths(oldFleetServerHost);
|
||||
const updatedSecretPaths = getFleetServerHostsSecretPaths(fleetServerHostUpdate);
|
||||
const secretsRes = await extractAndUpdateSOSecrets<FleetServerHost>({
|
||||
updatedSoObject: fleetServerHostUpdate,
|
||||
oldSecretPaths,
|
||||
updatedSecretPaths,
|
||||
esClient,
|
||||
secretHashes,
|
||||
});
|
||||
return {
|
||||
fleetServerHostUpdate: secretsRes.updatedSoObject,
|
||||
secretReferences: secretsRes.secretReferences,
|
||||
secretsToDelete: secretsRes.secretsToDelete,
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteFleetServerHostsSecrets(opts: {
|
||||
fleetServerHost: NewFleetServerHost;
|
||||
esClient: ElasticsearchClient;
|
||||
}): Promise<void> {
|
||||
const { fleetServerHost, esClient } = opts;
|
||||
|
||||
const secretPaths = getFleetServerHostsSecretPaths(fleetServerHost).filter(
|
||||
(path) => typeof path.value === 'string'
|
||||
);
|
||||
|
||||
await deleteSOSecrets(esClient, secretPaths);
|
||||
}
|
||||
|
||||
export function getFleetServerHostsSecretReferences(
|
||||
fleetServerHost: FleetServerHost
|
||||
): PolicySecretReference[] {
|
||||
const secretPaths: PolicySecretReference[] = [];
|
||||
|
||||
if (typeof fleetServerHost.secrets?.ssl?.key === 'object') {
|
||||
secretPaths.push({
|
||||
id: fleetServerHost.secrets.ssl.key.id,
|
||||
});
|
||||
}
|
||||
if (typeof fleetServerHost.secrets?.ssl?.es_key === 'object') {
|
||||
secretPaths.push({
|
||||
id: fleetServerHost.secrets.ssl.es_key.id,
|
||||
});
|
||||
}
|
||||
|
||||
return secretPaths;
|
||||
}
|
||||
|
|
|
@ -19,14 +19,14 @@ import { DeleteUnenrolledAgentsPreconfiguredError } from '../errors';
|
|||
import { appContextService } from './app_context';
|
||||
import { getSettings, saveSettings, settingsSetup } from './settings';
|
||||
import { auditLoggingService } from './audit_logging';
|
||||
import { listFleetServerHosts } from './fleet_server_host';
|
||||
import { fleetServerHostService } from './fleet_server_host';
|
||||
|
||||
jest.mock('./app_context');
|
||||
jest.mock('./audit_logging');
|
||||
jest.mock('./fleet_server_host');
|
||||
|
||||
const mockListFleetServerHosts = listFleetServerHosts as jest.MockedFunction<
|
||||
typeof listFleetServerHosts
|
||||
const mockedFleetServerHostService = fleetServerHostService as jest.Mocked<
|
||||
typeof fleetServerHostService
|
||||
>;
|
||||
const mockedAuditLoggingService = auditLoggingService as jest.Mocked<typeof auditLoggingService>;
|
||||
const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>;
|
||||
|
@ -36,6 +36,7 @@ mockedAppContextService.getSecuritySetup.mockImplementation(() => ({
|
|||
|
||||
describe('settingsSetup', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
mockedAppContextService.getCloud.mockReset();
|
||||
mockedAppContextService.getConfig.mockReset();
|
||||
});
|
||||
|
@ -86,7 +87,7 @@ describe('settingsSetup', () => {
|
|||
type: 'so_type',
|
||||
});
|
||||
|
||||
mockListFleetServerHosts.mockResolvedValueOnce({
|
||||
mockedFleetServerHostService.list.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
id: 'fleet-server-host',
|
||||
|
@ -126,7 +127,7 @@ describe('getSettings', () => {
|
|||
total: 1,
|
||||
});
|
||||
|
||||
mockListFleetServerHosts.mockResolvedValueOnce({
|
||||
mockedFleetServerHostService.list.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
id: 'fleet-server-host',
|
||||
|
@ -182,6 +183,9 @@ describe('getSettings', () => {
|
|||
});
|
||||
|
||||
describe('saveSettings', () => {
|
||||
afterEach(() => {
|
||||
mockedAuditLoggingService.writeCustomSoAuditLog.mockReset();
|
||||
});
|
||||
describe('when settings object exists', () => {
|
||||
it('should call audit logger', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
|
@ -212,7 +216,7 @@ describe('saveSettings', () => {
|
|||
type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
});
|
||||
|
||||
mockListFleetServerHosts.mockResolvedValueOnce({
|
||||
mockedFleetServerHostService.list.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
id: 'fleet-server-host',
|
||||
|
@ -229,11 +233,7 @@ describe('saveSettings', () => {
|
|||
|
||||
await saveSettings(soClient, newData);
|
||||
|
||||
expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenCalledWith({
|
||||
action: 'create',
|
||||
id: GLOBAL_SETTINGS_ID,
|
||||
savedObjectType: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
});
|
||||
expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when settings object does not exist', () => {
|
||||
|
@ -293,7 +293,7 @@ describe('saveSettings', () => {
|
|||
per_page: 10,
|
||||
total: 1,
|
||||
});
|
||||
mockListFleetServerHosts.mockResolvedValueOnce({
|
||||
mockedFleetServerHostService.list.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
id: 'fleet-server-host',
|
||||
|
@ -349,7 +349,7 @@ describe('saveSettings', () => {
|
|||
per_page: 10,
|
||||
total: 1,
|
||||
});
|
||||
mockListFleetServerHosts.mockResolvedValueOnce({
|
||||
mockedFleetServerHostService.list.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
id: 'fleet-server-host',
|
||||
|
|
|
@ -5,8 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
import type { ElasticsearchClientMock } from '@kbn/core/server/mocks';
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
|
||||
import { MessageSigningError } from '../../common/errors';
|
||||
import { createAppContextStartContractMock, xpackMocks } from '../mocks';
|
||||
|
@ -16,12 +19,19 @@ import { ensurePreconfiguredPackagesAndPolicies } from '.';
|
|||
import { appContextService } from './app_context';
|
||||
import { getInstallations } from './epm/packages';
|
||||
import { setupUpgradeManagedPackagePolicies } from './setup/managed_package_policies';
|
||||
import { getPreconfiguredDeleteUnenrolledAgentsSettingFromConfig } from './preconfiguration/delete_unenrolled_agent_setting';
|
||||
import { setupFleet } from './setup';
|
||||
import { isPackageInstalled } from './epm/packages/install';
|
||||
import { upgradeAgentPolicySchemaVersion } from './setup/upgrade_agent_policy_schema_version';
|
||||
import { createOrUpdateFleetSyncedIntegrationsIndex } from './setup/fleet_synced_integrations';
|
||||
|
||||
jest.mock('./app_context');
|
||||
jest.mock('./preconfiguration');
|
||||
jest.mock('./preconfiguration/outputs');
|
||||
jest.mock('./preconfiguration/fleet_proxies');
|
||||
jest.mock('./preconfiguration/space_settings');
|
||||
jest.mock('./preconfiguration/fleet_server_host');
|
||||
jest.mock('./preconfiguration/delete_unenrolled_agent_setting');
|
||||
jest.mock('./settings');
|
||||
jest.mock('./output');
|
||||
jest.mock('./download_source');
|
||||
|
@ -35,6 +45,13 @@ jest.mock('./epm/elasticsearch/template/install', () => {
|
|||
};
|
||||
});
|
||||
jest.mock('./backfill_agentless');
|
||||
jest.mock('./epm/packages/install');
|
||||
jest.mock('./setup/upgrade_agent_policy_schema_version');
|
||||
jest.mock('./setup/fleet_synced_integrations');
|
||||
|
||||
const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>;
|
||||
|
||||
let mockedLogger: jest.Mocked<Logger>;
|
||||
|
||||
const mockedMethodThrowsError = (mockFn: jest.Mock) =>
|
||||
mockFn.mockImplementation(() => {
|
||||
|
@ -47,17 +64,30 @@ const mockedMethodThrowsCustom = (mockFn: jest.Mock) =>
|
|||
throw new CustomTestError('method mocked to throw');
|
||||
});
|
||||
|
||||
function getMockedSoClient() {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
mockedAppContextService.getInternalUserSOClient.mockReturnValue(soClient);
|
||||
|
||||
soClient.get.mockResolvedValue({ attributes: {} } as any);
|
||||
soClient.find.mockResolvedValue({ saved_objects: [] } as any);
|
||||
soClient.bulkGet.mockResolvedValue({ saved_objects: [] } as any);
|
||||
soClient.create.mockResolvedValue({ attributes: {} } as any);
|
||||
soClient.delete.mockResolvedValue({});
|
||||
|
||||
return soClient;
|
||||
}
|
||||
|
||||
describe('setupFleet', () => {
|
||||
let context: ReturnType<typeof xpackMocks.createRequestHandlerContext>;
|
||||
let soClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let esClient: ElasticsearchClientMock;
|
||||
|
||||
beforeEach(async () => {
|
||||
context = xpackMocks.createRequestHandlerContext();
|
||||
// prevents `Logger not set.` and other appContext errors
|
||||
appContextService.start(createAppContextStartContractMock());
|
||||
soClient = context.core.savedObjects.client;
|
||||
mockedAppContextService.start(createAppContextStartContractMock());
|
||||
esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
mockedLogger = loggerMock.create();
|
||||
mockedAppContextService.getLogger.mockReturnValue(mockedLogger);
|
||||
|
||||
(getInstallations as jest.Mock).mockResolvedValueOnce({
|
||||
saved_objects: [],
|
||||
|
@ -68,22 +98,21 @@ describe('setupFleet', () => {
|
|||
});
|
||||
|
||||
(setupUpgradeManagedPackagePolicies as jest.Mock).mockResolvedValue([]);
|
||||
|
||||
soClient.get.mockResolvedValue({ attributes: {} } as any);
|
||||
soClient.find.mockResolvedValue({ saved_objects: [] } as any);
|
||||
soClient.bulkGet.mockResolvedValue({ saved_objects: [] } as any);
|
||||
soClient.create.mockResolvedValue({ attributes: {} } as any);
|
||||
soClient.delete.mockResolvedValue({});
|
||||
(getPreconfiguredDeleteUnenrolledAgentsSettingFromConfig as jest.Mock).mockResolvedValue([]);
|
||||
(isPackageInstalled as jest.Mock).mockResolvedValue(true);
|
||||
(upgradeAgentPolicySchemaVersion as jest.Mock).mockResolvedValue(undefined);
|
||||
(createOrUpdateFleetSyncedIntegrationsIndex as jest.Mock).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
appContextService.stop();
|
||||
mockedAppContextService.stop();
|
||||
});
|
||||
|
||||
describe('should reject with any error thrown underneath', () => {
|
||||
it('SO client throws plain Error', async () => {
|
||||
mockedMethodThrowsError(setupUpgradeManagedPackagePolicies as jest.Mock);
|
||||
const soClient = getMockedSoClient();
|
||||
mockedMethodThrowsError(getPreconfiguredDeleteUnenrolledAgentsSettingFromConfig as jest.Mock);
|
||||
|
||||
const setupPromise = setupFleet(soClient, esClient);
|
||||
await expect(setupPromise).rejects.toThrow('SO method mocked to throw');
|
||||
|
@ -91,6 +120,8 @@ describe('setupFleet', () => {
|
|||
});
|
||||
|
||||
it('SO client throws other error', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
|
||||
mockedMethodThrowsCustom(setupUpgradeManagedPackagePolicies as jest.Mock);
|
||||
|
||||
const setupPromise = setupFleet(soClient, esClient);
|
||||
|
@ -100,6 +131,8 @@ describe('setupFleet', () => {
|
|||
});
|
||||
|
||||
it('should not return non fatal errors when upgrade result has no errors', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
|
||||
const result = await setupFleet(soClient, esClient);
|
||||
|
||||
expect(result).toEqual({
|
||||
|
@ -108,25 +141,9 @@ describe('setupFleet', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should return non fatal errors when generateKeyPair result has errors', async () => {
|
||||
const messageSigninError = new MessageSigningError('test');
|
||||
jest
|
||||
.mocked(appContextService.getMessageSigningService()!.generateKeyPair)
|
||||
.mockRejectedValue(messageSigninError);
|
||||
|
||||
const result = await setupFleet(soClient, esClient);
|
||||
|
||||
expect(result).toEqual({
|
||||
isInitialized: true,
|
||||
nonFatalErrors: [
|
||||
{
|
||||
error: messageSigninError,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create and delete lock if not exists', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
|
||||
soClient.get.mockRejectedValue({ isBoom: true, output: { statusCode: 404 } } as any);
|
||||
|
||||
const result = await setupFleet(soClient, esClient, { useLock: true });
|
||||
|
@ -144,6 +161,8 @@ describe('setupFleet', () => {
|
|||
});
|
||||
|
||||
it('should return not initialized if lock exists', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
|
||||
const result = await setupFleet(soClient, esClient, { useLock: true });
|
||||
|
||||
expect(result).toEqual({
|
||||
|
@ -155,6 +174,8 @@ describe('setupFleet', () => {
|
|||
});
|
||||
|
||||
it('should return not initialized if lock could not be created', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
|
||||
soClient.get.mockRejectedValue({ isBoom: true, output: { statusCode: 404 } } as any);
|
||||
soClient.create.mockRejectedValue({ isBoom: true, output: { statusCode: 409 } } as any);
|
||||
const result = await setupFleet(soClient, esClient, { useLock: true });
|
||||
|
@ -167,6 +188,8 @@ describe('setupFleet', () => {
|
|||
});
|
||||
|
||||
it('should delete previous lock if created more than 1 hour ago', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
|
||||
soClient.get.mockResolvedValue({
|
||||
attributes: { started_at: new Date(Date.now() - 60 * 60 * 1000 - 1000).toISOString() },
|
||||
} as any);
|
||||
|
@ -180,4 +203,28 @@ describe('setupFleet', () => {
|
|||
expect(soClient.create).toHaveBeenCalled();
|
||||
expect(soClient.delete).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should return non fatal errors when generateKeyPair result has errors', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
|
||||
const messageSigningError = new MessageSigningError('test');
|
||||
mockedAppContextService.getMessageSigningService.mockImplementation(() => ({
|
||||
generateKeyPair: jest.fn().mockRejectedValueOnce(messageSigningError),
|
||||
rotateKeyPair: jest.fn(),
|
||||
isEncryptionAvailable: true,
|
||||
sign: jest.fn(),
|
||||
getPublicKey: jest.fn(),
|
||||
}));
|
||||
|
||||
const result = await setupFleet(soClient, esClient);
|
||||
|
||||
expect(result).toEqual({
|
||||
isInitialized: true,
|
||||
nonFatalErrors: [
|
||||
{
|
||||
error: messageSigningError,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -186,7 +186,7 @@ async function createSetupSideEffects(
|
|||
let packages = packagesOrUndefined ?? [];
|
||||
|
||||
logger.debug('Setting Fleet server config');
|
||||
await migrateSettingsToFleetServerHost(soClient);
|
||||
await migrateSettingsToFleetServerHost(soClient, esClient);
|
||||
logger.debug('Setting up Fleet download source');
|
||||
const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient);
|
||||
// Need to be done before outputs and fleet server hosts as these object can reference a proxy
|
||||
|
|
|
@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import semverValid from 'semver/functions/valid';
|
||||
|
||||
import { PRECONFIGURATION_LATEST_KEYWORD } from '../../constants';
|
||||
import type { PreconfiguredOutput } from '../../../common/types';
|
||||
import { clientAuth, type PreconfiguredOutput } from '../../../common/types';
|
||||
|
||||
import {
|
||||
ElasticSearchSchema,
|
||||
|
@ -35,6 +35,13 @@ const varsSchema = schema.maybe(
|
|||
)
|
||||
);
|
||||
|
||||
const secretRefSchema = schema.oneOf([
|
||||
schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
schema.string(),
|
||||
]);
|
||||
|
||||
export const PreconfiguredPackagesSchema = schema.arrayOf(
|
||||
schema.object({
|
||||
name: schema.string(),
|
||||
|
@ -109,6 +116,31 @@ export const PreconfiguredFleetServerHostsSchema = schema.arrayOf(
|
|||
is_internal: schema.maybe(schema.boolean()),
|
||||
host_urls: schema.arrayOf(schema.string(), { minSize: 1 }),
|
||||
proxy_id: schema.nullable(schema.string()),
|
||||
secrets: schema.maybe(
|
||||
schema.object({
|
||||
ssl: schema.maybe(schema.object({ key: schema.maybe(secretRefSchema) })),
|
||||
})
|
||||
),
|
||||
ssl: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.literal(null),
|
||||
schema.object({
|
||||
certificate_authorities: schema.maybe(schema.arrayOf(schema.string())),
|
||||
certificate: schema.maybe(schema.string()),
|
||||
key: schema.maybe(schema.string()),
|
||||
es_certificate_authorities: schema.maybe(schema.arrayOf(schema.string())),
|
||||
es_certificate: schema.maybe(schema.string()),
|
||||
es_key: schema.maybe(schema.string()),
|
||||
client_auth: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.literal(clientAuth.Optional),
|
||||
schema.literal(clientAuth.Required),
|
||||
schema.literal(clientAuth.None),
|
||||
])
|
||||
),
|
||||
}),
|
||||
])
|
||||
),
|
||||
}),
|
||||
{ defaultValue: [] }
|
||||
);
|
||||
|
|
|
@ -7,7 +7,51 @@
|
|||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
export const FleetServerHostSchema = schema.object({
|
||||
import { clientAuth } from '../../../common/types';
|
||||
|
||||
const secretRefSchema = schema.oneOf([
|
||||
schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
schema.string(),
|
||||
]);
|
||||
|
||||
export const FleetServerHostBaseSchema = schema.object({
|
||||
name: schema.maybe(schema.string()),
|
||||
host_urls: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })),
|
||||
is_default: schema.maybe(schema.boolean({ defaultValue: false })),
|
||||
is_internal: schema.maybe(schema.boolean()),
|
||||
proxy_id: schema.nullable(schema.string()),
|
||||
secrets: schema.maybe(
|
||||
schema.object({
|
||||
ssl: schema.maybe(
|
||||
schema.object({ key: schema.maybe(secretRefSchema), es_key: schema.maybe(secretRefSchema) })
|
||||
),
|
||||
})
|
||||
),
|
||||
ssl: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.literal(null),
|
||||
schema.object({
|
||||
certificate_authorities: schema.maybe(schema.arrayOf(schema.string())),
|
||||
certificate: schema.maybe(schema.string()),
|
||||
key: schema.maybe(schema.string()),
|
||||
es_certificate_authorities: schema.maybe(schema.arrayOf(schema.string())),
|
||||
es_certificate: schema.maybe(schema.string()),
|
||||
es_key: schema.maybe(schema.string()),
|
||||
client_auth: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.literal(clientAuth.Optional),
|
||||
schema.literal(clientAuth.Required),
|
||||
schema.literal(clientAuth.None),
|
||||
])
|
||||
),
|
||||
}),
|
||||
])
|
||||
),
|
||||
});
|
||||
|
||||
export const FleetServerHostSchema = FleetServerHostBaseSchema.extends({
|
||||
id: schema.string(),
|
||||
name: schema.string(),
|
||||
host_urls: schema.arrayOf(schema.string(), { minSize: 1 }),
|
||||
|
@ -33,13 +77,7 @@ export const GetOneFleetServerHostRequestSchema = {
|
|||
|
||||
export const PutFleetServerHostRequestSchema = {
|
||||
params: schema.object({ itemId: schema.string() }),
|
||||
body: schema.object({
|
||||
name: schema.maybe(schema.string()),
|
||||
host_urls: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })),
|
||||
is_default: schema.maybe(schema.boolean({ defaultValue: false })),
|
||||
is_internal: schema.maybe(schema.boolean()),
|
||||
proxy_id: schema.nullable(schema.string()),
|
||||
}),
|
||||
body: FleetServerHostBaseSchema,
|
||||
};
|
||||
|
||||
export const GetAllFleetServerHostRequestSchema = {};
|
||||
|
|
|
@ -12,6 +12,7 @@ import { isDiffPathProtocol } from '../../../common/services';
|
|||
import { OutputSchema } from '../models';
|
||||
|
||||
import { FleetProxySchema } from './fleet_proxies';
|
||||
import { FleetServerHostSchema } from './fleet_server_policy_config';
|
||||
|
||||
export const GetSettingsRequestSchema = {};
|
||||
|
||||
|
@ -113,17 +114,7 @@ export const GetEnrollmentSettingsResponseSchema = schema.object({
|
|||
})
|
||||
),
|
||||
has_active: schema.boolean(),
|
||||
host: schema.maybe(
|
||||
schema.object({
|
||||
id: schema.string(),
|
||||
name: schema.string(),
|
||||
host_urls: schema.arrayOf(schema.string()),
|
||||
is_default: schema.boolean(),
|
||||
is_preconfigured: schema.boolean(),
|
||||
is_internal: schema.maybe(schema.boolean()),
|
||||
proxy_id: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
|
||||
})
|
||||
),
|
||||
host: schema.maybe(FleetServerHostSchema),
|
||||
host_proxy: schema.maybe(FleetProxySchema),
|
||||
es_output: schema.maybe(OutputSchema),
|
||||
es_output_proxy: schema.maybe(FleetProxySchema),
|
||||
|
|
|
@ -111,6 +111,13 @@ export interface FleetServerHostSOAttributes {
|
|||
is_preconfigured: boolean;
|
||||
is_internal?: boolean;
|
||||
proxy_id?: string | null;
|
||||
secrets?: {
|
||||
ssl?: {
|
||||
key?: { id: string };
|
||||
es_key?: { id: string };
|
||||
};
|
||||
};
|
||||
ssl?: string | null;
|
||||
}
|
||||
|
||||
export interface PackagePolicySOAttributes {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
|
||||
import { skipIfNoDockerRegistry } from '../../helpers';
|
||||
|
||||
|
@ -15,9 +16,87 @@ export default function (providerContext: FtrProviderContext) {
|
|||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const fleetAndAgents = getService('fleetAndAgents');
|
||||
const es = getService('es');
|
||||
|
||||
const getSecretById = (id: string) => {
|
||||
return es.get({
|
||||
index: '.fleet-secrets',
|
||||
id,
|
||||
});
|
||||
};
|
||||
|
||||
const deleteAllSecrets = async () => {
|
||||
try {
|
||||
await es.deleteByQuery({
|
||||
index: '.fleet-secrets',
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
// index doesn't exist
|
||||
}
|
||||
};
|
||||
|
||||
const createFleetServerAgent = async (
|
||||
agentPolicyId: string,
|
||||
hostname: string,
|
||||
agentVersion: string
|
||||
) => {
|
||||
const agentResponse = await es.index({
|
||||
index: '.fleet-agents',
|
||||
refresh: true,
|
||||
body: {
|
||||
access_api_key_id: 'api-key-3',
|
||||
active: true,
|
||||
policy_id: agentPolicyId,
|
||||
type: 'PERMANENT',
|
||||
local_metadata: {
|
||||
host: { hostname },
|
||||
elastic: { agent: { version: agentVersion } },
|
||||
},
|
||||
user_provided_metadata: {},
|
||||
enrolled_at: new Date().toISOString(),
|
||||
last_checkin: new Date().toISOString(),
|
||||
tags: ['tag1'],
|
||||
},
|
||||
});
|
||||
|
||||
return agentResponse._id;
|
||||
};
|
||||
|
||||
const createFleetServerPolicy = async (id: string) => {
|
||||
await kibanaServer.savedObjects.create({
|
||||
id: `package-policy-test`,
|
||||
type: PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
overwrite: true,
|
||||
attributes: {
|
||||
policy_ids: [id],
|
||||
name: 'Fleet Server',
|
||||
package: {
|
||||
name: 'fleet_server',
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const clearAgents = async () => {
|
||||
try {
|
||||
await es.deleteByQuery({
|
||||
index: '.fleet-agents',
|
||||
refresh: true,
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
// index doesn't exist
|
||||
}
|
||||
};
|
||||
|
||||
describe('fleet_fleet_server_hosts_crud', function () {
|
||||
let defaultFleetServerHostId: string;
|
||||
let fleetServerPolicyId: string;
|
||||
|
||||
skipIfNoDockerRegistry(providerContext);
|
||||
|
||||
|
@ -29,17 +108,16 @@ export default function (providerContext: FtrProviderContext) {
|
|||
await kibanaServer.savedObjects.clean({
|
||||
types: ['fleet-fleet-server-host'],
|
||||
});
|
||||
|
||||
const { body: defaultRes } = await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
id: 'test-default-123',
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
|
@ -48,8 +126,22 @@ export default function (providerContext: FtrProviderContext) {
|
|||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
})
|
||||
.expect(200);
|
||||
const { body: apiResponse } = await supertest
|
||||
.post(`/api/fleet/agent_policies`)
|
||||
.set('kbn-xsrf', 'kibana')
|
||||
.send({
|
||||
name: 'Fleet Server policy 1',
|
||||
namespace: 'default',
|
||||
has_fleet_server: true,
|
||||
})
|
||||
.expect(200);
|
||||
const fleetServerPolicy = apiResponse.item;
|
||||
fleetServerPolicyId = fleetServerPolicy.id;
|
||||
|
||||
defaultFleetServerHostId = defaultRes.item.id;
|
||||
await createFleetServerPolicy(fleetServerPolicyId);
|
||||
|
||||
await deleteAllSecrets();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
@ -73,7 +165,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
|
||||
expect(fleetServerHost).to.eql({
|
||||
item: {
|
||||
id: 'test-default-123',
|
||||
id: defaultFleetServerHostId,
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
|
@ -87,36 +179,6 @@ export default function (providerContext: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PUT /fleet_server_hosts/{itemId}', () => {
|
||||
it('should allow to update an existing fleet server host', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'Default updated',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const {
|
||||
body: { item: fleetServerHost },
|
||||
} = await supertest
|
||||
.get(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(fleetServerHost.name).to.eql('Default updated');
|
||||
});
|
||||
|
||||
it('should return a 404 when updating a non existing fleet server host', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/fleet_server_hosts/idonotexists`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new host1',
|
||||
})
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /fleet_server_hosts', () => {
|
||||
it('should allow to create a default fleet server host with id', async function () {
|
||||
const id = `test-${Date.now()}`;
|
||||
|
@ -139,6 +201,30 @@ export default function (providerContext: FtrProviderContext) {
|
|||
expect(fleetServerHost.is_default).to.be(true);
|
||||
});
|
||||
|
||||
it('should allow to create a default fleet server host with SSL options', async function () {
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `Default ${Date.now()}`,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
is_default: true,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
es_certificate_authorities: ['ES cert authorities'],
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const {
|
||||
body: { item: fleetServerHost },
|
||||
} = await supertest.get(`/api/fleet/fleet_server_hosts/${res.body.item.id}`).expect(200);
|
||||
|
||||
expect(fleetServerHost.is_default).to.be(true);
|
||||
});
|
||||
|
||||
it('should not unset default fleet server host on id conflict', async function () {
|
||||
const id = `test-${Date.now()}`;
|
||||
|
||||
|
@ -170,6 +256,288 @@ export default function (providerContext: FtrProviderContext) {
|
|||
|
||||
expect(fleetServerHost.is_default).to.be(true);
|
||||
});
|
||||
|
||||
it('should not allow ssl.key and secrets.ssl.key to be set at the same time', async function () {
|
||||
const id = `test-${Date.now()}`;
|
||||
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `Default ${Date.now()}`,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
is_default: true,
|
||||
id,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
es_certificate_authorities: ['ES cert authorities'],
|
||||
key: 'KEY',
|
||||
},
|
||||
secrets: { ssl: { key: 'KEY' } },
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body.message).to.equal('Cannot specify both ssl.key and secrets.ssl.key');
|
||||
});
|
||||
|
||||
it('should not allow ssl.es_key and secrets.ssl.es_key to be set at the same time', async function () {
|
||||
const id = `test-${Date.now()}`;
|
||||
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `Default ${Date.now()}`,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
is_default: true,
|
||||
id,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
es_certificate_authorities: ['ES cert authorities'],
|
||||
es_key: 'KEY',
|
||||
},
|
||||
secrets: { ssl: { es_key: 'KEY' } },
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body.message).to.equal('Cannot specify both ssl.es_key and secrets.ssl.es_key');
|
||||
});
|
||||
|
||||
it('should not store secrets if fleet server does not meet minimum version', async function () {
|
||||
await clearAgents();
|
||||
await createFleetServerAgent(fleetServerPolicyId, 'server_1', '7.0.0');
|
||||
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `Default ${Date.now()}`,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
is_default: true,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
es_certificate_authorities: ['ES cert authorities'],
|
||||
},
|
||||
secrets: { ssl: { key: 'KEY1', es_key: 'KEY2' } },
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(Object.keys(res.body.item)).not.to.contain('secrets');
|
||||
expect(Object.keys(res.body.item)).to.contain('ssl');
|
||||
expect(Object.keys(res.body.item.ssl)).to.contain('key');
|
||||
expect(res.body.item.ssl.key).to.equal('KEY1');
|
||||
});
|
||||
|
||||
it('should store secrets if fleet server meets minimum version', async function () {
|
||||
await clearAgents();
|
||||
await createFleetServerAgent(fleetServerPolicyId, 'server_1', '8.12.0');
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `Default ${Date.now()}`,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
is_default: true,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
es_certificate_authorities: ['ES cert authorities'],
|
||||
},
|
||||
secrets: { ssl: { key: 'KEY1', es_key: 'KEY2' } },
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(Object.keys(res.body.item)).to.contain('secrets');
|
||||
const secretId1 = res.body.item.secrets.ssl.key.id;
|
||||
const secret1 = await getSecretById(secretId1);
|
||||
// @ts-ignore _source unknown type
|
||||
expect(secret1._source.value).to.equal('KEY1');
|
||||
|
||||
const secretId2 = res.body.item.secrets.ssl.es_key.id;
|
||||
const secret2 = await getSecretById(secretId2);
|
||||
// @ts-ignore _source unknown type
|
||||
expect(secret2._source.value).to.equal('KEY2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /fleet_server_hosts/{itemId}', () => {
|
||||
it('should allow to update an existing fleet server host', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'Default updated',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const {
|
||||
body: { item: fleetServerHost },
|
||||
} = await supertest
|
||||
.get(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(fleetServerHost.name).to.eql('Default updated');
|
||||
});
|
||||
|
||||
it('should allow to update an existing fleet server host with SSL options', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'Default',
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
es_certificate_authorities: ['ES cert authorities'],
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const {
|
||||
body: { item: fleetServerHost },
|
||||
} = await supertest
|
||||
.get(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(fleetServerHost.ssl.certificate).to.eql('path/to/cert');
|
||||
expect(fleetServerHost.ssl.certificate_authorities).to.eql(['cert authorities']);
|
||||
expect(fleetServerHost.ssl.es_certificate).to.eql('path/to/EScert');
|
||||
expect(fleetServerHost.ssl.es_certificate_authorities).to.eql(['ES cert authorities']);
|
||||
});
|
||||
|
||||
it('should return a 404 when updating a non existing fleet server host', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/fleet_server_hosts/idonotexists`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new host1',
|
||||
})
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('should allow secrets to be updated + delete unused secret', async function () {
|
||||
await createFleetServerAgent(fleetServerPolicyId, 'server_1', '8.12.0');
|
||||
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `Default ${Date.now()}`,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
is_default: true,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
es_certificate_authorities: ['ES cert authorities'],
|
||||
},
|
||||
secrets: { ssl: { key: 'KEY1', es_key: 'KEY2' } },
|
||||
})
|
||||
.expect(200);
|
||||
const hostId = res.body.item.id;
|
||||
const secretId = res.body.item.secrets.ssl.key.id;
|
||||
const secret = await getSecretById(secretId);
|
||||
// @ts-ignore _source unknown type
|
||||
expect(secret._source.value).to.equal('KEY1');
|
||||
|
||||
const updatedRes = await supertest
|
||||
.put(`/api/fleet/fleet_server_hosts/${hostId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `Default ${Date.now()}`,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
is_default: true,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
es_certificate_authorities: ['ES cert authorities'],
|
||||
},
|
||||
secrets: { ssl: { key: 'NEW_KEY' } },
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const updatedSecretId = updatedRes.body.item.secrets.ssl.key.id;
|
||||
|
||||
expect(updatedSecretId).not.to.equal(secretId);
|
||||
|
||||
const updatedSecret = await getSecretById(updatedSecretId);
|
||||
|
||||
// @ts-ignore _source unknown type
|
||||
expect(updatedSecret._source.value).to.equal('NEW_KEY');
|
||||
|
||||
try {
|
||||
await getSecretById(secretId);
|
||||
expect().fail('Secret should have been deleted');
|
||||
} catch (e) {
|
||||
// not found
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /fleet_server_hosts/{itemId}', () => {
|
||||
let hostId1: string;
|
||||
before(async () => {
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'Test',
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
})
|
||||
.expect(200);
|
||||
hostId1 = res.body.item.id;
|
||||
});
|
||||
|
||||
it('should allow to delete an a fleet server host', async function () {
|
||||
const { body: deleteResponse } = await supertest
|
||||
.delete(`/api/fleet/fleet_server_hosts/${hostId1}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.expect(200);
|
||||
|
||||
expect(deleteResponse.id).to.eql(hostId1);
|
||||
});
|
||||
|
||||
it('should delete secrets when deleting a fleet server host', async function () {
|
||||
await createFleetServerAgent(fleetServerPolicyId, 'server_1', '8.12.0');
|
||||
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `Default ${Date.now()}`,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
es_certificate: 'path/to/EScert',
|
||||
es_certificate_authorities: ['ES cert authorities'],
|
||||
},
|
||||
secrets: { ssl: { es_key: 'KEY2' } },
|
||||
})
|
||||
.expect(200);
|
||||
const hostWithSecretsId = res.body.item.id;
|
||||
const secretId = res.body.item.secrets.ssl.es_key.id;
|
||||
|
||||
await supertest
|
||||
.delete(`/api/fleet/fleet_server_hosts/${hostWithSecretsId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.expect(200);
|
||||
try {
|
||||
await getSecretById(secretId);
|
||||
expect().fail('Secret should have been deleted');
|
||||
} catch (e) {
|
||||
// not found
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue